Commit efbb21673c59cfbf6a74de6866a59cb2dbb8e59f

Authored by Jay Berkenbilt
1 parent e2593e2e

Add functional versions of QPDFObjectHandle::replaceStreamData

Also fix a bug in checking consistency of length for stream data
providers. Length should not be checked or recorded if the provider
says it failed to generate the data.
ChangeLog
  1 +2021-02-14 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * Add new versions of QPDFObjectHandle::replaceStreamData that
  4 + take std::function objects for cases when you need something
  5 + between a static string and a full-fledged StreamDataProvider.
  6 + Using this with QUtil::file_provider is a very easy way to create
  7 + a stream from the contents of a file.
  8 +
1 2021-02-12 Jay Berkenbilt <ejb@ql.org> 9 2021-02-12 Jay Berkenbilt <ejb@ql.org>
2 10
3 * Move formerly internal QPDFMatrix class to the public API. This 11 * Move formerly internal QPDFMatrix class to the public API. This
include/qpdf/QPDFObjectHandle.hh
@@ -30,6 +30,7 @@ @@ -30,6 +30,7 @@
30 #include <vector> 30 #include <vector>
31 #include <set> 31 #include <set>
32 #include <map> 32 #include <map>
  33 +#include <functional>
33 34
34 #include <qpdf/QPDFObjGen.hh> 35 #include <qpdf/QPDFObjGen.hh>
35 #include <qpdf/PointerHolder.hh> 36 #include <qpdf/PointerHolder.hh>
@@ -986,6 +987,26 @@ class QPDFObjectHandle @@ -986,6 +987,26 @@ class QPDFObjectHandle
986 QPDFObjectHandle const& filter, 987 QPDFObjectHandle const& filter,
987 QPDFObjectHandle const& decode_parms); 988 QPDFObjectHandle const& decode_parms);
988 989
  990 + // Starting in qpdf 10.2, you can use C++-11 function objects
  991 + // instead of StreamDataProvider.
  992 +
  993 + // The provider should write the stream data to the pipeline. For
  994 + // a one-liner to replace stream data with the contents of a file,
  995 + // pass QUtil::file_provider(filename) as provider.
  996 + QPDF_DLL
  997 + void replaceStreamData(std::function<void(Pipeline*)> provider,
  998 + QPDFObjectHandle const& filter,
  999 + QPDFObjectHandle const& decode_parms);
  1000 + // The provider should write the stream data to the pipeline,
  1001 + // returning true if it succeeded without errors.
  1002 + QPDF_DLL
  1003 + void replaceStreamData(
  1004 + std::function<bool(Pipeline*,
  1005 + bool suppress_warnings,
  1006 + bool will_retry)> provider,
  1007 + QPDFObjectHandle const& filter,
  1008 + QPDFObjectHandle const& decode_parms);
  1009 +
989 // Access object ID and generation. For direct objects, return 1010 // Access object ID and generation. For direct objects, return
990 // object ID 0. 1011 // object ID 0.
991 1012
libqpdf/QPDFObjectHandle.cc
@@ -1347,6 +1347,62 @@ QPDFObjectHandle::replaceStreamData(PointerHolder&lt;StreamDataProvider&gt; provider, @@ -1347,6 +1347,62 @@ QPDFObjectHandle::replaceStreamData(PointerHolder&lt;StreamDataProvider&gt; provider,
1347 provider, filter, decode_parms); 1347 provider, filter, decode_parms);
1348 } 1348 }
1349 1349
  1350 +class FunctionProvider: public QPDFObjectHandle::StreamDataProvider
  1351 +{
  1352 + public:
  1353 + FunctionProvider(std::function<void(Pipeline*)> provider) :
  1354 + StreamDataProvider(false),
  1355 + p1(provider),
  1356 + p2(nullptr)
  1357 + {
  1358 + }
  1359 + FunctionProvider(std::function<bool(Pipeline*, bool, bool)> provider) :
  1360 + StreamDataProvider(true),
  1361 + p1(nullptr),
  1362 + p2(provider)
  1363 + {
  1364 + }
  1365 +
  1366 + virtual void provideStreamData(int, int, Pipeline* pipeline) override
  1367 + {
  1368 + p1(pipeline);
  1369 + }
  1370 +
  1371 + virtual bool provideStreamData(int, int, Pipeline* pipeline,
  1372 + bool suppress_warnings,
  1373 + bool will_retry) override
  1374 + {
  1375 + return p2(pipeline, suppress_warnings, will_retry);
  1376 + }
  1377 +
  1378 + private:
  1379 + std::function<void(Pipeline*)> p1;
  1380 + std::function<bool(Pipeline*, bool, bool)> p2;
  1381 +};
  1382 +
  1383 +void
  1384 +QPDFObjectHandle::replaceStreamData(std::function<void(Pipeline*)> provider,
  1385 + QPDFObjectHandle const& filter,
  1386 + QPDFObjectHandle const& decode_parms)
  1387 +{
  1388 + assertStream();
  1389 + PointerHolder<StreamDataProvider> sdp = new FunctionProvider(provider);
  1390 + dynamic_cast<QPDF_Stream*>(obj.getPointer())->replaceStreamData(
  1391 + sdp, filter, decode_parms);
  1392 +}
  1393 +
  1394 +void
  1395 +QPDFObjectHandle::replaceStreamData(
  1396 + std::function<bool(Pipeline*, bool, bool)> provider,
  1397 + QPDFObjectHandle const& filter,
  1398 + QPDFObjectHandle const& decode_parms)
  1399 +{
  1400 + assertStream();
  1401 + PointerHolder<StreamDataProvider> sdp = new FunctionProvider(provider);
  1402 + dynamic_cast<QPDF_Stream*>(obj.getPointer())->replaceStreamData(
  1403 + sdp, filter, decode_parms);
  1404 +}
  1405 +
