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 8 2010-06-06 Jay Berkenbilt <ejb@ql.org>
2 9  
3 10 * Fix memory leak for QPDF objects whose underlying PDF objects
... ...
... ... @@ -7,20 +7,19 @@ Next
7 7 in August, 2009. He seems to like to send encrypted mail (key
8 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 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 17 * Create an example that does some kind of manipulation on every
21 18 image. Use QPDF::getAllPages and QPDFObjectHandle::getPageImages
22 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 24 General
26 25 =======
... ...
include/qpdf/QPDFObjectHandle.hh
... ... @@ -288,14 +288,22 @@ class QPDFObjectHandle
288 288 QPDF_DLL
289 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 295 QPDF_DLL
297 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 307 // Initializers for objects. This Factory class gives the QPDF
300 308 // class specific permission to call factory methods without
301 309 // making it a friend of the whole QPDFObjectHandle class.
... ...
libqpdf/QPDFObjectHandle.cc
... ... @@ -472,6 +472,35 @@ QPDFObjectHandle::getPageContents()
472 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 504 std::string
476 505 QPDFObjectHandle::unparse()
477 506 {
... ...
qpdf/qpdf.testcov
... ... @@ -181,3 +181,5 @@ QPDF_Stream provider length mismatch 0
181 181 QPDFObjectHandle newStream 0
182 182 QPDFObjectHandle newStream with data 0
183 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 76  
77 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 82 $td->runtest("replace stream data",
101 83 {$td->COMMAND => "test_driver 7 qstream.pdf"},
... ... @@ -118,6 +100,36 @@ $td-&gt;runtest(&quot;new streams&quot;,
118 100 $td->runtest("new stream",
119 101 {$td->FILE => "a.pdf"},
120 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 134 # Make sure we ignore decode parameters that we don't understand
123 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 427 w.setStreamDataMode(qpdf_s_preserve);
428 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 448 else
431 449 {
432 450 throw std::runtime_error(std::string("invalid test ") +
... ...