Commit e2dedde4bdb5fa68c86d412e534a4b2750739988

Authored by Jay Berkenbilt
1 parent 8705e2e8

Don't require stream data provider to know length in advance

Breaking API change: length parameter has disappeared from the
StreamDataProvider version of QPDFObjectHandle::replaceStreamData
since it is no longer necessary to compute it in advance.  This
breaking change is justified by the fact that removing the length
parameter provides the caller an opportunity to simplify the calling
code.
ChangeLog
  1 +2012-07-07 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * NOTE: BREAKING API CHANGE. Remove previously required length
  4 + parameter from the version QPDFObjectHandle::replaceStreamData
  5 + that uses a stream data provider. Prior to qpdf 3.0.0, you had to
  6 + compute the stream length in advance so that qpdf could internally
  7 + verify that the stream data had the same length every time the
  8 + provider was invoked. Now this requirement is enforced a
  9 + different way, and the length parameter is no longer required.
  10 + Note that I take API-breaking changes very seriously and only did
  11 + it in this case since the lack of need to know length in advance
  12 + could significantly simplify people's code. If you were
  13 + previously going to a lot of trouble to compute the length of the
  14 + new stream data in advance, you now no longer have to do that.
  15 + You can just drop the length parameter and remove any code that
  16 + was previously computing the length. Thanks to Tobias Hoffmann
  17 + for pointing out how annoying the original interface was.
  18 +
1 19 2012-07-05 Jay Berkenbilt <ejb@ql.org>
2 20  
3 21 * Add QPDFWriter methods to write to an already open stdio FILE*.
... ...
... ... @@ -17,8 +17,8 @@ Next
17 17  
18 18 * Document that your compiler has to support long long.
19 19  
20   - * Figure out why we have to specify a stream's length in advance when
21   - providing stream data, and remove this restriction if possible.
  20 + * Make sure that the release notes call attention to the one API
  21 + breaking change: removal of length from replaceStreamData.
22 22  
23 23 * Add a way to create new QPDFObjectHandles with a string
24 24 representation of them, such as
... ...
examples/pdf-create.cc
... ... @@ -17,7 +17,6 @@ class ImageProvider: public QPDFObjectHandle::StreamDataProvider
17 17 virtual ~ImageProvider();
18 18 virtual void provideStreamData(int objid, int generation,
19 19 Pipeline* pipeline);
20   - size_t getLength() const;
21 20  
22 21 private:
23 22 int width;
... ... @@ -45,12 +44,6 @@ ImageProvider::provideStreamData(int objid, int generation,
45 44 pipeline->finish();
46 45 }
47 46  
48   -size_t
49   -ImageProvider::getLength() const
50   -{
51   - return 3 * width * height;
52   -}
53   -
54 47 void usage()
55 48 {
56 49 std::cerr << "Usage: " << whoami << " filename" << std::endl
... ... @@ -111,8 +104,7 @@ static void create_pdf(char const* filename)
111 104 PointerHolder<QPDFObjectHandle::StreamDataProvider> provider(p);
112 105 image.replaceStreamData(provider,
113 106 QPDFObjectHandle::newNull(),
114   - QPDFObjectHandle::newNull(),
115   - p->getLength());
  107 + QPDFObjectHandle::newNull());
116 108  
117 109 // Create direct objects as needed by the page dictionary.
118 110 QPDFObjectHandle procset = QPDFObjectHandle::newArray();
... ...
examples/pdf-invert-images.cc
... ... @@ -141,8 +141,7 @@ int main(int argc, char* argv[])
141 141 image.replaceStreamData(
142 142 p,
143 143 QPDFObjectHandle::newNull(),
144   - QPDFObjectHandle::newNull(),
145   - inv->image_data[objid][gen]->getSize());
  144 + QPDFObjectHandle::newNull());
146 145 }
147 146 }
148 147 }
... ...
include/qpdf/QPDFObjectHandle.hh
... ... @@ -294,18 +294,31 @@ class QPDFObjectHandle
294 294 // directly providing a buffer with the stream data, call the
295 295 // given provider's provideStreamData method. See comments on the
296 296 // StreamDataProvider class (defined above) for details on the
297   - // method. The provider must write the number of bytes as
298   - // indicated by the length parameter, and the data must be
299   - // consistent with filter and decode_parms as provided. Although
300   - // it is more complex to use this form of replaceStreamData than
301   - // the one that takes a buffer, it makes it possible to avoid
302   - // allocating memory for the stream data. Example programs are
303   - // provided that use both forms of replaceStreamData.
  297 + // method. The data must be consistent with filter and
  298 + // decode_parms as provided. Although it is more complex to use
  299 + // this form of replaceStreamData than the one that takes a
  300 + // buffer, it makes it possible to avoid allocating memory for the
  301 + // stream data. Example programs are provided that use both forms
  302 + // of replaceStreamData.
  303 +
  304 + // Note about stream length: for any given stream, the provider
  305 + // must provide the same amount of data each time it is called.
  306 + // This is critical for making linearization work properly.
  307 + // Versions of qpdf before 3.0.0 required a length to be specified
  308 + // here. Starting with version 3.0.0, this is no longer necessary
  309 + // (or permitted). The first time the stream data provider is
  310 + // invoked for a given stream, the actual length is stored.
  311 + // Subsequent times, it is enforced that the length be the same as
  312 + // the first time.
  313 +
  314 + // If you have gotten a compile error here while building code
  315 + // that worked with older versions of qpdf, just omit the length
  316 + // parameter. You can also simplify your code by not having to
  317 + // compute the length in advance.
