aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/qpdf/QPDF.hh30
-rw-r--r--libqpdf/QPDF.cc33
-rw-r--r--libqpdf/QPDFWriter.cc2
-rw-r--r--qpdf/qpdf.testcov1
-rw-r--r--qpdf/qtest/qpdf.test11
-rw-r--r--qpdf/qtest/qpdf/test14-in.pdf264
-rw-r--r--qpdf/qtest/qpdf/test14-out.pdf105
-rw-r--r--qpdf/qtest/qpdf/test14.out6
-rw-r--r--qpdf/test_driver.cc56
9 files changed, 506 insertions, 2 deletions
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
@@ -1888,6 +1888,39 @@ QPDF::getObjectByID(int objid, int 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()
{
// Note that removing the encryption dictionary does not interfere
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<QPDFObjectHandle> 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<std::string, QPDFObjectHandle> 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 ") +