From 2dbc1006fb4a176c6ca7418f6e6e27251a4b8142 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Thu, 5 Aug 2010 21:06:49 +0000 Subject: [PATCH] addPageContents git-svn-id: svn+q:///qpdf/trunk@995 71b93d88-0707-0410-a8cf-f5a4172ac649 --- ChangeLog | 7 +++++ TODO | 13 ++++---- include/qpdf/QPDFObjectHandle.hh | 18 ++++++++--- libqpdf/QPDFObjectHandle.cc | 29 +++++++++++++++++ qpdf/qpdf.testcov | 2 ++ qpdf/qtest/qpdf.test | 52 ++++++++++++++++++------------ qpdf/qtest/qpdf/add-contents.pdf | 54 ++++++++++++++++++++++++++++++++ qpdf/test_driver.cc | 18 +++++++++++ 8 files changed, 161 insertions(+), 32 deletions(-) create mode 100644 qpdf/qtest/qpdf/add-contents.pdf diff --git a/ChangeLog b/ChangeLog index 1caf6ed7..1592daaf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2010-08-05 Jay Berkenbilt + + * Add new methods to QPDFObjectHandle: replaceStreamData and + newStream. These methods allow users of the qpdf library to add + new streams and to replace data of existing streams. Examples are + provided. + 2010-06-06 Jay Berkenbilt * Fix memory leak for QPDF objects whose underlying PDF objects diff --git a/TODO b/TODO index 403bc979..55980081 100644 --- a/TODO +++ b/TODO @@ -7,20 +7,19 @@ Next in August, 2009. He seems to like to send encrypted mail (key 01FCC336). Tell him about newStream and replaceStreamData. + * Tell stronghorse@tom.com about QPDFObjectHandle::addPageContents. + See message from stronghorse@tom.com ("Suggestion for qpdf") from + 2010-06-09 and my response. + 2.2 === - * Add helper routines for manipulating page content streams. - Operations should include ability to convert page contents from a - stream to an array of streams and to append or prepend to the page - contents. See message from stronghorse@tom.com ("Suggestion for - qpdf") from 2010-06-09 and my response. Consider providing an - example program that adds the line he suggests. - * Create an example that does some kind of manipulation on every image. Use QPDF::getAllPages and QPDFObjectHandle::getPageImages along with new stream data and dictionary manipulation. + * Add example program for addPageContents using suggestion from + stronghorse@tom.com's message above. General ======= diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh index 298e480b..b7df3750 100644 --- a/include/qpdf/QPDFObjectHandle.hh +++ b/include/qpdf/QPDFObjectHandle.hh @@ -288,14 +288,22 @@ class QPDFObjectHandle QPDF_DLL std::map getPageImages(); - // Throws an exception if this is not a Page object. Returns a - // vector of stream objects representing the content streams for - // the given page. This routine allows the caller to not care - // whether there are one or more than one content streams for a - // page. + // Returns a vector of stream objects representing the content + // streams for the given page. This routine allows the caller to + // not care whether there are one or more than one content streams + // for a page. Throws an exception if this is not a Page object. QPDF_DLL std::vector getPageContents(); + // Add the given object as a new content stream for this page. If + // parameter 'first' is true, add to the beginning. Otherwise, + // add to the end. This routine automatically converts the page + // contents to an array if it is a scalar, allowing the caller not + // to care what the initial structure is. Throws an exception if + // this is not a Page object. + QPDF_DLL + void addPageContents(QPDFObjectHandle contents, bool first); + // Initializers for objects. This Factory class gives the QPDF // class specific permission to call factory methods without // making it a friend of the whole QPDFObjectHandle class. diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index 19b4f94e..143a3f42 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -472,6 +472,35 @@ QPDFObjectHandle::getPageContents() return result; } +void +QPDFObjectHandle::addPageContents(QPDFObjectHandle new_contents, bool first) +{ + assertPageObject(); + new_contents.assertType("Stream", new_contents.isStream()); + + std::vector orig_contents = getPageContents(); + + std::vector content_streams; + if (first) + { + QTC::TC("qpdf", "QPDFObjectHandle prepend page contents"); + content_streams.push_back(new_contents); + } + for (std::vector::iterator iter = orig_contents.begin(); + iter != orig_contents.end(); ++iter) + { + QTC::TC("qpdf", "QPDFObjectHandle append page contents"); + content_streams.push_back(*iter); + } + if (! first) + { + content_streams.push_back(new_contents); + } + + QPDFObjectHandle contents = QPDFObjectHandle::newArray(content_streams); + this->replaceKey("/Contents", contents); +} + std::string QPDFObjectHandle::unparse() { diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 49b03c42..4a868227 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -181,3 +181,5 @@ QPDF_Stream provider length mismatch 0 QPDFObjectHandle newStream 0 QPDFObjectHandle newStream with data 0 QPDF_Stream pipe no stream data 0 +QPDFObjectHandle prepend page contents 0 +QPDFObjectHandle append page contents 0 diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index d40822cd..7d14722e 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -76,26 +76,8 @@ flush_tiff_cache(); show_ntests(); # ---------- -$td->notify("--- Miscellaneous Tests ---"); -$n_tests += 28; - -$td->runtest("qpdf version", - {$td->COMMAND => "qpdf --version"}, - {$td->REGEXP => "qpdf version \\S+\n.*", $td->EXIT_STATUS => 0}, - $td->NORMALIZE_NEWLINES); -$td->runtest("C API: qpdf version", - {$td->COMMAND => "qpdf-ctest --version"}, - {$td->REGEXP => "qpdf-ctest version \\S+\n", - $td->EXIT_STATUS => 0}, - $td->NORMALIZE_NEWLINES); - -foreach (my $i = 1; $i <= 3; ++$i) -{ - $td->runtest("misc tests", - {$td->COMMAND => "test_driver 5 misc-$i.pdf"}, - {$td->FILE => "misc-$i.out", $td->EXIT_STATUS => 0}, - $td->NORMALIZE_NEWLINES); -} +$td->notify("--- Stream Replacement Tests ---"); +$n_tests += 8; $td->runtest("replace stream data", {$td->COMMAND => "test_driver 7 qstream.pdf"}, @@ -118,6 +100,36 @@ $td->runtest("new streams", $td->runtest("new stream", {$td->FILE => "a.pdf"}, {$td->FILE => "new-streams.pdf"}); +$td->runtest("add page contents", + {$td->COMMAND => "test_driver 10 minimal.pdf"}, + {$td->STRING => "test 10 done\n", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +$td->runtest("new stream", + {$td->FILE => "a.pdf"}, + {$td->FILE => "add-contents.pdf"}); + +show_ntests(); +# ---------- +$td->notify("--- Miscellaneous Tests ---"); +$n_tests += 22; + +$td->runtest("qpdf version", + {$td->COMMAND => "qpdf --version"}, + {$td->REGEXP => "qpdf version \\S+\n.*", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +$td->runtest("C API: qpdf version", + {$td->COMMAND => "qpdf-ctest --version"}, + {$td->REGEXP => "qpdf-ctest version \\S+\n", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + +foreach (my $i = 1; $i <= 3; ++$i) +{ + $td->runtest("misc tests", + {$td->COMMAND => "test_driver 5 misc-$i.pdf"}, + {$td->FILE => "misc-$i.out", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +} # Make sure we ignore decode parameters that we don't understand $td->runtest("unknown decode parameters", diff --git a/qpdf/qtest/qpdf/add-contents.pdf b/qpdf/qtest/qpdf/add-contents.pdf new file mode 100644 index 00000000..06b683ac --- /dev/null +++ b/qpdf/qtest/qpdf/add-contents.pdf @@ -0,0 +1,54 @@ +%PDF-1.3 +%¿÷¢þ +1 0 obj +<< /Pages 2 0 R /Type /Catalog >> +endobj +2 0 obj +<< /Count 1 /Kids [ 3 0 R ] /Type /Pages >> +endobj +3 0 obj +<< /Contents [ 4 0 R 5 0 R 6 0 R ] /MediaBox [ 0 0 612 792 ] /Parent 2 0 R /Resources << /Font << /F1 7 0 R >> /ProcSet 8 0 R >> /Type /Page >> +endobj +4 0 obj +<< /Length 37 >> +stream +BT /F1 12 Tf 72 620 Td (Baked) Tj ET +endstream +endobj +5 0 obj +<< /Length 44 >> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato) Tj +ET +endstream +endobj +6 0 obj +<< /Length 38 >> +stream +BT /F1 18 Tf 72 520 Td (Mashed) Tj ET +endstream +endobj +7 0 obj +<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >> +endobj +8 0 obj +[ /PDF /Text ] +endobj +xref +0 9 +0000000000 65535 f +0000000015 00000 n +0000000064 00000 n +0000000123 00000 n +0000000282 00000 n +0000000368 00000 n +0000000461 00000 n +0000000548 00000 n +0000000655 00000 n +trailer << /Root 1 0 R /Size 9 /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] >> +startxref +685 +%%EOF diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index 09458227..710f4c6e 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -427,6 +427,24 @@ void runtest(int n, char const* filename) w.setStreamDataMode(qpdf_s_preserve); w.write(); } + else if (n == 10) + { + PointerHolder b1 = new Buffer(37); + unsigned char* bp = b1.getPointer()->getBuffer(); + memcpy(bp, (char*)"BT /F1 12 Tf 72 620 Td (Baked) Tj ET\n", 37); + PointerHolder b2 = new Buffer(38); + bp = b2.getPointer()->getBuffer(); + memcpy(bp, (char*)"BT /F1 18 Tf 72 520 Td (Mashed) Tj ET\n", 38); + + std::vector pages = pdf.getAllPages(); + pages[0].addPageContents(QPDFObjectHandle::newStream(&pdf, b1), true); + pages[0].addPageContents(QPDFObjectHandle::newStream(&pdf, b2), false); + + QPDFWriter w(pdf, "a.pdf"); + w.setStaticID(true); + w.setStreamDataMode(qpdf_s_preserve); + w.write(); + } else { throw std::runtime_error(std::string("invalid test ") +