diff --git a/ChangeLog b/ChangeLog index ad86b396..ce6018dc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,11 @@ 2012-09-06 Jay Berkenbilt + * Add new method QPDFWriter::setExtraHeaderText to add extra text, + such as application-specific comments, to near the beginning of a + PDF file. For linearized files, this appears after the + linearization parameter dictionary. For non-linearized files, it + appears right after the PDF header and non-ASCII comment. + * Make it possible to write the same QPDF object with two different QPDFWriter objects that have both called setLinearization(true) by making private method diff --git a/include/qpdf/QPDFWriter.hh b/include/qpdf/QPDFWriter.hh index 5ee90262..2c1c32f6 100644 --- a/include/qpdf/QPDFWriter.hh +++ b/include/qpdf/QPDFWriter.hh @@ -163,6 +163,18 @@ class QPDFWriter QPDF_DLL void forcePDFVersion(std::string const&); + // Provide additional text to insert in the PDF file somewhere + // near the beginning of the file. This can be used to add + // comments to the beginning of a PDF file, for example, if those + // comments are to be consumed by some other application. No + // checks are performed to ensure that the text inserted here is + // valid PDF. If you want to insert multiline comments, you will + // need to include \n in the string yourself and start each line + // with %. An extra newline will be appended if one is not + // already present at the end of your text. + QPDF_DLL + void setExtraHeaderText(std::string const&); + // Cause a static /ID value to be generated. Use only in test // suites. QPDF_DLL @@ -354,6 +366,7 @@ class QPDFWriter std::string id2; // trailer dictionary std::string min_pdf_version; std::string forced_pdf_version; + std::string extra_header_text; int encryption_dict_objid; std::string cur_data_key; std::list > to_delete; diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index 19ab1359..eb08488a 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -195,6 +195,22 @@ QPDFWriter::forcePDFVersion(std::string const& version) this->forced_pdf_version = version; } +void +QPDFWriter::setExtraHeaderText(std::string const& text) +{ + this->extra_header_text = text; + if ((this->extra_header_text.length() > 0) && + (*(this->extra_header_text.rbegin()) != '\n')) + { + QTC::TC("qpdf", "QPDFWriter extra header text add newline"); + this->extra_header_text += "\n"; + } + else + { + QTC::TC("qpdf", "QPDFWriter extra header text no newline"); + } +} + void QPDFWriter::setStaticID(bool val) { @@ -1832,6 +1848,12 @@ QPDFWriter::writeHeader() // it really should be treated as binary. writeString("\n%\xbf\xf7\xa2\xfe\n"); writeStringQDF("%QDF-1.0\n\n"); + + // Note: do not write extra header text here. Linearized PDFs + // must include the entire linearization parameter dictionary + // within the first 1024 characters of the PDF file, so for + // linearized files, we have to write extra header text after the + // linearization parameter dictionary. } void @@ -2189,7 +2211,9 @@ QPDFWriter::writeLinearized() // space to write real dictionary. 200 characters is enough // space if all numerical values in the parameter dictionary // that contain offsets are 20 digits long plus a few extra - // characters for safety. + // characters for safety. The entire linearization parameter + // dictionary must appear within the first 1024 characters of + // the file. qpdf_offset_t pos = this->pipeline->getCount(); openObject(lindict_id); @@ -2225,6 +2249,10 @@ QPDFWriter::writeLinearized() writePad(spaces); writeString("\n"); + // If the user supplied any additional header text, write it + // here after the linearization parameter dictionary. + writeString(this->extra_header_text); + // Part 3: first page cross reference table and trailer. qpdf_offset_t first_xref_offset = this->pipeline->getCount(); @@ -2396,6 +2424,7 @@ QPDFWriter::writeStandard() // Start writing writeHeader(); + writeString(this->extra_header_text); // Put root first on queue. QPDFObjectHandle trailer = pdf.getTrailer(); diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 0e062af4..f9663375 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -240,3 +240,5 @@ QPDFObjectHandle trailing data in parse 0 qpdf pages encryption password 0 QPDF_Tokenizer EOF reading token 0 QPDF_Tokenizer EOF reading appendable token 0 +QPDFWriter extra header text no newline 0 +QPDFWriter extra header text add newline 0 diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index b2ee28eb..d12f0644 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -149,7 +149,7 @@ $td->runtest("remove page we don't have", $td->NORMALIZE_NEWLINES); # ---------- $td->notify("--- Miscellaneous Tests ---"); -$n_tests += 48; +$n_tests += 53; $td->runtest("qpdf version", {$td->COMMAND => "qpdf --version"}, @@ -387,6 +387,22 @@ $td->runtest("EOF reading token", {$td->COMMAND => "qpdf --check eof-reading-token.pdf"}, {$td->FILE => "eof-reading-token.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); +$td->runtest("extra header text", + {$td->COMMAND => "test_driver 32 minimal.pdf"}, + {$td->FILE => "test-32.out", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +$td->runtest("check output", + {$td->FILE => "a.pdf"}, + {$td->FILE => "extra-header-no-newline.pdf"}); +$td->runtest("check output", + {$td->FILE => "b.pdf"}, + {$td->FILE => "extra-header-lin-no-newline.pdf"}); +$td->runtest("check output", + {$td->FILE => "c.pdf"}, + {$td->FILE => "extra-header-newline.pdf"}); +$td->runtest("check output", + {$td->FILE => "d.pdf"}, + {$td->FILE => "extra-header-lin-newline.pdf"}); show_ntests(); # ---------- diff --git a/qpdf/qtest/qpdf/extra-header-lin-newline.pdf b/qpdf/qtest/qpdf/extra-header-lin-newline.pdf new file mode 100644 index 00000000..7a5ff3e6 Binary files /dev/null and b/qpdf/qtest/qpdf/extra-header-lin-newline.pdf differ diff --git a/qpdf/qtest/qpdf/extra-header-lin-no-newline.pdf b/qpdf/qtest/qpdf/extra-header-lin-no-newline.pdf new file mode 100644 index 00000000..e115c462 Binary files /dev/null and b/qpdf/qtest/qpdf/extra-header-lin-no-newline.pdf differ diff --git a/qpdf/qtest/qpdf/extra-header-newline.pdf b/qpdf/qtest/qpdf/extra-header-newline.pdf new file mode 100644 index 00000000..427623a7 Binary files /dev/null and b/qpdf/qtest/qpdf/extra-header-newline.pdf differ diff --git a/qpdf/qtest/qpdf/extra-header-no-newline.pdf b/qpdf/qtest/qpdf/extra-header-no-newline.pdf new file mode 100644 index 00000000..1df11d30 Binary files /dev/null and b/qpdf/qtest/qpdf/extra-header-no-newline.pdf differ diff --git a/qpdf/qtest/qpdf/test-32.out b/qpdf/qtest/qpdf/test-32.out new file mode 100644 index 00000000..ff8d922b --- /dev/null +++ b/qpdf/qtest/qpdf/test-32.out @@ -0,0 +1,13 @@ +file: a.pdf +linearized: no +newline: no +file: b.pdf +linearized: yes +newline: no +file: c.pdf +linearized: no +newline: yes +file: d.pdf +linearized: yes +newline: yes +test 32 done diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index b277cdf1..311097f6 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -1091,6 +1091,27 @@ void runtest(int n, char const* filename1, char const* filename2) << std::endl; } } + else if (n == 32) + { + // Extra header text + char const* filenames[] = {"a.pdf", "b.pdf", "c.pdf", "d.pdf"}; + for (int i = 0; i < 4; ++i) + { + bool linearized = ((i & 1) != 0); + bool newline = ((i & 2) != 0); + QPDFWriter w(pdf, filenames[i]); + w.setStaticID(true); + std::cout + << "file: " << filenames[i] << std::endl + << "linearized: " << (linearized ? "yes" : "no") << std::endl + << "newline: " << (newline ? "yes" : "no") << std::endl; + w.setLinearization(linearized); + w.setExtraHeaderText(newline + ? "%% Comment with newline\n" + : "%% Comment\n% No newline"); + w.write(); + } + } else { throw std::runtime_error(std::string("invalid test ") +