Commit 6f2bd7eb3a64ae6ffbdf9ae256d822056ddcb7b0
1 parent
11df7809
newStream
git-svn-id: svn+q:///qpdf/trunk@991 71b93d88-0707-0410-a8cf-f5a4172ac649
Showing
11 changed files
with
290 additions
and
30 deletions
include/qpdf/QPDFObjectHandle.hh
| ... | ... | @@ -28,6 +28,34 @@ class QPDF_Array; |
| 28 | 28 | class QPDFObjectHandle |
| 29 | 29 | { |
| 30 | 30 | public: |
| 31 | + // This class is used by replaceStreamData. It provides an | |
| 32 | + // alternative way of associating stream data with a stream. See | |
| 33 | + // comments on replaceStreamData and newStream for additional | |
| 34 | + // details. | |
| 35 | + class StreamDataProvider | |
| 36 | + { | |
| 37 | + public: | |
| 38 | + QPDF_DLL | |
| 39 | + virtual ~StreamDataProvider() | |
| 40 | + { | |
| 41 | + } | |
| 42 | + // The implementation of this function must write the | |
| 43 | + // unencrypted, raw stream data to the given pipeline. Every | |
| 44 | + // call to provideStreamData for a given stream must write the | |
| 45 | + // same data. The number of bytes written must agree with the | |
| 46 | + // length provided at the time the StreamDataProvider object | |
| 47 | + // was associated with the stream. The object ID and | |
| 48 | + // generation passed to this method are those that belong to | |
| 49 | + // the stream on behalf of which the provider is called. They | |
| 50 | + // may be ignored or used by the implementation for indexing | |
| 51 | + // or other purposes. This information is made available just | |
| 52 | + // to make it more convenient to use a single | |
| 53 | + // StreamDataProvider object to provide data for multiple | |
| 54 | + // streams. | |
| 55 | + virtual void provideStreamData(int objid, int generation, | |
| 56 | + Pipeline* pipeline) = 0; | |
| 57 | + }; | |
| 58 | + | |
| 31 | 59 | QPDF_DLL |
| 32 | 60 | QPDFObjectHandle(); |
| 33 | 61 | QPDF_DLL |
| ... | ... | @@ -83,6 +111,30 @@ class QPDFObjectHandle |
| 83 | 111 | static QPDFObjectHandle newDictionary( |
| 84 | 112 | std::map<std::string, QPDFObjectHandle> const& items); |
| 85 | 113 | |
| 114 | + // Create a new stream and associate it with the given qpdf | |
| 115 | + // object. A subsequent call must be made to replaceStreamData() | |
| 116 | + // to provide data for the stream. The stream's dictionary may be | |
| 117 | + // retrieved by calling getDict(), and the resulting dictionary | |
| 118 | + // may be modified. | |
| 119 | + QPDF_DLL | |
| 120 | + static QPDFObjectHandle newStream(QPDF* qpdf); | |
| 121 | + | |
| 122 | + // Create a new stream and associate it with the given qpdf | |
| 123 | + // object. Use the given buffer as the stream data. The stream | |
| 124 | + // dictionary's /Length key will automatically be set to the size | |
| 125 | + // of the data buffer. If additional keys are required, the | |
| 126 | + // stream's dictionary may be retrieved by calling getDict(), and | |
| 127 | + // the resulting dictionary may be modified. This method is just | |
| 128 | + // a convient wrapper around the newStream() and | |
| 129 | + // replaceStreamData(). It is a convenience methods for streams | |
| 130 | + // that require no parameters beyond the stream length. Note that | |
| 131 | + // you don't have to deal with compression yourself if you use | |
| 132 | + // QPDFWriter. By default, QPDFWriter will automatically compress | |
| 133 | + // uncompressed stream data. Example programs are provided that | |
| 134 | + // illustrate this. | |
| 135 | + QPDF_DLL | |
| 136 | + static QPDFObjectHandle newStream(QPDF* qpdf, PointerHolder<Buffer> data); | |
| 137 | + | |
| 86 | 138 | // Accessor methods. If an accessor method that is valid for only |
| 87 | 139 | // a particular object type is called on an object of the wrong |
| 88 | 140 | // type, an exception is thrown. |
| ... | ... | @@ -198,34 +250,17 @@ class QPDFObjectHandle |
| 198 | 250 | QPDFObjectHandle const& filter, |
| 199 | 251 | QPDFObjectHandle const& decode_parms); |
| 200 | 252 | |
| 201 | - class StreamDataProvider | |
| 202 | - { | |
| 203 | - public: | |
| 204 | - QPDF_DLL | |
| 205 | - virtual ~StreamDataProvider() | |
| 206 | - { | |
| 207 | - } | |
| 208 | - // See replaceStreamData below for details on how to override | |
| 209 | - // this method. | |
| 210 | - virtual void provideStreamData(int objid, int generation, | |
| 211 | - Pipeline* pipeline) = 0; | |
| 212 | - }; | |
| 213 | 253 | // As above, replace this stream's stream data. Instead of |
| 214 | 254 | // directly providing a buffer with the stream data, call the |
| 215 | - // given provider's provideStreamData method. The method is to | |
| 216 | - // write the unencrypted, raw stream data to the provided | |
| 217 | - // pipeline. The stream's /Length key will be set to the length | |
| 218 | - // as provided. This must match the number of bytes written to | |
| 219 | - // the pipeline. The provider must write exactly the same data to | |
| 220 | - // the pipeline every time it is called. The method is invoked | |
| 221 | - // with the object ID and generation number, which are just there | |
| 222 | - // to be available to the handler in case it is useful for | |
| 223 | - // indexing purposes. This makes it easier to reuse the same | |
| 224 | - // StreamDataProvider object for multiple streams. Although it is | |
| 225 | - // more complex to use this form of replaceStreamData, it makes it | |
| 226 | - // possible to avoid allocating memory for the stream data. | |
| 227 | - // Example programs are provided that use both forms of | |
| 228 | - // replaceStreamData. | |
| 255 | + // given provider's provideStreamData method. See comments on the | |
| 256 | + // StreamDataProvider class (defined above) for details on the | |
| 257 | + // method. The provider must write the number of bytes as | |
| 258 | + // indicated by the length parameter, and the data must be | |
| 259 | + // consistent with filter and decode_parms as provided. Although | |
| 260 | + // it is more complex to use this form of replaceStreamData than | |
| 261 | + // the one that takes a buffer, it makes it possible to avoid | |
| 262 | + // allocating memory for the stream data. Example programs are | |
| 263 | + // provided that use both forms of replaceStreamData. | |
| 229 | 264 | QPDF_DLL |
| 230 | 265 | void replaceStreamData(PointerHolder<StreamDataProvider> provider, |
| 231 | 266 | QPDFObjectHandle const& filter, | ... | ... |
libqpdf/QPDF.cc
| ... | ... | @@ -1779,7 +1779,11 @@ QPDF::resolveObjectsInStream(int obj_stream_number) |
| 1779 | 1779 | QPDFObjectHandle |
| 1780 | 1780 | QPDF::makeIndirectObject(QPDFObjectHandle oh) |
| 1781 | 1781 | { |
| 1782 | - ObjGen o1 = (*(this->obj_cache.rbegin())).first; | |
| 1782 | + ObjGen o1(0, 0); | |
| 1783 | + if (! this->obj_cache.empty()) | |
| 1784 | + { | |
| 1785 | + o1 = (*(this->obj_cache.rbegin())).first; | |
| 1786 | + } | |
| 1783 | 1787 | ObjGen o2 = (*(this->xref_table.rbegin())).first; |
| 1784 | 1788 | QTC::TC("qpdf", "QPDF indirect last obj from xref", |
| 1785 | 1789 | (o2.obj > o1.obj) ? 1 : 0); | ... | ... |
libqpdf/QPDFObjectHandle.cc
| ... | ... | @@ -561,6 +561,30 @@ QPDFObjectHandle::newStream(QPDF* qpdf, int objid, int generation, |
| 561 | 561 | stream_dict, offset, length)); |
| 562 | 562 | } |
| 563 | 563 | |
| 564 | +QPDFObjectHandle | |
| 565 | +QPDFObjectHandle::newStream(QPDF* qpdf) | |
| 566 | +{ | |
| 567 | + QTC::TC("qpdf", "QPDFObjectHandle newStream"); | |
| 568 | + std::map<std::string, QPDFObjectHandle> keys; | |
| 569 | + QPDFObjectHandle stream_dict = newDictionary(keys); | |
| 570 | + QPDFObjectHandle result = qpdf->makeIndirectObject( | |
| 571 | + QPDFObjectHandle( | |
| 572 | + new QPDF_Stream(qpdf, 0, 0, stream_dict, 0, 0))); | |
| 573 | + result.dereference(); | |
| 574 | + QPDF_Stream* stream = dynamic_cast<QPDF_Stream*>(result.obj.getPointer()); | |
| 575 | + stream->setObjGen(result.getObjectID(), result.getGeneration()); | |
| 576 | + return result; | |
| 577 | +} | |
| 578 | + | |
| 579 | +QPDFObjectHandle | |
| 580 | +QPDFObjectHandle::newStream(QPDF* qpdf, PointerHolder<Buffer> data) | |
| 581 | +{ | |
| 582 | + QTC::TC("qpdf", "QPDFObjectHandle newStream with data"); | |
| 583 | + QPDFObjectHandle result = newStream(qpdf); | |
| 584 | + result.replaceStreamData(data, newNull(), newNull()); | |
| 585 | + return result; | |
| 586 | +} | |
| 587 | + | |
| 564 | 588 | void |
| 565 | 589 | QPDFObjectHandle::makeDirectInternal(std::set<int>& visited) |
| 566 | 590 | { |
| ... | ... | @@ -649,7 +673,7 @@ QPDFObjectHandle::makeDirectInternal(std::set<int>& visited) |
| 649 | 673 | } |
| 650 | 674 | else |
| 651 | 675 | { |
| 652 | - throw std::logic_error("QPDFObjectHandle::makeIndirect: " | |
| 676 | + throw std::logic_error("QPDFObjectHandle::makeDirectInternal: " | |
| 653 | 677 | "unknown object type"); |
| 654 | 678 | } |
| 655 | 679 | ... | ... |
libqpdf/QPDF_Stream.cc
| ... | ... | @@ -40,6 +40,19 @@ QPDF_Stream::~QPDF_Stream() |
| 40 | 40 | { |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | +void | |
| 44 | +QPDF_Stream::setObjGen(int objid, int generation) | |
| 45 | +{ | |
| 46 | + if (! ((this->objid == 0) && (this->generation == 0))) | |
| 47 | + { | |
| 48 | + throw std::logic_error( | |
| 49 | + "attempt to set object ID and generation of a stream" | |
| 50 | + " that already has them"); | |
| 51 | + } | |
| 52 | + this->objid = objid; | |
| 53 | + this->generation = generation; | |
| 54 | +} | |
| 55 | + | |
| 43 | 56 | std::string |
| 44 | 57 | QPDF_Stream::unparse() |
| 45 | 58 | { |
| ... | ... | @@ -353,6 +366,12 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool filter, |
| 353 | 366 | QUtil::int_to_string(desired_length) + " bytes"); |
| 354 | 367 | } |
| 355 | 368 | } |
| 369 | + else if (this->offset == 0) | |
| 370 | + { | |
| 371 | + QTC::TC("qpdf", "QPDF_Stream pipe no stream data"); | |
| 372 | + throw std::logic_error( | |
| 373 | + "pipeStreamData called for stream with no data"); | |
| 374 | + } | |
| 356 | 375 | else |
| 357 | 376 | { |
| 358 | 377 | QTC::TC("qpdf", "QPDF_Stream pipe original stream data"); | ... | ... |
libqpdf/qpdf/QPDF_Stream.hh
| ... | ... | @@ -31,6 +31,11 @@ class QPDF_Stream: public QPDFObject |
| 31 | 31 | QPDFObjectHandle const& decode_parms, |
| 32 | 32 | size_t length); |
| 33 | 33 | |
| 34 | + // Replace object ID and generation. This may only be called if | |
| 35 | + // object ID and generation are 0. It is used by QPDFObjectHandle | |
| 36 | + // when adding streams to files. | |
| 37 | + void setObjGen(int objid, int generation); | |
| 38 | + | |
| 34 | 39 | private: |
| 35 | 40 | void replaceFilterData(QPDFObjectHandle const& filter, |
| 36 | 41 | QPDFObjectHandle const& decode_parms, | ... | ... |
qpdf/qpdf.testcov
| ... | ... | @@ -178,3 +178,6 @@ QPDF_Stream pipe original stream data 0 |
| 178 | 178 | QPDF_Stream pipe replaced stream data 0 |
| 179 | 179 | QPDF_Stream pipe use stream provider 0 |
| 180 | 180 | QPDF_Stream provider length mismatch 0 |
| 181 | +QPDFObjectHandle newStream 0 | |
| 182 | +QPDFObjectHandle newStream with data 0 | |
| 183 | +QPDF_Stream pipe no stream data 0 | ... | ... |
qpdf/qtest/qpdf.test
| ... | ... | @@ -77,7 +77,7 @@ flush_tiff_cache(); |
| 77 | 77 | show_ntests(); |
| 78 | 78 | # ---------- |
| 79 | 79 | $td->notify("--- Miscellaneous Tests ---"); |
| 80 | -$n_tests += 26; | |
| 80 | +$n_tests += 28; | |
| 81 | 81 | |
| 82 | 82 | $td->runtest("qpdf version", |
| 83 | 83 | {$td->COMMAND => "qpdf --version"}, |
| ... | ... | @@ -104,7 +104,6 @@ $td->runtest("replace stream data", |
| 104 | 104 | $td->runtest("check output", |
| 105 | 105 | {$td->FILE => "a.pdf"}, |
| 106 | 106 | {$td->FILE => "replaced-stream-data.out"}); |
| 107 | - | |
| 108 | 107 | $td->runtest("replace stream data compressed", |
| 109 | 108 | {$td->COMMAND => "test_driver 8 qstream.pdf"}, |
| 110 | 109 | {$td->FILE => "test8.out", $td->EXIT_STATUS => 0}, |
| ... | ... | @@ -112,6 +111,13 @@ $td->runtest("replace stream data compressed", |
| 112 | 111 | $td->runtest("check output", |
| 113 | 112 | {$td->FILE => "a.pdf"}, |
| 114 | 113 | {$td->FILE => "replaced-stream-data-flate.out"}); |
| 114 | +$td->runtest("new streams", | |
| 115 | + {$td->COMMAND => "test_driver 9 minimal.pdf"}, | |
| 116 | + {$td->FILE => "test9.out", $td->EXIT_STATUS => 0}, | |
| 117 | + $td->NORMALIZE_NEWLINES); | |
| 118 | +$td->runtest("new stream", | |
| 119 | + {$td->FILE => "a.pdf"}, | |
| 120 | + {$td->FILE => "new-streams.pdf"}); | |
| 115 | 121 | |
| 116 | 122 | # Make sure we ignore decode parameters that we don't understand |
| 117 | 123 | $td->runtest("unknown decode parameters", | ... | ... |
qpdf/qtest/qpdf/minimal.pdf
0 → 100644
| 1 | +%PDF-1.3 | |
| 2 | +1 0 obj | |
| 3 | +<< | |
| 4 | + /Type /Catalog | |
| 5 | + /Pages 2 0 R | |
| 6 | +>> | |
| 7 | +endobj | |
| 8 | + | |
| 9 | +2 0 obj | |
| 10 | +<< | |
| 11 | + /Type /Pages | |
| 12 | + /Kids [ | |
| 13 | + 3 0 R | |
| 14 | + ] | |
| 15 | + /Count 1 | |
| 16 | +>> | |
| 17 | +endobj | |
| 18 | + | |
| 19 | +3 0 obj | |
| 20 | +<< | |
| 21 | + /Type /Page | |
| 22 | + /Parent 2 0 R | |
| 23 | + /MediaBox [0 0 612 792] | |
| 24 | + /Contents 4 0 R | |
| 25 | + /Resources << | |
| 26 | + /ProcSet 5 0 R | |
| 27 | + /Font << | |
| 28 | + /F1 6 0 R | |
| 29 | + >> | |
| 30 | + >> | |
| 31 | +>> | |
| 32 | +endobj | |
| 33 | + | |
| 34 | +4 0 obj | |
| 35 | +<< | |
| 36 | + /Length 44 | |
| 37 | +>> | |
| 38 | +stream | |
| 39 | +BT | |
| 40 | + /F1 24 Tf | |
| 41 | + 72 720 Td | |
| 42 | + (Potato) Tj | |
| 43 | +ET | |
| 44 | +endstream | |
| 45 | +endobj | |
| 46 | + | |
| 47 | +5 0 obj | |
| 48 | +[ | |
| 49 | ||
| 50 | + /Text | |
| 51 | +] | |
| 52 | +endobj | |
| 53 | + | |
| 54 | +6 0 obj | |
| 55 | +<< | |
| 56 | + /Type /Font | |
| 57 | + /Subtype /Type1 | |
| 58 | + /Name /F1 | |
| 59 | + /BaseFont /Helvetica | |
| 60 | + /Encoding /WinAnsiEncoding | |
| 61 | +>> | |
| 62 | +endobj | |
| 63 | + | |
| 64 | +xref | |
| 65 | +0 7 | |
| 66 | +0000000000 65535 f | |
| 67 | +0000000009 00000 n | |
| 68 | +0000000063 00000 n | |
| 69 | +0000000135 00000 n | |
| 70 | +0000000307 00000 n | |
| 71 | +0000000403 00000 n | |
| 72 | +0000000438 00000 n | |
| 73 | +trailer << | |
| 74 | + /Size 7 | |
| 75 | + /Root 1 0 R | |
| 76 | +>> | |
| 77 | +startxref | |
| 78 | +556 | |
| 79 | +%%EOF | ... | ... |
qpdf/qtest/qpdf/new-streams.pdf
0 → 100644
| 1 | +%PDF-1.3 | |
| 2 | +%¿÷¢þ | |
| 3 | +1 0 obj | |
| 4 | +<< /Pages 2 0 R /QStream 3 0 R /RStream 4 0 R /Type /Catalog >> | |
| 5 | +endobj | |
| 6 | +2 0 obj | |
| 7 | +<< /Count 1 /Kids [ 5 0 R ] /Type /Pages >> | |
| 8 | +endobj | |
| 9 | +3 0 obj | |
| 10 | +<< /Length 20 >> | |
| 11 | +stream | |
| 12 | +data for new stream | |
| 13 | +endstream | |
| 14 | +endobj | |
| 15 | +4 0 obj | |
| 16 | +<< /Length 22 >> | |
| 17 | +stream | |
| 18 | +data for other stream | |
| 19 | +endstream | |
| 20 | +endobj | |
| 21 | +5 0 obj | |
| 22 | +<< /Contents 6 0 R /MediaBox [ 0 0 612 792 ] /Parent 2 0 R /Resources << /Font << /F1 7 0 R >> /ProcSet 8 0 R >> /Type /Page >> | |
| 23 | +endobj | |
| 24 | +6 0 obj | |
| 25 | +<< /Length 44 >> | |
| 26 | +stream | |
| 27 | +BT | |
| 28 | + /F1 24 Tf | |
| 29 | + 72 720 Td | |
| 30 | + (Potato) Tj | |
| 31 | +ET | |
| 32 | +endstream | |
| 33 | +endobj | |
| 34 | +7 0 obj | |
| 35 | +<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >> | |
| 36 | +endobj | |
| 37 | +8 0 obj | |
| 38 | +[ /PDF /Text ] | |
| 39 | +endobj | |
| 40 | +xref | |
| 41 | +0 9 | |
| 42 | +0000000000 65535 f | |
| 43 | +0000000015 00000 n | |
| 44 | +0000000094 00000 n | |
| 45 | +0000000153 00000 n | |
| 46 | +0000000222 00000 n | |
| 47 | +0000000293 00000 n | |
| 48 | +0000000436 00000 n | |
| 49 | +0000000529 00000 n | |
| 50 | +0000000636 00000 n | |
| 51 | +trailer << /Root 1 0 R /Size 9 /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] >> | |
| 52 | +startxref | |
| 53 | +666 | |
| 54 | +%%EOF | ... | ... |
qpdf/qtest/qpdf/test9.out
0 → 100644
qpdf/test_driver.cc
| ... | ... | @@ -398,6 +398,35 @@ void runtest(int n, char const* filename) |
| 398 | 398 | w.setStreamDataMode(qpdf_s_preserve); |
| 399 | 399 | w.write(); |
| 400 | 400 | } |
| 401 | + else if (n == 9) | |
| 402 | + { | |
| 403 | + QPDFObjectHandle root = pdf.getRoot(); | |
| 404 | + PointerHolder<Buffer> b1 = new Buffer(20); | |
| 405 | + unsigned char* bp = b1.getPointer()->getBuffer(); | |
| 406 | + memcpy(bp, (char*)"data for new stream\n", 20); // no null! | |
| 407 | + QPDFObjectHandle qstream = QPDFObjectHandle::newStream(&pdf, b1); | |
| 408 | + QPDFObjectHandle rstream = QPDFObjectHandle::newStream(&pdf); | |
| 409 | + try | |
| 410 | + { | |
| 411 | + rstream.getStreamData(); | |
| 412 | + std::cout << "oops -- getStreamData didn't throw" << std::endl; | |
| 413 | + } | |
| 414 | + catch (std::logic_error const& e) | |
| 415 | + { | |
| 416 | + std::cout << "exception: " << e.what() << std::endl; | |
| 417 | + } | |
| 418 | + PointerHolder<Buffer> b2 = new Buffer(22); | |
| 419 | + bp = b2.getPointer()->getBuffer(); | |
| 420 | + memcpy(bp, (char*)"data for other stream\n", 22); // no null! | |
| 421 | + rstream.replaceStreamData( | |
| 422 | + b2, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull()); | |
| 423 | + root.replaceKey("/QStream", qstream); | |
| 424 | + root.replaceKey("/RStream", rstream); | |
| 425 | + QPDFWriter w(pdf, "a.pdf"); | |
| 426 | + w.setStaticID(true); | |
| 427 | + w.setStreamDataMode(qpdf_s_preserve); | |
| 428 | + w.write(); | |
| 429 | + } | |
| 401 | 430 | else |
| 402 | 431 | { |
| 403 | 432 | throw std::runtime_error(std::string("invalid test ") + | ... | ... |