Commit 16fd6e64f947e17144e05325e51d792df33eaa61

Authored by Jay Berkenbilt
1 parent 837dcf8f

Add QPDFWriter::getFinalVersion (fixes #266)

ChangeLog
1 2019-01-04 Jay Berkenbilt <ejb@ql.org> 1 2019-01-04 Jay Berkenbilt <ejb@ql.org>
2 2
  3 + * Add new method QPDFWriter::getFinalVersion, which returns the
  4 + PDF version that will ultimately be written to the final file. See
  5 + comments in QPDFWriter.hh for some restrictions on its use. Fixes
  6 + #266.
  7 +
3 * When unexpected errors are found while checking linearization 8 * When unexpected errors are found while checking linearization
4 data, print an error message instead of calling assert, which 9 data, print an error message instead of calling assert, which
5 cause the program to crash. Fixes #209, #231. 10 cause the program to crash. Fixes #209, #231.
include/qpdf/QPDFWriter.hh
@@ -404,6 +404,18 @@ class QPDFWriter @@ -404,6 +404,18 @@ class QPDFWriter
404 QPDF_DLL 404 QPDF_DLL
405 void registerProgressReporter(PointerHolder<ProgressReporter>); 405 void registerProgressReporter(PointerHolder<ProgressReporter>);
406 406
  407 + // Return the PDF version that will be written into the header.
  408 + // Calling this method does all the preparation for writing, so it
  409 + // is an error to call any methods that may cause a change to the
  410 + // version. Adding new objects to the original file after calling
  411 + // this may also cause problems. It is safe to update existing
  412 + // objects or stream contents after calling this method, e.g., to
  413 + // include the final version number in metadata.
  414 + QPDF_DLL
  415 + std::string getFinalVersion();
  416 +
  417 + // Write the final file. There is no expectation of being able to
  418 + // call write() more than once.
407 QPDF_DLL 419 QPDF_DLL
408 void write(); 420 void write();
409 421
@@ -473,6 +485,7 @@ class QPDFWriter @@ -473,6 +485,7 @@ class QPDFWriter
473 void writeLinearized(); 485 void writeLinearized();
474 void enqueuePart(std::vector<QPDFObjectHandle>& part); 486 void enqueuePart(std::vector<QPDFObjectHandle>& part);
475 void writeEncryptionDictionary(); 487 void writeEncryptionDictionary();
  488 + void doWriteSetup();
476 void writeHeader(); 489 void writeHeader();
477 void writeHintStream(int hint_id); 490 void writeHintStream(int hint_id);
478 qpdf_offset_t writeXRefTable( 491 qpdf_offset_t writeXRefTable(
@@ -598,6 +611,7 @@ class QPDFWriter @@ -598,6 +611,7 @@ class QPDFWriter
598 bool deterministic_id; 611 bool deterministic_id;
599 Pl_MD5* md5_pipeline; 612 Pl_MD5* md5_pipeline;
600 std::string deterministic_id_data; 613 std::string deterministic_id_data;
  614 + bool did_write_setup;
601 615
602 // For linearization only 616 // For linearization only
603 std::string lin_pass1_filename; 617 std::string lin_pass1_filename;
libqpdf/QPDFWriter.cc
@@ -63,6 +63,7 @@ QPDFWriter::Members::Members(QPDF&amp; pdf) : @@ -63,6 +63,7 @@ QPDFWriter::Members::Members(QPDF&amp; pdf) :
63 max_ostream_index(0), 63 max_ostream_index(0),
64 deterministic_id(false), 64 deterministic_id(false),
65 md5_pipeline(0), 65 md5_pipeline(0),
  66 + did_write_setup(false),
66 events_expected(0), 67 events_expected(0),
67 events_seen(0), 68 events_seen(0),
68 next_progress_report(0) 69 next_progress_report(0)
@@ -2358,8 +2359,14 @@ QPDFWriter::prepareFileForWrite() @@ -2358,8 +2359,14 @@ QPDFWriter::prepareFileForWrite()
2358 } 2359 }
2359 2360
2360 void 2361 void
2361 -QPDFWriter::write() 2362 +QPDFWriter::doWriteSetup()
2362 { 2363 {
  2364 + if (this->m->did_write_setup)
  2365 + {
  2366 + return;
  2367 + }
  2368 + this->m->did_write_setup = true;
  2369 +
2363 // Do preliminary setup 2370 // Do preliminary setup
2364 2371
2365 if (this->m->linearized) 2372 if (this->m->linearized)
@@ -2507,6 +2514,23 @@ QPDFWriter::write() @@ -2507,6 +2514,23 @@ QPDFWriter::write()
2507 setMinimumPDFVersion("1.5"); 2514 setMinimumPDFVersion("1.5");
2508 } 2515 }
2509 2516
  2517 + setMinimumPDFVersion(this->m->pdf.getPDFVersion(),
  2518 + this->m->pdf.getExtensionLevel());
  2519 + this->m->final_pdf_version = this->m->min_pdf_version;
  2520 + this->m->final_extension_level = this->m->min_extension_level;
  2521 + if (! this->m->forced_pdf_version.empty())
  2522 + {
  2523 + QTC::TC("qpdf", "QPDFWriter using forced PDF version");
  2524 + this->m->final_pdf_version = this->m->forced_pdf_version;
  2525 + this->m->final_extension_level = this->m->forced_extension_level;
  2526 + }
  2527 +}
  2528 +
  2529 +void
  2530 +QPDFWriter::write()
  2531 +{
  2532 + doWriteSetup();
  2533 +
2510 // Set up progress reporting. We spent about equal amounts of time 2534 // Set up progress reporting. We spent about equal amounts of time
2511 // preparing and writing one pass. To get a rough estimate of 2535 // preparing and writing one pass. To get a rough estimate of
2512 // progress, we track handling of indirect objects. For linearized 2536 // progress, we track handling of indirect objects. For linearized
@@ -2569,20 +2593,16 @@ QPDFWriter::writeEncryptionDictionary() @@ -2569,20 +2593,16 @@ QPDFWriter::writeEncryptionDictionary()
2569 closeObject(this->m->encryption_dict_objid); 2593 closeObject(this->m->encryption_dict_objid);
2570 } 2594 }
2571 2595
  2596 +std::string
  2597 +QPDFWriter::getFinalVersion()
  2598 +{
  2599 + doWriteSetup();
  2600 + return this->m->final_pdf_version;
  2601 +}
  2602 +
