Commit 8a217eb3a26931453b4f003c6c18ad8569230cf1
1 parent
af64668a
Add concept of reserved objects
QPDFObjectHandle::{new,is,assert}Reserved, QPDF::replaceReserved
provide a mechanism to add objects to a PDF file when there are
circular references. This is a prerequisite to copying objects from
one PDF to another.
Showing
13 changed files
with
280 additions
and
7 deletions
ChangeLog
| 1 | +2012-07-08 Jay Berkenbilt <ejb@ql.org> | ||
| 2 | + | ||
| 3 | + * Add QPDFObjectHandle::newReserved to create a reserved object | ||
| 4 | + and QPDF::replaceReserved to replace it with a real object. | ||
| 5 | + QPDFObjectHandle::newReserved reserves an object ID in a QPDF | ||
| 6 | + object and ensures that any references to it remain unresolved. | ||
| 7 | + When QPDF::replaceReserved is later called, previous references to | ||
| 8 | + the reserved object will properly resolve to the replaced object. | ||
| 9 | + | ||
| 1 | 2012-07-07 Jay Berkenbilt <ejb@ql.org> | 10 | 2012-07-07 Jay Berkenbilt <ejb@ql.org> |
| 2 | 11 | ||
| 3 | * NOTE: BREAKING API CHANGE. Remove previously required length | 12 | * NOTE: BREAKING API CHANGE. Remove previously required length |
include/qpdf/QPDF.hh
| @@ -161,7 +161,8 @@ class QPDF | @@ -161,7 +161,8 @@ class QPDF | ||
| 161 | // be associated with the PDF file. Note that replacing an object | 161 | // be associated with the PDF file. Note that replacing an object |
| 162 | // with QPDFObjectHandle::newNull() effectively removes the object | 162 | // with QPDFObjectHandle::newNull() effectively removes the object |
| 163 | // from the file since a non-existent object is treated as a null | 163 | // from the file since a non-existent object is treated as a null |
| 164 | - // object. | 164 | + // object. To replace a reserved object, call replaceReserved |
| 165 | + // instead. | ||
| 165 | QPDF_DLL | 166 | QPDF_DLL |
| 166 | void replaceObject(int objid, int generation, QPDFObjectHandle); | 167 | void replaceObject(int objid, int generation, QPDFObjectHandle); |
| 167 | 168 | ||
| @@ -180,6 +181,15 @@ class QPDF | @@ -180,6 +181,15 @@ class QPDF | ||
| 180 | void swapObjects(int objid1, int generation1, | 181 | void swapObjects(int objid1, int generation1, |
| 181 | int objid2, int generation2); | 182 | int objid2, int generation2); |
| 182 | 183 | ||
| 184 | + // Replace a reserved object. This is a wrapper around | ||
| 185 | + // replaceObject but it guarantees that the underlying object is a | ||
| 186 | + // reserved object. After this call, reserved will be a reference | ||
| 187 | + // to replacement. | ||
| 188 | + QPDF_DLL | ||
| 189 | + void | ||
| 190 | + replaceReserved(QPDFObjectHandle reserved, | ||
| 191 | + QPDFObjectHandle replacement); | ||
| 192 | + | ||
| 183 | // Encryption support | 193 | // Encryption support |
| 184 | 194 | ||
| 185 | enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes }; | 195 | enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes }; |
include/qpdf/QPDFObjectHandle.hh
| @@ -81,6 +81,8 @@ class QPDFObjectHandle | @@ -81,6 +81,8 @@ class QPDFObjectHandle | ||
| 81 | bool isDictionary(); | 81 | bool isDictionary(); |
| 82 | QPDF_DLL | 82 | QPDF_DLL |
| 83 | bool isStream(); | 83 | bool isStream(); |
| 84 | + QPDF_DLL | ||
| 85 | + bool isReserved(); | ||
| 84 | 86 | ||
| 85 | // This returns true in addition to the query for the specific | 87 | // This returns true in addition to the query for the specific |
| 86 | // type for indirect objects. | 88 | // type for indirect objects. |
| @@ -148,6 +150,24 @@ class QPDFObjectHandle | @@ -148,6 +150,24 @@ class QPDFObjectHandle | ||
| 148 | QPDF_DLL | 150 | QPDF_DLL |
| 149 | static QPDFObjectHandle newStream(QPDF* qpdf, std::string const& data); | 151 | static QPDFObjectHandle newStream(QPDF* qpdf, std::string const& data); |
| 150 | 152 | ||
| 153 | + // A reserved object is a special sentinel used for qpdf to | ||
| 154 | + // reserve a spot for an object that is going to be added to the | ||
| 155 | + // QPDF object. Normally you don't have to use this type since | ||
| 156 | + // you can just call QPDF::makeIndirectObject. However, in some | ||
| 157 | + // cases, if you have to create objects with circular references, | ||
| 158 | + // you may need to create a reserved object so that you can have a | ||
| 159 | + // reference to it and then replace the object later. Reserved | ||
| 160 | + // objects have the special property that they can't be resolved | ||
| 161 | + // to direct objects. This makes it possible to replace a | ||
| 162 | + // reserved object with a new object while preserving existing | ||
| 163 | + // references to them. When you are ready to replace a reserved | ||
| 164 | + // object with its replacement, use QPDF::replaceReserved for this | ||
| 165 | + // purpose rather than the more general QPDF::replaceObject. It | ||
| 166 | + // is an error to try to write a QPDF with QPDFWriter if it has | ||
| 167 | + // any reserved objects in it. | ||
| 168 | + QPDF_DLL | ||
| 169 | + static QPDFObjectHandle newReserved(QPDF* qpdf); | ||
| 170 | + | ||
| 151 | // Accessor methods. If an accessor method that is valid for only | 171 | // Accessor methods. If an accessor method that is valid for only |
| 152 | // a particular object type is called on an object of the wrong | 172 | // a particular object type is called on an object of the wrong |
| 153 | // type, an exception is thrown. | 173 | // type, an exception is thrown. |
| @@ -430,6 +450,8 @@ class QPDFObjectHandle | @@ -430,6 +450,8 @@ class QPDFObjectHandle | ||
| 430 | void assertDictionary(); | 450 | void assertDictionary(); |
| 431 | QPDF_DLL | 451 | QPDF_DLL |
| 432 | void assertStream(); | 452 | void assertStream(); |
| 453 | + QPDF_DLL | ||
| 454 | + void assertReserved(); | ||
| 433 | 455 | ||
| 434 | QPDF_DLL | 456 | QPDF_DLL |
| 435 | void assertScalar(); | 457 | void assertScalar(); |
| @@ -459,6 +481,7 @@ class QPDFObjectHandle | @@ -459,6 +481,7 @@ class QPDFObjectHandle | ||
| 459 | int objid; // 0 for direct object | 481 | int objid; // 0 for direct object |
| 460 | int generation; | 482 | int generation; |
| 461 | PointerHolder<QPDFObject> obj; | 483 | PointerHolder<QPDFObject> obj; |
| 484 | + bool reserved; | ||
| 462 | }; | 485 | }; |
| 463 | 486 | ||
| 464 | #endif // __QPDFOBJECTHANDLE_HH__ | 487 | #endif // __QPDFOBJECTHANDLE_HH__ |
libqpdf/QPDF.cc
| @@ -2057,6 +2057,18 @@ QPDF::replaceObject(int objid, int generation, QPDFObjectHandle oh) | @@ -2057,6 +2057,18 @@ QPDF::replaceObject(int objid, int generation, QPDFObjectHandle oh) | ||
| 2057 | } | 2057 | } |
| 2058 | 2058 | ||
| 2059 | void | 2059 | void |
| 2060 | +QPDF::replaceReserved(QPDFObjectHandle reserved, | ||
| 2061 | + QPDFObjectHandle replacement) | ||
| 2062 | +{ | ||
| 2063 | + QTC::TC("qpdf", "QPDF replaceReserved"); | ||
| 2064 | + reserved.assertReserved(); | ||
| 2065 | + replaceObject(reserved.getObjectID(), | ||
| 2066 | + reserved.getGeneration(), | ||
| 2067 | + replacement); | ||
| 2068 | +} | ||
| 2069 | + | ||
| 2070 | + | ||
| 2071 | +void | ||
| 2060 | QPDF::swapObjects(int objid1, int generation1, int objid2, int generation2) | 2072 | QPDF::swapObjects(int objid1, int generation1, int objid2, int generation2) |
| 2061 | { | 2073 | { |
| 2062 | // Force objects to be loaded into cache; then swap them in the | 2074 | // Force objects to be loaded into cache; then swap them in the |
libqpdf/QPDFObjectHandle.cc
| @@ -10,6 +10,7 @@ | @@ -10,6 +10,7 @@ | ||
| 10 | #include <qpdf/QPDF_Array.hh> | 10 | #include <qpdf/QPDF_Array.hh> |
| 11 | #include <qpdf/QPDF_Dictionary.hh> | 11 | #include <qpdf/QPDF_Dictionary.hh> |
| 12 | #include <qpdf/QPDF_Stream.hh> | 12 | #include <qpdf/QPDF_Stream.hh> |
| 13 | +#include <qpdf/QPDF_Reserved.hh> | ||
| 13 | 14 | ||
| 14 | #include <qpdf/QTC.hh> | 15 | #include <qpdf/QTC.hh> |
| 15 | #include <qpdf/QUtil.hh> | 16 | #include <qpdf/QUtil.hh> |
| @@ -20,7 +21,8 @@ | @@ -20,7 +21,8 @@ | ||
| 20 | QPDFObjectHandle::QPDFObjectHandle() : | 21 | QPDFObjectHandle::QPDFObjectHandle() : |
| 21 | initialized(false), | 22 | initialized(false), |
| 22 | objid(0), | 23 | objid(0), |
| 23 | - generation(0) | 24 | + generation(0), |
| 25 | + reserved(false) | ||
| 24 | { | 26 | { |
| 25 | } | 27 | } |
| 26 | 28 | ||
| @@ -28,7 +30,8 @@ QPDFObjectHandle::QPDFObjectHandle(QPDF* qpdf, int objid, int generation) : | @@ -28,7 +30,8 @@ QPDFObjectHandle::QPDFObjectHandle(QPDF* qpdf, int objid, int generation) : | ||
| 28 | initialized(true), | 30 | initialized(true), |
| 29 | qpdf(qpdf), | 31 | qpdf(qpdf), |
| 30 | objid(objid), | 32 | objid(objid), |
| 31 | - generation(generation) | 33 | + generation(generation), |
| 34 | + reserved(false) | ||
| 32 | { | 35 | { |
| 33 | } | 36 | } |
| 34 | 37 | ||
| @@ -37,7 +40,8 @@ QPDFObjectHandle::QPDFObjectHandle(QPDFObject* data) : | @@ -37,7 +40,8 @@ QPDFObjectHandle::QPDFObjectHandle(QPDFObject* data) : | ||
| 37 | qpdf(0), | 40 | qpdf(0), |
| 38 | objid(0), | 41 | objid(0), |
| 39 | generation(0), | 42 | generation(0), |
| 40 | - obj(data) | 43 | + obj(data), |
| 44 | + reserved(false) | ||
| 41 | { | 45 | { |
| 42 | } | 46 | } |
| 43 | 47 | ||
| @@ -166,6 +170,14 @@ QPDFObjectHandle::isStream() | @@ -166,6 +170,14 @@ QPDFObjectHandle::isStream() | ||
| 166 | } | 170 | } |
| 167 | 171 | ||
| 168 | bool | 172 | bool |
| 173 | +QPDFObjectHandle::isReserved() | ||
| 174 | +{ | ||
| 175 | + // dereference will clear reserved if this has been replaced | ||
| 176 | + dereference(); | ||
| 177 | + return this->reserved; | ||
| 178 | +} | ||
| 179 | + | ||
| 180 | +bool | ||
| 169 | QPDFObjectHandle::isIndirect() | 181 | QPDFObjectHandle::isIndirect() |
| 170 | { | 182 | { |
| 171 | assertInitialized(); | 183 | assertInitialized(); |
| @@ -568,6 +580,11 @@ QPDFObjectHandle::unparse() | @@ -568,6 +580,11 @@ QPDFObjectHandle::unparse() | ||
| 568 | std::string | 580 | std::string |
| 569 | QPDFObjectHandle::unparseResolved() | 581 | QPDFObjectHandle::unparseResolved() |
| 570 | { | 582 | { |
| 583 | + if (this->reserved) | ||
| 584 | + { | ||
| 585 | + throw std::logic_error( | ||
| 586 | + "QPDFObjectHandle: attempting to unparse a reserved object"); | ||
| 587 | + } | ||
| 571 | dereference(); | 588 | dereference(); |
| 572 | return this->obj->unparse(); | 589 | return this->obj->unparse(); |
| 573 | } | 590 | } |
| @@ -690,6 +707,19 @@ QPDFObjectHandle::newStream(QPDF* qpdf, std::string const& data) | @@ -690,6 +707,19 @@ QPDFObjectHandle::newStream(QPDF* qpdf, std::string const& data) | ||
| 690 | } | 707 | } |
| 691 | 708 | ||
| 692 | QPDFObjectHandle | 709 | QPDFObjectHandle |
| 710 | +QPDFObjectHandle::newReserved(QPDF* qpdf) | ||
| 711 | +{ | ||
| 712 | + // Reserve a spot for this object by assigning it an object | ||
| 713 | + // number, but then return an unresolved handle to the object. | ||
| 714 | + QPDFObjectHandle reserved = qpdf->makeIndirectObject( | ||
| 715 | + QPDFObjectHandle(new QPDF_Reserved())); | ||
| 716 | + QPDFObjectHandle result = | ||
| 717 | + newIndirect(qpdf, reserved.objid, reserved.generation); | ||
| 718 | + result.reserved = true; | ||
| 719 | + return result; | ||
| 720 | +} | ||
| 721 | + | ||
| 722 | +QPDFObjectHandle | ||
| 693 | QPDFObjectHandle::shallowCopy() | 723 | QPDFObjectHandle::shallowCopy() |
| 694 | { | 724 | { |
| 695 | assertInitialized(); | 725 | assertInitialized(); |
| @@ -746,6 +776,13 @@ QPDFObjectHandle::makeDirectInternal(std::set<int>& visited) | @@ -746,6 +776,13 @@ QPDFObjectHandle::makeDirectInternal(std::set<int>& visited) | ||
| 746 | visited.insert(cur_objid); | 776 | visited.insert(cur_objid); |
| 747 | } | 777 | } |
| 748 | 778 | ||
| 779 | + if (isReserved()) | ||
| 780 | + { | ||
| 781 | + throw std::logic_error( | ||
| 782 | + "QPDFObjectHandle: attempting to make a" | ||
| 783 | + " reserved object handle direct"); | ||
| 784 | + } | ||
| 785 | + | ||
| 749 | dereference(); | 786 | dereference(); |
| 750 | this->objid = 0; | 787 | this->objid = 0; |
| 751 | this->generation = 0; | 788 | this->generation = 0; |
| @@ -903,6 +940,12 @@ QPDFObjectHandle::assertStream() | @@ -903,6 +940,12 @@ QPDFObjectHandle::assertStream() | ||
| 903 | } | 940 | } |
| 904 | 941 | ||
| 905 | void | 942 | void |
| 943 | +QPDFObjectHandle::assertReserved() | ||
| 944 | +{ | ||
| 945 | + assertType("Reserved", isReserved()); | ||
| 946 | +} | ||
| 947 | + | ||
| 948 | +void | ||
| 906 | QPDFObjectHandle::assertScalar() | 949 | QPDFObjectHandle::assertScalar() |
| 907 | { | 950 | { |
| 908 | assertType("Scalar", isScalar()); | 951 | assertType("Scalar", isScalar()); |
| @@ -929,12 +972,21 @@ QPDFObjectHandle::dereference() | @@ -929,12 +972,21 @@ QPDFObjectHandle::dereference() | ||
| 929 | { | 972 | { |
| 930 | if (this->obj.getPointer() == 0) | 973 | if (this->obj.getPointer() == 0) |
| 931 | { | 974 | { |
| 932 | - this->obj = QPDF::Resolver::resolve( | 975 | + PointerHolder<QPDFObject> obj = QPDF::Resolver::resolve( |
| 933 | this->qpdf, this->objid, this->generation); | 976 | this->qpdf, this->objid, this->generation); |
| 934 | - if (this->obj.getPointer() == 0) | 977 | + if (obj.getPointer() == 0) |
| 935 | { | 978 | { |
| 936 | QTC::TC("qpdf", "QPDFObjectHandle indirect to unknown"); | 979 | QTC::TC("qpdf", "QPDFObjectHandle indirect to unknown"); |
| 937 | this->obj = new QPDF_Null(); | 980 | this->obj = new QPDF_Null(); |
| 938 | } | 981 | } |
| 982 | + else if (dynamic_cast<QPDF_Reserved*>(obj.getPointer())) | ||
| 983 | + { | ||
| 984 | + // Do not resolve | ||
| 985 | + } | ||
| 986 | + else | ||
| 987 | + { | ||
| 988 | + this->reserved = false; | ||
| 989 | + this->obj = obj; | ||
| 990 | + } | ||
| 939 | } | 991 | } |
| 940 | } | 992 | } |
libqpdf/QPDF_Reserved.cc
0 โ 100644
libqpdf/build.mk
| @@ -39,6 +39,7 @@ SRCS_libqpdf = \ | @@ -39,6 +39,7 @@ SRCS_libqpdf = \ | ||
| 39 | libqpdf/QPDF_Name.cc \ | 39 | libqpdf/QPDF_Name.cc \ |
| 40 | libqpdf/QPDF_Null.cc \ | 40 | libqpdf/QPDF_Null.cc \ |
| 41 | libqpdf/QPDF_Real.cc \ | 41 | libqpdf/QPDF_Real.cc \ |
| 42 | + libqpdf/QPDF_Reserved.cc \ | ||
| 42 | libqpdf/QPDF_Stream.cc \ | 43 | libqpdf/QPDF_Stream.cc \ |
| 43 | libqpdf/QPDF_String.cc \ | 44 | libqpdf/QPDF_String.cc \ |
| 44 | libqpdf/QPDF_encryption.cc \ | 45 | libqpdf/QPDF_encryption.cc \ |
libqpdf/qpdf/QPDF_Reserved.hh
0 โ 100644
qpdf/qpdf.testcov
| @@ -217,3 +217,4 @@ QPDFObjectHandle newStream with string 0 | @@ -217,3 +217,4 @@ QPDFObjectHandle newStream with string 0 | ||
| 217 | QPDF unknown key not inherited 0 | 217 | QPDF unknown key not inherited 0 |
| 218 | QPDF_Stream provider length not provided 0 | 218 | QPDF_Stream provider length not provided 0 |
| 219 | QPDF_Stream unknown stream length 0 | 219 | QPDF_Stream unknown stream length 0 |
| 220 | +QPDF replaceReserved 0 |
qpdf/qtest/qpdf.test
| @@ -149,7 +149,7 @@ $td->runtest("remove page we don't have", | @@ -149,7 +149,7 @@ $td->runtest("remove page we don't have", | ||
| 149 | $td->NORMALIZE_NEWLINES); | 149 | $td->NORMALIZE_NEWLINES); |
| 150 | # ---------- | 150 | # ---------- |
| 151 | $td->notify("--- Miscellaneous Tests ---"); | 151 | $td->notify("--- Miscellaneous Tests ---"); |
| 152 | -$n_tests += 41; | 152 | +$n_tests += 43; |
| 153 | 153 | ||
| 154 | $td->runtest("qpdf version", | 154 | $td->runtest("qpdf version", |
| 155 | {$td->COMMAND => "qpdf --version"}, | 155 | {$td->COMMAND => "qpdf --version"}, |
| @@ -358,6 +358,13 @@ $td->runtest("warn for unknown key in Pages", | @@ -358,6 +358,13 @@ $td->runtest("warn for unknown key in Pages", | ||
| 358 | {$td->COMMAND => "test_driver 23 lin-special.pdf"}, | 358 | {$td->COMMAND => "test_driver 23 lin-special.pdf"}, |
| 359 | {$td->FILE => "pages-warning.out", $td->EXIT_STATUS => 0}, | 359 | {$td->FILE => "pages-warning.out", $td->EXIT_STATUS => 0}, |
| 360 | $td->NORMALIZE_NEWLINES); | 360 | $td->NORMALIZE_NEWLINES); |
| 361 | +$td->runtest("reserved objects", | ||
| 362 | + {$td->COMMAND => "test_driver 24 minimal.pdf"}, | ||
| 363 | + {$td->FILE => "reserved-objects.out", $td->EXIT_STATUS => 0}, | ||
| 364 | + $td->NORMALIZE_NEWLINES); | ||
| 365 | +$td->runtest("check output", | ||
| 366 | + {$td->FILE => "a.pdf"}, | ||
| 367 | + {$td->FILE => "reserved-objects.pdf"}); | ||
| 361 | 368 | ||
| 362 | show_ntests(); | 369 | show_ntests(); |
| 363 | # ---------- | 370 | # ---------- |
qpdf/qtest/qpdf/reserved-objects.out
0 โ 100644
| 1 | +res1 is still reserved after checking if array | ||
| 2 | +res1 is no longer reserved | ||
| 3 | +res1 is an array | ||
| 4 | +logic error: QPDFObjectHandle: attempting to unparse a reserved object | ||
| 5 | +logic error: QPDFObjectHandle: attempting to make a reserved object handle direct | ||
| 6 | +res2 is an array | ||
| 7 | +circular access and lazy resolution worked | ||
| 8 | +test 24 done |
qpdf/qtest/qpdf/reserved-objects.pdf
0 โ 100644
| 1 | +%PDF-1.3 | ||
| 2 | +%ยฟรทยขรพ | ||
| 3 | +1 0 obj | ||
| 4 | +<< /Pages 4 0 R /Type /Catalog >> | ||
| 5 | +endobj | ||
| 6 | +2 0 obj | ||
| 7 | +[ 3 0 R 1 ] | ||
| 8 | +endobj | ||
| 9 | +3 0 obj | ||
| 10 | +[ 2 0 R 2 ] | ||
| 11 | +endobj | ||
| 12 | +4 0 obj | ||
| 13 | +<< /Count 1 /Kids [ 5 0 R ] /Type /Pages >> | ||
| 14 | +endobj | ||
| 15 | +5 0 obj | ||
| 16 | +<< /Contents 6 0 R /MediaBox [ 0 0 612 792 ] /Parent 4 0 R /Resources << /Font << /F1 7 0 R >> /ProcSet 8 0 R >> /Type /Page >> | ||
| 17 | +endobj | ||
| 18 | +6 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 | +7 0 obj | ||
| 29 | +<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >> | ||
| 30 | +endobj | ||
| 31 | +8 0 obj | ||
| 32 | +[ /PDF /Text ] | ||
| 33 | +endobj | ||
| 34 | +xref | ||
| 35 | +0 9 | ||
| 36 | +0000000000 65535 f | ||
| 37 | +0000000015 00000 n | ||
| 38 | +0000000064 00000 n | ||
| 39 | +0000000091 00000 n | ||
| 40 | +0000000118 00000 n | ||
| 41 | +0000000177 00000 n | ||
| 42 | +0000000320 00000 n | ||
| 43 | +0000000413 00000 n | ||
| 44 | +0000000520 00000 n | ||
| 45 | +trailer << /Root 1 0 R /Size 9 Array1 2 0 R Array2 3 0 R /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] >> | ||
| 46 | +startxref | ||
| 47 | +550 | ||
| 48 | +%%EOF |
qpdf/test_driver.cc
| @@ -840,6 +840,82 @@ void runtest(int n, char const* filename) | @@ -840,6 +840,82 @@ void runtest(int n, char const* filename) | ||
| 840 | std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages(); | 840 | std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages(); |
| 841 | pdf.removePage(pages.back()); | 841 | pdf.removePage(pages.back()); |
| 842 | } | 842 | } |
| 843 | + else if (n == 24) | ||
| 844 | + { | ||
| 845 | + // Test behavior of reserved objects | ||
| 846 | + QPDFObjectHandle res1 = QPDFObjectHandle::newReserved(&pdf); | ||
| 847 | + QPDFObjectHandle res2 = QPDFObjectHandle::newReserved(&pdf); | ||
| 848 | + QPDFObjectHandle trailer = pdf.getTrailer(); | ||
| 849 | + trailer.replaceKey("Array1", res1); | ||
| 850 | + trailer.replaceKey("Array2", res2); | ||
| 851 | + | ||
| 852 | + QPDFObjectHandle array1 = QPDFObjectHandle::newArray(); | ||
| 853 | + QPDFObjectHandle array2 = QPDFObjectHandle::newArray(); | ||
| 854 | + array1.appendItem(res2); | ||
| 855 | + array1.appendItem(QPDFObjectHandle::newInteger(1)); | ||
| 856 | + array2.appendItem(res1); | ||
| 857 | + array2.appendItem(QPDFObjectHandle::newInteger(2)); | ||
| 858 | + // Make sure trying to ask questions about a reserved object | ||
| 859 | + // doesn't break it. | ||
| 860 | + if (res1.isArray()) | ||
| 861 | + { | ||
| 862 | + std::cout << "oops -- res1 is an array" << std::endl; | ||
| 863 | + } | ||
| 864 | + if (res1.isReserved()) | ||
| 865 | + { | ||
| 866 | + std::cout << "res1 is still reserved after checking if array" | ||
| 867 | + << std::endl; | ||
| 868 | + } | ||
| 869 | + pdf.replaceReserved(res1, array1); | ||
| 870 | + if (res1.isReserved()) | ||
| 871 | + { | ||
| 872 | + std::cout << "oops -- res1 is still reserved" << std::endl; | ||
| 873 | + } | ||
| 874 | + else | ||
| 875 | + { | ||
| 876 | + std::cout << "res1 is no longer reserved" << std::endl; | ||
| 877 | + } | ||
| 878 | + res1.assertArray(); | ||
| 879 | + std::cout << "res1 is an array" << std::endl; | ||
| 880 | + | ||
| 881 | + try | ||
| 882 | + { | ||
| 883 | + res2.unparseResolved(); | ||
| 884 | + std::cout << "oops -- didn't throw" << std::endl; | ||
| 885 | + } | ||
| 886 | + catch (std::logic_error e) | ||
| 887 | + { | ||
| 888 | + std::cout << "logic error: " << e.what() << std::endl; | ||
| 889 | + } | ||
| 890 | + try | ||
| 891 | + { | ||
| 892 | + res2.makeDirect(); | ||
| 893 | + std::cout << "oops -- didn't throw" << std::endl; | ||
| 894 | + } | ||
| 895 | + catch (std::logic_error e) | ||
| 896 | + { | ||
| 897 | + std::cout << "logic error: " << e.what() << std::endl; | ||
| 898 | + } | ||
| 899 | + | ||
| 900 | + pdf.replaceReserved(res2, array2); | ||
| 901 | + | ||
| 902 | + res2.assertArray(); | ||
| 903 | + std::cout << "res2 is an array" << std::endl; | ||
| 904 | + | ||
| 905 | + // Verify that the previously added reserved keys can be | ||
| 906 | + // dereferenced properly now | ||
| 907 | + int i1 = res1.getArrayItem(0).getArrayItem(1).getIntValue(); | ||
| 908 | + int i2 = res2.getArrayItem(0).getArrayItem(1).getIntValue(); | ||
| 909 | + if ((i1 == 2) && (i2 == 1)) | ||
| 910 | + { | ||
| 911 | + std::cout << "circular access and lazy resolution worked" << std::endl; | ||
| 912 | + } | ||
| 913 | + | ||
| 914 | + QPDFWriter w(pdf, "a.pdf"); | ||
| 915 | + w.setStaticID(true); | ||
| 916 | + w.setStreamDataMode(qpdf_s_preserve); | ||
| 917 | + w.write(); | ||
| 918 | + } | ||
| 843 | else | 919 | else |
| 844 | { | 920 | { |
| 845 | throw std::runtime_error(std::string("invalid test ") + | 921 | throw std::runtime_error(std::string("invalid test ") + |