aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2022-09-07 01:00:40 +0200
committerJay Berkenbilt <ejb@ql.org>2022-09-07 01:00:40 +0200
commit76cd7ea67aee7edd1004a68a28e63a00a38239dd (patch)
tree66d131387939bc7bf88e802a9f11221aba5eda2e
parentc1def4ead4c578862ad6fcda35a9ca65be407893 (diff)
downloadqpdf-76cd7ea67aee7edd1004a68a28e63a00a38239dd.tar.zst
Clarify and improve QPDFPageObjectHelper::get*Box methods
Add copy_if_fallback and explain how it differs from copy_if_shared.
-rw-r--r--ChangeLog8
-rw-r--r--include/qpdf/QPDFPageObjectHelper.hh157
-rw-r--r--libqpdf/QPDFPageObjectHelper.cc77
-rw-r--r--manual/release-notes.rst5
-rw-r--r--qpdf/qpdf.testcov2
-rw-r--r--qpdf/qtest/page-api.test9
-rw-r--r--qpdf/qtest/qpdf/boxes2.pdf491
-rw-r--r--qpdf/test_driver.cc104
8 files changed, 818 insertions, 35 deletions
diff --git a/ChangeLog b/ChangeLog
index 6ce558d6..adc6181d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
2022-09-06 Jay Berkenbilt <ejb@ql.org>
+ * For all bounding box methods in QPDFPageObjectHelper other than
+ MediaBox, add a parameter `copy_if_fallback`, and add comments
+ explaining in depth exactly what copy_if_shared and
+ copy_if_fallback mean. Fixes #664.
+
+ * Add new methods getArtBox and getBleedBox to
+ QPDFPageObjectHelper, completing the set of bounding box methods.
+
* Add == equality for QPDFObjectHandle. Two QPDFObjectHandle
objects are equal if they point to the same underlying object,
meaning changes to one will be reflected in the other.
diff --git a/include/qpdf/QPDFPageObjectHelper.hh b/include/qpdf/QPDFPageObjectHelper.hh
index df4c7864..ef3c09ca 100644
--- a/include/qpdf/QPDFPageObjectHelper.hh
+++ b/include/qpdf/QPDFPageObjectHelper.hh
@@ -45,29 +45,151 @@ class QPDFPageObjectHelper: public QPDFObjectHelper
QPDF_DLL
virtual ~QPDFPageObjectHelper() = default;
- // Works with pages and form XObjects. Return the effective value
- // of this attribute for the page/form XObject. For pages, if the
- // requested attribute is not present on the page but is
- // inheritable, look up through the page's ancestors in the page
- // tree. If copy_if_shared is true, then this method will replace
- // the attribute with a shallow copy if it is in indirect or
- // inherited and return the copy. You should do this if you are
- // going to modify the returned object and want the modifications
- // to apply to the current page/form XObject only.
+ // PAGE ATTRIBUTES
+
+ // The getAttribute method works with pages and form XObjects. It
+ // return the value of the requested attribute from the page/form
+ // XObject's dictionary, taking inheritance from the pages tree
+ // into consideration. For pages, the attributes /MediaBox,
+ // /CropBox, /Resources, and /Rotate are inheritable, meaning that
+ // if they are not present directly on the page node, they may be
+ // inherited from ancestor nodes in the pages tree.
+ //
+ // There are two ways that an attribute can be "shared":
+ //
+ // * For inheritable attributes on pages, it may appear in a
+ // higher level node of the pages tree
+ //
+ // * For any attribute, the attribute may be an indirect object
+ // which may be referenced by more than one page/form XObject.
+ //
+ // If copy_if_shared is true, then this method will replace the
+ // attribute with a shallow copy if it is indirect or inherited
+ // and return the copy. You should do this if you are going to
+ // modify the returned object and want the modifications to apply
+ // to the current page/form XObject only.
QPDF_DLL
QPDFObjectHandle getAttribute(std::string const& name, bool copy_if_shared);
- // Return the TrimBox. If not defined, fall back to CropBox
+ // PAGE BOXES
+ //
+ // Pages have various types of boundary boxes. These are described
+ // in detail in the PDF specification (section 14.11.2 Page
+ // boundaries). They are, by key in the page dictionary:
+ //
+ // * /MediaBox -- boundaries of physical page
+ // * /CropBox -- clipping region of what is displayed
+ // * /BleedBox -- clipping region for production environments
+ // * /TrimBox -- dimensions of final printed page after trimming
+ // * /ArtBox -- extent of meaningful content including margins
+ //
+ // Of these, only /MediaBox is required. If any are absent, the
+ // fallback value for /CropBox is /MediaBox, and the fallback
+ // values for the other boxes are /CropBox.
+ //
+ // As noted above (PAGE ATTRIBUTES), /MediaBox and /CropBox can be
+ // inherited from parent nodes in the pages tree. The other boxes
+ // can't be inherited.
+ //
+ // When the comments below refer to the "effective value" of an
+ // box, this takes into consideration both inheritance through the
+ // pages tree (in the case of /MediaBox and /CropBox) and fallback
+ // values for missing attributes (for all except /MediaBox).
+ //
+ // For the methods below, copy_if_shared is passed to getAttribute
+ // and therefore refers only to indirect objects and values that
+ // are inherited through the pages tree.
+ //
+ // If copy_if_fallback is true, a copy is made if the object's
+ // value was obtained by falling back to a different box.
+ //
+ // The copy_if_shared and copy_if_fallback parameters carry across
+ // multiple layers. This is explained below.
+ //
+ // You should set copy_if_shared to true if you want to modify a
+ // bounding box for the current page without affecting other pages
+ // but you don't want to change the fallback behavior. For
+ // example, if you want to modify the /TrimBox for the current
+ // page only but have it continue to fall back to the value of
+ // /CropBox or /MediaBox if they are not defined, you could set
+ // copy_if_shared to true.
+ //
+ // You should set copy_if_fallback to true if you want to modify a
+ // specific box as distinct from any other box. For example, if
+ // you want to make /TrimBox differ from /CropBox, then you should
+ // set copy_if_fallback to true.
+ //
+ // The copy_if_fallback flags were added in qpdf 11.
+ //
+ // For example, suppose that neither /CropBox nor /TrimBox is
+ // present on a page but /CropBox is present in the page's parent
+ // node in the page tree.
+ //
+ // * getTrimBox(false, false) would return the /CropBox from the
+ // parent node.
+ //
+ // * getTrimBox(true, false) would make a shallow copy of the
+ // /CropBox from the parent node into the current node and
+ // return it.
+ //
+ // * getTrimBox(false, true) would make a shallow copy of the
+ // /CropBox from the parent node into /TrimBox of the current
+ // node and return it.
+ //
+ // * getTrimBox(true, true) would make a shallow copy of the
+ // /CropBox from the parent node into the current node, then
+ // make a shallow copy of the resulting copy to /TrimBox of the
+ // current node, and then return that.
+ //
+ // To illustrate how these parameters carry across multiple
+ // layers, suppose that neither /MediaBox, /CropBox, nor /TrimBox
+ // is present on a page but /MediaBox is present on the parent. In
+ // this case:
+ //
+ // * getTrimBox(false, false) would return the value of /MediaBox
+ // from the parent node.
+ //
+ // * getTrimBox(true, false) would copy /MediaBox to the current
+ // node and return it.
+ //
+ // * getTrimBox(false, true) would first copy /MediaBox from the
+ // parent to /CropBox, then copy /CropBox to /TrimBox, and then
+ // return the result.
+ //
+ // * getTrimBox(true, true) would first copy /MediaBox from the
+ // parent to the current page, then copy it to /CropBox, then
+ // copy /CropBox to /TrimBox, and then return the result.
+ //
+ // If you need different behavior, call getAttribute directly and
+ // take care of your own copying.
+
+ // Return the effective MediaBox
QPDF_DLL
- QPDFObjectHandle getTrimBox(bool copy_if_shared = false);
+ QPDFObjectHandle getMediaBox(bool copy_if_shared = false);
- // Return the CropBox. If not defined, fall back to MediaBox
+ // Return the effective CropBox. If not defined, fall back to
+ // MediaBox
QPDF_DLL
- QPDFObjectHandle getCropBox(bool copy_if_shared = false);
+ QPDFObjectHandle
+ getCropBox(bool copy_if_shared = false, bool copy_if_fallback = false);
- // Return the MediaBox
+ // Return the effective BleedBox. If not defined, fall back to
+ // CropBox.
QPDF_DLL
- QPDFObjectHandle getMediaBox(bool copy_if_shared = false);
+ QPDFObjectHandle
+ getBleedBox(bool copy_if_shared = false, bool copy_if_fallback = false);
+
+ // Return the effective TrimBox. If not defined, fall back to
+ // CropBox.
+ QPDF_DLL
+ QPDFObjectHandle
+ getTrimBox(bool copy_if_shared = false, bool copy_if_fallback = false);
+
+ // Return the effective ArtBox. If not defined, fall back to
+ // CropBox.
+ QPDF_DLL
+ QPDFObjectHandle
+ getArtBox(bool copy_if_shared = false, bool copy_if_fallback = false);
// Iterate through XObjects, possibly recursing into form
// XObjects. This works with pages or form XObjects. Call action
@@ -373,6 +495,11 @@ class QPDFPageObjectHelper: public QPDFObjectHelper
QPDFAcroFormDocumentHelper* from_afdh = nullptr);
private:
+ QPDFObjectHandle getAttribute(
+ std::string const& name,
+ bool copy_if_shared,
+ std::function<QPDFObjectHandle()> get_fallback,
+ bool copy_if_fallback);
static bool removeUnreferencedResourcesHelper(
QPDFPageObjectHelper ph, std::set<std::string>& unresolved);
diff --git a/libqpdf/QPDFPageObjectHelper.cc b/libqpdf/QPDFPageObjectHelper.cc
index 23a54231..1c810318 100644
--- a/libqpdf/QPDFPageObjectHelper.cc
+++ b/libqpdf/QPDFPageObjectHelper.cc
@@ -238,6 +238,16 @@ QPDFPageObjectHelper::QPDFPageObjectHelper(QPDFObjectHandle oh) :
QPDFObjectHandle
QPDFPageObjectHelper::getAttribute(std::string const& name, bool copy_if_shared)
{
+ return getAttribute(name, copy_if_shared, nullptr, false);
+}
+
+QPDFObjectHandle
+QPDFPageObjectHelper::getAttribute(
+ std::string const& name,
+ bool copy_if_shared,
+ std::function<QPDFObjectHandle()> get_fallback,
+ bool copy_if_fallback)
+{
QPDFObjectHandle result;
QPDFObjectHandle dict;
bool is_form_xobject = this->oh.isFormXObject();
@@ -272,36 +282,71 @@ QPDFPageObjectHelper::getAttribute(std::string const& name, bool copy_if_shared)
"qpdf",
"QPDFPageObjectHelper copy shared attribute",
is_form_xobject ? 0 : 1);
- result = result.shallowCopy();
- dict.replaceKey(name, result);
+ result = dict.replaceKeyAndGetNew(name, result.shallowCopy());
+ }
+ if (result.isNull() && get_fallback) {
+ result = get_fallback();
+ if (copy_if_fallback && !result.isNull()) {
+ QTC::TC("qpdf", "QPDFPageObjectHelper copied fallback");
+ result = dict.replaceKeyAndGetNew(name, result.shallowCopy());
+ } else {
+ QTC::TC(
+ "qpdf", "QPDFPageObjectHelper used fallback without copying");
+ }
}
return result;
}
QPDFObjectHandle
-QPDFPageObjectHelper::getTrimBox(bool copy_if_shared)
+QPDFPageObjectHelper::getMediaBox(bool copy_if_shared)
{
- QPDFObjectHandle result = getAttribute("/TrimBox", copy_if_shared);
- if (result.isNull()) {
- result = getCropBox(copy_if_shared);
- }
- return result;
+ return getAttribute("/MediaBox", copy_if_shared);
}
QPDFObjectHandle
-QPDFPageObjectHelper::getCropBox(bool copy_if_shared)
+QPDFPageObjectHelper::getCropBox(bool copy_if_shared, bool copy_if_fallback)
{
- QPDFObjectHandle result = getAttribute("/CropBox", copy_if_shared);
- if (result.isNull()) {
- result = getMediaBox();
- }
- return result;
+ return getAttribute(
+ "/CropBox",
+ copy_if_shared,
+ [this, copy_if_shared]() { return this->getMediaBox(copy_if_shared); },
+ copy_if_fallback);
}
QPDFObjectHandle
-QPDFPageObjectHelper::getMediaBox(bool copy_if_shared)
+QPDFPageObjectHelper::getTrimBox(bool copy_if_shared, bool copy_if_fallback)
{
- return getAttribute("/MediaBox", copy_if_shared);
+ return getAttribute(
+ "/TrimBox",
+ copy_if_shared,
+ [this, copy_if_shared, copy_if_fallback]() {
+ return this->getCropBox(copy_if_shared, copy_if_fallback);
+ },
+ copy_if_fallback);
+}
+
+QPDFObjectHandle
+QPDFPageObjectHelper::getArtBox(bool copy_if_shared, bool copy_if_fallback)
+{
+ return getAttribute(
+ "/ArtBox",
+ copy_if_shared,
+ [this, copy_if_shared, copy_if_fallback]() {
+ return this->getCropBox(copy_if_shared, copy_if_fallback);
+ },
+ copy_if_fallback);
+}
+
+QPDFObjectHandle
+QPDFPageObjectHelper::getBleedBox(bool copy_if_shared, bool copy_if_fallback)
+{
+ return getAttribute(
+ "/BleedBox",
+ copy_if_shared,
+ [this, copy_if_shared, copy_if_fallback]() {
+ return this->getCropBox(copy_if_shared, copy_if_fallback);
+ },
+ copy_if_fallback);
}
void
diff --git a/manual/release-notes.rst b/manual/release-notes.rst
index d5ee4013..ada6d661 100644
--- a/manual/release-notes.rst
+++ b/manual/release-notes.rst
@@ -261,6 +261,11 @@ For a detailed list of changes, please see the file
generation parameters. The old versions will continue to be
supported and are not deprecated.
+ - In ``QPDFPageObjectHelper``, add a ``copy_if_fallback``
+ parameter to most of the page bounding box methods, and clarify
+ in the comments about the difference between ``copy_if_shared``
+ and ``copy_if_fallback``.
+
- Add a move constructor to the ``Buffer`` class.
- Other changes
diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov
index c3ab0a07..e89f63a0 100644
--- a/qpdf/qpdf.testcov
+++ b/qpdf/qpdf.testcov
@@ -676,3 +676,5 @@ QPDF_json missing json version 0
QPDF_json bad json version 0
QPDF_json bad calledgetallpages 0
QPDF_json bad pushedinheritedpageresources 0
+QPDFPageObjectHelper copied fallback 0
+QPDFPageObjectHelper used fallback without copying 0
diff --git a/qpdf/qtest/page-api.test b/qpdf/qtest/page-api.test
index 0bfff47d..c976a6e6 100644
--- a/qpdf/qtest/page-api.test
+++ b/qpdf/qtest/page-api.test
@@ -14,8 +14,6 @@ cleanup();
my $td = new TestDriver('page-api');
-my $n_tests = 11;
-
$td->runtest("basic page API",
{$td->COMMAND => "test_driver 15 page_api_1.pdf"},
{$td->STRING => "test 15 done\n", $td->EXIT_STATUS => 0},
@@ -58,5 +56,10 @@ $td->runtest("flatten rotation",
$td->runtest("check output",
{$td->FILE => "a.pdf"},
{$td->FILE => "boxes-flattened.pdf"});
+$td->runtest("get box methods",
+ {$td->COMMAND => "test_driver 94 boxes2.pdf"},
+ {$td->STRING => "test 94 done\n", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+
cleanup();
-$td->report($n_tests);
+$td->report(12);
diff --git a/qpdf/qtest/qpdf/boxes2.pdf b/qpdf/qtest/qpdf/boxes2.pdf
new file mode 100644
index 00000000..71a95670
--- /dev/null
+++ b/qpdf/qtest/qpdf/boxes2.pdf
@@ -0,0 +1,491 @@
+%PDF-1.3
+%¿÷¢þ
+%QDF-1.0
+
+1 0 obj
+<<
+ /Pages 2 0 R
+ /Type /Catalog
+>>
+endobj
+
+2 0 obj
+<<
+ /Count 5
+ /Kids [
+ 3 0 R
+ 4 0 R
+ 5 0 R
+ 6 0 R
+ 7 0 R
+ ]
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Type /Pages
+>>
+endobj
+
+%% Page 1
+3 0 obj
+<<
+ /Contents 8 0 R
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 10 0 R
+ >>
+ /ProcSet 11 0 R
+ /XObject <<
+ /Fx1 12 0 R
+ >>
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Page 2
+4 0 obj
+<<
+ /Contents 14 0 R
+ /CropBox [
+ 10
+ 20
+ 582
+ 752
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 16 0 R
+ >>
+ /ProcSet 17 0 R
+ /XObject <<
+ /Fx1 12 0 R
+ >>
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Page 3
+5 0 obj
+<<
+ /Contents 18 0 R
+ /CropBox [
+ 10
+ 20
+ 582
+ 752
+ ]
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 20 0 R
+ >>
+ /ProcSet 21 0 R
+ /XObject <<
+ /Fx1 12 0 R
+ >>
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Page 4
+6 0 obj
+<<
+ /BleedBox [
+ 20
+ 40
+ 552
+ 712
+ ]
+ /Contents 22 0 R
+ /CropBox 24 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 25 0 R
+ >>
+ /ProcSet 26 0 R
+ /XObject <<
+ /Fx1 12 0 R
+ >>
+ >>
+ /TrimBox [
+ 30
+ 60
+ 522
+ 672
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Page 5
+7 0 obj
+<<
+ /ArtBox [
+ 25
+ 50
+ 527
+ 722
+ ]
+ /Contents 27 0 R
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 29 0 R
+ >>
+ /ProcSet 30 0 R
+ /XObject <<
+ /Fx1 12 0 R
+ >>
+ >>
+ /TrimBox [
+ 30
+ 60
+ 522
+ 672
+ ]
+ /Type /Page
+>>
+endobj
+
+%% Contents for page 1
+8 0 obj
+<<
+ /Length 9 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 470 Td
+ (Media inherited) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+9 0 obj
+121
+endobj
+
+10 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+11 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+12 0 obj
+<<
+ /BBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Resources <<
+ /Font <<
+ /F1 10 0 R
+ >>
+ /ProcSet 31 0 R
+ >>
+ /Subtype /Form
+ /Type /XObject
+ /Length 13 0 R
+>>
+stream
+BT
+ /F1 12 Tf
+ 144 600 Td
+ 1 0 0 rg
+ (red rectangle at media [0 0 612 792]) Tj
+ 0 -15 Td
+ 0 1 0 rg
+ (green at crop [10 20 582 752]) Tj
+ 0 -15 Td
+ 0 0 1 rg
+ (blue at bleed [20 40 552 712]) Tj
+ 0 -15 Td
+ 1 .5 0 rg
+ (orange at trim [30 60 522 672]) Tj
+ 0 -15 Td
+ 1 0 1 rg
+ (purple at art [40 80 452 552]) Tj
+ 0 -15 Td
+ 0 0 0 rg
+ (if crop is present, page is cropped) Tj
+ET
+5 w
+1 0 0 RG
+0 0 612 792 re s
+0 1 0 RG
+10 20 572 732 re s
+0 0 1 RG
+20 40 532 672 re s
+1 .5 0 RG
+30 60 492 612 re s
+1 0 1 RG
+40 80 452 552 re s
+endstream
+endobj
+
+13 0 obj
+532
+endobj
+
+%% Contents for page 2
+14 0 obj
+<<
+ /Length 15 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 470 Td
+ (Media inherited, Crop present) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+15 0 obj
+135
+endobj
+
+16 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+17 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+%% Contents for page 3
+18 0 obj
+<<
+ /Length 19 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 470 Td
+ (Media, Crop present) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+19 0 obj
+125
+endobj
+
+20 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+21 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+%% Contents for page 4
+22 0 obj
+<<
+ /Length 23 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 470 Td
+ (Media, Trim, Bleed present, Crop indirect) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+23 0 obj
+147
+endobj
+
+24 0 obj
+[
+ 10
+ 20
+ 582
+ 752
+]
+endobj
+
+25 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+26 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+%% Contents for page 5
+27 0 obj
+<<
+ /Length 28 0 R
+>>
+stream
+q
+BT
+ /F1 12 Tf
+ 144 470 Td
+ (Media inherited, Trim, Art present) Tj
+ET
+Q
+q
+1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
+/Fx1 Do
+Q
+endstream
+endobj
+
+28 0 obj
+140
+endobj
+
+29 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+30 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+31 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+xref
+0 32
+0000000000 65535 f
+0000000025 00000 n
+0000000079 00000 n
+0000000247 00000 n
+0000000446 00000 n
+0000000693 00000 n
+0000000986 00000 n
+0000001345 00000 n
+0000001651 00000 n
+0000001827 00000 n
+0000001847 00000 n
+0000001966 00000 n
+0000002002 00000 n
+0000002745 00000 n
+0000002789 00000 n
+0000002981 00000 n
+0000003002 00000 n
+0000003121 00000 n
+0000003180 00000 n
+0000003362 00000 n
+0000003383 00000 n
+0000003502 00000 n
+0000003561 00000 n
+0000003765 00000 n
+0000003786 00000 n
+0000003829 00000 n
+0000003948 00000 n
+0000004007 00000 n
+0000004204 00000 n
+0000004225 00000 n
+0000004344 00000 n
+0000004380 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 32
+ /ID [<42ed290ee4e4c51171853f92a1a7642d><4529bd7e2686f4deaa59ab2fa8e0338d>]
+>>
+startxref
+4416
+%%EOF
diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc
index 812d6c07..30e7d677 100644
--- a/qpdf/test_driver.cc
+++ b/qpdf/test_driver.cc
@@ -3302,6 +3302,108 @@ test_93(QPDF& pdf, char const* arg2)
assert(trailer.getKey("/Potato") == oh2);
}
+static void
+test_94(QPDF& pdf, char const* arg2)
+{
+ // Exercise methods to get page boxes. This test is built for
+ // boxes2.pdf.
+
+ // /MediaBox is present in the pages tree root.
+ // Each page has the following boxes present directly:
+ // 1. none
+ // 2. crop
+ // 3. media, crop
+ // 4. media, crop, trim, bleed; crop is indirect
+ // 5. trim, art
+
+ auto pages_root = pdf.getRoot().getKey("/Pages");
+ auto root_media = pages_root.getKey("/MediaBox");
+ auto root_media_unparse = root_media.unparse();
+ auto pages = QPDFPageDocumentHelper(pdf).getAllPages();
+ assert(pages.size() == 5);
+ auto& p1 = pages[0];
+ auto& p2 = pages[1];
+ auto& p3 = pages[2];
+ auto& p4 = pages[3];
+ auto& p5 = pages[4];
+
+ assert(p1.getObjectHandle().getKey("/MediaBox").isNull());
+ // MediaBox not present, so get inherited one
+ assert(p1.getMediaBox(false) == root_media);
+ // Other boxesBox not present, so fall back to MediaBox
+ assert(p1.getCropBox(false, false) == root_media);
+ assert(p1.getBleedBox(false, false) == root_media);
+ assert(p1.getTrimBox(false, false) == root_media);
+ assert(p1.getArtBox(false, false) == root_media);
+ // Make copy of artbox
+ auto p1_new_art = p1.getArtBox(false, true);
+ assert(p1_new_art.unparse() == root_media_unparse);
+ assert(p1_new_art != root_media);
+ // This also copied cropbox
+ auto p1_new_crop = p1.getCropBox(false, false);
+ assert(p1_new_crop != root_media);
+ assert(p1_new_crop != p1_new_art);
+ assert(p1_new_crop.unparse() == root_media_unparse);
+ // But it didn't copy Media
+ assert(p1.getMediaBox(false) == root_media);
+ // Now fall back to new crop
+ assert(p1.getTrimBox(false, false) == p1_new_crop);
+ // Request copy. The value returned has the same structure but is
+ // a different object.
+ auto p1_effective_media = p1.getMediaBox(true);
+ assert(p1_effective_media.unparse() == root_media_unparse);
+ assert(p1_effective_media != root_media);
+
+ // copy_on_fallback didn't have to copy media to crop
+ assert(p2.getMediaBox(false) == root_media);
+ auto p2_crop = p2.getCropBox(false, false);
+ auto p2_new_trim = p2.getTrimBox(false, true);
+ assert(p2_new_trim.unparse() == p2_crop.unparse());
+ assert(p2_new_trim != p2_crop);
+ assert(p2.getMediaBox(false) == root_media);
+
+ // We didn't need to copy anything
+ auto p3_media = p3.getMediaBox(false);
+ auto p3_crop = p3.getCropBox(false, false);
+ assert(p3.getMediaBox(true) == p3_media);
+ assert(p3.getCropBox(true, true) == p3_crop);
+
+ // We didn't have to copy for bleed but we did for art
+ auto p4_orig_crop = p4.getObjectHandle().getKey("/CropBox");
+ auto p4_crop = p4.getCropBox(false, false);
+ assert(p4_orig_crop == p4_crop);
+ auto p4_bleed1 = p4.getBleedBox(false, false);
+ auto p4_bleed2 = p4.getBleedBox(false, true);
+ assert(p4_bleed1 != p4_crop);
+ assert(p4_bleed1 == p4_bleed2);
+ auto p4_art1 = p4.getArtBox(false, false);
+ assert(p4_art1 == p4_crop);
+ auto p4_art2 = p4.getArtBox(false, true);
+ assert(p4_art2 != p4_crop);
+ auto p4_new_crop = p4.getCropBox(true, false);
+ assert(p4_new_crop != p4_orig_crop);
+ assert(p4_orig_crop.isIndirect());
+ assert(!p4_new_crop.isIndirect());
+ assert(p4_new_crop.unparse() == p4_orig_crop.unparseResolved());
+
+ // Exercise copying for inheritence and fallback
+ assert(p5.getMediaBox(false) == root_media);
+ assert(p5.getCropBox(false, false) == root_media);
+ assert(p5.getBleedBox(false, false) == root_media);
+ auto p5_new_bleed = p5.getBleedBox(true, true);
+ auto p5_new_media = p5.getMediaBox(false);
+ auto p5_new_crop = p5.getCropBox(false, false);
+ assert(p5_new_media != root_media);
+ assert(p5_new_crop != root_media);
+ assert(p5_new_crop != p5_new_media);
+ assert(p5_new_bleed != root_media);
+ assert(p5_new_bleed != p5_new_media);
+ assert(p5_new_bleed != p5_new_crop);
+ assert(p5_new_media.unparse() == root_media_unparse);
+ assert(p5_new_crop.unparse() == root_media_unparse);
+ assert(p5_new_bleed.unparse() == root_media_unparse);
+}
+
void
runtest(int n, char const* filename1, char const* arg2)
{
@@ -3411,7 +3513,7 @@ runtest(int n, char const* filename1, char const* arg2)
{80, test_80}, {81, test_81}, {82, test_82}, {83, test_83},
{84, test_84}, {85, test_85}, {86, test_86}, {87, test_87},
{88, test_88}, {89, test_89}, {90, test_90}, {91, test_91},
- {92, test_92}, {93, test_93}};
+ {92, test_92}, {93, test_93}, {94, test_94}};
auto fn = test_functions.find(n);
if (fn == test_functions.end()) {