2572 void 2603 void
2573 QPDFWriter::writeHeader() 2604 QPDFWriter::writeHeader()
2574 { 2605 {
2575 - setMinimumPDFVersion(this->m->pdf.getPDFVersion(),  
2576 - this->m->pdf.getExtensionLevel());  
2577 - this->m->final_pdf_version = this->m->min_pdf_version;  
2578 - this->m->final_extension_level = this->m->min_extension_level;  
2579 - if (! this->m->forced_pdf_version.empty())  
2580 - {  
2581 - QTC::TC("qpdf", "QPDFWriter using forced PDF version");  
2582 - this->m->final_pdf_version = this->m->forced_pdf_version;  
2583 - this->m->final_extension_level = this->m->forced_extension_level;  
2584 - }  
2585 -  
2586 writeString("%PDF-"); 2606 writeString("%PDF-");
2587 writeString(this->m->final_pdf_version); 2607 writeString(this->m->final_pdf_version);
2588 if (this->m->pclm) 2608 if (this->m->pclm)
qpdf/qtest/qpdf.test
@@ -175,6 +175,16 @@ $td-&gt;runtest(&quot;\@file exists and file doesn&#39;t&quot;, @@ -175,6 +175,16 @@ $td-&gt;runtest(&quot;\@file exists and file doesn&#39;t&quot;,
175 175
176 show_ntests(); 176 show_ntests();
177 # ---------- 177 # ----------
  178 +$td->notify("--- Final Version ---");
  179 +$n_tests += 1;
  180 +
  181 +$td->runtest("check final version",
  182 + {$td->COMMAND => "test_driver 54 minimal.pdf"},
  183 + {$td->STRING => "test 54 done\n", $td->EXIT_STATUS => 0},
  184 + $td->NORMALIZE_NEWLINES);
  185 +
  186 +show_ntests();
  187 +# ----------
178 $td->notify("--- Dangling Refs ---"); 188 $td->notify("--- Dangling Refs ---");
179 my @dangling = (qw(minimal dangling-refs)); 189 my @dangling = (qw(minimal dangling-refs));
180 $n_tests += 2 * scalar(@dangling); 190 $n_tests += 2 * scalar(@dangling);
qpdf/test_driver.cc
@@ -1865,6 +1865,18 @@ void runtest(int n, char const* filename1, char const* arg2) @@ -1865,6 +1865,18 @@ void runtest(int n, char const* filename1, char const* arg2)
1865 w.setStaticID(true); 1865 w.setStaticID(true);
1866 w.write(); 1866 w.write();
1867 } 1867 }
  1868 + else if (n == 54)
  1869 + {
  1870 + // Test getFinalVersion. This must be invoked with a file
  1871 + // whose final version is not 1.5.
  1872 + QPDFWriter w(pdf, "a.pdf");
  1873 + assert(pdf.getPDFVersion() != "1.5");
  1874 + w.setObjectStreamMode(qpdf_o_generate);
  1875 + if (w.getFinalVersion() != "1.5")
  1876 + {
  1877 + std::cout << "oops: " << w.getFinalVersion() << std::endl;
  1878 + }
  1879 + }
1868 else 1880 else
1869 { 1881 {
1870 throw std::runtime_error(std::string("invalid test ") + 1882 throw std::runtime_error(std::string("invalid test ") +