304 318 QPDF_DLL
305 319 void replaceStreamData(PointerHolder<StreamDataProvider> provider,
306 320 QPDFObjectHandle const& filter,
307   - QPDFObjectHandle const& decode_parms,
308   - size_t length);
  321 + QPDFObjectHandle const& decode_parms);
309 322  
310 323 // return 0 for direct objects
311 324 QPDF_DLL
... ...
libqpdf/QPDFObjectHandle.cc
... ... @@ -414,12 +414,11 @@ QPDFObjectHandle::replaceStreamData(PointerHolder&lt;Buffer&gt; data,
414 414 void
415 415 QPDFObjectHandle::replaceStreamData(PointerHolder<StreamDataProvider> provider,
416 416 QPDFObjectHandle const& filter,
417   - QPDFObjectHandle const& decode_parms,
418   - size_t length)
  417 + QPDFObjectHandle const& decode_parms)
419 418 {
420 419 assertType("Stream", isStream());
421 420 dynamic_cast<QPDF_Stream*>(obj.getPointer())->replaceStreamData(
422   - provider, filter, decode_parms, length);
  421 + provider, filter, decode_parms);
423 422 }
424 423  
425 424 int
... ...
libqpdf/QPDF_Stream.cc
... ... @@ -380,24 +380,33 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool filter,
380 380 this->stream_provider->provideStreamData(
381 381 this->objid, this->generation, &count);
382 382 qpdf_offset_t actual_length = count.getCount();
383   - qpdf_offset_t desired_length =
384   - this->stream_dict.getKey("/Length").getIntValue();
385   - if (actual_length == desired_length)
386   - {
387   - QTC::TC("qpdf", "QPDF_Stream pipe use stream provider");
388   - }
389   - else
390   - {
391   - QTC::TC("qpdf", "QPDF_Stream provider length mismatch");
392   - throw std::logic_error(
393   - "stream data provider for " +
394   - QUtil::int_to_string(this->objid) + " " +
395   - QUtil::int_to_string(this->generation) +
396   - " provided " +
397   - QUtil::int_to_string(actual_length) +
398   - " bytes instead of expected " +
399   - QUtil::int_to_string(desired_length) + " bytes");
400   - }
  383 + qpdf_offset_t desired_length = 0;
  384 + if (this->stream_dict.hasKey("/Length"))
  385 + {
  386 + desired_length = this->stream_dict.getKey("/Length").getIntValue();
  387 + if (actual_length == desired_length)
  388 + {
  389 + QTC::TC("qpdf", "QPDF_Stream pipe use stream provider");
  390 + }
  391 + else
  392 + {
  393 + QTC::TC("qpdf", "QPDF_Stream provider length mismatch");
  394 + throw std::logic_error(
  395 + "stream data provider for " +
  396 + QUtil::int_to_string(this->objid) + " " +
  397 + QUtil::int_to_string(this->generation) +
  398 + " provided " +
  399 + QUtil::int_to_string(actual_length) +
  400 + " bytes instead of expected " +
  401 + QUtil::int_to_string(desired_length) + " bytes");
  402 + }
  403 + }
  404 + else
  405 + {
  406 + QTC::TC("qpdf", "QPDF_Stream provider length not provided");
  407 + this->stream_dict.replaceKey(
  408 + "/Length", QPDFObjectHandle::newInteger(actual_length));
  409 + }
401 410 }
402 411 else if (this->offset == 0)
403 412 {
... ... @@ -430,12 +439,11 @@ void
430 439 QPDF_Stream::replaceStreamData(
431 440 PointerHolder<QPDFObjectHandle::StreamDataProvider> provider,
432 441 QPDFObjectHandle const& filter,
433   - QPDFObjectHandle const& decode_parms,
434   - size_t length)
  442 + QPDFObjectHandle const& decode_parms)
