From 7dc197ef88a3f19a830f38d19aba649175d53c5e Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Wed, 10 Aug 2011 12:42:48 -0400 Subject: implement replace and swap --- include/qpdf/QPDF.hh | 30 +++++ libqpdf/QPDF.cc | 33 ++++++ libqpdf/QPDFWriter.cc | 2 +- qpdf/qpdf.testcov | 1 + qpdf/qtest/qpdf.test | 11 +- qpdf/qtest/qpdf/test14-in.pdf | 264 +++++++++++++++++++++++++++++++++++++++++ qpdf/qtest/qpdf/test14-out.pdf | 105 ++++++++++++++++ qpdf/qtest/qpdf/test14.out | 6 + qpdf/test_driver.cc | 56 +++++++++ 9 files changed, 506 insertions(+), 2 deletions(-) create mode 100644 qpdf/qtest/qpdf/test14-in.pdf create mode 100644 qpdf/qtest/qpdf/test14-out.pdf create mode 100644 qpdf/qtest/qpdf/test14.out diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh index 1ef353e7..dc2b4c22 100644 --- a/include/qpdf/QPDF.hh +++ b/include/qpdf/QPDF.hh @@ -128,6 +128,36 @@ class QPDF QPDF_DLL QPDFObjectHandle getObjectByID(int objid, int generation); + // Replace the object with the given object id with the given + // object. The object handle passed in must be a direct object, + // though it may contain references to other indirect objects + // within it. Calling this method can have somewhat confusing + // results. Any existing QPDFObjectHandle instances that point to + // the old object and that have been resolved (which happens + // automatically if you access them in any way) will continue to + // point to the old object even though that object will no longer + // be associated with the PDF file. Note that replacing an object + // with QPDFObjectHandle::newNull() effectively removes the object + // from the file since a non-existent object is treated as a null + // object. + QPDF_DLL + void replaceObject(int objid, int generation, QPDFObjectHandle); + + // Swap two objects given by ID. Calling this method can have + // confusing results. After swapping two objects, existing + // QPDFObjectHandle instances that reference them will still + // reference the same underlying objects, at which point those + // existing QPDFObjectHandle instances will have incorrect + // information about the object and generation number of those + // objects. While this does not necessarily cause a problem, it + // can certainly be confusing. It is therefore recommended that + // you replace any existing QPDFObjectHandle instances that point + // to the swapped objects with new ones, possibly by calling + // getObjectByID. + QPDF_DLL + void swapObjects(int objid1, int generation1, + int objid2, int generation2); + // Encryption support enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes }; diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index c9eee703..9bdbbdb6 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -1887,6 +1887,39 @@ QPDF::getObjectByID(int objid, int generation) return QPDFObjectHandle::Factory::newIndirect(this, objid, generation); } +void +QPDF::replaceObject(int objid, int generation, QPDFObjectHandle oh) +{ + if (oh.isIndirect()) + { + QTC::TC("qpdf", "QPDF replaceObject called with indirect object"); + throw std::logic_error( + "QPDF::replaceObject called with indirect object handle"); + } + + // Force new object to appear in the cache + resolve(objid, generation); + + // Replace the object in the object cache + ObjGen og(objid, generation); + this->obj_cache[og] = + ObjCache(QPDFObjectHandle::ObjAccessor::getObject(oh), -1, -1); +} + +void +QPDF::swapObjects(int objid1, int generation1, int objid2, int generation2) +{ + // Force objects to be loaded into cache; then swap them in the + // cache. + resolve(objid1, generation1); + resolve(objid2, generation2); + ObjGen og1(objid1, generation1); + ObjGen og2(objid2, generation2); + ObjCache t = this->obj_cache[og1]; + this->obj_cache[og1] = this->obj_cache[og2]; + this->obj_cache[og2] = t; +} + void QPDF::trimTrailerForWrite() { diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index 0cba17d9..40368f69 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -70,7 +70,7 @@ QPDFWriter::QPDFWriter(QPDF& pdf, char const* filename) : QPDFWriter::~QPDFWriter() { - if (file) + if (file && close_file) { fclose(file); } diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 4e7e292a..37ad350a 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -192,3 +192,4 @@ QPDF stream without newline 0 QPDF stream with CR only 0 QPDF stream with CRNL 0 QPDF stream with NL only 0 +QPDF replaceObject called with indirect object 0 diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index 75a92aa4..de82ce3b 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -111,7 +111,7 @@ $td->runtest("new stream", show_ntests(); # ---------- $td->notify("--- Miscellaneous Tests ---"); -$n_tests += 31; +$n_tests += 33; $td->runtest("qpdf version", {$td->COMMAND => "qpdf --version"}, @@ -276,6 +276,15 @@ $td->runtest("check output", {$td->FILE => "a.qdf"}, {$td->FILE => "stream-line-enders.qdf"}); +$td->runtest("swap and replace", + {$td->COMMAND => "test_driver 14 test14-in.pdf"}, + {$td->FILE => "test14.out", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +$td->runtest("check output", + {$td->FILE => "a.pdf"}, + {$td->FILE => "test14-out.pdf"}); + show_ntests(); # ---------- $td->notify("--- Error Condition Tests ---"); diff --git a/qpdf/qtest/qpdf/test14-in.pdf b/qpdf/qtest/qpdf/test14-in.pdf new file mode 100644 index 00000000..4d761020 --- /dev/null +++ b/qpdf/qtest/qpdf/test14-in.pdf @@ -0,0 +1,264 @@ +%PDF-1.3 +%¿÷¢þ +%QDF-1.0 + +1 0 obj +<< + /Pages 4 0 R + /Type /Catalog +>> +endobj + +2 0 obj +[ + /Array +] +endobj + +3 0 obj +<< + /Dict 1 +>> +endobj + +4 0 obj +<< + /Count 4 + /Kids [ + 5 0 R + 6 0 R + 7 0 R + 8 0 R + ] + /Type /Pages +>> +endobj + +%% Page 1 +5 0 obj +<< + /Contents 9 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 4 0 R + /Resources << + /Font << + /F1 11 0 R + >> + /ProcSet 12 0 R + >> + /Type /Page +>> +endobj + +%% Page 2 +6 0 obj +<< + /Contents 13 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 4 0 R + /Resources << + /Font << + /F1 11 0 R + >> + /ProcSet 15 0 R + >> + /Type /Page +>> +endobj + +%% Page 3 +7 0 obj +<< + /Contents 16 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 4 0 R + /Resources << + /Font << + /F1 11 0 R + >> + /ProcSet 18 0 R + >> + /Type /Page +>> +endobj + +%% Page 4 +8 0 obj +<< + /Contents 19 0 R + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 4 0 R + /Resources << + /Font << + /F1 11 0 R + >> + /ProcSet 21 0 R + >> + /Type /Page +>> +endobj + +%% Contents for page 1 +9 0 obj +<< + /Length 10 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 1) Tj +ET +endstream +endobj + +10 0 obj +46 +endobj + +11 0 obj +<< + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding + /Name /F1 + /Subtype /Type1 + /Type /Font +>> +endobj + +12 0 obj +[ + /PDF + /Text +] +endobj + +%% Contents for page 2 +13 0 obj +<< + /Length 14 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 2) Tj +ET +endstream +endobj + +14 0 obj +46 +endobj + +15 0 obj +[ + /PDF + /Text +] +endobj + +%% Contents for page 3 +16 0 obj +<< + /Length 17 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 3) Tj +ET +endstream +endobj + +17 0 obj +46 +endobj + +18 0 obj +[ + /PDF + /Text +] +endobj + +%% Contents for page 4 +19 0 obj +<< + /Length 20 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 4) Tj +ET +endstream +endobj + +20 0 obj +46 +endobj + +21 0 obj +[ + /PDF + /Text +] +endobj + +xref +0 22 +0000000000 65535 f +0000000025 00000 n +0000000079 00000 n +0000000108 00000 n +0000000140 00000 n +0000000252 00000 n +0000000456 00000 n +0000000661 00000 n +0000000866 00000 n +0000001084 00000 n +0000001186 00000 n +0000001206 00000 n +0000001325 00000 n +0000001384 00000 n +0000001487 00000 n +0000001507 00000 n +0000001566 00000 n +0000001669 00000 n +0000001689 00000 n +0000001748 00000 n +0000001851 00000 n +0000001871 00000 n +trailer << + /QArray 2 0 R + /QDict 3 0 R + /Root 1 0 R + /Size 22 + /ID [<20eb74876a3e8212c1b4fd43153860b0><1bb7a926da191c58f675435d77997d21>] +>> +startxref +1907 +%%EOF diff --git a/qpdf/qtest/qpdf/test14-out.pdf b/qpdf/qtest/qpdf/test14-out.pdf new file mode 100644 index 00000000..315d174d --- /dev/null +++ b/qpdf/qtest/qpdf/test14-out.pdf @@ -0,0 +1,105 @@ +%PDF-1.3 +%¿÷¢þ +1 0 obj +<< /Pages 4 0 R /Type /Catalog >> +endobj +2 0 obj +<< /NewDict 2 >> +endobj +3 0 obj +[ /Array ] +endobj +4 0 obj +<< /Count 4 /Kids [ 5 0 R 6 0 R 7 0 R 8 0 R ] /Type /Pages >> +endobj +5 0 obj +<< /Contents 9 0 R /MediaBox [ 0 0 612 792 ] /Parent 4 0 R /Resources << /Font << /F1 10 0 R >> /ProcSet 11 0 R >> /Type /Page >> +endobj +6 0 obj +<< /Contents 12 0 R /MediaBox [ 0 0 612 792 ] /Parent 4 0 R /Resources << /Font << /F1 10 0 R >> /ProcSet 13 0 R >> /Type /Page >> +endobj +7 0 obj +<< /Contents 14 0 R /MediaBox [ 0 0 612 792 ] /Parent 4 0 R /Resources << /Font << /F1 10 0 R >> /ProcSet 15 0 R >> /Type /Page >> +endobj +8 0 obj +<< /Contents 16 0 R /MediaBox [ 0 0 612 792 ] /Parent 4 0 R /Resources << /Font << /F1 10 0 R >> /ProcSet 17 0 R >> /Type /Page >> +endobj +9 0 obj +<< /Length 46 >> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 1) Tj +ET +endstream +endobj +10 0 obj +<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >> +endobj +11 0 obj +[ /PDF /Text ] +endobj +12 0 obj +<< /Length 46 >> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 3) Tj +ET +endstream +endobj +13 0 obj +[ /PDF /Text ] +endobj +14 0 obj +<< /Length 46 >> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 2) Tj +ET +endstream +endobj +15 0 obj +[ /PDF /Text ] +endobj +16 0 obj +<< /Length 46 >> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato 4) Tj +ET +endstream +endobj +17 0 obj +[ /PDF /Text ] +endobj +xref +0 18 +0000000000 65535 f +0000000015 00000 n +0000000064 00000 n +0000000096 00000 n +0000000122 00000 n +0000000199 00000 n +0000000344 00000 n +0000000490 00000 n +0000000636 00000 n +0000000782 00000 n +0000000877 00000 n +0000000985 00000 n +0000001016 00000 n +0000001112 00000 n +0000001143 00000 n +0000001239 00000 n +0000001270 00000 n +0000001366 00000 n +trailer << /QArray 2 0 R /QDict 3 0 R /Root 1 0 R /Size 18 /ID [<20eb74876a3e8212c1b4fd43153860b0><31415926535897932384626433832795>] >> +startxref +1397 +%%EOF diff --git a/qpdf/qtest/qpdf/test14.out b/qpdf/qtest/qpdf/test14.out new file mode 100644 index 00000000..5b68d9f6 --- /dev/null +++ b/qpdf/qtest/qpdf/test14.out @@ -0,0 +1,6 @@ +caught logic error as expected +old dict: 1 +old dict: 1 +new dict: 2 +swapped array: /Array +test 14 done diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index c7b6a6b4..17451dc7 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -519,6 +519,62 @@ void runtest(int n, char const* filename) << "---error---" << std::endl << err.str(); } + else if (n == 14) + { + // Exercise swap and replace. This test case is designed for + // a specific file. + std::vector pages = pdf.getAllPages(); + if (pages.size() != 4) + { + throw std::logic_error("test " + QUtil::int_to_string(n) + + " not called 4-page file"); + } + // Swap pages 2 and 3 + pdf.swapObjects(pages[1].getObjectID(), pages[1].getGeneration(), + pages[2].getObjectID(), pages[2].getGeneration()); + // Replace object and swap objects + QPDFObjectHandle trailer = pdf.getTrailer(); + QPDFObjectHandle qdict = trailer.getKey("/QDict"); + QPDFObjectHandle qarray = trailer.getKey("/QArray"); + // Force qdict but not qarray to resolve + qdict.isDictionary(); + std::map dict_keys; + dict_keys["/NewDict"] = QPDFObjectHandle::newInteger(2); + QPDFObjectHandle new_dict = QPDFObjectHandle::newDictionary(dict_keys); + try + { + // Do it wrong first... + pdf.replaceObject(qdict.getObjectID(), qdict.getGeneration(), + qdict); + } + catch (std::logic_error) + { + std::cout << "caught logic error as expected" << std::endl; + } + pdf.replaceObject(qdict.getObjectID(), qdict.getGeneration(), + new_dict); + // Now qdict still points to the old dictionary + std::cout << "old dict: " << qdict.getKey("/Dict").getIntValue() + << std::endl; + // Swap dict and array + pdf.swapObjects(qdict.getObjectID(), qdict.getGeneration(), + qarray.getObjectID(), qarray.getGeneration()); + // Now qarray will resolve to new object but qdict is still + // the old object + std::cout << "old dict: " << qdict.getKey("/Dict").getIntValue() + << std::endl; + std::cout << "new dict: " << qarray.getKey("/NewDict").getIntValue() + << std::endl; + // Reread qdict, now pointing to an array + qdict = pdf.getObjectByID(qdict.getObjectID(), qdict.getGeneration()); + std::cout << "swapped array: " << qdict.getArrayItem(0).getName() + << std::endl; + + QPDFWriter w(pdf, "a.pdf"); + w.setStaticID(true); + w.setStreamDataMode(qpdf_s_preserve); + w.write(); + } else { throw std::runtime_error(std::string("invalid test ") + -- cgit v1.2.3-54-g00ecf