Commit 6f2bd7eb3a64ae6ffbdf9ae256d822056ddcb7b0

Authored by Jay Berkenbilt
1 parent 11df7809

newStream

git-svn-id: svn+q:///qpdf/trunk@991 71b93d88-0707-0410-a8cf-f5a4172ac649
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&lt;int&gt;&amp; 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-&gt;runtest(&quot;replace stream data&quot;,
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-&gt;runtest(&quot;replace stream data compressed&quot;,
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 + /PDF
  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
  1 +exception: pipeStreamData called for stream with no data
  2 +test 9 done
... ...
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 ") +
... ...