Commit 7dc197ef88a3f19a830f38d19aba649175d53c5e

Authored by Jay Berkenbilt
1 parent b1a5b7e5

implement replace and swap

include/qpdf/QPDF.hh
... ... @@ -128,6 +128,36 @@ class QPDF
128 128 QPDF_DLL
129 129 QPDFObjectHandle getObjectByID(int objid, int generation);
130 130  
  131 + // Replace the object with the given object id with the given
  132 + // object. The object handle passed in must be a direct object,
  133 + // though it may contain references to other indirect objects
  134 + // within it. Calling this method can have somewhat confusing
  135 + // results. Any existing QPDFObjectHandle instances that point to
  136 + // the old object and that have been resolved (which happens
  137 + // automatically if you access them in any way) will continue to
  138 + // point to the old object even though that object will no longer
  139 + // be associated with the PDF file. Note that replacing an object
  140 + // with QPDFObjectHandle::newNull() effectively removes the object
  141 + // from the file since a non-existent object is treated as a null
  142 + // object.
  143 + QPDF_DLL
  144 + void replaceObject(int objid, int generation, QPDFObjectHandle);
  145 +
  146 + // Swap two objects given by ID. Calling this method can have
  147 + // confusing results. After swapping two objects, existing
  148 + // QPDFObjectHandle instances that reference them will still
  149 + // reference the same underlying objects, at which point those
  150 + // existing QPDFObjectHandle instances will have incorrect
  151 + // information about the object and generation number of those
  152 + // objects. While this does not necessarily cause a problem, it
  153 + // can certainly be confusing. It is therefore recommended that
  154 + // you replace any existing QPDFObjectHandle instances that point
  155 + // to the swapped objects with new ones, possibly by calling
  156 + // getObjectByID.
  157 + QPDF_DLL
  158 + void swapObjects(int objid1, int generation1,
  159 + int objid2, int generation2);
  160 +
131 161 // Encryption support
132 162  
133 163 enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes };
... ...
libqpdf/QPDF.cc
... ... @@ -1888,6 +1888,39 @@ QPDF::getObjectByID(int objid, int generation)
1888 1888 }
1889 1889  
1890 1890 void
  1891 +QPDF::replaceObject(int objid, int generation, QPDFObjectHandle oh)
  1892 +{
  1893 + if (oh.isIndirect())
  1894 + {
  1895 + QTC::TC("qpdf", "QPDF replaceObject called with indirect object");
  1896 + throw std::logic_error(
  1897 + "QPDF::replaceObject called with indirect object handle");
  1898 + }
  1899 +
  1900 + // Force new object to appear in the cache
  1901 + resolve(objid, generation);
  1902 +
  1903 + // Replace the object in the object cache
  1904 + ObjGen og(objid, generation);
  1905 + this->obj_cache[og] =
  1906 + ObjCache(QPDFObjectHandle::ObjAccessor::getObject(oh), -1, -1);
  1907 +}
  1908 +
  1909 +void
  1910 +QPDF::swapObjects(int objid1, int generation1, int objid2, int generation2)
  1911 +{
  1912 + // Force objects to be loaded into cache; then swap them in the
  1913 + // cache.
  1914 + resolve(objid1, generation1);
  1915 + resolve(objid2, generation2);
  1916 + ObjGen og1(objid1, generation1);
  1917 + ObjGen og2(objid2, generation2);
  1918 + ObjCache t = this->obj_cache[og1];
  1919 + this->obj_cache[og1] = this->obj_cache[og2];
  1920 + this->obj_cache[og2] = t;
  1921 +}
  1922 +
  1923 +void
