Commit efbb21673c59cfbf6a74de6866a59cb2dbb8e59f
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.
Showing
9 changed files
with
298 additions
and
3 deletions
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<StreamDataProvider> 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 "\n"; |
| 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->runtest("check dates", |
| 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->runtest("add page contents", |
| 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
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 | ||
| 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 ") + | ... | ... |