From 16fd6e64f947e17144e05325e51d792df33eaa61 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Fri, 4 Jan 2019 12:32:02 -0500 Subject: [PATCH] Add QPDFWriter::getFinalVersion (fixes #266) --- ChangeLog | 5 +++++ include/qpdf/QPDFWriter.hh | 14 ++++++++++++ libqpdf/QPDFWriter.cc | 44 +++++++++++++++++++++++++++----------- qpdf/qtest/qpdf.test | 10 +++++++++ qpdf/test_driver.cc | 12 +++++++++++ 5 files changed, 73 insertions(+), 12 deletions(-) diff --git a/ChangeLog b/ChangeLog index 7814dadb..3d889d78 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,10 @@ 2019-01-04 Jay Berkenbilt + * Add new method QPDFWriter::getFinalVersion, which returns the + PDF version that will ultimately be written to the final file. See + comments in QPDFWriter.hh for some restrictions on its use. Fixes + #266. + * When unexpected errors are found while checking linearization data, print an error message instead of calling assert, which cause the program to crash. Fixes #209, #231. diff --git a/include/qpdf/QPDFWriter.hh b/include/qpdf/QPDFWriter.hh index 4385a409..8f18dad1 100644 --- a/include/qpdf/QPDFWriter.hh +++ b/include/qpdf/QPDFWriter.hh @@ -404,6 +404,18 @@ class QPDFWriter QPDF_DLL void registerProgressReporter(PointerHolder); + // Return the PDF version that will be written into the header. + // Calling this method does all the preparation for writing, so it + // is an error to call any methods that may cause a change to the + // version. Adding new objects to the original file after calling + // this may also cause problems. It is safe to update existing + // objects or stream contents after calling this method, e.g., to + // include the final version number in metadata. + QPDF_DLL + std::string getFinalVersion(); + + // Write the final file. There is no expectation of being able to + // call write() more than once. QPDF_DLL void write(); @@ -473,6 +485,7 @@ class QPDFWriter void writeLinearized(); void enqueuePart(std::vector& part); void writeEncryptionDictionary(); + void doWriteSetup(); void writeHeader(); void writeHintStream(int hint_id); qpdf_offset_t writeXRefTable( @@ -598,6 +611,7 @@ class QPDFWriter bool deterministic_id; Pl_MD5* md5_pipeline; std::string deterministic_id_data; + bool did_write_setup; // For linearization only std::string lin_pass1_filename; diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index 22b990ff..598d4654 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -63,6 +63,7 @@ QPDFWriter::Members::Members(QPDF& pdf) : max_ostream_index(0), deterministic_id(false), md5_pipeline(0), + did_write_setup(false), events_expected(0), events_seen(0), next_progress_report(0) @@ -2358,8 +2359,14 @@ QPDFWriter::prepareFileForWrite() } void -QPDFWriter::write() +QPDFWriter::doWriteSetup() { + if (this->m->did_write_setup) + { + return; + } + this->m->did_write_setup = true; + // Do preliminary setup if (this->m->linearized) @@ -2507,6 +2514,23 @@ QPDFWriter::write() setMinimumPDFVersion("1.5"); } + setMinimumPDFVersion(this->m->pdf.getPDFVersion(), + this->m->pdf.getExtensionLevel()); + this->m->final_pdf_version = this->m->min_pdf_version; + this->m->final_extension_level = this->m->min_extension_level; + if (! this->m->forced_pdf_version.empty()) + { + QTC::TC("qpdf", "QPDFWriter using forced PDF version"); + this->m->final_pdf_version = this->m->forced_pdf_version; + this->m->final_extension_level = this->m->forced_extension_level; + } +} + +void +QPDFWriter::write() +{ + doWriteSetup(); + // Set up progress reporting. We spent about equal amounts of time // preparing and writing one pass. To get a rough estimate of // progress, we track handling of indirect objects. For linearized @@ -2569,20 +2593,16 @@ QPDFWriter::writeEncryptionDictionary() closeObject(this->m->encryption_dict_objid); } +std::string +QPDFWriter::getFinalVersion() +{ + doWriteSetup(); + return this->m->final_pdf_version; +} + void QPDFWriter::writeHeader() { - setMinimumPDFVersion(this->m->pdf.getPDFVersion(), - this->m->pdf.getExtensionLevel()); - this->m->final_pdf_version = this->m->min_pdf_version; - this->m->final_extension_level = this->m->min_extension_level; - if (! this->m->forced_pdf_version.empty()) - { - QTC::TC("qpdf", "QPDFWriter using forced PDF version"); - this->m->final_pdf_version = this->m->forced_pdf_version; - this->m->final_extension_level = this->m->forced_extension_level; - } - writeString("%PDF-"); writeString(this->m->final_pdf_version); if (this->m->pclm) diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index 0ef397c2..cad3633c 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -173,6 +173,16 @@ $td->runtest("\@file exists and file doesn't", {$td->FILE => "check-at-file.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); +show_ntests(); +# ---------- +$td->notify("--- Final Version ---"); +$n_tests += 1; + +$td->runtest("check final version", + {$td->COMMAND => "test_driver 54 minimal.pdf"}, + {$td->STRING => "test 54 done\n", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + show_ntests(); # ---------- $td->notify("--- Dangling Refs ---"); diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index 1f4b865c..8031d0cc 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -1865,6 +1865,18 @@ void runtest(int n, char const* filename1, char const* arg2) w.setStaticID(true); w.write(); } + else if (n == 54) + { + // Test getFinalVersion. This must be invoked with a file + // whose final version is not 1.5. + QPDFWriter w(pdf, "a.pdf"); + assert(pdf.getPDFVersion() != "1.5"); + w.setObjectStreamMode(qpdf_o_generate); + if (w.getFinalVersion() != "1.5") + { + std::cout << "oops: " << w.getFinalVersion() << std::endl; + } + } else { throw std::runtime_error(std::string("invalid test ") +