1891 1924 QPDF::trimTrailerForWrite()
1892 1925 {
1893 1926 // Note that removing the encryption dictionary does not interfere
... ...
libqpdf/QPDFWriter.cc
... ... @@ -70,7 +70,7 @@ QPDFWriter::QPDFWriter(QPDF& pdf, char const* filename) :
70 70  
71 71 QPDFWriter::~QPDFWriter()
72 72 {
73   - if (file)
  73 + if (file && close_file)
74 74 {
75 75 fclose(file);
76 76 }
... ...
qpdf/qpdf.testcov
... ... @@ -192,3 +192,4 @@ QPDF stream without newline 0
192 192 QPDF stream with CR only 0
193 193 QPDF stream with CRNL 0
194 194 QPDF stream with NL only 0
  195 +QPDF replaceObject called with indirect object 0
... ...
qpdf/qtest/qpdf.test
... ... @@ -111,7 +111,7 @@ $td->runtest("new stream",
111 111 show_ntests();
112 112 # ----------
113 113 $td->notify("--- Miscellaneous Tests ---");
114   -$n_tests += 31;
  114 +$n_tests += 33;
115 115  
116 116 $td->runtest("qpdf version",
117 117 {$td->COMMAND => "qpdf --version"},
... ... @@ -276,6 +276,15 @@ $td->runtest("check output",
276 276 {$td->FILE => "a.qdf"},
277 277 {$td->FILE => "stream-line-enders.qdf"});
278 278  
  279 +$td->runtest("swap and replace",
  280 + {$td->COMMAND => "test_driver 14 test14-in.pdf"},
  281 + {$td->FILE => "test14.out",
  282 + $td->EXIT_STATUS => 0},
  283 + $td->NORMALIZE_NEWLINES);
  284 +$td->runtest("check output",
  285 + {$td->FILE => "a.pdf"},
  286 + {$td->FILE => "test14-out.pdf"});
  287 +
279 288 show_ntests();
280 289 # ----------
281 290 $td->notify("--- Error Condition Tests ---");
... ...
qpdf/qtest/qpdf/test14-in.pdf 0 → 100644
  1 +%PDF-1.3
  2 +%¿÷¢þ
  3 +%QDF-1.0
  4 +
  5 +1 0 obj
  6 +<<
  7 + /Pages 4 0 R
  8 + /Type /Catalog
  9 +>>
  10 +endobj
  11 +
  12 +2 0 obj
  13 +[
  14 + /Array
  15 +]
  16 +endobj
  17 +
  18 +3 0 obj
  19 +<<
  20 + /Dict 1
  21 +>>
  22 +endobj
  23 +
  24 +4 0 obj
  25 +<<
  26 + /Count 4
  27 + /Kids [
  28 + 5 0 R
  29 + 6 0 R
  30 + 7 0 R
  31 + 8 0 R
  32 + ]
  33 + /Type /Pages
  34 +>>
  35 +endobj
  36 +
  37 +%% Page 1
  38 +5 0 obj
  39 +<<
  40 + /Contents 9 0 R
  41 + /MediaBox [
  42 + 0
  43 + 0
  44 + 612
  45 + 792
  46 + ]
  47 + /Parent 4 0 R
  48 + /Resources <<
  49 + /Font <<
  50 + /F1 11 0 R
  51 + >>
  52 + /ProcSet 12 0 R
  53 + >>
  54 + /Type /Page
  55 +>>
  56 +endobj
  57 +
  58 +%% Page 2
  59 +6 0 obj
  60 +<<
  61 + /Contents 13 0 R
  62 + /MediaBox [
  63 + 0
  64 + 0
  65 + 612
  66 + 792
  67 + ]
  68 + /Parent 4 0 R
  69 + /Resources <<
  70 + /Font <<
  71 + /F1 11 0 R
  72 + >>
  73 + /ProcSet 15 0 R
  74 + >>
  75 + /Type /Page
  76 +>>
  77 +endobj
  78 +
  79 +%% Page 3
  80 +7 0 obj
  81 +<<
  82 + /Contents 16 0 R
  83 + /MediaBox [
  84 + 0
  85 + 0
  86 + 612
  87 + 792
  88 + ]
  89 + /Parent 4 0 R
  90 + /Resources <<
  91 + /Font <<
  92 + /F1 11 0 R
  93 + >>
  94 + /ProcSet 18 0 R
  95 + >>
  96 + /Type /Page
  97 +>>
  98 +endobj
  99 +
  100 +%% Page 4
  101 +8 0 obj
  102 +<<
  103 + /Contents 19 0 R
  104 + /MediaBox [
  105 + 0
  106 + 0
  107 + 612
  108 + 792
  109 + ]
  110 + /Parent 4 0 R
  111 + /Resources <<
  112 + /Font <<
  113 + /F1 11 0 R
  114 + >>
  115 + /ProcSet 21 0 R
  116 + >>
  117 + /Type /Page
  118 +>>
  119 +endobj
  120 +
  121 +%% Contents for page 1
  122 +9 0 obj
  123 +<<
  124 + /Length 10 0 R
  125 +>>
  126 +stream
  127 +BT
  128 + /F1 24 Tf
  129 + 72 720 Td
  130 + (Potato 1) Tj
  131 +ET
  132 +endstream
  133 +endobj
  134 +
  135 +10 0 obj
  136 +46
  137 +endobj
  138 +
  139 +11 0 obj
  140 +<<
  141 + /BaseFont /Helvetica
  142 + /Encoding /WinAnsiEncoding
  143 + /Name /F1
  144 + /Subtype /Type1
  145 + /Type /Font
  146 +>>
  147 +endobj
  148 +
  149 +12 0 obj
  150 +[
  151 + /PDF
  152 + /Text
  153 +]
  154 +endobj
  155 +
  156 +%% Contents for page 2
  157 +13 0 obj
  158 +<<
  159 + /Length 14 0 R
  160 +>>
  161 +stream
  162 +BT
  163 + /F1 24 Tf
  164 + 72 720 Td
  165 + (Potato 2) Tj
  166 +ET
  167 +endstream
  168 +endobj
  169 +
  170 +14 0 obj
  171 +46
  172 +endobj
  173 +
  174 +15 0 obj
  175 +[
  176 + /PDF
  177 + /Text
  178 +]
  179 +endobj
  180 +
  181 +%% Contents for page 3
  182 +16 0 obj
  183 +<<
  184 + /Length 17 0 R
  185 +>>
  186 +stream
  187 +BT
  188 + /F1 24 Tf
  189 + 72 720 Td
  190 + (Potato 3) Tj
  191 +ET
  192 +endstream
  193 +endobj
  194 +
  195 +17 0 obj
  196 +46
  197 +endobj
  198 +
  199 +18 0 obj
  200 +[
  201 + /PDF
  202 + /Text
  203 +]
  204 +endobj
  205 +
  206 +%% Contents for page 4
  207 +19 0 obj
  208 +<<
  209 + /Length 20 0 R
  210 +>>
  211 +stream
  212 +BT
  213 + /F1 24 Tf
  214 + 72 720 Td
  215 + (Potato 4) Tj
  216 +ET
  217 +endstream
  218 +endobj
  219 +
  220 +20 0 obj
  221 +46
  222 +endobj
  223 +
  224 +21 0 obj
  225 +[
  226 + /PDF
  227 + /Text
  228 +]
  229 +endobj
  230 +
  231 +xref
  232 +0 22
  233 +0000000000 65535 f
  234 +0000000025 00000 n
  235 +0000000079 00000 n
  236 +0000000108 00000 n
  237 +0000000140 00000 n
  238 +0000000252 00000 n
  239 +0000000456 00000 n
  240 +0000000661 00000 n
  241 +0000000866 00000 n
  242 +0000001084 00000 n
  243 +0000001186 00000 n
  244 +0000001206 00000 n
  245 +0000001325 00000 n
  246 +0000001384 00000 n
  247 +0000001487 00000 n
  248 +0000001507 00000 n
  249 +0000001566 00000 n
  250 +0000001669 00000 n
  251 +0000001689 00000 n
  252 +0000001748 00000 n
  253 +0000001851 00000 n
  254 +0000001871 00000 n
  255 +trailer <<
  256 + /QArray 2 0 R
  257 + /QDict 3 0 R
  258 + /Root 1 0 R
  259 + /Size 22
  260 + /ID [<20eb74876a3e8212c1b4fd43153860b0><1bb7a926da191c58f675435d77997d21>]
  261 +>>
  262 +startxref
  263 +1907
  264 +%%EOF
... ...
qpdf/qtest/qpdf/test14-out.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 +<< /NewDict 2 >>
  8 +endobj
  9 +3 0 obj
  10 +[ /Array ]
  11 +endobj
  12 +4 0 obj
  13 +<< /Count 4 /Kids [ 5 0 R 6 0 R 7 0 R 8 0 R ] /Type /Pages >>
  14 +endobj
  15 +5 0 obj
  16 +<< /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 >>
  17 +endobj
  18 +6 0 obj
  19 +<< /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 >>
  20 +endobj
  21 +7 0 obj
  22 +<< /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 >>
  23 +endobj
  24 +8 0 obj
  25 +<< /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 >>
  26 +endobj
  27 +9 0 obj
  28 +<< /Length 46 >>
  29 +stream
  30 +BT
  31 + /F1 24 Tf
  32 + 72 720 Td
  33 + (Potato 1) Tj
  34 +ET
  35 +endstream
  36 +endobj
  37 +10 0 obj
  38 +<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >>
  39 +endobj
  40 +11 0 obj
  41 +[ /PDF /Text ]
  42 +endobj
  43 +12 0 obj
  44 +<< /Length 46 >>
  45 +stream
  46 +BT
  47 + /F1 24 Tf
  48 + 72 720 Td
  49 + (Potato 3) Tj
  50 +ET
  51 +endstream
  52 +endobj
  53 +13 0 obj
  54 +[ /PDF /Text ]
  55 +endobj
  56 +14 0 obj
  57 +<< /Length 46 >>
  58 +stream
  59 +BT
  60 + /F1 24 Tf
  61 + 72 720 Td
  62 + (Potato 2) Tj
  63 +ET
  64 +endstream
  65 +endobj
  66 +15 0 obj
  67 +[ /PDF /Text ]
  68 +endobj
  69 +16 0 obj
  70 +<< /Length 46 >>
  71 +stream
  72 +BT
  73 + /F1 24 Tf
  74 + 72 720 Td
  75 + (Potato 4) Tj
  76 +ET
  77 +endstream
  78 +endobj
  79 +17 0 obj
  80 +[ /PDF /Text ]
  81 +endobj
  82 +xref
  83 +0 18
  84 +0000000000 65535 f
  85 +0000000015 00000 n
  86 +0000000064 00000 n
  87 +0000000096 00000 n
  88 +0000000122 00000 n
  89 +0000000199 00000 n
  90 +0000000344 00000 n
  91 +0000000490 00000 n
  92 +0000000636 00000 n
  93 +0000000782 00000 n
  94 +0000000877 00000 n
  95 +0000000985 00000 n
  96 +0000001016 00000 n
  97 +0000001112 00000 n
  98 +0000001143 00000 n
  99 +0000001239 00000 n
  100 +0000001270 00000 n
  101 +0000001366 00000 n
  102 +trailer << /QArray 2 0 R /QDict 3 0 R /Root 1 0 R /Size 18 /ID [<20eb74876a3e8212c1b4fd43153860b0><31415926535897932384626433832795>] >>
  103 +startxref
  104 +1397
  105 +%%EOF
... ...
qpdf/qtest/qpdf/test14.out 0 → 100644
  1 +caught logic error as expected
  2 +old dict: 1
  3 +old dict: 1
  4 +new dict: 2
  5 +swapped array: /Array
  6 +test 14 done
... ...
qpdf/test_driver.cc
... ... @@ -519,6 +519,62 @@ void runtest(int n, char const* filename)
519 519 << "---error---" << std::endl
520 520 << err.str();
521 521 }
  522 + else if (n == 14)
  523 + {
  524 + // Exercise swap and replace. This test case is designed for
  525 + // a specific file.
  526 + std::vector<QPDFObjectHandle> pages = pdf.getAllPages();
  527 + if (pages.size() != 4)
  528 + {
  529 + throw std::logic_error("test " + QUtil::int_to_string(n) +
  530 + " not called 4-page file");
  531 + }
  532 + // Swap pages 2 and 3
  533 + pdf.swapObjects(pages[1].getObjectID(), pages[1].getGeneration(),
  534 + pages[2].getObjectID(), pages[2].getGeneration());
  535 + // Replace object and swap objects
  536 + QPDFObjectHandle trailer = pdf.getTrailer();
  537 + QPDFObjectHandle qdict = trailer.getKey("/QDict");
  538 + QPDFObjectHandle qarray = trailer.getKey("/QArray");
  539 + // Force qdict but not qarray to resolve
  540 + qdict.isDictionary();
  541 + std::map<std::string, QPDFObjectHandle> dict_keys;
  542 + dict_keys["/NewDict"] = QPDFObjectHandle::newInteger(2);
  543 + QPDFObjectHandle new_dict = QPDFObjectHandle::newDictionary(dict_keys);
  544 + try
  545 + {
  546 + // Do it wrong first...
  547 + pdf.replaceObject(qdict.getObjectID(), qdict.getGeneration(),
  548 + qdict);
  549 + }
  550 + catch (std::logic_error)
  551 + {
  552 + std::cout << "caught logic error as expected" << std::endl;
  553 + }
  554 + pdf.replaceObject(qdict.getObjectID(), qdict.getGeneration(),
  555 + new_dict);
  556 + // Now qdict still points to the old dictionary
  557 + std::cout << "old dict: " << qdict.getKey("/Dict").getIntValue()
  558 + << std::endl;
  559 + // Swap dict and array
  560 + pdf.swapObjects(qdict.getObjectID(), qdict.getGeneration(),
  561 + qarray.getObjectID(), qarray.getGeneration());
  562 + // Now qarray will resolve to new object but qdict is still
  563 + // the old object
  564 + std::cout << "old dict: " << qdict.getKey("/Dict").getIntValue()
  565 + << std::endl;
  566 + std::cout << "new dict: " << qarray.getKey("/NewDict").getIntValue()
  567 + << std::endl;
  568 + // Reread qdict, now pointing to an array
  569 + qdict = pdf.getObjectByID(qdict.getObjectID(), qdict.getGeneration());
  570 + std::cout << "swapped array: " << qdict.getArrayItem(0).getName()
  571 + << std::endl;
  572 +
  573 + QPDFWriter w(pdf, "a.pdf");
  574 + w.setStaticID(true);
  575 + w.setStreamDataMode(qpdf_s_preserve);
  576 + w.write();
  577 + }
522 578 else
523 579 {
524 580 throw std::runtime_error(std::string("invalid test ") +
... ...