1350 QPDFObjGen 1406 QPDFObjGen
1351 QPDFObjectHandle::getObjGen() const 1407 QPDFObjectHandle::getObjGen() const
1352 { 1408 {
libqpdf/QPDF_Stream.cc
@@ -533,7 +533,7 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool* filterp, @@ -533,7 +533,7 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool* filterp,
533 } 533 }
534 qpdf_offset_t actual_length = count.getCount(); 534 qpdf_offset_t actual_length = count.getCount();
535 qpdf_offset_t desired_length = 0; 535 qpdf_offset_t desired_length = 0;
536 - if (this->stream_dict.hasKey("/Length")) 536 + if (success && this->stream_dict.hasKey("/Length"))
537 { 537 {
538 desired_length = this->stream_dict.getKey("/Length").getIntValue(); 538 desired_length = this->stream_dict.getKey("/Length").getIntValue();
539 if (actual_length == desired_length) 539 if (actual_length == desired_length)
@@ -555,7 +555,7 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool* filterp, @@ -555,7 +555,7 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool* filterp,
555 QUtil::int_to_string(desired_length) + " bytes"); 555 QUtil::int_to_string(desired_length) + " bytes");
556 } 556 }
557 } 557 }
558 - else 558 + else if (success)
559 { 559 {
560 QTC::TC("qpdf", "QPDF_Stream provider length not provided"); 560 QTC::TC("qpdf", "QPDF_Stream provider length not provided");
561 this->stream_dict.replaceKey( 561 this->stream_dict.replaceKey(
manual/qpdf-manual.xml
@@ -5213,6 +5213,17 @@ print &quot;\n&quot;; @@ -5213,6 +5213,17 @@ print &quot;\n&quot;;
5213 </listitem> 5213 </listitem>
5214 <listitem> 5214 <listitem>
5215 <para> 5215 <para>
  5216 + Add new versions of
  5217 + <function>QPDFObjectHandle::replaceStreamData</function>
  5218 + that take <classname>std::function</classname> objects for
  5219 + cases when you need something between a static string and a
  5220 + full-fledged StreamDataProvider. Using this with
  5221 + QUtil::file_provider is a very easy way to create a stream
  5222 + from the contents of a file.
  5223 + </para>
  5224 + </listitem>
  5225 + <listitem>
  5226 + <para>
5216 Add option to <function>QUtil::double_to_string</function> 5227 Add option to <function>QUtil::double_to_string</function>
5217 to trim trailing zeroes, which is on by default. Within the 5228 to trim trailing zeroes, which is on by default. Within the
5218 qpdf library, this causes changes to output the from code 5229 qpdf library, this causes changes to output the from code
qpdf/qtest/qpdf.test
@@ -717,7 +717,7 @@ $td-&gt;runtest(&quot;check dates&quot;, @@ -717,7 +717,7 @@ $td-&gt;runtest(&quot;check dates&quot;,
717 show_ntests(); 717 show_ntests();
718 # ---------- 718 # ----------
719 $td->notify("--- Stream Replacement Tests ---"); 719 $td->notify("--- Stream Replacement Tests ---");
720 -$n_tests += 8; 720 +$n_tests += 10;
721 721
722 $td->runtest("replace stream data", 722 $td->runtest("replace stream data",
723 {$td->COMMAND => "test_driver 7 qstream.pdf"}, 723 {$td->COMMAND => "test_driver 7 qstream.pdf"},
@@ -747,6 +747,13 @@ $td-&gt;runtest(&quot;add page contents&quot;, @@ -747,6 +747,13 @@ $td-&gt;runtest(&quot;add page contents&quot;,
747 $td->runtest("new stream", 747 $td->runtest("new stream",
748 {$td->FILE => "a.pdf"}, 748 {$td->FILE => "a.pdf"},
749 {$td->FILE => "add-contents.pdf"}); 749 {$td->FILE => "add-contents.pdf"});
  750 +$td->runtest("functional replace stream data",
  751 + {$td->COMMAND => "test_driver 78 minimal.pdf"},
  752 + {$td->FILE => "test78.out", $td->EXIT_STATUS => 0},
  753 + $td->NORMALIZE_NEWLINES);
  754 +$td->runtest("check output",
  755 + {$td->FILE => "a.pdf"},
  756 + {$td->FILE => "test78.pdf"});
750 757
751 show_ntests(); 758 show_ntests();
752 # ---------- 759 # ----------
qpdf/qtest/qpdf/test78.out 0 โ†’ 100644
  1 +piping with warning suppression
  2 +f2
  3 +f2 done
  4 +writing
  5 +f2
  6 +failing
  7 +f2
  8 +warning
  9 +f2 done
  10 +test 78 done
qpdf/qtest/qpdf/test78.pdf 0 โ†’ 100644
  1 +%PDF-1.3
  2 +%ยฟรทยขรพ
  3 +%QDF-1.0
  4 +
  5 +%% Original object ID: 1 0
  6 +1 0 obj
  7 +<<
  8 + /Pages 6 0 R
  9 + /Type /Catalog
  10 +>>
  11 +endobj
  12 +
  13 +%% Original object ID: 7 0
  14 +2 0 obj
  15 +<<
  16 + /Length 3 0 R
  17 +>>
  18 +stream
  19 +potato
  20 +endstream
  21 +endobj
  22 +
  23 +%QDF: ignore_newline
  24 +3 0 obj
  25 +6
  26 +endobj
  27 +
  28 +%% Original object ID: 8 0
  29 +4 0 obj
  30 +<<
  31 + /Length 5 0 R
  32 +>>
  33 +stream
  34 +salad
  35 +endstream
  36 +endobj
  37 +
  38 +%QDF: ignore_newline
  39 +5 0 obj
  40 +5
  41 +endobj
  42 +
  43 +%% Original object ID: 2 0
  44 +6 0 obj
  45 +<<
  46 + /Count 1
  47 + /Kids [
  48 + 7 0 R
  49 + ]
  50 + /Type /Pages
  51 +>>
  52 +endobj
  53 +
  54 +%% Page 1
  55 +%% Original object ID: 3 0
  56 +7 0 obj
  57 +<<
  58 + /Contents 8 0 R
  59 + /MediaBox [
  60 + 0
  61 + 0
  62 + 612
  63 + 792
  64 + ]
  65 + /Parent 6 0 R
  66 + /Resources <<
  67 + /Font <<
  68 + /F1 10 0 R
  69 + >>
  70 + /ProcSet 11 0 R
  71 + >>
  72 + /Type /Page
  73 +>>
  74 +endobj
  75 +
  76 +%% Contents for page 1
  77 +%% Original object ID: 4 0
  78 +8 0 obj
  79 +<<
  80 + /Length 9 0 R
  81 +>>
  82 +stream
  83 +BT
  84 + /F1 24 Tf
  85 + 72 720 Td
  86 + (Potato) Tj
  87 +ET
  88 +endstream
  89 +endobj
  90 +
  91 +9 0 obj
  92 +44
  93 +endobj
  94 +
  95 +%% Original object ID: 6 0
  96 +10 0 obj
  97 +<<
  98 + /BaseFont /Helvetica
  99 + /Encoding /WinAnsiEncoding
  100 + /Name /F1
  101 + /Subtype /Type1
  102 + /Type /Font
  103 +>>
  104 +endobj
  105 +
  106 +%% Original object ID: 5 0
  107 +11 0 obj
  108 +[
  109 + /PDF
  110 + /Text
  111 +]
  112 +endobj
  113 +
  114 +xref
  115 +0 12
  116 +0000000000 65535 f
  117 +0000000052 00000 n
  118 +0000000133 00000 n
  119 +0000000216 00000 n
  120 +0000000261 00000 n
  121 +0000000343 00000 n
  122 +0000000388 00000 n
  123 +0000000497 00000 n
  124 +0000000741 00000 n
  125 +0000000840 00000 n
  126 +0000000886 00000 n
  127 +0000001032 00000 n
  128 +trailer <<
  129 + /Root 1 0 R
  130 + /Size 12
  131 + /Streams [
  132 + 2 0 R
  133 + 4 0 R
  134 + ]
  135 + /ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
  136 +>>
  137 +startxref
  138 +1068
  139 +%%EOF
qpdf/test_driver.cc
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 #include <qpdf/Pl_StdioFile.hh> 16 #include <qpdf/Pl_StdioFile.hh>
17 #include <qpdf/Pl_Buffer.hh> 17 #include <qpdf/Pl_Buffer.hh>
18 #include <qpdf/Pl_Flate.hh> 18 #include <qpdf/Pl_Flate.hh>
  19 +#include <qpdf/Pl_Discard.hh>
19 #include <qpdf/QPDFWriter.hh> 20 #include <qpdf/QPDFWriter.hh>
20 #include <qpdf/QPDFSystemError.hh> 21 #include <qpdf/QPDFSystemError.hh>
21 #include <qpdf/QIntC.hh> 22 #include <qpdf/QIntC.hh>
@@ -2798,6 +2799,48 @@ void runtest(int n, char const* filename1, char const* arg2) @@ -2798,6 +2799,48 @@ void runtest(int n, char const* filename1, char const* arg2)
2798 w.setQDFMode(true); 2799 w.setQDFMode(true);
2799 w.write(); 2800 w.write();
2800 } 2801 }
  2802 + else if (n == 78)
  2803 + {
  2804 + // Test functional versions of replaceStreamData()
  2805 +
  2806 + auto f1 = [](Pipeline* p) {
  2807 + p->write(QUtil::unsigned_char_pointer("potato"), 6);
  2808 + p->finish();
  2809 + };
  2810 + auto f2 = [](Pipeline* p, bool suppress_warnings, bool will_retry) {
  2811 + std::cerr << "f2" << std::endl;
  2812 + if (will_retry)
  2813 + {
  2814 + std::cerr << "failing" << std::endl;
  2815 + return false;
  2816 + }
  2817 + if (! suppress_warnings)
  2818 + {
  2819 + std::cerr << "warning" << std::endl;
  2820 + }
  2821 + p->write(QUtil::unsigned_char_pointer("salad"), 5);
  2822 + p->finish();
  2823 + std::cerr << "f2 done" << std::endl;
  2824 + return true;
  2825 + };
  2826 +
  2827 + auto null = QPDFObjectHandle::newNull();
  2828 + auto s1 = QPDFObjectHandle::newStream(&pdf);
  2829 + s1.replaceStreamData(f1, null, null);
  2830 + auto s2 = QPDFObjectHandle::newStream(&pdf);
  2831 + s2.replaceStreamData(f2, null, null);
  2832 + pdf.getTrailer().replaceKey(
  2833 + "/Streams", QPDFObjectHandle::newArray({s1, s2}));
  2834 + std::cout << "piping with warning suppression" << std::endl;
  2835 + Pl_Discard d;
  2836 + s2.pipeStreamData(&d, nullptr, 0, qpdf_dl_all, true, false);
  2837 +
  2838 + std::cout << "writing" << std::endl;
  2839 + QPDFWriter w(pdf, "a.pdf");
  2840 + w.setStaticID(true);
  2841 + w.setQDFMode(true);
  2842 + w.write();
  2843 + }
2801 else 2844 else
2802 { 2845 {
2803 throw std::runtime_error(std::string("invalid test ") + 2846 throw std::runtime_error(std::string("invalid test ") +