Commit e7b8f297ba92f4cadf88efcb394830dc24d54738
1 parent
8a217eb3
Support copying objects from another QPDF object
This includes QPDF::copyForeignObject and supporting foreign objects as arguments to addPage*.
Showing
16 changed files
with
1142 additions
and
99 deletions
ChangeLog
| 1 | +2012-07-11 Jay Berkenbilt <ejb@ql.org> | |
| 2 | + | |
| 3 | + * Added new APIs to copy objects from one QPDF to another. This | |
| 4 | + includes letting QPDF::addPage() (and QPDF::addPageAt()) accept a | |
| 5 | + page object from another QPDF and adding | |
| 6 | + QPDF::copyForeignObject(). See QPDF.hh for details. | |
| 7 | + | |
| 8 | + * Add method QPDFObjectHandle::getOwningQPDF() to return the QPDF | |
| 9 | + object associated with an indirect QPDFObjectHandle. | |
| 10 | + | |
| 11 | + * Add convenience methods to QPDFObjectHandle: assertIndirect(), | |
| 12 | + isPageObject(), isPagesObject() | |
| 13 | + | |
| 14 | + * Cache when QPDF::pushInheritedAttributesToPage() has been called | |
| 15 | + to avoid traversing the pages trees multiple times. This state is | |
| 16 | + cleared by QPDF::updateAllPagesCache() and ignored by | |
| 17 | + QPDF::flattenPagesTree(). | |
| 18 | + | |
| 1 | 19 | 2012-07-08 Jay Berkenbilt <ejb@ql.org> |
| 2 | 20 | |
| 3 | 21 | * Add QPDFObjectHandle::newReserved to create a reserved object | ... | ... |
TODO
| ... | ... | @@ -28,76 +28,54 @@ Next |
| 28 | 28 | can only be used by one thread at a time, but multiple threads can |
| 29 | 29 | simultaneously use separate objects. |
| 30 | 30 | |
| 31 | + * Write some documentation about the design of copyForeignObject. | |
| 31 | 32 | |
| 32 | -Soon | |
| 33 | -==== | |
| 33 | + * copyForeignObject still to do: | |
| 34 | 34 | |
| 35 | - * Provide an option to copy encryption parameters from another file. | |
| 36 | - This would make it possible to decrypt a file, manually work with | |
| 37 | - it, and then re-encrypt it using the original encryption parameters | |
| 38 | - including a possibly unknown owner password. | |
| 35 | + - qpdf command | |
| 39 | 36 | |
| 40 | - * See if I can support the new encryption formats mentioned in the | |
| 41 | - open bug on sourceforge. Check other sourceforge bugs. | |
| 37 | + Command line could be something like | |
| 42 | 38 | |
| 43 | - * Splitting/merging concepts | |
| 39 | + --pages [ --new ] { file [password] numeric-range ... } ... -- | |
| 44 | 40 | |
| 45 | - newPDF() could create a PDF with just a trailer, no pages, and a | |
| 46 | - minimal info. Then the page routines could be used to add pages to | |
| 47 | - it. | |
| 41 | + The first file referenced would be the one whose other data would | |
| 42 | + be preserved (like trailer, info, encryption, outlines, etc.). | |
| 43 | + --new as first file would just use an empty file as the starting | |
| 44 | + point. Be explicit about whether outlines, etc., are handled. | |
| 45 | + They are not handled initially. | |
| 48 | 46 | |
| 49 | - Starting with any pdf, you should be able to copy objects from | |
| 50 | - another pdf. The copy should be smart about never traversing into | |
| 51 | - a /Page or /Pages. | |
| 47 | + Example: to grab pages 1-5 from file1 and 11-15 from file2 | |
| 52 | 48 | |
| 53 | - We could provide a method of copying objects from one PDF into | |
| 54 | - another. This would do whatever optimization is necessary (maybe | |
| 55 | - just optimizePagesTree) and then traverse the set of objects | |
| 56 | - specified to find all objects referenced by the set. Each of those | |
| 57 | - would be copied over with a table mapping old ID to new ID. This | |
| 58 | - would be done from bottom up most likely disallowing cycles or | |
| 59 | - handling them sanely. | |
| 49 | + --pages file1.pdf 1-5 file2.pdf 11-15 -- | |
| 60 | 50 | |
| 61 | - Command line could be something like | |
| 51 | + To implement this, we would remove all pages from file1 except | |
| 52 | + pages 1 through 5. Then we would take pages 11 through 15 from | |
| 53 | + file2, copy them to the file, and add them as pages. | |
| 62 | 54 | |
| 63 | - --pages [ --new ] { file [password] numeric-range ... } ... -- | |
| 55 | + - document that makeIndirectObject doesn't handle foreign objects | |
| 56 | + automatically because copying a foreign object is a big enough | |
| 57 | + deal that it should be explicit. However addPages* does handle | |
| 58 | + foreign page objects automatically. | |
| 64 | 59 | |
| 65 | - The first file referenced would be the one whose other data would | |
| 66 | - be preserved (like trailer, info, encryption, outlines, etc.). | |
| 67 | - --new as first file would just use an empty file as the starting | |
| 68 | - point. | |
| 60 | + - Test /Outlines and see whether there's any point in handling | |
| 61 | + them in the API. Maybe just copying them over works. What | |
| 62 | + about command line tool? Also think about page labels. | |
| 69 | 63 | |
| 70 | - Example: to grab pages 1-5 from file1 and 11-15 from file2 | |
| 64 | + - Tests through qpdf command line: copy pages from multiple PDFs | |
| 65 | + starting with one PDF and also starting with empty. | |
| 71 | 66 | |
| 72 | - --pages file1.pdf 1-5 file2.pdf 11-15 -- | |
| 67 | + * (Hopefully) Provide an option to copy encryption parameters from | |
| 68 | + another file. This would make it possible to decrypt a file, | |
| 69 | + manually work with it, and then re-encrypt it using the original | |
| 70 | + encryption parameters including a possibly unknown owner password. | |
| 73 | 71 | |
| 74 | - To implement this, we would remove all pages from file1 except | |
| 75 | - pages 1 through 5. Then we would take pages 11 through 15 from | |
| 76 | - file2 and add them to a set for transfer. This would end up | |
| 77 | - generating a list of indirect objects. We would copy those objects | |
| 78 | - shallowly to the new PDF keeping track of the mapping and replacing | |
| 79 | - any indirect object keys as appropriate, much like QPDFWriter does. | |
| 80 | 72 | |
| 81 | - When all the objects are registered, we would add those pages to | |
| 82 | - the result. | |
| 83 | - | |
| 84 | - This approach could work for both splitting and merging. It's | |
| 85 | - possible it could be implemented now without any new APIs, but most | |
| 86 | - of the work should be doable by the library with only a small set | |
| 87 | - of additions. | |
| 73 | +Soon | |
| 74 | +==== | |
| 88 | 75 | |
| 89 | - newPDF() | |
| 90 | - QPDFObjectCopier c(qpdf1, qpdf2) | |
| 91 | - QPDFObjectHandle obj = c.copyObject(<object from qpdf1>) | |
| 92 | - Without traversing pages, copies all indirect objects referenced | |
| 93 | - by <object from qpdf1> preserving referential integrity and | |
| 94 | - returns an object handle in qpdf2 of the same object. If called | |
| 95 | - multiple times on the same object, retraverses in case there were | |
| 96 | - changes. | |
| 76 | + * See if I can support the new encryption formats mentioned in the | |
| 77 | + open bug on sourceforge. Check other sourceforge bugs. | |
| 97 | 78 | |
| 98 | - QPDFObjectHandle obj = c.getMapping(<object from qpdf1>) | |
| 99 | - find the object in qpdf2 corresponding to the object from qpdf1. | |
| 100 | - Return the null object if none. | |
| 101 | 79 | |
| 102 | 80 | General |
| 103 | 81 | ======= |
| ... | ... | @@ -110,23 +88,11 @@ General |
| 110 | 88 | * Update qpdf docs about non-ascii passwords. See thread from |
| 111 | 89 | 2010-12-07,08 for details. |
| 112 | 90 | |
| 113 | - * Look at page splitting. Subramanyam provided a test file; see | |
| 114 | - ../misc/article-threads.pdf. Email Q-Count: 431864 from | |
| 115 | - 2009-11-03. See also "Splitting by Pages" below. | |
| 116 | - | |
| 117 | - * Consider writing a PDF merge utility. With 2.2, it would be | |
| 118 | - possible to have a StreamDataProvider that would allow stream data | |
| 119 | - to be directly copied from one PDF file to another. One possible | |
| 120 | - strategy would be to have a program that adds all the pages of one | |
| 121 | - file to the end of another file. The basic | |
| 122 | - strategy would be to create a table that adds new streams to the | |
| 123 | - original file, mapping the new streams' obj/gen to a stream in the | |
| 124 | - file whose pages are being appended. The StreamDataProvider, when | |
| 125 | - asked, could simply pipe the streams of the file being appended to | |
| 126 | - the provided pipeline and could copy the filter and decode | |
| 127 | - parameters from the original file. Being able to do this requires | |
| 128 | - a lot of the same logic as being able to do splitting, so a general | |
| 129 | - split/merge program would be a great addition. | |
| 91 | + * Consider impact of article threads on page splitting/merging. | |
| 92 | + Subramanyam provided a test file; see ../misc/article-threads.pdf. | |
| 93 | + Email Q-Count: 431864 from 2009-11-03. Other things to consider: | |
| 94 | + outlines, page labels, thumbnails, zones. There are probably | |
| 95 | + others. | |
| 130 | 96 | |
| 131 | 97 | * See whether it's possible to remove the call to |
| 132 | 98 | flattenScalarReferences. I can't easily figure out why I do it, |
| ... | ... | @@ -279,26 +245,3 @@ Index: QPDFWriter.cc |
| 279 | 245 | |
| 280 | 246 | * From a suggestion in bug 3152169, consisder having an option to |
| 281 | 247 | re-encode inline images with an ASCII encoding. |
| 282 | - | |
| 283 | - | |
| 284 | -Splitting by Pages | |
| 285 | -================== | |
| 286 | - | |
| 287 | -Although qpdf does not currently support splitting a file into pages, | |
| 288 | -the work done for linearization covers almost all the work. To do | |
| 289 | -page splitting. If this functionality is needed, study | |
| 290 | -obj_user_to_objects and object_to_obj_users created in | |
| 291 | -QPDF_optimization for ideas. It's quite possible that the information | |
| 292 | -computed by calculateLinearizationData is actually sufficient to do | |
| 293 | -page splitting in many circumstances. That code knows which objects | |
| 294 | -are used by which pages, though it doesn't do anything page-specific | |
| 295 | -with outlines, thumbnails, page labels, or anything else. | |
| 296 | - | |
| 297 | -Another approach would be to traverse only pages that are being output | |
| 298 | -taking care not to traverse into the pages tree, and then to fabricate | |
| 299 | -a new pages tree. | |
| 300 | - | |
| 301 | -Either way, care must be taken to handle other things such as | |
| 302 | -outlines, page labels, thumbnails, threads, zones, etc. in a sensible | |
| 303 | -way. This may include simply omitting information other than page | |
| 304 | -content. | ... | ... |
include/qpdf/QPDF.hh
| ... | ... | @@ -190,6 +190,28 @@ class QPDF |
| 190 | 190 | replaceReserved(QPDFObjectHandle reserved, |
| 191 | 191 | QPDFObjectHandle replacement); |
| 192 | 192 | |
| 193 | + // Copy an object from another QPDF to this one. The return value | |
| 194 | + // is an indirect reference to the copied object in this file. | |
| 195 | + // This method is intended to be used to copy non-page objects and | |
| 196 | + // will not copy page objects. To copy page objects, pass the | |
| 197 | + // foreign page object directly to addPage (or addPageAt). If you | |
| 198 | + // copy objects that contain references to pages, you should copy | |
| 199 | + // the pages first using addPage(At). Otherwise references to the | |
| 200 | + // pages that have not been copied will be replaced with nulls. | |
| 201 | + | |
| 202 | + // When copying objects with this method, object structure will be | |
| 203 | + // preserved, so all indirectly referenced indirect objects will | |
| 204 | + // be copied as well. This includes any circular references that | |
| 205 | + // may exist. The QPDF object keeps a record of what has already | |
| 206 | + // been copied, so shared objects will not be copied multiple | |
| 207 | + // times. This also means that if you mutate an object that has | |
| 208 | + // already been copied and try to copy it again, it won't work | |
| 209 | + // since the modified object will not be recopied. Therefore, you | |
| 210 | + // should do all mutation on the original file that you are going | |
| 211 | + // to do before you start copying its objects to a new file. | |
| 212 | + QPDF_DLL | |
| 213 | + QPDFObjectHandle copyForeignObject(QPDFObjectHandle foreign); | |
| 214 | + | |
| 193 | 215 | // Encryption support |
| 194 | 216 | |
| 195 | 217 | enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes }; |
| ... | ... | @@ -380,7 +402,10 @@ class QPDF |
| 380 | 402 | // modify /Pages structures directly, you must call this method |
| 381 | 403 | // afterwards. This method updates the internal list of pages, so |
| 382 | 404 | // after calling this method, any previous references returned by |
| 383 | - // getAllPages() will be valid again. | |
| 405 | + // getAllPages() will be valid again. It also resets any state | |
| 406 | + // about having pushed inherited attributes in /Pages objects down | |
| 407 | + // to the pages, so if you add any inheritable attributes to a | |
| 408 | + // /Pages object, you should also call this method. | |
| 384 | 409 | QPDF_DLL |
| 385 | 410 | void updateAllPagesCache(); |
| 386 | 411 | |
| ... | ... | @@ -389,11 +414,19 @@ class QPDF |
| 389 | 414 | // resolved by explicitly setting the values in each /Page. |
| 390 | 415 | void pushInheritedAttributesToPage(); |
| 391 | 416 | |
| 392 | - // Add new page at the beginning or the end of the current pdf | |
| 417 | + // Add new page at the beginning or the end of the current pdf. | |
| 418 | + // The newpage parameter may be either a direct object, an | |
| 419 | + // indirect object from this QPDF, or an indirect object from | |
| 420 | + // another QPDF. If it is a direct object, it will be made | |
| 421 | + // indirect. If it is an indirect object from another QPDF, this | |
| 422 | + // method will call pushInheritedAttributesToPage on the other | |
| 423 | + // file and then copy the page to this QPDF using the same | |
| 424 | + // underlying code as copyForeignObject. | |
| 393 | 425 | QPDF_DLL |
| 394 | 426 | void addPage(QPDFObjectHandle newpage, bool first); |
| 395 | 427 | |
| 396 | - // Add new page before or after refpage | |
| 428 | + // Add new page before or after refpage. See comments for addPage | |
| 429 | + // for details about what newpage should be. | |
| 397 | 430 | QPDF_DLL |
| 398 | 431 | void addPageAt(QPDFObjectHandle newpage, bool before, |
| 399 | 432 | QPDFObjectHandle refpage); |
| ... | ... | @@ -542,6 +575,29 @@ class QPDF |
| 542 | 575 | qpdf_offset_t end_after_space; |
| 543 | 576 | }; |
| 544 | 577 | |
| 578 | + class ObjCopier | |
| 579 | + { | |
| 580 | + public: | |
| 581 | + std::map<ObjGen, QPDFObjectHandle> object_map; | |
| 582 | + std::vector<QPDFObjectHandle> to_copy; | |
| 583 | + std::set<ObjGen> visiting; | |
| 584 | + }; | |
| 585 | + | |
| 586 | + class CopiedStreamDataProvider: public QPDFObjectHandle::StreamDataProvider | |
| 587 | + { | |
| 588 | + public: | |
| 589 | + virtual ~CopiedStreamDataProvider() | |
| 590 | + { | |
| 591 | + } | |
| 592 | + virtual void provideStreamData(int objid, int generation, | |
| 593 | + Pipeline* pipeline); | |
| 594 | + void registerForeignStream(ObjGen const& local_og, | |
| 595 | + QPDFObjectHandle foreign_stream); | |
| 596 | + | |
| 597 | + private: | |
| 598 | + std::map<ObjGen, QPDFObjectHandle> foreign_streams; | |
| 599 | + }; | |
| 600 | + | |
| 545 | 601 | void parse(char const* password); |
| 546 | 602 | void warn(QPDFExc const& e); |
| 547 | 603 | void setTrailer(QPDFObjectHandle obj); |
| ... | ... | @@ -602,6 +658,14 @@ class QPDF |
| 602 | 658 | QPDFObjectHandle& stream_dict, |
| 603 | 659 | std::vector<PointerHolder<Pipeline> >& heap); |
| 604 | 660 | |
| 661 | + // Methods to support object copying | |
| 662 | + QPDFObjectHandle copyForeignObject( | |
| 663 | + QPDFObjectHandle foreign, bool allow_page); | |
| 664 | + void reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, | |
| 665 | + bool top); | |
| 666 | + QPDFObjectHandle replaceForeignIndirectObjects( | |
| 667 | + QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top); | |
| 668 | + | |
| 605 | 669 | // Linearization Hint table structures. |
| 606 | 670 | // Naming conventions: |
| 607 | 671 | |
| ... | ... | @@ -960,7 +1024,12 @@ class QPDF |
| 960 | 1024 | QPDFObjectHandle trailer; |
| 961 | 1025 | std::vector<QPDFObjectHandle> all_pages; |
| 962 | 1026 | std::map<ObjGen, int> pageobj_to_pages_pos; |
| 1027 | + bool pushed_inherited_attributes_to_pages; | |
| 963 | 1028 | std::vector<QPDFExc> warnings; |
| 1029 | + std::map<QPDF*, ObjCopier> object_copiers; | |
| 1030 | + PointerHolder<QPDFObjectHandle::StreamDataProvider> copied_streams; | |
| 1031 | + // copied_stream_data_provider is owned by copied_streams | |
| 1032 | + CopiedStreamDataProvider* copied_stream_data_provider; | |
| 964 | 1033 | |
| 965 | 1034 | // Linearization data |
| 966 | 1035 | qpdf_offset_t first_xref_item_offset; // actual value from file | ... | ... |
include/qpdf/QPDFObjectHandle.hh
| ... | ... | @@ -222,6 +222,11 @@ class QPDFObjectHandle |
| 222 | 222 | QPDF_DLL |
| 223 | 223 | bool isOrHasName(std::string const&); |
| 224 | 224 | |
| 225 | + // Return the QPDF object that owns an indirect object. Returns | |
| 226 | + // null for a direct object. | |
| 227 | + QPDF_DLL | |
| 228 | + QPDF* getOwningQPDF(); | |
| 229 | + | |
| 225 | 230 | // Create a shallow copy of an object as a direct object. Since |
| 226 | 231 | // this is a shallow copy, for dictionaries and arrays, any keys |
| 227 | 232 | // or items that were indirect objects will still be indirect |
| ... | ... | @@ -454,9 +459,16 @@ class QPDFObjectHandle |
| 454 | 459 | void assertReserved(); |
| 455 | 460 | |
| 456 | 461 | QPDF_DLL |
| 462 | + void assertIndirect(); | |
| 463 | + QPDF_DLL | |
| 457 | 464 | void assertScalar(); |
| 458 | 465 | QPDF_DLL |
| 459 | 466 | void assertNumber(); |
| 467 | + | |
| 468 | + QPDF_DLL | |
| 469 | + bool isPageObject(); | |
| 470 | + QPDF_DLL | |
| 471 | + bool isPagesObject(); | |
| 460 | 472 | QPDF_DLL |
| 461 | 473 | void assertPageObject(); |
| 462 | 474 | ... | ... |
libqpdf/QPDF.cc
| ... | ... | @@ -348,6 +348,23 @@ QPDF::ObjGen::operator<(ObjGen const& rhs) const |
| 348 | 348 | ((this->obj == rhs.obj) && (this->gen < rhs.gen))); |
| 349 | 349 | } |
| 350 | 350 | |
| 351 | +void | |
| 352 | +QPDF::CopiedStreamDataProvider::provideStreamData( | |
| 353 | + int objid, int generation, Pipeline* pipeline) | |
| 354 | +{ | |
| 355 | + QPDFObjectHandle foreign_stream = | |
| 356 | + this->foreign_streams[ObjGen(objid, generation)]; | |
| 357 | + foreign_stream.pipeStreamData(pipeline, false, false, false); | |
| 358 | +} | |
| 359 | + | |
| 360 | +void | |
| 361 | +QPDF::CopiedStreamDataProvider::registerForeignStream( | |
| 362 | + ObjGen const& local_og, QPDFObjectHandle foreign_stream) | |
| 363 | +{ | |
| 364 | + this->foreign_streams[local_og] = foreign_stream; | |
| 365 | +} | |
| 366 | + | |
| 367 | + | |
| 351 | 368 | std::string const& |
| 352 | 369 | QPDF::QPDFVersion() |
| 353 | 370 | { |
| ... | ... | @@ -369,6 +386,8 @@ QPDF::QPDF() : |
| 369 | 386 | cf_file(e_none), |
| 370 | 387 | cached_key_objid(0), |
| 371 | 388 | cached_key_generation(0), |
| 389 | + pushed_inherited_attributes_to_pages(false), | |
| 390 | + copied_stream_data_provider(0), | |
| 372 | 391 | first_xref_item_offset(0), |
| 373 | 392 | uncompressed_after_compressed(false) |
| 374 | 393 | { |
| ... | ... | @@ -2067,6 +2086,244 @@ QPDF::replaceReserved(QPDFObjectHandle reserved, |
| 2067 | 2086 | replacement); |
| 2068 | 2087 | } |
| 2069 | 2088 | |
| 2089 | +QPDFObjectHandle | |
| 2090 | +QPDF::copyForeignObject(QPDFObjectHandle foreign) | |
| 2091 | +{ | |
| 2092 | + return copyForeignObject(foreign, false); | |
| 2093 | +} | |
| 2094 | + | |
| 2095 | +QPDFObjectHandle | |
| 2096 | +QPDF::copyForeignObject(QPDFObjectHandle foreign, bool allow_page) | |
| 2097 | +{ | |
| 2098 | + if (! foreign.isIndirect()) | |
| 2099 | + { | |
| 2100 | + QTC::TC("qpdf", "QPDF copyForeign direct"); | |
| 2101 | + throw std::logic_error( | |
| 2102 | + "QPDF::copyForeign called with direct object handle"); | |
| 2103 | + } | |
| 2104 | + QPDF* other = foreign.getOwningQPDF(); | |
| 2105 | + if (other == this) | |
| 2106 | + { | |
| 2107 | + QTC::TC("qpdf", "QPDF copyForeign not foreign"); | |
| 2108 | + throw std::logic_error( | |
| 2109 | + "QPDF::copyForeign called with object from this QPDF"); | |
| 2110 | + } | |
| 2111 | + | |
| 2112 | + ObjCopier& obj_copier = this->object_copiers[other]; | |
| 2113 | + if (! obj_copier.visiting.empty()) | |
| 2114 | + { | |
| 2115 | + throw std::logic_error("obj_copier.visiting is not empty" | |
| 2116 | + " at the beginning of copyForeignObject"); | |
| 2117 | + } | |
| 2118 | + | |
| 2119 | + // Make sure we have an object in this file for every referenced | |
| 2120 | + // object in the old file. obj_copier.object_map maps foreign | |
| 2121 | + // ObjGen to local objects. For everything new that we have to | |
| 2122 | + // copy, the local object will be a reservation, unless it is a | |
| 2123 | + // stream, in which case the local object will already be a | |
| 2124 | + // stream. | |
| 2125 | + reserveObjects(foreign, obj_copier, true); | |
| 2126 | + | |
| 2127 | + if (! obj_copier.visiting.empty()) | |
| 2128 | + { | |
| 2129 | + throw std::logic_error("obj_copier.visiting is not empty" | |
| 2130 | + " after reserving objects"); | |
| 2131 | + } | |
| 2132 | + | |
| 2133 | + // Copy any new objects and replace the reservations. | |
| 2134 | + for (std::vector<QPDFObjectHandle>::iterator iter = | |
| 2135 | + obj_copier.to_copy.begin(); | |
| 2136 | + iter != obj_copier.to_copy.end(); ++iter) | |
| 2137 | + { | |
| 2138 | + QPDFObjectHandle& to_copy = *iter; | |
| 2139 | + QPDFObjectHandle copy = | |
| 2140 | + replaceForeignIndirectObjects(to_copy, obj_copier, true); | |
| 2141 | + if (! to_copy.isStream()) | |
| 2142 | + { | |
| 2143 | + ObjGen og(to_copy.getObjectID(), to_copy.getGeneration()); | |
| 2144 | + replaceReserved(obj_copier.object_map[og], copy); | |
| 2145 | + } | |
| 2146 | + } | |
| 2147 | + obj_copier.to_copy.clear(); | |
| 2148 | + | |
| 2149 | + return obj_copier.object_map[ObjGen(foreign.getObjectID(), | |
| 2150 | + foreign.getGeneration())]; | |
| 2151 | +} | |
| 2152 | + | |
| 2153 | +void | |
| 2154 | +QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, | |
| 2155 | + bool top) | |
| 2156 | +{ | |
| 2157 | + if (foreign.isReserved()) | |
| 2158 | + { | |
| 2159 | + throw std::logic_error( | |
| 2160 | + "QPDF: attempting to copy a foreign reserved object"); | |
| 2161 | + } | |
| 2162 | + | |
| 2163 | + if (foreign.isPagesObject()) | |
| 2164 | + { | |
| 2165 | + QTC::TC("qpdf", "QPDF not copying pages object"); | |
| 2166 | + return; | |
| 2167 | + } | |
| 2168 | + | |
| 2169 | + if ((! top) && foreign.isPageObject()) | |
| 2170 | + { | |
| 2171 | + QTC::TC("qpdf", "QPDF not crossing page boundary"); | |
| 2172 | + return; | |
| 2173 | + } | |
| 2174 | + | |
| 2175 | + if (foreign.isIndirect()) | |
| 2176 | + { | |
| 2177 | + ObjGen foreign_og(foreign.getObjectID(), foreign.getGeneration()); | |
| 2178 | + if (obj_copier.visiting.find(foreign_og) != obj_copier.visiting.end()) | |
| 2179 | + { | |
| 2180 | + QTC::TC("qpdf", "QPDF loop reserving objects"); | |
| 2181 | + return; | |
| 2182 | + } | |
| 2183 | + QTC::TC("qpdf", "QPDF copy indirect"); | |
| 2184 | + obj_copier.visiting.insert(foreign_og); | |
| 2185 | + std::map<ObjGen, QPDFObjectHandle>::iterator mapping = | |
| 2186 | + obj_copier.object_map.find(foreign_og); | |
| 2187 | + if (mapping == obj_copier.object_map.end()) | |
| 2188 | + { | |
| 2189 | + obj_copier.to_copy.push_back(foreign); | |
| 2190 | + QPDFObjectHandle reservation; | |
| 2191 | + if (foreign.isStream()) | |
| 2192 | + { | |
| 2193 | + reservation = QPDFObjectHandle::newStream(this); | |
| 2194 | + } | |
| 2195 | + else | |
| 2196 | + { | |
| 2197 | + reservation = QPDFObjectHandle::newReserved(this); | |
| 2198 | + } | |
| 2199 | + obj_copier.object_map[foreign_og] = reservation; | |
| 2200 | + } | |
| 2201 | + } | |
| 2202 | + | |
| 2203 | + if (foreign.isArray()) | |
| 2204 | + { | |
| 2205 | + QTC::TC("qpdf", "QPDF reserve array"); | |
| 2206 | + int n = foreign.getArrayNItems(); | |
| 2207 | + for (int i = 0; i < n; ++i) | |
| 2208 | + { | |
| 2209 | + reserveObjects(foreign.getArrayItem(i), obj_copier, false); | |
| 2210 | + } | |
| 2211 | + } | |
| 2212 | + else if (foreign.isDictionary()) | |
| 2213 | + { | |
| 2214 | + QTC::TC("qpdf", "QPDF reserve dictionary"); | |
| 2215 | + std::set<std::string> keys = foreign.getKeys(); | |
| 2216 | + for (std::set<std::string>::iterator iter = keys.begin(); | |
| 2217 | + iter != keys.end(); ++iter) | |
| 2218 | + { | |
| 2219 | + reserveObjects(foreign.getKey(*iter), obj_copier, false); | |
| 2220 | + } | |
| 2221 | + } | |
| 2222 | + else if (foreign.isStream()) | |
| 2223 | + { | |
| 2224 | + QTC::TC("qpdf", "QPDF reserve stream"); | |
| 2225 | + reserveObjects(foreign.getDict(), obj_copier, false); | |
| 2226 | + } | |
| 2227 | + | |
| 2228 | + if (foreign.isIndirect()) | |
| 2229 | + { | |
| 2230 | + ObjGen foreign_og(foreign.getObjectID(), foreign.getGeneration()); | |
| 2231 | + obj_copier.visiting.erase(foreign_og); | |
| 2232 | + } | |
| 2233 | +} | |
| 2234 | + | |
| 2235 | +QPDFObjectHandle | |
| 2236 | +QPDF::replaceForeignIndirectObjects( | |
| 2237 | + QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top) | |
| 2238 | +{ | |
| 2239 | + QPDFObjectHandle result; | |
| 2240 | + if ((! top) && foreign.isIndirect()) | |
| 2241 | + { | |
| 2242 | + QTC::TC("qpdf", "QPDF replace indirect"); | |
| 2243 | + ObjGen foreign_og(foreign.getObjectID(), foreign.getGeneration()); | |
| 2244 | + std::map<ObjGen, QPDFObjectHandle>::iterator mapping = | |
| 2245 | + obj_copier.object_map.find(foreign_og); | |
| 2246 | + if (mapping == obj_copier.object_map.end()) | |
| 2247 | + { | |
| 2248 | + // This case would occur if this is a reference to a Page | |
| 2249 | + // or Pages object that we didn't traverse into. | |
| 2250 | + QTC::TC("qpdf", "QPDF replace foreign indirect with null"); | |
| 2251 | + result = QPDFObjectHandle::newNull(); | |
| 2252 | + } | |
| 2253 | + else | |
| 2254 | + { | |
| 2255 | + result = obj_copier.object_map[foreign_og]; | |
| 2256 | + } | |
| 2257 | + } | |
| 2258 | + else if (foreign.isArray()) | |
| 2259 | + { | |
| 2260 | + QTC::TC("qpdf", "QPDF replace array"); | |
| 2261 | + result = QPDFObjectHandle::newArray(); | |
| 2262 | + int n = foreign.getArrayNItems(); | |
| 2263 | + for (int i = 0; i < n; ++i) | |
| 2264 | + { | |
| 2265 | + result.appendItem( | |
| 2266 | + replaceForeignIndirectObjects( | |
| 2267 | + foreign.getArrayItem(i), obj_copier, false)); | |
| 2268 | + } | |
| 2269 | + } | |
| 2270 | + else if (foreign.isDictionary()) | |
| 2271 | + { | |
| 2272 | + QTC::TC("qpdf", "QPDF replace dictionary"); | |
| 2273 | + result = QPDFObjectHandle::newDictionary(); | |
| 2274 | + std::set<std::string> keys = foreign.getKeys(); | |
| 2275 | + for (std::set<std::string>::iterator iter = keys.begin(); | |
| 2276 | + iter != keys.end(); ++iter) | |
| 2277 | + { | |
| 2278 | + result.replaceKey( | |
| 2279 | + *iter, | |
| 2280 | + replaceForeignIndirectObjects( | |
| 2281 | + foreign.getKey(*iter), obj_copier, false)); | |
| 2282 | + } | |
| 2283 | + } | |
| 2284 | + else if (foreign.isStream()) | |
| 2285 | + { | |
| 2286 | + QTC::TC("qpdf", "QPDF replace stream"); | |
| 2287 | + ObjGen foreign_og(foreign.getObjectID(), foreign.getGeneration()); | |
| 2288 | + result = obj_copier.object_map[foreign_og]; | |
| 2289 | + result.assertStream(); | |
| 2290 | + QPDFObjectHandle dict = result.getDict(); | |
| 2291 | + QPDFObjectHandle old_dict = foreign.getDict(); | |
| 2292 | + std::set<std::string> keys = old_dict.getKeys(); | |
| 2293 | + for (std::set<std::string>::iterator iter = keys.begin(); | |
| 2294 | + iter != keys.end(); ++iter) | |
| 2295 | + { | |
| 2296 | + dict.replaceKey( | |
| 2297 | + *iter, | |
| 2298 | + replaceForeignIndirectObjects( | |
| 2299 | + old_dict.getKey(*iter), obj_copier, false)); | |
| 2300 | + } | |
| 2301 | + if (this->copied_stream_data_provider == 0) | |
| 2302 | + { | |
| 2303 | + this->copied_stream_data_provider = new CopiedStreamDataProvider(); | |
| 2304 | + this->copied_streams = this->copied_stream_data_provider; | |
| 2305 | + } | |
| 2306 | + ObjGen local_og(result.getObjectID(), result.getGeneration()); | |
| 2307 | + this->copied_stream_data_provider->registerForeignStream( | |
| 2308 | + local_og, foreign); | |
| 2309 | + result.replaceStreamData(this->copied_streams, | |
| 2310 | + dict.getKey("/Filter"), | |
| 2311 | + dict.getKey("/DecodeParms")); | |
| 2312 | + } | |
| 2313 | + else | |
| 2314 | + { | |
| 2315 | + foreign.assertScalar(); | |
| 2316 | + result = foreign; | |
| 2317 | + result.makeDirect(); | |
| 2318 | + } | |
| 2319 | + | |
| 2320 | + if (top && (! result.isStream()) && result.isIndirect()) | |
| 2321 | + { | |
| 2322 | + throw std::logic_error("replacement for foreign object is indirect"); | |
| 2323 | + } | |
| 2324 | + | |
| 2325 | + return result; | |
| 2326 | +} | |
| 2070 | 2327 | |
| 2071 | 2328 | void |
| 2072 | 2329 | QPDF::swapObjects(int objid1, int generation1, int objid2, int generation2) | ... | ... |
libqpdf/QPDFObjectHandle.cc
| ... | ... | @@ -355,6 +355,14 @@ QPDFObjectHandle::isOrHasName(std::string const& value) |
| 355 | 355 | return false; |
| 356 | 356 | } |
| 357 | 357 | |
| 358 | +// Indirect object accessors | |
| 359 | +QPDF* | |
| 360 | +QPDFObjectHandle::getOwningQPDF() | |
| 361 | +{ | |
| 362 | + // Will be null for direct objects | |
| 363 | + return this->qpdf; | |
| 364 | +} | |
| 365 | + | |
| 358 | 366 | // Dictionary mutators |
| 359 | 367 | |
| 360 | 368 | void |
| ... | ... | @@ -784,6 +792,7 @@ QPDFObjectHandle::makeDirectInternal(std::set<int>& visited) |
| 784 | 792 | } |
| 785 | 793 | |
| 786 | 794 | dereference(); |
| 795 | + this->qpdf = 0; | |
| 787 | 796 | this->objid = 0; |
| 788 | 797 | this->generation = 0; |
| 789 | 798 | |
| ... | ... | @@ -946,6 +955,16 @@ QPDFObjectHandle::assertReserved() |
| 946 | 955 | } |
| 947 | 956 | |
| 948 | 957 | void |
| 958 | +QPDFObjectHandle::assertIndirect() | |
| 959 | +{ | |
| 960 | + if (! isIndirect()) | |
| 961 | + { | |
| 962 | + throw std::logic_error( | |
| 963 | + "operation for indirect object attempted on direct object"); | |
| 964 | + } | |
| 965 | +} | |
| 966 | + | |
| 967 | +void | |
| 949 | 968 | QPDFObjectHandle::assertScalar() |
| 950 | 969 | { |
| 951 | 970 | assertType("Scalar", isScalar()); |
| ... | ... | @@ -957,11 +976,24 @@ QPDFObjectHandle::assertNumber() |
| 957 | 976 | assertType("Number", isNumber()); |
| 958 | 977 | } |
| 959 | 978 | |
| 979 | +bool | |
| 980 | +QPDFObjectHandle::isPageObject() | |
| 981 | +{ | |
| 982 | + return (this->isDictionary() && this->hasKey("/Type") && | |
| 983 | + (this->getKey("/Type").getName() == "/Page")); | |
| 984 | +} | |
| 985 | + | |
| 986 | +bool | |
| 987 | +QPDFObjectHandle::isPagesObject() | |
| 988 | +{ | |
| 989 | + return (this->isDictionary() && this->hasKey("/Type") && | |
| 990 | + (this->getKey("/Type").getName() == "/Pages")); | |
| 991 | +} | |
| 992 | + | |
| 960 | 993 | void |
| 961 | 994 | QPDFObjectHandle::assertPageObject() |
| 962 | 995 | { |
| 963 | - if (! (this->isDictionary() && this->hasKey("/Type") && | |
| 964 | - (this->getKey("/Type").getName() == "/Page"))) | |
| 996 | + if (! isPageObject()) | |
| 965 | 997 | { |
| 966 | 998 | throw std::logic_error("page operation called on non-Page object"); |
| 967 | 999 | } | ... | ... |
libqpdf/QPDF_optimization.cc
| ... | ... | @@ -232,6 +232,14 @@ QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys) |
| 232 | 232 | // Traverse pages tree pushing all inherited resources down to the |
| 233 | 233 | // page level. |
| 234 | 234 | |
| 235 | + // The record of whether we've done this is cleared by | |
| 236 | + // updateAllPagesCache(). If we're warning for skipped keys, | |
| 237 | + // re-traverse unconditionally. | |
| 238 | + if (this->pushed_inherited_attributes_to_pages && (! warn_skipped_keys)) | |
| 239 | + { | |
| 240 | + return; | |
| 241 | + } | |
| 242 | + | |
| 235 | 243 | // key_ancestors is a mapping of page attribute keys to a stack of |
| 236 | 244 | // Pages nodes that contain values for them. |
| 237 | 245 | std::map<std::string, std::vector<QPDFObjectHandle> > key_ancestors; |
| ... | ... | @@ -240,6 +248,7 @@ QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys) |
| 240 | 248 | this->trailer.getKey("/Root").getKey("/Pages"), |
| 241 | 249 | key_ancestors, this->all_pages, allow_changes, warn_skipped_keys); |
| 242 | 250 | assert(key_ancestors.empty()); |
| 251 | + this->pushed_inherited_attributes_to_pages = true; | |
| 243 | 252 | } |
| 244 | 253 | |
| 245 | 254 | void | ... | ... |
libqpdf/QPDF_pages.cc
| ... | ... | @@ -89,6 +89,7 @@ QPDF::updateAllPagesCache() |
| 89 | 89 | QTC::TC("qpdf", "QPDF updateAllPagesCache"); |
| 90 | 90 | this->all_pages.clear(); |
| 91 | 91 | this->pageobj_to_pages_pos.clear(); |
| 92 | + this->pushed_inherited_attributes_to_pages = false; | |
| 92 | 93 | getAllPages(); |
| 93 | 94 | } |
| 94 | 95 | |
| ... | ... | @@ -161,6 +162,12 @@ QPDF::insertPage(QPDFObjectHandle newpage, int pos) |
| 161 | 162 | QTC::TC("qpdf", "QPDF insert non-indirect page"); |
| 162 | 163 | newpage = this->makeIndirectObject(newpage); |
| 163 | 164 | } |
| 165 | + else if (newpage.getOwningQPDF() != this) | |
| 166 | + { | |
| 167 | + QTC::TC("qpdf", "QPDF insert foreign page"); | |
| 168 | + newpage.getOwningQPDF()->pushInheritedAttributesToPage(); | |
| 169 | + newpage = this->copyForeignObject(newpage, true); | |
| 170 | + } | |
| 164 | 171 | else |
| 165 | 172 | { |
| 166 | 173 | QTC::TC("qpdf", "QPDF insert indirect page"); | ... | ... |
qpdf/qpdf.testcov
| ... | ... | @@ -218,3 +218,18 @@ QPDF unknown key not inherited 0 |
| 218 | 218 | QPDF_Stream provider length not provided 0 |
| 219 | 219 | QPDF_Stream unknown stream length 0 |
| 220 | 220 | QPDF replaceReserved 0 |
| 221 | +QPDF copyForeign direct 0 | |
| 222 | +QPDF copyForeign not foreign 0 | |
| 223 | +QPDF copy indirect 0 | |
| 224 | +QPDF loop reserving objects 0 | |
| 225 | +QPDF replace indirect 0 | |
| 226 | +QPDF replace array 0 | |
| 227 | +QPDF replace dictionary 0 | |
| 228 | +QPDF replace stream 0 | |
| 229 | +QPDF reserve array 0 | |
| 230 | +QPDF reserve dictionary 0 | |
| 231 | +QPDF reserve stream 0 | |
| 232 | +QPDF not crossing page boundary 0 | |
| 233 | +QPDF replace foreign indirect with null 0 | |
| 234 | +QPDF not copying pages object 0 | |
| 235 | +QPDF insert foreign page 0 | ... | ... |
qpdf/qtest/qpdf.test
| ... | ... | @@ -379,6 +379,27 @@ $td->runtest("check output", |
| 379 | 379 | {$td->FILE => "a.pdf"}, |
| 380 | 380 | {$td->FILE => "from-scratch-0.pdf"}); |
| 381 | 381 | # ---------- |
| 382 | +$td->notify("--- Copy Foreign Objects ---"); | |
| 383 | +$n_tests += 7; | |
| 384 | + | |
| 385 | +foreach my $d ([25, 1], [26, 2], [27, 3]) | |
| 386 | +{ | |
| 387 | + my ($testn, $outn) = @$d; | |
| 388 | + $td->runtest("copy objects $outn", | |
| 389 | + {$td->COMMAND => "test_driver $testn" . | |
| 390 | + " copy-foreign-objects-in.pdf"}, | |
| 391 | + {$td->STRING => "test $testn done\n", $td->EXIT_STATUS => 0}, | |
| 392 | + $td->NORMALIZE_NEWLINES); | |
| 393 | + $td->runtest("check output", | |
| 394 | + {$td->FILE => "a.pdf"}, | |
| 395 | + {$td->FILE => "copy-foreign-objects-out$outn.pdf"}); | |
| 396 | +} | |
| 397 | +$td->runtest("copy objects error", | |
| 398 | + {$td->COMMAND => "test_driver 28 copy-foreign-objects-in.pdf"}, | |
| 399 | + {$td->FILE => "copy-foreign-objects-errors.out", | |
| 400 | + $td->EXIT_STATUS => 0}, | |
| 401 | + $td->NORMALIZE_NEWLINES); | |
| 402 | +# ---------- | |
| 382 | 403 | $td->notify("--- Error Condition Tests ---"); |
| 383 | 404 | # $n_tests incremented after initialization of badfiles below. |
| 384 | 405 | ... | ... |
qpdf/qtest/qpdf/copy-foreign-objects-errors.out
0 โ 100644
qpdf/qtest/qpdf/copy-foreign-objects-in.pdf
0 โ 100644
| 1 | +%PDF-1.3 | |
| 2 | +%ยฟรทยขรพ | |
| 3 | +%QDF-1.0 | |
| 4 | + | |
| 5 | +% This test file is specifically crafted for testing copyForeignObject | |
| 6 | +% and also for testing addPage when called with a page from another | |
| 7 | +% file. | |
| 8 | + | |
| 9 | +% The /QTest key in trailer has pointers to several indirect objects: | |
| 10 | +% O1, O2, O3 where O1 is an array that contains a dictionary that has | |
| 11 | +% a key that points to O2, O2 is a dictionary that contains an array | |
| 12 | +% that points to O1, and O3 is a page object that inherits some | |
| 13 | +% resource from its parent /Pages and also points to some other page. | |
| 14 | +% O1 also points to a stream whose dictionary has a key that points to | |
| 15 | +% another stream whose dictionary points back to the first stream. | |
| 16 | + | |
| 17 | +1 0 obj | |
| 18 | +<< | |
| 19 | + /Pages 2 0 R | |
| 20 | + /Type /Catalog | |
| 21 | +>> | |
| 22 | +endobj | |
| 23 | + | |
| 24 | +2 0 obj | |
| 25 | +<< | |
| 26 | + /Count 5 | |
| 27 | + /Kids [ | |
| 28 | + 3 0 R | |
| 29 | + 4 0 R | |
| 30 | + 5 0 R | |
| 31 | + 6 0 R | |
| 32 | + 7 0 R | |
| 33 | + ] | |
| 34 | + /Rotate 180 | |
| 35 | + /Type /Pages | |
| 36 | +>> | |
| 37 | +endobj | |
| 38 | + | |
| 39 | +%% Page 1 | |
| 40 | +3 0 obj | |
| 41 | +<< | |
| 42 | + /Contents 8 0 R | |
| 43 | + /MediaBox [ | |
| 44 | + 0 | |
| 45 | + 0 | |
| 46 | + 612 | |
| 47 | + 792 | |
| 48 | + ] | |
| 49 | + /Parent 2 0 R | |
| 50 | + /Resources << | |
| 51 | + /Font << | |
| 52 | + /F1 10 0 R | |
| 53 | + >> | |
| 54 | + /ProcSet [ | |
| 55 | ||
| 56 | + /Text | |
| 57 | + ] | |
| 58 | + >> | |
| 59 | + /Type /Page | |
| 60 | +>> | |
| 61 | +endobj | |
| 62 | + | |
| 63 | +%% Page 2 | |
| 64 | +4 0 obj | |
| 65 | +<< | |
| 66 | + /Contents 11 0 R | |
| 67 | + /MediaBox [ | |
| 68 | + 0 | |
| 69 | + 0 | |
| 70 | + 612 | |
| 71 | + 792 | |
| 72 | + ] | |
| 73 | + /Parent 2 0 R | |
| 74 | + /Resources << | |
| 75 | + /Font << | |
| 76 | + /F1 10 0 R | |
| 77 | + >> | |
| 78 | + /ProcSet [ | |
| 79 | ||
| 80 | + /Text | |
| 81 | + ] | |
| 82 | + >> | |
| 83 | + /Type /Page | |
| 84 | +>> | |
| 85 | +endobj | |
| 86 | + | |
| 87 | +%% Page 3, object O3 | |
| 88 | +5 0 obj | |
| 89 | +<< | |
| 90 | + /This-is-O3 true | |
| 91 | + /Contents 13 0 R | |
| 92 | + /MediaBox [ | |
| 93 | + 0 | |
| 94 | + 0 | |
| 95 | + 612 | |
| 96 | + 792 | |
| 97 | + ] | |
| 98 | + /Parent 2 0 R | |
| 99 | + /Resources << | |
| 100 | + /Font << | |
| 101 | + /F1 10 0 R | |
| 102 | + >> | |
| 103 | + /ProcSet [ | |
| 104 | ||
| 105 | + /Text | |
| 106 | + ] | |
| 107 | + >> | |
| 108 | + /OtherPage 6 0 R | |
| 109 | + /Type /Page | |
| 110 | +>> | |
| 111 | +endobj | |
| 112 | + | |
| 113 | +%% Page 4 | |
| 114 | +6 0 obj | |
| 115 | +<< | |
| 116 | + /This-is-O3-other-page true | |
| 117 | + /Contents 15 0 R | |
| 118 | + /MediaBox [ | |
| 119 | + 0 | |
| 120 | + 0 | |
| 121 | + 612 | |
| 122 | + 792 | |
| 123 | + ] | |
| 124 | + /Parent 2 0 R | |
| 125 | + /Resources << | |
| 126 | + /Font << | |
| 127 | + /F1 10 0 R | |
| 128 | + >> | |
| 129 | + /ProcSet [ | |
| 130 | ||
| 131 | + /Text | |
| 132 | + ] | |
| 133 | + >> | |
| 134 | + /Type /Page | |
| 135 | +>> | |
| 136 | +endobj | |
| 137 | + | |
| 138 | +%% Page 5 | |
| 139 | +7 0 obj | |
| 140 | +<< | |
| 141 | + /Contents 17 0 R | |
| 142 | + /MediaBox [ | |
| 143 | + 0 | |
| 144 | + 0 | |
| 145 | + 612 | |
| 146 | + 792 | |
| 147 | + ] | |
| 148 | + /Parent 2 0 R | |
| 149 | + /Resources << | |
| 150 | + /Font << | |
| 151 | + /F1 10 0 R | |
| 152 | + >> | |
| 153 | + /ProcSet [ | |
| 154 | ||
| 155 | + /Text | |
| 156 | + ] | |
| 157 | + >> | |
| 158 | + /Type /Page | |
| 159 | +>> | |
| 160 | +endobj | |
| 161 | + | |
| 162 | +%% Contents for page 1 | |
| 163 | +8 0 obj | |
| 164 | +<< | |
| 165 | + /Length 9 0 R | |
| 166 | +>> | |
| 167 | +stream | |
| 168 | +BT /F1 15 Tf 72 720 Td (Original page 0) Tj ET | |
| 169 | +endstream | |
| 170 | +endobj | |
| 171 | + | |
| 172 | +9 0 obj | |
| 173 | +47 | |
| 174 | +endobj | |
| 175 | + | |
| 176 | +10 0 obj | |
| 177 | +<< | |
| 178 | + /BaseFont /Times-Roman | |
| 179 | + /Encoding /WinAnsiEncoding | |
| 180 | + /Subtype /Type1 | |
| 181 | + /Type /Font | |
| 182 | +>> | |
| 183 | +endobj | |
| 184 | + | |
| 185 | +%% Contents for page 2 | |
| 186 | +11 0 obj | |
| 187 | +<< | |
| 188 | + /Length 12 0 R | |
| 189 | +>> | |
| 190 | +stream | |
| 191 | +BT /F1 15 Tf 72 720 Td (Original page 1) Tj ET | |
| 192 | +endstream | |
| 193 | +endobj | |
| 194 | + | |
| 195 | +12 0 obj | |
| 196 | +47 | |
| 197 | +endobj | |
| 198 | + | |
| 199 | +%% Contents for page 3 | |
| 200 | +13 0 obj | |
| 201 | +<< | |
| 202 | + /Length 14 0 R | |
| 203 | +>> | |
| 204 | +stream | |
| 205 | +BT /F1 15 Tf 72 720 Td (Original page 2) Tj ET | |
| 206 | +endstream | |
| 207 | +endobj | |
| 208 | + | |
| 209 | +14 0 obj | |
| 210 | +47 | |
| 211 | +endobj | |
| 212 | + | |
| 213 | +%% Contents for page 4 | |
| 214 | +15 0 obj | |
| 215 | +<< | |
| 216 | + /Length 16 0 R | |
| 217 | +>> | |
| 218 | +stream | |
| 219 | +BT /F1 15 Tf 72 720 Td (Original page 3) Tj ET | |
| 220 | +endstream | |
| 221 | +endobj | |
| 222 | + | |
| 223 | +16 0 obj | |
| 224 | +47 | |
| 225 | +endobj | |
| 226 | + | |
| 227 | +%% Contents for page 5 | |
| 228 | +17 0 obj | |
| 229 | +<< | |
| 230 | + /Length 18 0 R | |
| 231 | +>> | |
| 232 | +stream | |
| 233 | +BT /F1 15 Tf 72 720 Td (Original page 4) Tj ET | |
| 234 | +endstream | |
| 235 | +endobj | |
| 236 | + | |
| 237 | +18 0 obj | |
| 238 | +47 | |
| 239 | +endobj | |
| 240 | + | |
| 241 | +% O1 | |
| 242 | +19 0 obj | |
| 243 | +[ | |
| 244 | + /This-is-O1 | |
| 245 | + /potato | |
| 246 | + << /O2 [3.14159 << /O2 20 0 R >> 2.17828 ] >> | |
| 247 | + /salad | |
| 248 | + /O2 20 0 R | |
| 249 | + /Stream1 21 0 R | |
| 250 | +] | |
| 251 | +endobj | |
| 252 | + | |
| 253 | +% O2 | |
| 254 | +20 0 obj | |
| 255 | +<< | |
| 256 | + /This-is-O2 true | |
| 257 | + /K1 [2.236 /O1 19 0 R 1.732] | |
| 258 | + /O1 19 0 R | |
| 259 | +>> | |
| 260 | +endobj | |
| 261 | + | |
| 262 | +% stream1 | |
| 263 | +21 0 obj | |
| 264 | +<< | |
| 265 | + /This-is-Stream1 true | |
| 266 | + /Length 22 0 R | |
| 267 | + /Stream2 23 0 R | |
| 268 | +>> | |
| 269 | +stream | |
| 270 | +This is stream 1. | |
| 271 | +endstream | |
| 272 | +endobj | |
| 273 | + | |
| 274 | +22 0 obj | |
| 275 | +18 | |
| 276 | +endobj | |
| 277 | + | |
| 278 | +% stream2 | |
| 279 | +23 0 obj | |
| 280 | +<< | |
| 281 | + /This-is-Stream2 true | |
| 282 | + /Length 24 0 R | |
| 283 | + /Stream1 21 0 R | |
| 284 | +>> | |
| 285 | +stream | |
| 286 | +This is stream 2. | |
| 287 | +endstream | |
| 288 | +endobj | |
| 289 | + | |
| 290 | +24 0 obj | |
| 291 | +18 | |
| 292 | +endobj | |
| 293 | + | |
| 294 | +% QTest | |
| 295 | +25 0 obj | |
| 296 | +<< /This-is-QTest true /O1 19 0 R /O2 20 0 R /O3 5 0 R >> | |
| 297 | +endobj | |
| 298 | + | |
| 299 | +xref | |
| 300 | +0 26 | |
| 301 | +0000000000 65535 f | |
| 302 | +0000000655 00000 n | |
| 303 | +0000000709 00000 n | |
| 304 | +0000000845 00000 n | |
| 305 | +0000001073 00000 n | |
| 306 | +0000001313 00000 n | |
| 307 | +0000001580 00000 n | |
| 308 | +0000001839 00000 n | |
| 309 | +0000002081 00000 n | |
| 310 | +0000002183 00000 n | |
| 311 | +0000002202 00000 n | |
| 312 | +0000002334 00000 n | |
| 313 | +0000002438 00000 n | |
| 314 | +0000002481 00000 n | |
| 315 | +0000002585 00000 n | |
| 316 | +0000002628 00000 n | |
| 317 | +0000002732 00000 n | |
| 318 | +0000002775 00000 n | |
| 319 | +0000002879 00000 n | |
| 320 | +0000002904 00000 n | |
| 321 | +0000003042 00000 n | |
| 322 | +0000003138 00000 n | |
| 323 | +0000003255 00000 n | |
| 324 | +0000003285 00000 n | |
| 325 | +0000003402 00000 n | |
| 326 | +0000003430 00000 n | |
| 327 | +trailer << | |
| 328 | + /Root 1 0 R | |
| 329 | + /Size 26 | |
| 330 | + /QTest 25 0 R | |
| 331 | + /ID [<d15f7aca3be584a96c1c94adb0931e71><9adb6b2fdb22e857340f7103917b16e4>] | |
| 332 | +>> | |
| 333 | +startxref | |
| 334 | +3505 | |
| 335 | +%%EOF | ... | ... |
qpdf/qtest/qpdf/copy-foreign-objects-out1.pdf
0 โ 100644
| 1 | +%PDF-1.3 | |
| 2 | +%ยฟรทยขรพ | |
| 3 | +1 0 obj | |
| 4 | +<< /Pages 3 0 R /Type /Catalog >> | |
| 5 | +endobj | |
| 6 | +2 0 obj | |
| 7 | +<< /O1 4 0 R /O2 5 0 R /This-is-QTest true >> | |
| 8 | +endobj | |
| 9 | +3 0 obj | |
| 10 | +<< /Count 1 /Kids [ 6 0 R ] /Type /Pages >> | |
| 11 | +endobj | |
| 12 | +4 0 obj | |
| 13 | +[ /This-is-O1 /potato << /O2 [ 3.14159 << /O2 5 0 R >> 2.17828 ] >> /salad /O2 5 0 R /Stream1 7 0 R ] | |
| 14 | +endobj | |
| 15 | +5 0 obj | |
| 16 | +<< /K1 [ 2.236 /O1 4 0 R 1.732 ] /O1 4 0 R /This-is-O2 true >> | |
| 17 | +endobj | |
| 18 | +6 0 obj | |
| 19 | +<< /Contents 8 0 R /MediaBox [ 0 0 612 792 ] /Parent 3 0 R /Resources << /Font << /F1 9 0 R >> /ProcSet 10 0 R >> /Type /Page >> | |
| 20 | +endobj | |
| 21 | +7 0 obj | |
| 22 | +<< /Stream2 11 0 R /This-is-Stream1 true /Length 18 >> | |
| 23 | +stream | |
| 24 | +This is stream 1. | |
| 25 | +endstream | |
| 26 | +endobj | |
| 27 | +8 0 obj | |
| 28 | +<< /Length 44 >> | |
| 29 | +stream | |
| 30 | +BT | |
| 31 | + /F1 24 Tf | |
| 32 | + 72 720 Td | |
| 33 | + (Potato) Tj | |
| 34 | +ET | |
| 35 | +endstream | |
| 36 | +endobj | |
| 37 | +9 0 obj | |
| 38 | +<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >> | |
| 39 | +endobj | |
| 40 | +10 0 obj | |
| 41 | +[ /PDF /Text ] | |
| 42 | +endobj | |
| 43 | +11 0 obj | |
| 44 | +<< /Stream1 7 0 R /This-is-Stream2 true /Length 18 >> | |
| 45 | +stream | |
| 46 | +This is stream 2. | |
| 47 | +endstream | |
| 48 | +endobj | |
| 49 | +xref | |
| 50 | +0 12 | |
| 51 | +0000000000 65535 f | |
| 52 | +0000000015 00000 n | |
| 53 | +0000000064 00000 n | |
| 54 | +0000000125 00000 n | |
| 55 | +0000000184 00000 n | |
| 56 | +0000000301 00000 n | |
| 57 | +0000000379 00000 n | |
| 58 | +0000000523 00000 n | |
| 59 | +0000000628 00000 n | |
| 60 | +0000000721 00000 n | |
| 61 | +0000000828 00000 n | |
| 62 | +0000000859 00000 n | |
| 63 | +trailer << /QTest 2 0 R /Root 1 0 R /Size 12 /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] >> | |
| 64 | +startxref | |
| 65 | +964 | |
| 66 | +%%EOF | ... | ... |
qpdf/qtest/qpdf/copy-foreign-objects-out2.pdf
0 โ 100644
| 1 | +%PDF-1.3 | |
| 2 | +%ยฟรทยขรพ | |
| 3 | +1 0 obj | |
| 4 | +<< /Pages 3 0 R /Type /Catalog >> | |
| 5 | +endobj | |
| 6 | +2 0 obj | |
| 7 | +<< /O1 4 0 R /O2 5 0 R /O3 6 0 R /This-is-QTest true >> | |
| 8 | +endobj | |
| 9 | +3 0 obj | |
| 10 | +<< /Count 2 /Kids [ 7 0 R 6 0 R ] /Type /Pages >> | |
| 11 | +endobj | |
| 12 | +4 0 obj | |
| 13 | +[ /This-is-O1 /potato << /O2 [ 3.14159 << /O2 5 0 R >> 2.17828 ] >> /salad /O2 5 0 R /Stream1 8 0 R ] | |
| 14 | +endobj | |
| 15 | +5 0 obj | |
| 16 | +<< /K1 [ 2.236 /O1 4 0 R 1.732 ] /O1 4 0 R /This-is-O2 true >> | |
| 17 | +endobj | |
| 18 | +6 0 obj | |
| 19 | +<< /Contents 9 0 R /MediaBox [ 0 0 612 792 ] /Parent 3 0 R /Resources << /Font << /F1 10 0 R >> /ProcSet [ /PDF /Text ] >> /Rotate 180 /This-is-O3 true /Type /Page >> | |
| 20 | +endobj | |
| 21 | +7 0 obj | |
| 22 | +<< /Contents 11 0 R /MediaBox [ 0 0 612 792 ] /Parent 3 0 R /Resources << /Font << /F1 12 0 R >> /ProcSet 13 0 R >> /Type /Page >> | |
| 23 | +endobj | |
| 24 | +8 0 obj | |
| 25 | +<< /Stream2 14 0 R /This-is-Stream1 true /Length 18 >> | |
| 26 | +stream | |
| 27 | +This is stream 1. | |
| 28 | +endstream | |
| 29 | +endobj | |
| 30 | +9 0 obj | |
| 31 | +<< /Length 47 >> | |
| 32 | +stream | |
| 33 | +BT /F1 15 Tf 72 720 Td (Original page 2) Tj ET | |
| 34 | +endstream | |
| 35 | +endobj | |
| 36 | +10 0 obj | |
| 37 | +<< /BaseFont /Times-Roman /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >> | |
| 38 | +endobj | |
| 39 | +11 0 obj | |
| 40 | +<< /Length 44 >> | |
| 41 | +stream | |
| 42 | +BT | |
| 43 | + /F1 24 Tf | |
| 44 | + 72 720 Td | |
| 45 | + (Potato) Tj | |
| 46 | +ET | |
| 47 | +endstream | |
| 48 | +endobj | |
| 49 | +12 0 obj | |
| 50 | +<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >> | |
| 51 | +endobj | |
| 52 | +13 0 obj | |
| 53 | +[ /PDF /Text ] | |
| 54 | +endobj | |
| 55 | +14 0 obj | |
| 56 | +<< /Stream1 8 0 R /This-is-Stream2 true /Length 18 >> | |
| 57 | +stream | |
| 58 | +This is stream 2. | |
| 59 | +endstream | |
| 60 | +endobj | |
| 61 | +xref | |
| 62 | +0 15 | |
| 63 | +0000000000 65535 f | |
| 64 | +0000000015 00000 n | |
| 65 | +0000000064 00000 n | |
| 66 | +0000000135 00000 n | |
| 67 | +0000000200 00000 n | |
| 68 | +0000000317 00000 n | |
| 69 | +0000000395 00000 n | |
| 70 | +0000000577 00000 n | |
| 71 | +0000000723 00000 n | |
| 72 | +0000000828 00000 n | |
| 73 | +0000000924 00000 n | |
| 74 | +0000001024 00000 n | |
| 75 | +0000001118 00000 n | |
| 76 | +0000001226 00000 n | |
| 77 | +0000001257 00000 n | |
| 78 | +trailer << /QTest 2 0 R /Root 1 0 R /Size 15 /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] >> | |
| 79 | +startxref | |
| 80 | +1362 | |
| 81 | +%%EOF | ... | ... |
qpdf/qtest/qpdf/copy-foreign-objects-out3.pdf
0 โ 100644
| 1 | +%PDF-1.3 | |
| 2 | +%ยฟรทยขรพ | |
| 3 | +1 0 obj | |
| 4 | +<< /Pages 3 0 R /Type /Catalog >> | |
| 5 | +endobj | |
| 6 | +2 0 obj | |
| 7 | +<< /O1 4 0 R /O2 5 0 R /O3 6 0 R /This-is-QTest true >> | |
| 8 | +endobj | |
| 9 | +3 0 obj | |
| 10 | +<< /Count 3 /Kids [ 7 0 R 8 0 R 6 0 R ] /Type /Pages >> | |
| 11 | +endobj | |
| 12 | +4 0 obj | |
| 13 | +[ /This-is-O1 /potato << /O2 [ 3.14159 << /O2 5 0 R >> 2.17828 ] >> /salad /O2 5 0 R /Stream1 9 0 R ] | |
| 14 | +endobj | |
| 15 | +5 0 obj | |
| 16 | +<< /K1 [ 2.236 /O1 4 0 R 1.732 ] /O1 4 0 R /This-is-O2 true >> | |
| 17 | +endobj | |
| 18 | +6 0 obj | |
| 19 | +<< /Contents 10 0 R /MediaBox [ 0 0 612 792 ] /OtherPage 8 0 R /Parent 3 0 R /Resources << /Font << /F1 11 0 R >> /ProcSet [ /PDF /Text ] >> /Rotate 180 /This-is-O3 true /Type /Page >> | |
| 20 | +endobj | |
| 21 | +7 0 obj | |
| 22 | +<< /Contents 12 0 R /MediaBox [ 0 0 612 792 ] /Parent 3 0 R /Resources << /Font << /F1 13 0 R >> /ProcSet 14 0 R >> /Type /Page >> | |
| 23 | +endobj | |
| 24 | +8 0 obj | |
| 25 | +<< /Contents 15 0 R /MediaBox [ 0 0 612 792 ] /Parent 3 0 R /Resources << /Font << /F1 11 0 R >> /ProcSet [ /PDF /Text ] >> /Rotate 180 /This-is-O3-other-page true /Type /Page >> | |
| 26 | +endobj | |
| 27 | +9 0 obj | |
| 28 | +<< /Stream2 16 0 R /This-is-Stream1 true /Length 18 >> | |
| 29 | +stream | |
| 30 | +This is stream 1. | |
| 31 | +endstream | |
| 32 | +endobj | |
| 33 | +10 0 obj | |
| 34 | +<< /Length 47 >> | |
| 35 | +stream | |
| 36 | +BT /F1 15 Tf 72 720 Td (Original page 2) Tj ET | |
| 37 | +endstream | |
| 38 | +endobj | |
| 39 | +11 0 obj | |
| 40 | +<< /BaseFont /Times-Roman /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >> | |
| 41 | +endobj | |
| 42 | +12 0 obj | |
| 43 | +<< /Length 44 >> | |
| 44 | +stream | |
| 45 | +BT | |
| 46 | + /F1 24 Tf | |
| 47 | + 72 720 Td | |
| 48 | + (Potato) Tj | |
| 49 | +ET | |
| 50 | +endstream | |
| 51 | +endobj | |
| 52 | +13 0 obj | |
| 53 | +<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >> | |
| 54 | +endobj | |
| 55 | +14 0 obj | |
| 56 | +[ /PDF /Text ] | |
| 57 | +endobj | |
| 58 | +15 0 obj | |
| 59 | +<< /Length 47 >> | |
| 60 | +stream | |
| 61 | +BT /F1 15 Tf 72 720 Td (Original page 3) Tj ET | |
| 62 | +endstream | |
| 63 | +endobj | |
| 64 | +16 0 obj | |
| 65 | +<< /Stream1 9 0 R /This-is-Stream2 true /Length 18 >> | |
| 66 | +stream | |
| 67 | +This is stream 2. | |
| 68 | +endstream | |
| 69 | +endobj | |
| 70 | +xref | |
| 71 | +0 17 | |
| 72 | +0000000000 65535 f | |
| 73 | +0000000015 00000 n | |
| 74 | +0000000064 00000 n | |
| 75 | +0000000135 00000 n | |
| 76 | +0000000206 00000 n | |
| 77 | +0000000323 00000 n | |
| 78 | +0000000401 00000 n | |
| 79 | +0000000601 00000 n | |
| 80 | +0000000747 00000 n | |
| 81 | +0000000941 00000 n | |
| 82 | +0000001046 00000 n | |
| 83 | +0000001143 00000 n | |
| 84 | +0000001243 00000 n | |
| 85 | +0000001337 00000 n | |
| 86 | +0000001445 00000 n | |
| 87 | +0000001476 00000 n | |
| 88 | +0000001573 00000 n | |
| 89 | +trailer << /QTest 2 0 R /Root 1 0 R /Size 17 /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] >> | |
| 90 | +startxref | |
| 91 | +1678 | |
| 92 | +%%EOF | ... | ... |
qpdf/test_driver.cc
| ... | ... | @@ -916,6 +916,89 @@ void runtest(int n, char const* filename) |
| 916 | 916 | w.setStreamDataMode(qpdf_s_preserve); |
| 917 | 917 | w.write(); |
| 918 | 918 | } |
| 919 | + else if (n == 25) | |
| 920 | + { | |
| 921 | + // The copy object tests are designed to work with a specific | |
| 922 | + // file. Look at the test suite for the file, and look at the | |
| 923 | + // file for comments about the file's structure. | |
| 924 | + | |
| 925 | + // Copy qtest without crossing page boundaries. Should get O1 | |
| 926 | + // and O2 and their streams but not O3 or any other pages. | |
| 927 | + | |
| 928 | + QPDF newpdf; | |
| 929 | + newpdf.processFile("minimal.pdf"); | |
| 930 | + QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest"); | |
| 931 | + newpdf.getTrailer().replaceKey( | |
| 932 | + "/QTest", newpdf.copyForeignObject(qtest)); | |
| 933 | + | |
| 934 | + QPDFWriter w(newpdf, "a.pdf"); | |
| 935 | + w.setStaticID(true); | |
| 936 | + w.setStreamDataMode(qpdf_s_preserve); | |
| 937 | + w.write(); | |
| 938 | + } | |
| 939 | + else if (n == 26) | |
| 940 | + { | |
| 941 | + // Copy the O3 page using addPage. Copy qtest without | |
| 942 | + // crossing page boundaries. In addition to previous results, | |
| 943 | + // should get page O3 but no other pages including the page | |
| 944 | + // that O3 points to. Also, inherited object will have been | |
| 945 | + // pushed down and will be preserved. | |
| 946 | + | |
| 947 | + QPDF newpdf; | |
| 948 | + newpdf.processFile("minimal.pdf"); | |
| 949 | + QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest"); | |
| 950 | + QPDFObjectHandle O3 = qtest.getKey("/O3"); | |
| 951 | + newpdf.addPage(O3, false); | |
| 952 | + newpdf.getTrailer().replaceKey( | |
| 953 | + "/QTest", newpdf.copyForeignObject(qtest)); | |
| 954 | + | |
| 955 | + QPDFWriter w(newpdf, "a.pdf"); | |
| 956 | + w.setStaticID(true); | |
| 957 | + w.setStreamDataMode(qpdf_s_preserve); | |
| 958 | + w.write(); | |
| 959 | + } | |
| 960 | + else if (n == 27) | |
| 961 | + { | |
| 962 | + // Copy O3 and the page O3 refers to before copying qtest. | |
| 963 | + // Should get qtest plus only the O3 page and the page that O3 | |
| 964 | + // points to. Inherited objects should be preserved. | |
| 965 | + | |
| 966 | + QPDF newpdf; | |
| 967 | + newpdf.processFile("minimal.pdf"); | |
| 968 | + QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest"); | |
| 969 | + QPDFObjectHandle O3 = qtest.getKey("/O3"); | |
| 970 | + newpdf.addPage(O3.getKey("/OtherPage"), false); | |
| 971 | + newpdf.addPage(O3, false); | |
| 972 | + newpdf.getTrailer().replaceKey( | |
| 973 | + "/QTest", newpdf.copyForeignObject(qtest)); | |
| 974 | + | |
| 975 | + QPDFWriter w(newpdf, "a.pdf"); | |
| 976 | + w.setStaticID(true); | |
| 977 | + w.setStreamDataMode(qpdf_s_preserve); | |
| 978 | + w.write(); | |
| 979 | + } | |
| 980 | + else if (n == 28) | |
| 981 | + { | |
| 982 | + // Copy foreign object errors | |
| 983 | + try | |
| 984 | + { | |
| 985 | + pdf.copyForeignObject(pdf.getTrailer().getKey("/QTest")); | |
| 986 | + std::cout << "oops -- didn't throw" << std::endl; | |
| 987 | + } | |
| 988 | + catch (std::logic_error e) | |
| 989 | + { | |
| 990 | + std::cout << "logic error: " << e.what() << std::endl; | |
| 991 | + } | |
| 992 | + try | |
| 993 | + { | |
| 994 | + pdf.copyForeignObject(QPDFObjectHandle::newInteger(1)); | |
| 995 | + std::cout << "oops -- didn't throw" << std::endl; | |
| 996 | + } | |
| 997 | + catch (std::logic_error e) | |
| 998 | + { | |
| 999 | + std::cout << "logic error: " << e.what() << std::endl; | |
| 1000 | + } | |
| 1001 | + } | |
| 919 | 1002 | else |
| 920 | 1003 | { |
| 921 | 1004 | throw std::runtime_error(std::string("invalid test ") + | ... | ... |