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 2012-07-05 Jay Berkenbilt <ejb@ql.org> 19 2012-07-05 Jay Berkenbilt <ejb@ql.org>
2 20
3 * Add QPDFWriter methods to write to an already open stdio FILE*. 21 * Add QPDFWriter methods to write to an already open stdio FILE*.
@@ -17,8 +17,8 @@ Next @@ -17,8 +17,8 @@ Next
17 17
18 * Document that your compiler has to support long long. 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 * Add a way to create new QPDFObjectHandles with a string 23 * Add a way to create new QPDFObjectHandles with a string
24 representation of them, such as 24 representation of them, such as
examples/pdf-create.cc
@@ -17,7 +17,6 @@ class ImageProvider: public QPDFObjectHandle::StreamDataProvider @@ -17,7 +17,6 @@ class ImageProvider: public QPDFObjectHandle::StreamDataProvider
17 virtual ~ImageProvider(); 17 virtual ~ImageProvider();
18 virtual void provideStreamData(int objid, int generation, 18 virtual void provideStreamData(int objid, int generation,
19 Pipeline* pipeline); 19 Pipeline* pipeline);
20 - size_t getLength() const;  
21 20
22 private: 21 private:
23 int width; 22 int width;
@@ -45,12 +44,6 @@ ImageProvider::provideStreamData(int objid, int generation, @@ -45,12 +44,6 @@ ImageProvider::provideStreamData(int objid, int generation,
45 pipeline->finish(); 44 pipeline->finish();
46 } 45 }
47 46
48 -size_t  
49 -ImageProvider::getLength() const  
50 -{  
51 - return 3 * width * height;  
52 -}  
53 -  
54 void usage() 47 void usage()
55 { 48 {
56 std::cerr << "Usage: " << whoami << " filename" << std::endl 49 std::cerr << "Usage: " << whoami << " filename" << std::endl
@@ -111,8 +104,7 @@ static void create_pdf(char const* filename) @@ -111,8 +104,7 @@ static void create_pdf(char const* filename)
111 PointerHolder<QPDFObjectHandle::StreamDataProvider> provider(p); 104 PointerHolder<QPDFObjectHandle::StreamDataProvider> provider(p);
112 image.replaceStreamData(provider, 105 image.replaceStreamData(provider,
113 QPDFObjectHandle::newNull(), 106 QPDFObjectHandle::newNull(),
114 - QPDFObjectHandle::newNull(),  
115 - p->getLength()); 107 + QPDFObjectHandle::newNull());
116 108
117 // Create direct objects as needed by the page dictionary. 109 // Create direct objects as needed by the page dictionary.
118 QPDFObjectHandle procset = QPDFObjectHandle::newArray(); 110 QPDFObjectHandle procset = QPDFObjectHandle::newArray();
examples/pdf-invert-images.cc
@@ -141,8 +141,7 @@ int main(int argc, char* argv[]) @@ -141,8 +141,7 @@ int main(int argc, char* argv[])
141 image.replaceStreamData( 141 image.replaceStreamData(
142 p, 142 p,
143 QPDFObjectHandle::newNull(), 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,18 +294,31 @@ class QPDFObjectHandle
294 // directly providing a buffer with the stream data, call the 294 // directly providing a buffer with the stream data, call the
295 // given provider's provideStreamData method. See comments on the 295 // given provider's provideStreamData method. See comments on the
296 // StreamDataProvider class (defined above) for details on the 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 QPDF_DLL 318 QPDF_DLL
305 void replaceStreamData(PointerHolder<StreamDataProvider> provider, 319 void replaceStreamData(PointerHolder<StreamDataProvider> provider,
306 QPDFObjectHandle const& filter, 320 QPDFObjectHandle const& filter,
307 - QPDFObjectHandle const& decode_parms,  
308 - size_t length); 321 + QPDFObjectHandle const& decode_parms);
309 322
310 // return 0 for direct objects 323 // return 0 for direct objects
311 QPDF_DLL 324 QPDF_DLL
libqpdf/QPDFObjectHandle.cc
@@ -414,12 +414,11 @@ QPDFObjectHandle::replaceStreamData(PointerHolder&lt;Buffer&gt; data, @@ -414,12 +414,11 @@ QPDFObjectHandle::replaceStreamData(PointerHolder&lt;Buffer&gt; data,
414 void 414 void
415 QPDFObjectHandle::replaceStreamData(PointerHolder<StreamDataProvider> provider, 415 QPDFObjectHandle::replaceStreamData(PointerHolder<StreamDataProvider> provider,
416 QPDFObjectHandle const& filter, 416 QPDFObjectHandle const& filter,
417 - QPDFObjectHandle const& decode_parms,  
418 - size_t length) 417 + QPDFObjectHandle const& decode_parms)
419 { 418 {
420 assertType("Stream", isStream()); 419 assertType("Stream", isStream());
421 dynamic_cast<QPDF_Stream*>(obj.getPointer())->replaceStreamData( 420 dynamic_cast<QPDF_Stream*>(obj.getPointer())->replaceStreamData(
422 - provider, filter, decode_parms, length); 421 + provider, filter, decode_parms);
423 } 422 }
424 423
425 int 424 int
libqpdf/QPDF_Stream.cc
@@ -380,24 +380,33 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool filter, @@ -380,24 +380,33 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool filter,
380 this->stream_provider->provideStreamData( 380 this->stream_provider->provideStreamData(
381 this->objid, this->generation, &count); 381 this->objid, this->generation, &count);
382 qpdf_offset_t actual_length = count.getCount(); 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 else if (this->offset == 0) 411 else if (this->offset == 0)
403 { 412 {
@@ -430,12 +439,11 @@ void @@ -430,12 +439,11 @@ void
430 QPDF_Stream::replaceStreamData( 439 QPDF_Stream::replaceStreamData(
431 PointerHolder<QPDFObjectHandle::StreamDataProvider> provider, 440 PointerHolder<QPDFObjectHandle::StreamDataProvider> provider,
432 QPDFObjectHandle const& filter, 441 QPDFObjectHandle const& filter,
433 - QPDFObjectHandle const& decode_parms,  
434 - size_t length) 442 + QPDFObjectHandle const& decode_parms)
435 { 443 {
436 this->stream_provider = provider; 444 this->stream_provider = provider;
437 this->stream_data = 0; 445 this->stream_data = 0;
438 - replaceFilterData(filter, decode_parms, length); 446 + replaceFilterData(filter, decode_parms, 0);
439 } 447 }
440 448
441 void 449 void
@@ -445,6 +453,14 @@ QPDF_Stream::replaceFilterData(QPDFObjectHandle const&amp; filter, @@ -445,6 +453,14 @@ QPDF_Stream::replaceFilterData(QPDFObjectHandle const&amp; filter,
445 { 453 {
446 this->stream_dict.replaceOrRemoveKey("/Filter", filter); 454 this->stream_dict.replaceOrRemoveKey("/Filter", filter);
447 this->stream_dict.replaceOrRemoveKey("/DecodeParms", decode_parms); 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,8 +30,7 @@ class QPDF_Stream: public QPDFObject
30 void replaceStreamData( 30 void replaceStreamData(
31 PointerHolder<QPDFObjectHandle::StreamDataProvider> provider, 31 PointerHolder<QPDFObjectHandle::StreamDataProvider> provider,
32 QPDFObjectHandle const& filter, 32 QPDFObjectHandle const& filter,
33 - QPDFObjectHandle const& decode_parms,  
34 - size_t length); 33 + QPDFObjectHandle const& decode_parms);
35 34
36 // Replace object ID and generation. This may only be called if 35 // Replace object ID and generation. This may only be called if
37 // object ID and generation are 0. It is used by QPDFObjectHandle 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,3 +215,5 @@ QPDFObjectHandle shallow copy dictionary 0
215 QPDFObjectHandle shallow copy scalar 0 215 QPDFObjectHandle shallow copy scalar 0
216 QPDFObjectHandle newStream with string 0 216 QPDFObjectHandle newStream with string 0
217 QPDF unknown key not inherited 0 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,7 +478,17 @@ void runtest(int n, char const* filename)
478 PointerHolder<QPDFObjectHandle::StreamDataProvider> p = provider; 478 PointerHolder<QPDFObjectHandle::StreamDataProvider> p = provider;
479 qstream.replaceStreamData( 479 qstream.replaceStreamData(
480 p, QPDFObjectHandle::newName("/FlateDecode"), 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 provider->badLength(true); 492 provider->badLength(true);
483 try 493 try
484 { 494 {
@@ -489,11 +499,6 @@ void runtest(int n, char const* filename) @@ -489,11 +499,6 @@ void runtest(int n, char const* filename)
489 { 499 {
490 std::cout << "exception: " << e.what() << std::endl; 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 else if (n == 9) 503 else if (n == 9)
499 { 504 {
qpdf/test_large_file.cc
@@ -109,7 +109,6 @@ class ImageProvider: public QPDFObjectHandle::StreamDataProvider @@ -109,7 +109,6 @@ class ImageProvider: public QPDFObjectHandle::StreamDataProvider
109 virtual ~ImageProvider(); 109 virtual ~ImageProvider();
110 virtual void provideStreamData(int objid, int generation, 110 virtual void provideStreamData(int objid, int generation,
111 Pipeline* pipeline); 111 Pipeline* pipeline);
112 - size_t getLength() const;  
113 112
114 private: 113 private:
115 int n; 114 int n;
@@ -142,12 +141,6 @@ ImageProvider::provideStreamData(int objid, int generation, @@ -142,12 +141,6 @@ ImageProvider::provideStreamData(int objid, int generation,
142 pipeline->finish(); 141 pipeline->finish();
143 } 142 }
144 143
145 -size_t  
146 -ImageProvider::getLength() const  
147 -{  
148 - return width * height;  
149 -}  
150 -  
151 void usage() 144 void usage()
152 { 145 {
153 std::cerr << "Usage: " << whoami << " {read|write} {large|small} outfile" 146 std::cerr << "Usage: " << whoami << " {read|write} {large|small} outfile"
@@ -229,8 +222,7 @@ static void create_pdf(char const* filename) @@ -229,8 +222,7 @@ static void create_pdf(char const* filename)
229 PointerHolder<QPDFObjectHandle::StreamDataProvider> provider(p); 222 PointerHolder<QPDFObjectHandle::StreamDataProvider> provider(p);
230 image.replaceStreamData(provider, 223 image.replaceStreamData(provider,
231 QPDFObjectHandle::newNull(), 224 QPDFObjectHandle::newNull(),
232 - QPDFObjectHandle::newNull(),  
233 - p->getLength()); 225 + QPDFObjectHandle::newNull());
234 226
235 QPDFObjectHandle xobject = QPDFObjectHandle::newDictionary(); 227 QPDFObjectHandle xobject = QPDFObjectHandle::newDictionary();
236 xobject.replaceKey("/Im1", image); 228 xobject.replaceKey("/Im1", image);