Commit 16fd6e64f947e17144e05325e51d792df33eaa61

Authored by Jay Berkenbilt
1 parent 837dcf8f

Add QPDFWriter::getFinalVersion (fixes #266)

ChangeLog
1 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 8 * When unexpected errors are found while checking linearization
4 9 data, print an error message instead of calling assert, which
5 10 cause the program to crash. Fixes #209, #231.
... ...
include/qpdf/QPDFWriter.hh
... ... @@ -404,6 +404,18 @@ class QPDFWriter
404 404 QPDF_DLL
405 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 419 QPDF_DLL
408 420 void write();
409 421  
... ... @@ -473,6 +485,7 @@ class QPDFWriter
473 485 void writeLinearized();
474 486 void enqueuePart(std::vector<QPDFObjectHandle>& part);
475 487 void writeEncryptionDictionary();
  488 + void doWriteSetup();
476 489 void writeHeader();
477 490 void writeHintStream(int hint_id);
478 491 qpdf_offset_t writeXRefTable(
... ... @@ -598,6 +611,7 @@ class QPDFWriter
598 611 bool deterministic_id;
599 612 Pl_MD5* md5_pipeline;
600 613 std::string deterministic_id_data;
  614 + bool did_write_setup;
601 615  
602 616 // For linearization only
603 617 std::string lin_pass1_filename;
... ...
libqpdf/QPDFWriter.cc
... ... @@ -63,6 +63,7 @@ QPDFWriter::Members::Members(QPDF&amp; pdf) :
63 63 max_ostream_index(0),
64 64 deterministic_id(false),
65 65 md5_pipeline(0),
  66 + did_write_setup(false),
66 67 events_expected(0),
67 68 events_seen(0),
68 69 next_progress_report(0)
... ... @@ -2358,8 +2359,14 @@ QPDFWriter::prepareFileForWrite()
2358 2359 }
2359 2360  
2360 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 2370 // Do preliminary setup
2364 2371  
2365 2372 if (this->m->linearized)
... ... @@ -2507,6 +2514,23 @@ QPDFWriter::write()
2507 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 2534 // Set up progress reporting. We spent about equal amounts of time
2511 2535 // preparing and writing one pass. To get a rough estimate of
2512 2536 // progress, we track handling of indirect objects. For linearized
... ... @@ -2569,20 +2593,16 @@ QPDFWriter::writeEncryptionDictionary()
2569 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 2603 void
2573 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 2606 writeString("%PDF-");
2587 2607 writeString(this->m->final_pdf_version);
2588 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 175  
176 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 188 $td->notify("--- Dangling Refs ---");
179 189 my @dangling = (qw(minimal dangling-refs));
180 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 1865 w.setStaticID(true);
1866 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 1880 else
1869 1881 {
1870 1882 throw std::runtime_error(std::string("invalid test ") +
... ...