aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog14
-rw-r--r--TODO1
-rw-r--r--include/qpdf/QPDFObjectHandle.hh5
-rw-r--r--include/qpdf/QPDFPageObjectHelper.hh41
-rw-r--r--libqpdf/QPDFObjectHandle.cc15
-rw-r--r--libqpdf/QPDFPageObjectHelper.cc108
-rw-r--r--manual/qpdf-manual.xml54
-rw-r--r--qpdf/qpdf.testcov1
-rw-r--r--qpdf/qtest/qpdf.test7
-rw-r--r--qpdf/qtest/qpdf/nested-form-xobjects.out49
-rw-r--r--qpdf/qtest/qpdf/nested-form-xobjects.pdf397
-rw-r--r--qpdf/test_driver.cc51
12 files changed, 715 insertions, 28 deletions
diff --git a/ChangeLog b/ChangeLog
index 32291715..78aa88b8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
diff --git a/TODO b/TODO
index a07c93d3..857fb701 100644
--- a/TODO
+++ b/TODO
@@ -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 ") +