diff options
-rw-r--r-- | ChangeLog | 14 | ||||
-rw-r--r-- | TODO | 1 | ||||
-rw-r--r-- | include/qpdf/QPDFObjectHandle.hh | 5 | ||||
-rw-r--r-- | include/qpdf/QPDFPageObjectHelper.hh | 41 | ||||
-rw-r--r-- | libqpdf/QPDFObjectHandle.cc | 15 | ||||
-rw-r--r-- | libqpdf/QPDFPageObjectHelper.cc | 108 | ||||
-rw-r--r-- | manual/qpdf-manual.xml | 54 | ||||
-rw-r--r-- | qpdf/qpdf.testcov | 1 | ||||
-rw-r--r-- | qpdf/qtest/qpdf.test | 7 | ||||
-rw-r--r-- | qpdf/qtest/qpdf/nested-form-xobjects.out | 49 | ||||
-rw-r--r-- | qpdf/qtest/qpdf/nested-form-xobjects.pdf | 397 | ||||
-rw-r--r-- | qpdf/test_driver.cc | 51 |
12 files changed, 715 insertions, 28 deletions
@@ -1,3 +1,17 @@ +2021-01-01 Jay Berkenbilt <ejb@ql.org> + + * Add methods to QPDFPageObjectHelper: forEachXObject, + forEachImage, forEachFormXObject to call a function on each + XObject (or image or form XObject) in a page or form XObject, + possibly recursing into nested form XObjects. + + * Add method QPDFPageObjectHelper::getFormXObjects to return a map + of keys to form XObjects (non-recursively) from a page or form + XObject. + + * Add method QPDFObjectHandle::isImage to test whether an object + is an image. + 2020-12-31 Jay Berkenbilt <ejb@ql.org> * QPDFPageObjectHelper::removeUnreferencedResources can now be @@ -8,7 +8,6 @@ Candidates for upcoming release * #473: zsh completion with directories * Investigate how serverless does completion * Non-bugs - * #436: parsing of document with form xobject * #488: allow specification of jpeg compression quality; may also need an option to recompress jpeg diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh index e383e274..2a5d0392 100644 --- a/include/qpdf/QPDFObjectHandle.hh +++ b/include/qpdf/QPDFObjectHandle.hh @@ -1150,6 +1150,11 @@ class QPDFObjectHandle QPDF_DLL bool isFormXObject(); + // Indicate if this is an image. If exclude_imagemask is true, + // don't count image masks as images. + QPDF_DLL + bool isImage(bool exclude_imagemask=true); + private: QPDFObjectHandle(QPDF*, int objid, int generation); QPDFObjectHandle(QPDFObject*); diff --git a/include/qpdf/QPDFPageObjectHelper.hh b/include/qpdf/QPDFPageObjectHelper.hh index b25de1fe..93745446 100644 --- a/include/qpdf/QPDFPageObjectHelper.hh +++ b/include/qpdf/QPDFPageObjectHelper.hh @@ -72,11 +72,42 @@ class QPDFPageObjectHelper: public QPDFObjectHelper QPDFObjectHandle getMediaBox(bool copy_if_shared = false); + // Iterate through XObjects, possibly recursing into form + // XObjects. This works with pages or form XObjects. Call action + // on each XObject for which selector, if specified, returns true. + // With no selector, calls action for every object. In addition to + // the object being passed to action, the containing XObject + // dictionary and key are passed in. Remember that the XObject + // dictionary may be shared, and the object may appear in multiple + // XObject dictionaries. + QPDF_DLL + void forEachXObject( + bool recursive, + std::function<void(QPDFObjectHandle& obj, + QPDFObjectHandle& xobj_dict, + std::string const& key)> action, + std::function<bool(QPDFObjectHandle)> selector=nullptr); + // Only call action for images + QPDF_DLL + void forEachImage( + bool recursive, + std::function<void(QPDFObjectHandle& obj, + QPDFObjectHandle& xobj_dict, + std::string const& key)> action); + // Only call action for form XObjects + QPDF_DLL + void forEachFormXObject( + bool recursive, + std::function<void(QPDFObjectHandle& obj, + QPDFObjectHandle& xobj_dict, + std::string const& key)> action); + // Returns an empty map if there are no images or no resources. // Prior to qpdf 8.4.0, this function did not support inherited // resources, but it does now. Return value is a map from XObject // name to the image object, which is always a stream. Works with - // form XObjects as well as pages. + // form XObjects as well as pages. This method does not recurse + // into nested form XObjects. For that, use forEachImage. QPDF_DLL std::map<std::string, QPDFObjectHandle> getImages(); @@ -84,6 +115,14 @@ class QPDFPageObjectHelper: public QPDFObjectHelper QPDF_DLL std::map<std::string, QPDFObjectHandle> getPageImages(); + // Returns an empty map if there are no form XObjects or no + // resources. Otherwise, returns a map of keys to form XObjects + // directly referenced from this page or form XObjects. This does + // not recurse into nested form XObjects. For that, use + // forEachFormXObject. + QPDF_DLL + std::map<std::string, QPDFObjectHandle> getFormXObjects(); + // Convert each inline image to an external (normal) image if the // size is at least the specified number of bytes. QPDF_DLL diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index 180ccfa3..add0b14d 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -2948,6 +2948,21 @@ QPDFObjectHandle::isFormXObject() ("/Form" == dict.getKey("/Subtype").getName())); } +bool +QPDFObjectHandle::isImage(bool exclude_imagemask) +{ + if (! this->isStream()) + { + return false; + } + QPDFObjectHandle dict = this->getDict(); + return (dict.hasKey("/Subtype") && + (dict.getKey("/Subtype").getName() == "/Image") && + ((! exclude_imagemask) || + (! (dict.getKey("/ImageMask").isBool() && + dict.getKey("/ImageMask").getBoolValue())))); +} + void QPDFObjectHandle::assertPageObject() { diff --git a/libqpdf/QPDFPageObjectHelper.cc b/libqpdf/QPDFPageObjectHelper.cc index ce244253..ef563dc2 100644 --- a/libqpdf/QPDFPageObjectHelper.cc +++ b/libqpdf/QPDFPageObjectHelper.cc @@ -386,6 +386,73 @@ QPDFPageObjectHelper::getMediaBox(bool copy_if_shared) return getAttribute("/MediaBox", copy_if_shared); } +void +QPDFPageObjectHelper::forEachXObject( + bool recursive, + std::function<void(QPDFObjectHandle& obj, + QPDFObjectHandle& xobj_dict, + std::string const& key)> action, + std::function<bool(QPDFObjectHandle)> selector) +{ + QTC::TC("qpdf", "QPDFPageObjectHelper::forEachXObject", + recursive + ? (this->oh.isFormXObject() ? 0 : 1) + : (this->oh.isFormXObject() ? 2 : 3)); + std::set<QPDFObjGen> seen; + std::list<QPDFPageObjectHelper> queue; + queue.push_back(*this); + while (! queue.empty()) + { + QPDFPageObjectHelper ph = queue.front(); + queue.pop_front(); + QPDFObjGen og = ph.oh.getObjGen(); + if (seen.count(og)) + { + continue; + } + seen.insert(og); + QPDFObjectHandle resources = ph.getAttribute("/Resources", false); + if (resources.isDictionary() && resources.hasKey("/XObject")) + { + QPDFObjectHandle xobj_dict = resources.getKey("/XObject"); + for (auto const& key: xobj_dict.getKeys()) + { + QPDFObjectHandle obj = xobj_dict.getKey(key); + if ((! selector) || selector(obj)) + { + action(obj, xobj_dict, key); + } + if (recursive && obj.isFormXObject()) + { + queue.push_back(QPDFPageObjectHelper(obj)); + } + } + } + } +} + +void +QPDFPageObjectHelper::forEachImage( + bool recursive, + std::function<void(QPDFObjectHandle& obj, + QPDFObjectHandle& xobj_dict, + std::string const& key)> action) +{ + forEachXObject(recursive, action, + [](QPDFObjectHandle obj) { return obj.isImage(); }); +} + +void +QPDFPageObjectHelper::forEachFormXObject( + bool recursive, + std::function<void(QPDFObjectHandle& obj, + QPDFObjectHandle& xobj_dict, + std::string const& key)> action) +{ + forEachXObject(recursive, action, + [](QPDFObjectHandle obj) { return obj.isFormXObject(); }); +} + std::map<std::string, QPDFObjectHandle> QPDFPageObjectHelper::getPageImages() { @@ -396,32 +463,23 @@ std::map<std::string, QPDFObjectHandle> QPDFPageObjectHelper::getImages() { std::map<std::string, QPDFObjectHandle> result; - QPDFObjectHandle resources = getAttribute("/Resources", false); - if (resources.isDictionary()) - { - if (resources.hasKey("/XObject")) - { - QPDFObjectHandle xobject = resources.getKey("/XObject"); - std::set<std::string> keys = xobject.getKeys(); - for (std::set<std::string>::iterator iter = keys.begin(); - iter != keys.end(); ++iter) - { - std::string key = (*iter); - QPDFObjectHandle value = xobject.getKey(key); - if (value.isStream()) - { - QPDFObjectHandle dict = value.getDict(); - if (dict.hasKey("/Subtype") && - (dict.getKey("/Subtype").getName() == "/Image") && - (! dict.hasKey("/ImageMask"))) - { - result[key] = value; - } - } - } - } - } + forEachImage(false, [&result](QPDFObjectHandle& obj, + QPDFObjectHandle&, + std::string const& key) { + result[key] = obj; + }); + return result; +} +std::map<std::string, QPDFObjectHandle> +QPDFPageObjectHelper::getFormXObjects() +{ + std::map<std::string, QPDFObjectHandle> result; + forEachFormXObject(false, [&result](QPDFObjectHandle& obj, + QPDFObjectHandle&, + std::string const& key) { + result[key] = obj; + }); return result; } diff --git a/manual/qpdf-manual.xml b/manual/qpdf-manual.xml index 0b7156b2..bae87536 100644 --- a/manual/qpdf-manual.xml +++ b/manual/qpdf-manual.xml @@ -4859,6 +4859,60 @@ print "\n"; </listitem> <listitem> <para> + Add methods to <classname>QPDFPageObjectHelper</classname> + to iterate through XObjects on a page or form XObjects, + possibly recursing into nested form XObjects: + <function>forEachXObject</function>, + <function>ForEachImage</function>, + <function>forEachFormXObject</function>. + </para> + </listitem> + <listitem> + <para> + Enhance several methods in + <classname>QPDFPageObjectHelper</classname> to work with + form XObjects as well as pages, as noted in comments. See + <filename>ChangeLog</filename> for a full list. + </para> + </listitem> + <listitem> + <para> + Rename some functions in + <classname>QPDFPageObjectHelper</classname>, while keeping + old names for compatibility: + <itemizedlist> + <listitem> + <para> + <function>getPageImages</function> to + <function>getImages</function> + </para> + </listitem> + <listitem> + <para> + <function>filterPageContents</function> to + <function>filterContents</function> + </para> + </listitem> + </itemizedlist> + </para> + </listitem> + <listitem> + <para> + Add method + <function>QPDFPageObjectHelper::getFormXObjects</function> + to return a map of form XObjects directly on a page or form + XObject + </para> + </listitem> + <listitem> + <para> + Add new helper methods to + <classname>QPDFObjectHandle</classname>: + <function>isFormXObject</function>, <function>isImage</function> + </para> + </listitem> + <listitem> + <para> Add the optional <function>allow_streams</function> parameter <function>QPDFObjectHandle::makeDirect</function>. When <function>QPDFObjectHandle::makeDirect</function> is diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 2fddc87c..78ddc304 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -521,3 +521,4 @@ qpdf-c called qpdf_oh_unparse 0 qpdf-c called qpdf_oh_unparse_resolved 0 qpdf-c called qpdf_oh_unparse_binary 0 QPDFWriter getFilterOnWrite false 0 +QPDFPageObjectHelper::forEachXObject 3 diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index 1fdc5f09..32751a98 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -423,7 +423,7 @@ foreach my $i (@choice_values) show_ntests(); # ---------- $td->notify("--- Form XObject, underlay, overlay ---"); -$n_tests += 18; +$n_tests += 19; $td->runtest("form xobject creation", {$td->COMMAND => "test_driver 55 fxo-red.pdf"}, @@ -486,6 +486,11 @@ for (my $i = 1; $i <= scalar(@uo_cases); ++$i) {$td->FILE => "a.pdf"}, {$td->FILE => "$outbase.pdf"}); } +$td->runtest("foreach", + {$td->COMMAND => "test_driver 71 nested-form-xobjects.pdf"}, + {$td->FILE => "nested-form-xobjects.out", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); show_ntests(); # ---------- diff --git a/qpdf/qtest/qpdf/nested-form-xobjects.out b/qpdf/qtest/qpdf/nested-form-xobjects.out new file mode 100644 index 00000000..0fa2ebb1 --- /dev/null +++ b/qpdf/qtest/qpdf/nested-form-xobjects.out @@ -0,0 +1,49 @@ +--- recursive, all --- +<< /Fx1 12 0 R /Im1 14 0 R /Im2 16 0 R >> -> /Fx1 -> 12 0 R +<< /Fx1 12 0 R /Im1 14 0 R /Im2 16 0 R >> -> /Im1 -> 14 0 R +<< /Fx1 12 0 R /Im1 14 0 R /Im2 16 0 R >> -> /Im2 -> 16 0 R +<< /Fx1 20 0 R /Im1 22 0 R /Im2 24 0 R >> -> /Fx1 -> 20 0 R +<< /Fx1 20 0 R /Im1 22 0 R /Im2 24 0 R >> -> /Im1 -> 22 0 R +<< /Fx1 20 0 R /Im1 22 0 R /Im2 24 0 R >> -> /Im2 -> 24 0 R +<< /Im1 26 0 R /Im2 28 0 R >> -> /Im1 -> 26 0 R +<< /Im1 26 0 R /Im2 28 0 R >> -> /Im2 -> 28 0 R +--- non-recursive, all --- +<< /Fx1 12 0 R /Im1 14 0 R /Im2 16 0 R >> -> /Fx1 -> 12 0 R +<< /Fx1 12 0 R /Im1 14 0 R /Im2 16 0 R >> -> /Im1 -> 14 0 R +<< /Fx1 12 0 R /Im1 14 0 R /Im2 16 0 R >> -> /Im2 -> 16 0 R +--- recursive, images --- +<< /Fx1 12 0 R /Im1 14 0 R /Im2 16 0 R >> -> /Im1 -> 14 0 R +<< /Fx1 12 0 R /Im1 14 0 R /Im2 16 0 R >> -> /Im2 -> 16 0 R +<< /Fx1 20 0 R /Im1 22 0 R /Im2 24 0 R >> -> /Im1 -> 22 0 R +<< /Fx1 20 0 R /Im1 22 0 R /Im2 24 0 R >> -> /Im2 -> 24 0 R +<< /Im1 26 0 R /Im2 28 0 R >> -> /Im1 -> 26 0 R +<< /Im1 26 0 R /Im2 28 0 R >> -> /Im2 -> 28 0 R +--- non-recursive, images --- +<< /Fx1 12 0 R /Im1 14 0 R /Im2 16 0 R >> -> /Im1 -> 14 0 R +<< /Fx1 12 0 R /Im1 14 0 R /Im2 16 0 R >> -> /Im2 -> 16 0 R +--- recursive, form XObjects --- +<< /Fx1 12 0 R /Im1 14 0 R /Im2 16 0 R >> -> /Fx1 -> 12 0 R +<< /Fx1 20 0 R /Im1 22 0 R /Im2 24 0 R >> -> /Fx1 -> 20 0 R +--- non-recursive, form XObjects --- +<< /Fx1 12 0 R /Im1 14 0 R /Im2 16 0 R >> -> /Fx1 -> 12 0 R +--- recursive, all, from fx1 --- +<< /Fx1 20 0 R /Im1 22 0 R /Im2 24 0 R >> -> /Fx1 -> 20 0 R +<< /Fx1 20 0 R /Im1 22 0 R /Im2 24 0 R >> -> /Im1 -> 22 0 R +<< /Fx1 20 0 R /Im1 22 0 R /Im2 24 0 R >> -> /Im2 -> 24 0 R +<< /Im1 26 0 R /Im2 28 0 R >> -> /Im1 -> 26 0 R +<< /Im1 26 0 R /Im2 28 0 R >> -> /Im2 -> 28 0 R +--- non-recursive, all, from fx1 --- +<< /Fx1 20 0 R /Im1 22 0 R /Im2 24 0 R >> -> /Fx1 -> 20 0 R +<< /Fx1 20 0 R /Im1 22 0 R /Im2 24 0 R >> -> /Im1 -> 22 0 R +<< /Fx1 20 0 R /Im1 22 0 R /Im2 24 0 R >> -> /Im2 -> 24 0 R +--- get images, page --- +/Im1 -> 14 0 R +/Im2 -> 16 0 R +--- get images, fx --- +/Im1 -> 22 0 R +/Im2 -> 24 0 R +--- get form XObjects, page --- +/Fx1 -> 12 0 R +--- get form XObjects, fx --- +/Fx1 -> 20 0 R +test 71 done diff --git a/qpdf/qtest/qpdf/nested-form-xobjects.pdf b/qpdf/qtest/qpdf/nested-form-xobjects.pdf new file mode 100644 index 00000000..f4c761b7 --- /dev/null +++ b/qpdf/qtest/qpdf/nested-form-xobjects.pdf @@ -0,0 +1,397 @@ +%PDF-1.3 +%¿÷¢þ +%QDF-1.0 + +1 0 obj +<< + /Pages 2 0 R + /Type /Catalog +>> +endobj + +2 0 obj +<< + /Count 1 + /Kids [ + 3 0 R + ] + /Type /Pages +>> +endobj + +%% Page 1 +3 0 obj +<< + /Contents [ + 4 0 R + 6 0 R + 8 0 R + ] + /MediaBox [ + 0 + 0 + 612 + 792 + ] + /Parent 2 0 R + /Resources << + /Font << + /F1 10 0 R + >> + /ProcSet 11 0 R + /XObject << + /Fx1 12 0 R + /Im1 14 0 R + /Im2 16 0 R + >> + >> + /Type /Page +>> +endobj + +%% Contents for page 1 +4 0 obj +<< + /Length 5 0 R +>> +stream +q +endstream +endobj + +5 0 obj +2 +endobj + +%% Contents for page 1 +6 0 obj +<< + /Length 7 0 R +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Page) Tj +ET +q +100 0 0 100 72 600 cm +/Im1 Do +Q +q +100 0 0 100 192 600 cm +/Im2 Do +Q +endstream +endobj + +7 0 obj +111 +endobj + +%% Contents for page 1 +8 0 obj +<< + /Length 9 0 R +>> +stream + +Q +q +1.00000 0.00000 0.00000 1.00000 72.00000 200.00000 cm +/Fx1 Do +Q +endstream +endobj + +9 0 obj +69 +endobj + +10 0 obj +<< + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding + /Name /F1 + /Subtype /Type1 + /Type /Font +>> +endobj + +11 0 obj +[ + /PDF + /Text + /ImageC +] +endobj + +12 0 obj +<< + /BBox [ + 0 + 0 + 300 + 500 + ] + /Resources << + /Font << + /F1 18 0 R + >> + /ProcSet 19 0 R + /XObject << + /Fx1 20 0 R + /Im1 22 0 R + /Im2 24 0 R + >> + >> + /Subtype /Form + /Type /XObject + /Length 13 0 R +>> +stream +BT + /F1 24 Tf + 0 320 Td + (FX1) Tj +ET +q +100 0 0 100 000 200 cm +/Im1 Do +Q +q +100 0 0 100 120 200 cm +/Im2 Do +Q +q +1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm +/Fx1 Do +Q +endstream +endobj + +13 0 obj +173 +endobj + +14 0 obj +<< + /BitsPerComponent 8 + /ColorSpace /DeviceGray + /Height 15 + /Subtype /Image + /Type /XObject + /Width 15 + /Length 15 0 R +>> +stream +`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +endstream +endobj + +%QDF: ignore_newline +15 0 obj +225 +endobj + +16 0 obj +<< + /BitsPerComponent 8 + /ColorSpace /DeviceGray + /Height 15 + /Subtype /Image + /Type /XObject + /Width 15 + /Length 17 0 R +>> +stream +@@@@@`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +endstream +endobj + +%QDF: ignore_newline +17 0 obj +225 +endobj + +18 0 obj +<< + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding + /Name /F1 + /Subtype /Type1 + /Type /Font +>> +endobj + +19 0 obj +[ + /PDF + /Text + /ImageC +] +endobj + +20 0 obj +<< + /BBox [ + 0 + 0 + 300 + 200 + ] + /Resources << + /Font << + /F1 18 0 R + >> + /ProcSet 19 0 R + /XObject << + /Im1 26 0 R + /Im2 28 0 R + >> + >> + /Subtype /Form + /Type /XObject + /Length 21 0 R +>> +stream +BT + /F1 24 Tf + 0 120 Td + (FX2) Tj +ET +q +100 0 0 100 0 0 cm +/Im1 Do +Q +q +100 0 0 100 120 0 cm +/Im2 Do +Q +endstream +endobj + +21 0 obj +104 +endobj + +22 0 obj +<< + /BitsPerComponent 8 + /ColorSpace /DeviceGray + /Height 15 + /Subtype /Image + /Type /XObject + /Width 15 + /Length 23 0 R +>> +stream +@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +endstream +endobj + +%QDF: ignore_newline +23 0 obj +225 +endobj + +24 0 obj +<< + /BitsPerComponent 8 + /ColorSpace /DeviceGray + /Height 15 + /Subtype /Image + /Type /XObject + /Width 15 + /Length 25 0 R +>> +stream +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +endstream +endobj + +%QDF: ignore_newline +25 0 obj +225 +endobj + +26 0 obj +<< + /BitsPerComponent 8 + /ColorSpace /DeviceGray + /Height 15 + /Subtype /Image + /Type /XObject + /Width 15 + /Length 27 0 R +>> +stream +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +endstream +endobj + +%QDF: ignore_newline +27 0 obj +225 +endobj + +28 0 obj +<< + /BitsPerComponent 8 + /ColorSpace /DeviceGray + /Height 15 + /Subtype /Image + /Type /XObject + /Width 15 + /Length 29 0 R +>> +stream +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@`````@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +endstream +endobj + +%QDF: ignore_newline +29 0 obj +225 +endobj + +xref +0 30 +0000000000 65535 f +0000000025 00000 n +0000000079 00000 n +0000000161 00000 n +0000000485 00000 n +0000000542 00000 n +0000000583 00000 n +0000000749 00000 n +0000000792 00000 n +0000000916 00000 n +0000000935 00000 n +0000001054 00000 n +0000001100 00000 n +0000001561 00000 n +0000001582 00000 n +0000001994 00000 n +0000002015 00000 n +0000002427 00000 n +0000002448 00000 n +0000002567 00000 n +0000002613 00000 n +0000002987 00000 n +0000003008 00000 n +0000003420 00000 n +0000003441 00000 n +0000003853 00000 n +0000003874 00000 n +0000004286 00000 n +0000004307 00000 n +0000004719 00000 n +trailer << + /Root 1 0 R + /Size 30 + /ID [<55269d37282af9edc76855e4cb859987><633b13d949c2f1d115e24c686f36e362>] +>> +startxref +4740 +%%EOF diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index d8b40a0f..ff55e63f 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -2228,6 +2228,57 @@ void runtest(int n, char const* filename1, char const* arg2) w.setDecodeLevel(qpdf_dl_specialized); w.write(); } + else if (n == 71) + { + auto show = [](QPDFObjectHandle& obj, + QPDFObjectHandle& xobj_dict, + std::string const& key) { + std::cout << xobj_dict.unparse() << " -> " + << key << " -> " << obj.unparse() << std::endl; + }; + auto page = QPDFPageDocumentHelper(pdf).getAllPages().at(0); + std::cout << "--- recursive, all ---" << std::endl; + page.forEachXObject(true, show); + std::cout << "--- non-recursive, all ---" << std::endl; + page.forEachXObject(false, show); + std::cout << "--- recursive, images ---" << std::endl; + page.forEachImage(true, show); + std::cout << "--- non-recursive, images ---" << std::endl; + page.forEachImage(false, show); + std::cout << "--- recursive, form XObjects ---" << std::endl; + page.forEachFormXObject(true, show); + std::cout << "--- non-recursive, form XObjects ---" << std::endl; + page.forEachFormXObject(false, show); + auto fx1 = QPDFPageObjectHelper( + page.getObjectHandle() + .getKey("/Resources") + .getKey("/XObject") + .getKey("/Fx1")); + std::cout << "--- recursive, all, from fx1 ---" << std::endl; + fx1.forEachXObject(true, show); + std::cout << "--- non-recursive, all, from fx1 ---" << std::endl; + fx1.forEachXObject(false, show); + std::cout << "--- get images, page ---" << std::endl; + for (auto& i: page.getImages()) + { + std::cout << i.first << " -> " << i.second.unparse() << std::endl; + } + std::cout << "--- get images, fx ---" << std::endl; + for (auto& i: fx1.getImages()) + { + std::cout << i.first << " -> " << i.second.unparse() << std::endl; + } + std::cout << "--- get form XObjects, page ---" << std::endl; + for (auto& i: page.getFormXObjects()) + { + std::cout << i.first << " -> " << i.second.unparse() << std::endl; + } + std::cout << "--- get form XObjects, fx ---" << std::endl; + for (auto& i: fx1.getFormXObjects()) + { + std::cout << i.first << " -> " << i.second.unparse() << std::endl; + } + } else { throw std::runtime_error(std::string("invalid test ") + |