Commit 2dbc1006fb4a176c6ca7418f6e6e27251a4b8142

Authored by Jay Berkenbilt
1 parent c2924429

addPageContents

git-svn-id: svn+q:///qpdf/trunk@995 71b93d88-0707-0410-a8cf-f5a4172ac649
ChangeLog
  1 +2010-08-05 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * Add new methods to QPDFObjectHandle: replaceStreamData and
  4 + newStream. These methods allow users of the qpdf library to add
  5 + new streams and to replace data of existing streams. Examples are
  6 + provided.
  7 +
1 2010-06-06 Jay Berkenbilt <ejb@ql.org> 8 2010-06-06 Jay Berkenbilt <ejb@ql.org>
2 9
3 * Fix memory leak for QPDF objects whose underlying PDF objects 10 * Fix memory leak for QPDF objects whose underlying PDF objects
@@ -7,20 +7,19 @@ Next @@ -7,20 +7,19 @@ Next
7 in August, 2009. He seems to like to send encrypted mail (key 7 in August, 2009. He seems to like to send encrypted mail (key
8 01FCC336). Tell him about newStream and replaceStreamData. 8 01FCC336). Tell him about newStream and replaceStreamData.
9 9
  10 + * Tell stronghorse@tom.com about QPDFObjectHandle::addPageContents.
  11 + See message from stronghorse@tom.com ("Suggestion for qpdf") from
  12 + 2010-06-09 and my response.
  13 +
10 2.2 14 2.2
11 === 15 ===
12 16
13 - * Add helper routines for manipulating page content streams.  
14 - Operations should include ability to convert page contents from a  
15 - stream to an array of streams and to append or prepend to the page  
16 - contents. See message from stronghorse@tom.com ("Suggestion for  
17 - qpdf") from 2010-06-09 and my response. Consider providing an  
18 - example program that adds the line he suggests.  
19 -  
20 * Create an example that does some kind of manipulation on every 17 * Create an example that does some kind of manipulation on every
21 image. Use QPDF::getAllPages and QPDFObjectHandle::getPageImages 18 image. Use QPDF::getAllPages and QPDFObjectHandle::getPageImages
22 along with new stream data and dictionary manipulation. 19 along with new stream data and dictionary manipulation.
23 20
  21 + * Add example program for addPageContents using suggestion from
  22 + stronghorse@tom.com's message above.
24 23
25 General 24 General
26 ======= 25 =======
include/qpdf/QPDFObjectHandle.hh
@@ -288,14 +288,22 @@ class QPDFObjectHandle @@ -288,14 +288,22 @@ class QPDFObjectHandle
288 QPDF_DLL 288 QPDF_DLL
289 std::map<std::string, QPDFObjectHandle> getPageImages(); 289 std::map<std::string, QPDFObjectHandle> getPageImages();
290 290
291 - // Throws an exception if this is not a Page object. Returns a  
292 - // vector of stream objects representing the content streams for  
293 - // the given page. This routine allows the caller to not care  
294 - // whether there are one or more than one content streams for a  
295 - // page. 291 + // Returns a vector of stream objects representing the content
  292 + // streams for the given page. This routine allows the caller to
  293 + // not care whether there are one or more than one content streams
  294 + // for a page. Throws an exception if this is not a Page object.
296 QPDF_DLL 295 QPDF_DLL
297 std::vector<QPDFObjectHandle> getPageContents(); 296 std::vector<QPDFObjectHandle> getPageContents();
298 297
  298 + // Add the given object as a new content stream for this page. If
  299 + // parameter 'first' is true, add to the beginning. Otherwise,
  300 + // add to the end. This routine automatically converts the page
  301 + // contents to an array if it is a scalar, allowing the caller not
  302 + // to care what the initial structure is. Throws an exception if
  303 + // this is not a Page object.
  304 + QPDF_DLL
  305 + void addPageContents(QPDFObjectHandle contents, bool first);
  306 +
299 // Initializers for objects. This Factory class gives the QPDF 307 // Initializers for objects. This Factory class gives the QPDF
300 // class specific permission to call factory methods without 308 // class specific permission to call factory methods without
301 // making it a friend of the whole QPDFObjectHandle class. 309 // making it a friend of the whole QPDFObjectHandle class.
libqpdf/QPDFObjectHandle.cc
@@ -472,6 +472,35 @@ QPDFObjectHandle::getPageContents() @@ -472,6 +472,35 @@ QPDFObjectHandle::getPageContents()
472 return result; 472 return result;
473 } 473 }
474 474
  475 +void
  476 +QPDFObjectHandle::addPageContents(QPDFObjectHandle new_contents, bool first)
  477 +{
  478 + assertPageObject();
  479 + new_contents.assertType("Stream", new_contents.isStream());
  480 +
  481 + std::vector<QPDFObjectHandle> orig_contents = getPageContents();
  482 +
  483 + std::vector<QPDFObjectHandle> content_streams;
  484 + if (first)
  485 + {
  486 + QTC::TC("qpdf", "QPDFObjectHandle prepend page contents");
  487 + content_streams.push_back(new_contents);
  488 + }
  489 + for (std::vector<QPDFObjectHandle>::iterator iter = orig_contents.begin();
  490 + iter != orig_contents.end(); ++iter)
  491 + {
  492 + QTC::TC("qpdf", "QPDFObjectHandle append page contents");
  493 + content_streams.push_back(*iter);
  494 + }
  495 + if (! first)
  496 + {
  497 + content_streams.push_back(new_contents);
  498 + }
  499 +
  500 + QPDFObjectHandle contents = QPDFObjectHandle::newArray(content_streams);
  501 + this->replaceKey("/Contents", contents);
  502 +}
  503 +
