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 9 2021-02-12 Jay Berkenbilt <ejb@ql.org>
2 10  
3 11 * Move formerly internal QPDFMatrix class to the public API. This
... ...
include/qpdf/QPDFObjectHandle.hh
... ... @@ -30,6 +30,7 @@
30 30 #include <vector>
31 31 #include <set>
32 32 #include <map>
  33 +#include <functional>
33 34  
34 35 #include <qpdf/QPDFObjGen.hh>
35 36 #include <qpdf/PointerHolder.hh>
... ... @@ -986,6 +987,26 @@ class QPDFObjectHandle
986 987 QPDFObjectHandle const& filter,
987 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 1010 // Access object ID and generation. For direct objects, return
990 1011 // object ID 0.
991 1012  
... ...
libqpdf/QPDFObjectHandle.cc
... ... @@ -1347,6 +1347,62 @@ QPDFObjectHandle::replaceStreamData(PointerHolder&lt;StreamDataProvider&gt; provider,
1347 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 1406 QPDFObjGen
1351 1407 QPDFObjectHandle::getObjGen() const
1352 1408 {
... ...
libqpdf/QPDF_Stream.cc
... ... @@ -533,7 +533,7 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool* filterp,
533 533 }
534 534 qpdf_offset_t actual_length = count.getCount();
535 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 538 desired_length = this->stream_dict.getKey("/Length").getIntValue();
539 539 if (actual_length == desired_length)
... ... @@ -555,7 +555,7 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool* filterp,
555 555 QUtil::int_to_string(desired_length) + " bytes");
556 556 }
557 557 }
558   - else
  558 + else if (success)
559 559 {
560 560 QTC::TC("qpdf", "QPDF_Stream provider length not provided");
561 561 this->stream_dict.replaceKey(
... ...
manual/qpdf-manual.xml
... ... @@ -5213,6 +5213,17 @@ print &quot;\n&quot;;
5213 5213 </listitem>
5214 5214 <listitem>
5215 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 5227 Add option to <function>QUtil::double_to_string</function>
5217 5228 to trim trailing zeroes, which is on by default. Within the
5218 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 717 show_ntests();
718 718 # ----------
719 719 $td->notify("--- Stream Replacement Tests ---");
720   -$n_tests += 8;
  720 +$n_tests += 10;
721 721  
722 722 $td->runtest("replace stream data",
723 723 {$td->COMMAND => "test_driver 7 qstream.pdf"},
... ... @@ -747,6 +747,13 @@ $td-&gt;runtest(&quot;add page contents&quot;,
747 747 $td->runtest("new stream",
748 748 {$td->FILE => "a.pdf"},
749 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 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 16 #include <qpdf/Pl_StdioFile.hh>
17 17 #include <qpdf/Pl_Buffer.hh>
18 18 #include <qpdf/Pl_Flate.hh>
  19 +#include <qpdf/Pl_Discard.hh>
19 20 #include <qpdf/QPDFWriter.hh>
20 21 #include <qpdf/QPDFSystemError.hh>
21 22 #include <qpdf/QIntC.hh>
... ... @@ -2798,6 +2799,48 @@ void runtest(int n, char const* filename1, char const* arg2)
2798 2799 w.setQDFMode(true);
2799 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 2844 else
2802 2845 {
2803 2846 throw std::runtime_error(std::string("invalid test ") +
... ...