435 443 {
436 444 this->stream_provider = provider;
437 445 this->stream_data = 0;
438   - replaceFilterData(filter, decode_parms, length);
  446 + replaceFilterData(filter, decode_parms, 0);
439 447 }
440 448  
441 449 void
... ... @@ -445,6 +453,14 @@ QPDF_Stream::replaceFilterData(QPDFObjectHandle const&amp; filter,
445 453 {
446 454 this->stream_dict.replaceOrRemoveKey("/Filter", filter);
447 455 this->stream_dict.replaceOrRemoveKey("/DecodeParms", decode_parms);
448   - this->stream_dict.replaceKey("/Length",
449   - QPDFObjectHandle::newInteger((int)length));
  456 + if (length == 0)
  457 + {
  458 + QTC::TC("qpdf", "QPDF_Stream unknown stream length");
  459 + this->stream_dict.removeKey("/Length");
  460 + }
  461 + else
  462 + {
  463 + this->stream_dict.replaceKey(
  464 + "/Length", QPDFObjectHandle::newInteger((int)length));
  465 + }
450 466 }
... ...
libqpdf/qpdf/QPDF_Stream.hh
... ... @@ -30,8 +30,7 @@ class QPDF_Stream: public QPDFObject
30 30 void replaceStreamData(
31 31 PointerHolder<QPDFObjectHandle::StreamDataProvider> provider,
32 32 QPDFObjectHandle const& filter,
33   - QPDFObjectHandle const& decode_parms,
34   - size_t length);
  33 + QPDFObjectHandle const& decode_parms);
35 34  
36 35 // Replace object ID and generation. This may only be called if
37 36 // object ID and generation are 0. It is used by QPDFObjectHandle
... ...
qpdf/qpdf.testcov
... ... @@ -215,3 +215,5 @@ QPDFObjectHandle shallow copy dictionary 0
215 215 QPDFObjectHandle shallow copy scalar 0
216 216 QPDFObjectHandle newStream with string 0
217 217 QPDF unknown key not inherited 0
  218 +QPDF_Stream provider length not provided 0
  219 +QPDF_Stream unknown stream length 0
... ...
qpdf/qtest/qpdf/replaced-stream-data-flate.pdf
No preview for this file type
qpdf/test_driver.cc
... ... @@ -478,7 +478,17 @@ void runtest(int n, char const* filename)
478 478 PointerHolder<QPDFObjectHandle::StreamDataProvider> p = provider;
479 479 qstream.replaceStreamData(
480 480 p, QPDFObjectHandle::newName("/FlateDecode"),
481   - QPDFObjectHandle::newNull(), b->getSize());
  481 + QPDFObjectHandle::newNull());
  482 + provider->badLength(false);
  483 + QPDFWriter w(pdf, "a.pdf");
  484 + w.setStaticID(true);
  485 + // Linearize to force the provider to be called multiple times.
  486 + w.setLinearization(true);
  487 + w.setStreamDataMode(qpdf_s_preserve);
  488 + w.write();
  489 +
  490 + // Every time a provider pipes stream data, it has to provide
  491 + // the same amount of data.
482 492 provider->badLength(true);
483 493 try
484 494 {
... ... @@ -489,11 +499,6 @@ void runtest(int n, char const* filename)
489 499 {
490 500 std::cout << "exception: " << e.what() << std::endl;
491 501 }
492   - provider->badLength(false);
493   - QPDFWriter w(pdf, "a.pdf");
494   - w.setStaticID(true);
495   - w.setStreamDataMode(qpdf_s_preserve);
496   - w.write();
497 502 }
498 503 else if (n == 9)
499 504 {
... ...
qpdf/test_large_file.cc
... ... @@ -109,7 +109,6 @@ class ImageProvider: public QPDFObjectHandle::StreamDataProvider
109 109 virtual ~ImageProvider();
110 110 virtual void provideStreamData(int objid, int generation,
111 111 Pipeline* pipeline);
112   - size_t getLength() const;
113 112  
114 113 private:
115 114 int n;
... ... @@ -142,12 +141,6 @@ ImageProvider::provideStreamData(int objid, int generation,
142 141 pipeline->finish();
143 142 }
144 143  
145   -size_t
146   -ImageProvider::getLength() const
147   -{
148   - return width * height;
149   -}
150   -
151 144 void usage()
152 145 {
153 146 std::cerr << "Usage: " << whoami << " {read|write} {large|small} outfile"
... ... @@ -229,8 +222,7 @@ static void create_pdf(char const* filename)
229 222 PointerHolder<QPDFObjectHandle::StreamDataProvider> provider(p);
230 223 image.replaceStreamData(provider,
231 224 QPDFObjectHandle::newNull(),
232   - QPDFObjectHandle::newNull(),
233   - p->getLength());
  225 + QPDFObjectHandle::newNull());
234 226  
235 227 QPDFObjectHandle xobject = QPDFObjectHandle::newDictionary();
236 228 xobject.replaceKey("/Im1", image);
... ...