475 std::string 504 std::string
476 QPDFObjectHandle::unparse() 505 QPDFObjectHandle::unparse()
477 { 506 {
qpdf/qpdf.testcov
@@ -181,3 +181,5 @@ QPDF_Stream provider length mismatch 0 @@ -181,3 +181,5 @@ QPDF_Stream provider length mismatch 0
181 QPDFObjectHandle newStream 0 181 QPDFObjectHandle newStream 0
182 QPDFObjectHandle newStream with data 0 182 QPDFObjectHandle newStream with data 0
183 QPDF_Stream pipe no stream data 0 183 QPDF_Stream pipe no stream data 0
  184 +QPDFObjectHandle prepend page contents 0
  185 +QPDFObjectHandle append page contents 0
qpdf/qtest/qpdf.test
@@ -76,26 +76,8 @@ flush_tiff_cache(); @@ -76,26 +76,8 @@ flush_tiff_cache();
76 76
77 show_ntests(); 77 show_ntests();
78 # ---------- 78 # ----------
79 -$td->notify("--- Miscellaneous Tests ---");  
80 -$n_tests += 28;  
81 -  
82 -$td->runtest("qpdf version",  
83 - {$td->COMMAND => "qpdf --version"},  
84 - {$td->REGEXP => "qpdf version \\S+\n.*", $td->EXIT_STATUS => 0},  
85 - $td->NORMALIZE_NEWLINES);  
86 -$td->runtest("C API: qpdf version",  
87 - {$td->COMMAND => "qpdf-ctest --version"},  
88 - {$td->REGEXP => "qpdf-ctest version \\S+\n",  
89 - $td->EXIT_STATUS => 0},  
90 - $td->NORMALIZE_NEWLINES);  
91 -  
92 -foreach (my $i = 1; $i <= 3; ++$i)  
93 -{  
94 - $td->runtest("misc tests",  
95 - {$td->COMMAND => "test_driver 5 misc-$i.pdf"},  
96 - {$td->FILE => "misc-$i.out", $td->EXIT_STATUS => 0},  
97 - $td->NORMALIZE_NEWLINES);  
98 -} 79 +$td->notify("--- Stream Replacement Tests ---");
  80 +$n_tests += 8;
99 81
100 $td->runtest("replace stream data", 82 $td->runtest("replace stream data",
101 {$td->COMMAND => "test_driver 7 qstream.pdf"}, 83 {$td->COMMAND => "test_driver 7 qstream.pdf"},
@@ -118,6 +100,36 @@ $td-&gt;runtest(&quot;new streams&quot;, @@ -118,6 +100,36 @@ $td-&gt;runtest(&quot;new streams&quot;,
118 $td->runtest("new stream", 100 $td->runtest("new stream",
119 {$td->FILE => "a.pdf"}, 101 {$td->FILE => "a.pdf"},
120 {$td->FILE => "new-streams.pdf"}); 102 {$td->FILE => "new-streams.pdf"});
  103 +$td->runtest("add page contents",
  104 + {$td->COMMAND => "test_driver 10 minimal.pdf"},
  105 + {$td->STRING => "test 10 done\n", $td->EXIT_STATUS => 0},
  106 + $td->NORMALIZE_NEWLINES);
  107 +$td->runtest("new stream",
  108 + {$td->FILE => "a.pdf"},
  109 + {$td->FILE => "add-contents.pdf"});
  110 +
  111 +show_ntests();
  112 +# ----------
  113 +$td->notify("--- Miscellaneous Tests ---");
  114 +$n_tests += 22;
  115 +
  116 +$td->runtest("qpdf version",
  117 + {$td->COMMAND => "qpdf --version"},
  118 + {$td->REGEXP => "qpdf version \\S+\n.*", $td->EXIT_STATUS => 0},
  119 + $td->NORMALIZE_NEWLINES);
  120 +$td->runtest("C API: qpdf version",
  121 + {$td->COMMAND => "qpdf-ctest --version"},
  122 + {$td->REGEXP => "qpdf-ctest version \\S+\n",
  123 + $td->EXIT_STATUS => 0},
  124 + $td->NORMALIZE_NEWLINES);
  125 +
  126 +foreach (my $i = 1; $i <= 3; ++$i)
  127 +{
  128 + $td->runtest("misc tests",
  129 + {$td->COMMAND => "test_driver 5 misc-$i.pdf"},
  130 + {$td->FILE => "misc-$i.out", $td->EXIT_STATUS => 0},
  131 + $td->NORMALIZE_NEWLINES);
  132 +}
121 133
122 # Make sure we ignore decode parameters that we don't understand 134 # Make sure we ignore decode parameters that we don't understand
123 $td->runtest("unknown decode parameters", 135 $td->runtest("unknown decode parameters",
qpdf/qtest/qpdf/add-contents.pdf 0 → 100644
  1 +%PDF-1.3
  2 +%¿÷¢þ
  3 +1 0 obj
  4 +<< /Pages 2 0 R /Type /Catalog >>
  5 +endobj
  6 +2 0 obj
  7 +<< /Count 1 /Kids [ 3 0 R ] /Type /Pages >>
  8 +endobj
  9 +3 0 obj
  10 +<< /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 >>
  11 +endobj
  12 +4 0 obj
  13 +<< /Length 37 >>
  14 +stream
  15 +BT /F1 12 Tf 72 620 Td (Baked) Tj ET
  16 +endstream
  17 +endobj
  18 +5 0 obj
  19 +<< /Length 44 >>
  20 +stream
  21 +BT
  22 + /F1 24 Tf
  23 + 72 720 Td
  24 + (Potato) Tj
  25 +ET
  26 +endstream
  27 +endobj
  28 +6 0 obj
  29 +<< /Length 38 >>
  30 +stream
  31 +BT /F1 18 Tf 72 520 Td (Mashed) Tj ET
  32 +endstream
  33 +endobj
  34 +7 0 obj
  35 +<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >>
  36 +endobj
  37 +8 0 obj
  38 +[ /PDF /Text ]
  39 +endobj
  40 +xref
  41 +0 9
  42 +0000000000 65535 f
  43 +0000000015 00000 n
  44 +0000000064 00000 n
  45 +0000000123 00000 n
  46 +0000000282 00000 n
  47 +0000000368 00000 n
  48 +0000000461 00000 n
  49 +0000000548 00000 n
  50 +0000000655 00000 n
  51 +trailer << /Root 1 0 R /Size 9 /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] >>
  52 +startxref
  53 +685
  54 +%%EOF
qpdf/test_driver.cc
@@ -427,6 +427,24 @@ void runtest(int n, char const* filename) @@ -427,6 +427,24 @@ void runtest(int n, char const* filename)
427 w.setStreamDataMode(qpdf_s_preserve); 427 w.setStreamDataMode(qpdf_s_preserve);
428 w.write(); 428 w.write();
429 } 429 }
  430 + else if (n == 10)
  431 + {
  432 + PointerHolder<Buffer> b1 = new Buffer(37);
  433 + unsigned char* bp = b1.getPointer()->getBuffer();
  434 + memcpy(bp, (char*)"BT /F1 12 Tf 72 620 Td (Baked) Tj ET\n", 37);
  435 + PointerHolder<Buffer> b2 = new Buffer(38);
  436 + bp = b2.getPointer()->getBuffer();
  437 + memcpy(bp, (char*)"BT /F1 18 Tf 72 520 Td (Mashed) Tj ET\n", 38);
  438 +
  439 + std::vector<QPDFObjectHandle> pages = pdf.getAllPages();
  440 + pages[0].addPageContents(QPDFObjectHandle::newStream(&pdf, b1), true);
  441 + pages[0].addPageContents(QPDFObjectHandle::newStream(&pdf, b2), false);
  442 +
  443 + QPDFWriter w(pdf, "a.pdf");
  444 + w.setStaticID(true);
  445 + w.setStreamDataMode(qpdf_s_preserve);
  446 + w.write();
  447 + }
430 else 448 else
431 { 449 {
432 throw std::runtime_error(std::string("invalid test ") + 450 throw std::runtime_error(std::string("invalid test ") +