Commit 76cd7ea67aee7edd1004a68a28e63a00a38239dd

Authored by Jay Berkenbilt
1 parent c1def4ea

Clarify and improve QPDFPageObjectHelper::get*Box methods

Add copy_if_fallback and explain how it differs from copy_if_shared.
ChangeLog
1 2022-09-06 Jay Berkenbilt <ejb@ql.org> 1 2022-09-06 Jay Berkenbilt <ejb@ql.org>
2 2
  3 + * For all bounding box methods in QPDFPageObjectHelper other than
  4 + MediaBox, add a parameter `copy_if_fallback`, and add comments
  5 + explaining in depth exactly what copy_if_shared and
  6 + copy_if_fallback mean. Fixes #664.
  7 +
  8 + * Add new methods getArtBox and getBleedBox to
  9 + QPDFPageObjectHelper, completing the set of bounding box methods.
  10 +
3 * Add == equality for QPDFObjectHandle. Two QPDFObjectHandle 11 * Add == equality for QPDFObjectHandle. Two QPDFObjectHandle
4 objects are equal if they point to the same underlying object, 12 objects are equal if they point to the same underlying object,
5 meaning changes to one will be reflected in the other. 13 meaning changes to one will be reflected in the other.
include/qpdf/QPDFPageObjectHelper.hh
@@ -45,29 +45,151 @@ class QPDFPageObjectHelper: public QPDFObjectHelper @@ -45,29 +45,151 @@ class QPDFPageObjectHelper: public QPDFObjectHelper
45 QPDF_DLL 45 QPDF_DLL
46 virtual ~QPDFPageObjectHelper() = default; 46 virtual ~QPDFPageObjectHelper() = default;
47 47
48 - // Works with pages and form XObjects. Return the effective value  
49 - // of this attribute for the page/form XObject. For pages, if the  
50 - // requested attribute is not present on the page but is  
51 - // inheritable, look up through the page's ancestors in the page  
52 - // tree. If copy_if_shared is true, then this method will replace  
53 - // the attribute with a shallow copy if it is in indirect or  
54 - // inherited and return the copy. You should do this if you are  
55 - // going to modify the returned object and want the modifications  
56 - // to apply to the current page/form XObject only. 48 + // PAGE ATTRIBUTES
  49 +
  50 + // The getAttribute method works with pages and form XObjects. It
  51 + // return the value of the requested attribute from the page/form
  52 + // XObject's dictionary, taking inheritance from the pages tree
  53 + // into consideration. For pages, the attributes /MediaBox,
  54 + // /CropBox, /Resources, and /Rotate are inheritable, meaning that
  55 + // if they are not present directly on the page node, they may be
  56 + // inherited from ancestor nodes in the pages tree.
  57 + //
  58 + // There are two ways that an attribute can be "shared":
  59 + //
  60 + // * For inheritable attributes on pages, it may appear in a
  61 + // higher level node of the pages tree
  62 + //
  63 + // * For any attribute, the attribute may be an indirect object
  64 + // which may be referenced by more than one page/form XObject.
  65 + //
  66 + // If copy_if_shared is true, then this method will replace the
  67 + // attribute with a shallow copy if it is indirect or inherited
  68 + // and return the copy. You should do this if you are going to
  69 + // modify the returned object and want the modifications to apply
  70 + // to the current page/form XObject only.
57 QPDF_DLL 71 QPDF_DLL
58 QPDFObjectHandle getAttribute(std::string const& name, bool copy_if_shared); 72 QPDFObjectHandle getAttribute(std::string const& name, bool copy_if_shared);
59 73
60 - // Return the TrimBox. If not defined, fall back to CropBox 74 + // PAGE BOXES
  75 + //
  76 + // Pages have various types of boundary boxes. These are described
  77 + // in detail in the PDF specification (section 14.11.2 Page
  78 + // boundaries). They are, by key in the page dictionary:
  79 + //
  80 + // * /MediaBox -- boundaries of physical page
  81 + // * /CropBox -- clipping region of what is displayed
  82 + // * /BleedBox -- clipping region for production environments
  83 + // * /TrimBox -- dimensions of final printed page after trimming
  84 + // * /ArtBox -- extent of meaningful content including margins
  85 + //
  86 + // Of these, only /MediaBox is required. If any are absent, the
  87 + // fallback value for /CropBox is /MediaBox, and the fallback
  88 + // values for the other boxes are /CropBox.
  89 + //
  90 + // As noted above (PAGE ATTRIBUTES), /MediaBox and /CropBox can be
  91 + // inherited from parent nodes in the pages tree. The other boxes
  92 + // can't be inherited.
  93 + //
  94 + // When the comments below refer to the "effective value" of an
  95 + // box, this takes into consideration both inheritance through the
  96 + // pages tree (in the case of /MediaBox and /CropBox) and fallback
  97 + // values for missing attributes (for all except /MediaBox).
  98 + //
  99 + // For the methods below, copy_if_shared is passed to getAttribute
  100 + // and therefore refers only to indirect objects and values that
  101 + // are inherited through the pages tree.
  102 + //
  103 + // If copy_if_fallback is true, a copy is made if the object's
  104 + // value was obtained by falling back to a different box.
  105 + //
  106 + // The copy_if_shared and copy_if_fallback parameters carry across
  107 + // multiple layers. This is explained below.
  108 + //
  109 + // You should set copy_if_shared to true if you want to modify a
  110 + // bounding box for the current page without affecting other pages
  111 + // but you don't want to change the fallback behavior. For
  112 + // example, if you want to modify the /TrimBox for the current
  113 + // page only but have it continue to fall back to the value of
  114 + // /CropBox or /MediaBox if they are not defined, you could set
  115 + // copy_if_shared to true.
  116 + //
  117 + // You should set copy_if_fallback to true if you want to modify a
  118 + // specific box as distinct from any other box. For example, if
  119 + // you want to make /TrimBox differ from /CropBox, then you should
  120 + // set copy_if_fallback to true.
  121 + //
  122 + // The copy_if_fallback flags were added in qpdf 11.
  123 + //
  124 + // For example, suppose that neither /CropBox nor /TrimBox is
  125 + // present on a page but /CropBox is present in the page's parent
  126 + // node in the page tree.
  127 + //
  128 + // * getTrimBox(false, false) would return the /CropBox from the
  129 + // parent node.
  130 + //
  131 + // * getTrimBox(true, false) would make a shallow copy of the
  132 + // /CropBox from the parent node into the current node and
  133 + // return it.
  134 + //
  135 + // * getTrimBox(false, true) would make a shallow copy of the
  136 + // /CropBox from the parent node into /TrimBox of the current
  137 + // node and return it.
  138 + //
  139 + // * getTrimBox(true, true) would make a shallow copy of the
  140 + // /CropBox from the parent node into the current node, then
  141 + // make a shallow copy of the resulting copy to /TrimBox of the
  142 + // current node, and then return that.
  143 + //
  144 + // To illustrate how these parameters carry across multiple
  145 + // layers, suppose that neither /MediaBox, /CropBox, nor /TrimBox
  146 + // is present on a page but /MediaBox is present on the parent. In
  147 + // this case:
  148 + //
  149 + // * getTrimBox(false, false) would return the value of /MediaBox
  150 + // from the parent node.
  151 + //
  152 + // * getTrimBox(true, false) would copy /MediaBox to the current
  153 + // node and return it.
  154 + //
  155 + // * getTrimBox(false, true) would first copy /MediaBox from the
  156 + // parent to /CropBox, then copy /CropBox to /TrimBox, and then
  157 + // return the result.
  158 + //
  159 + // * getTrimBox(true, true) would first copy /MediaBox from the
  160 + // parent to the current page, then copy it to /CropBox, then
  161 + // copy /CropBox to /TrimBox, and then return the result.
  162 + //
  163 + // If you need different behavior, call getAttribute directly and
  164 + // take care of your own copying.
  165 +
  166 + // Return the effective MediaBox
61 QPDF_DLL 167 QPDF_DLL
62 - QPDFObjectHandle getTrimBox(bool copy_if_shared = false); 168 + QPDFObjectHandle getMediaBox(bool copy_if_shared = false);
63 169
64 - // Return the CropBox. If not defined, fall back to MediaBox 170 + // Return the effective CropBox. If not defined, fall back to
  171 + // MediaBox
65 QPDF_DLL 172 QPDF_DLL
66 - QPDFObjectHandle getCropBox(bool copy_if_shared = false); 173 + QPDFObjectHandle
  174 + getCropBox(bool copy_if_shared = false, bool copy_if_fallback = false);
67 175
68 - // Return the MediaBox 176 + // Return the effective BleedBox. If not defined, fall back to
  177 + // CropBox.
69 QPDF_DLL 178 QPDF_DLL
70 - QPDFObjectHandle getMediaBox(bool copy_if_shared = false); 179 + QPDFObjectHandle
  180 + getBleedBox(bool copy_if_shared = false, bool copy_if_fallback = false);
  181 +
  182 + // Return the effective TrimBox. If not defined, fall back to
  183 + // CropBox.
  184 + QPDF_DLL
  185 + QPDFObjectHandle
  186 + getTrimBox(bool copy_if_shared = false, bool copy_if_fallback = false);
  187 +
  188 + // Return the effective ArtBox. If not defined, fall back to
  189 + // CropBox.
  190 + QPDF_DLL
  191 + QPDFObjectHandle
  192 + getArtBox(bool copy_if_shared = false, bool copy_if_fallback = false);
71 193
72 // Iterate through XObjects, possibly recursing into form 194 // Iterate through XObjects, possibly recursing into form
73 // XObjects. This works with pages or form XObjects. Call action 195 // XObjects. This works with pages or form XObjects. Call action
@@ -373,6 +495,11 @@ class QPDFPageObjectHelper: public QPDFObjectHelper @@ -373,6 +495,11 @@ class QPDFPageObjectHelper: public QPDFObjectHelper
373 QPDFAcroFormDocumentHelper* from_afdh = nullptr); 495 QPDFAcroFormDocumentHelper* from_afdh = nullptr);
374 496
375 private: 497 private:
  498 + QPDFObjectHandle getAttribute(
  499 + std::string const& name,
  500 + bool copy_if_shared,
  501 + std::function<QPDFObjectHandle()> get_fallback,
  502 + bool copy_if_fallback);
376 static bool removeUnreferencedResourcesHelper( 503 static bool removeUnreferencedResourcesHelper(
377 QPDFPageObjectHelper ph, std::set<std::string>& unresolved); 504 QPDFPageObjectHelper ph, std::set<std::string>& unresolved);
378 505
libqpdf/QPDFPageObjectHelper.cc
@@ -238,6 +238,16 @@ QPDFPageObjectHelper::QPDFPageObjectHelper(QPDFObjectHandle oh) : @@ -238,6 +238,16 @@ QPDFPageObjectHelper::QPDFPageObjectHelper(QPDFObjectHandle oh) :
238 QPDFObjectHandle 238 QPDFObjectHandle
239 QPDFPageObjectHelper::getAttribute(std::string const& name, bool copy_if_shared) 239 QPDFPageObjectHelper::getAttribute(std::string const& name, bool copy_if_shared)
240 { 240 {
  241 + return getAttribute(name, copy_if_shared, nullptr, false);
  242 +}
  243 +
  244 +QPDFObjectHandle
  245 +QPDFPageObjectHelper::getAttribute(
  246 + std::string const& name,
  247 + bool copy_if_shared,
  248 + std::function<QPDFObjectHandle()> get_fallback,
  249 + bool copy_if_fallback)
  250 +{
241 QPDFObjectHandle result; 251 QPDFObjectHandle result;
242 QPDFObjectHandle dict; 252 QPDFObjectHandle dict;
243 bool is_form_xobject = this->oh.isFormXObject(); 253 bool is_form_xobject = this->oh.isFormXObject();
@@ -272,36 +282,71 @@ QPDFPageObjectHelper::getAttribute(std::string const&amp; name, bool copy_if_shared) @@ -272,36 +282,71 @@ QPDFPageObjectHelper::getAttribute(std::string const&amp; name, bool copy_if_shared)
272 "qpdf", 282 "qpdf",
273 "QPDFPageObjectHelper copy shared attribute", 283 "QPDFPageObjectHelper copy shared attribute",
274 is_form_xobject ? 0 : 1); 284 is_form_xobject ? 0 : 1);
275 - result = result.shallowCopy();  
276 - dict.replaceKey(name, result); 285 + result = dict.replaceKeyAndGetNew(name, result.shallowCopy());
  286 + }
  287 + if (result.isNull() && get_fallback) {
  288 + result = get_fallback();
  289 + if (copy_if_fallback && !result.isNull()) {
  290 + QTC::TC("qpdf", "QPDFPageObjectHelper copied fallback");
  291 + result = dict.replaceKeyAndGetNew(name, result.shallowCopy());
  292 + } else {
  293 + QTC::TC(
  294 + "qpdf", "QPDFPageObjectHelper used fallback without copying");
  295 + }
277 } 296 }
278 return result; 297 return result;
279 } 298 }
280 299
281 QPDFObjectHandle 300 QPDFObjectHandle
282 -QPDFPageObjectHelper::getTrimBox(bool copy_if_shared) 301 +QPDFPageObjectHelper::getMediaBox(bool copy_if_shared)
283 { 302 {
284 - QPDFObjectHandle result = getAttribute("/TrimBox", copy_if_shared);  
285 - if (result.isNull()) {  
286 - result = getCropBox(copy_if_shared);  
287 - }  
288 - return result; 303 + return getAttribute("/MediaBox", copy_if_shared);
289 } 304 }
290 305
291 QPDFObjectHandle 306 QPDFObjectHandle
292 -QPDFPageObjectHelper::getCropBox(bool copy_if_shared) 307 +QPDFPageObjectHelper::getCropBox(bool copy_if_shared, bool copy_if_fallback)
293 { 308 {
294 - QPDFObjectHandle result = getAttribute("/CropBox", copy_if_shared);  
295 - if (result.isNull()) {  
296 - result = getMediaBox();  
297 - }  
298 - return result; 309 + return getAttribute(
  310 + "/CropBox",
  311 + copy_if_shared,
  312 + [this, copy_if_shared]() { return this->getMediaBox(copy_if_shared); },
  313 + copy_if_fallback);
299 } 314 }
300 315
301 QPDFObjectHandle 316 QPDFObjectHandle
302 -QPDFPageObjectHelper::getMediaBox(bool copy_if_shared) 317 +QPDFPageObjectHelper::getTrimBox(bool copy_if_shared, bool copy_if_fallback)
303 { 318 {
304 - return getAttribute("/MediaBox", copy_if_shared); 319 + return getAttribute(
  320 + "/TrimBox",
  321 + copy_if_shared,
  322 + [this, copy_if_shared, copy_if_fallback]() {
  323 + return this->getCropBox(copy_if_shared, copy_if_fallback);
  324 + },
  325 + copy_if_fallback);
  326 +}
  327 +
  328 +QPDFObjectHandle
  329 +QPDFPageObjectHelper::getArtBox(bool copy_if_shared, bool copy_if_fallback)
  330 +{
  331 + return getAttribute(
  332 + "/ArtBox",
  333 + copy_if_shared,
  334 + [this, copy_if_shared, copy_if_fallback]() {
  335 + return this->getCropBox(copy_if_shared, copy_if_fallback);
  336 + },
  337 + copy_if_fallback);
  338 +}
  339 +
  340 +QPDFObjectHandle
  341 +QPDFPageObjectHelper::getBleedBox(bool copy_if_shared, bool copy_if_fallback)
  342 +{
  343 + return getAttribute(
  344 + "/BleedBox",
  345 + copy_if_shared,
  346 + [this, copy_if_shared, copy_if_fallback]() {
  347 + return this->getCropBox(copy_if_shared, copy_if_fallback);
  348 + },
  349 + copy_if_fallback);
305 } 350 }
306 351
307 void 352 void
manual/release-notes.rst
@@ -261,6 +261,11 @@ For a detailed list of changes, please see the file @@ -261,6 +261,11 @@ For a detailed list of changes, please see the file
261 generation parameters. The old versions will continue to be 261 generation parameters. The old versions will continue to be
262 supported and are not deprecated. 262 supported and are not deprecated.
263 263
  264 + - In ``QPDFPageObjectHelper``, add a ``copy_if_fallback``
  265 + parameter to most of the page bounding box methods, and clarify
  266 + in the comments about the difference between ``copy_if_shared``
  267 + and ``copy_if_fallback``.
  268 +
264 - Add a move constructor to the ``Buffer`` class. 269 - Add a move constructor to the ``Buffer`` class.
265 270
266 - Other changes 271 - Other changes
qpdf/qpdf.testcov
@@ -676,3 +676,5 @@ QPDF_json missing json version 0 @@ -676,3 +676,5 @@ QPDF_json missing json version 0
676 QPDF_json bad json version 0 676 QPDF_json bad json version 0
677 QPDF_json bad calledgetallpages 0 677 QPDF_json bad calledgetallpages 0
678 QPDF_json bad pushedinheritedpageresources 0 678 QPDF_json bad pushedinheritedpageresources 0
  679 +QPDFPageObjectHelper copied fallback 0
  680 +QPDFPageObjectHelper used fallback without copying 0
qpdf/qtest/page-api.test
@@ -14,8 +14,6 @@ cleanup(); @@ -14,8 +14,6 @@ cleanup();
14 14
15 my $td = new TestDriver('page-api'); 15 my $td = new TestDriver('page-api');
16 16
17 -my $n_tests = 11;  
18 -  
19 $td->runtest("basic page API", 17 $td->runtest("basic page API",
20 {$td->COMMAND => "test_driver 15 page_api_1.pdf"}, 18 {$td->COMMAND => "test_driver 15 page_api_1.pdf"},
21 {$td->STRING => "test 15 done\n", $td->EXIT_STATUS => 0}, 19 {$td->STRING => "test 15 done\n", $td->EXIT_STATUS => 0},
@@ -58,5 +56,10 @@ $td-&gt;runtest(&quot;flatten rotation&quot;, @@ -58,5 +56,10 @@ $td-&gt;runtest(&quot;flatten rotation&quot;,
58 $td->runtest("check output", 56 $td->runtest("check output",
59 {$td->FILE => "a.pdf"}, 57 {$td->FILE => "a.pdf"},
60 {$td->FILE => "boxes-flattened.pdf"}); 58 {$td->FILE => "boxes-flattened.pdf"});
  59 +$td->runtest("get box methods",
  60 + {$td->COMMAND => "test_driver 94 boxes2.pdf"},
  61 + {$td->STRING => "test 94 done\n", $td->EXIT_STATUS => 0},
  62 + $td->NORMALIZE_NEWLINES);
  63 +
61 cleanup(); 64 cleanup();
62 -$td->report($n_tests); 65 +$td->report(12);
qpdf/qtest/qpdf/boxes2.pdf 0 โ†’ 100644
  1 +%PDF-1.3
  2 +%ยฟรทยขรพ
  3 +%QDF-1.0
  4 +
  5 +1 0 obj
  6 +<<
  7 + /Pages 2 0 R
  8 + /Type /Catalog
  9 +>>
  10 +endobj
  11 +
  12 +2 0 obj
  13 +<<
  14 + /Count 5
  15 + /Kids [
  16 + 3 0 R
  17 + 4 0 R
  18 + 5 0 R
  19 + 6 0 R
  20 + 7 0 R
  21 + ]
  22 + /MediaBox [
  23 + 0
  24 + 0
  25 + 612
  26 + 792
  27 + ]
  28 + /Type /Pages
  29 +>>
  30 +endobj
  31 +
  32 +%% Page 1
  33 +3 0 obj
  34 +<<
  35 + /Contents 8 0 R
  36 + /Parent 2 0 R
  37 + /Resources <<
  38 + /Font <<
  39 + /F1 10 0 R
  40 + >>
  41 + /ProcSet 11 0 R
  42 + /XObject <<
  43 + /Fx1 12 0 R
  44 + >>
  45 + >>
  46 + /Type /Page
  47 +>>
  48 +endobj
  49 +
  50 +%% Page 2
  51 +4 0 obj
  52 +<<
  53 + /Contents 14 0 R
  54 + /CropBox [
  55 + 10
  56 + 20
  57 + 582
  58 + 752
  59 + ]
  60 + /Parent 2 0 R
  61 + /Resources <<
  62 + /Font <<
  63 + /F1 16 0 R
  64 + >>
  65 + /ProcSet 17 0 R
  66 + /XObject <<
  67 + /Fx1 12 0 R
  68 + >>
  69 + >>
  70 + /Type /Page
  71 +>>
  72 +endobj
  73 +
  74 +%% Page 3
  75 +5 0 obj
  76 +<<
  77 + /Contents 18 0 R
  78 + /CropBox [
  79 + 10
  80 + 20
  81 + 582
  82 + 752
  83 + ]
  84 + /MediaBox [
  85 + 0
  86 + 0
  87 + 612
  88 + 792
  89 + ]
  90 + /Parent 2 0 R
  91 + /Resources <<
  92 + /Font <<
  93 + /F1 20 0 R
  94 + >>
  95 + /ProcSet 21 0 R
  96 + /XObject <<
  97 + /Fx1 12 0 R
  98 + >>
  99 + >>
  100 + /Type /Page
  101 +>>
  102 +endobj
  103 +
  104 +%% Page 4
  105 +6 0 obj
  106 +<<
  107 + /BleedBox [
  108 + 20
  109 + 40
  110 + 552
  111 + 712
  112 + ]
  113 + /Contents 22 0 R
  114 + /CropBox 24 0 R
  115 + /MediaBox [
  116 + 0
  117 + 0
  118 + 612
  119 + 792
  120 + ]
  121 + /Parent 2 0 R
  122 + /Resources <<
  123 + /Font <<
  124 + /F1 25 0 R
  125 + >>
  126 + /ProcSet 26 0 R
  127 + /XObject <<
  128 + /Fx1 12 0 R
  129 + >>
  130 + >>
  131 + /TrimBox [
  132 + 30
  133 + 60
  134 + 522
  135 + 672
  136 + ]
  137 + /Type /Page
  138 +>>
  139 +endobj
  140 +
  141 +%% Page 5
  142 +7 0 obj
  143 +<<
  144 + /ArtBox [
  145 + 25
  146 + 50
  147 + 527
  148 + 722
  149 + ]
  150 + /Contents 27 0 R
  151 + /Parent 2 0 R
  152 + /Resources <<
  153 + /Font <<
  154 + /F1 29 0 R
  155 + >>
  156 + /ProcSet 30 0 R
  157 + /XObject <<
  158 + /Fx1 12 0 R
  159 + >>
  160 + >>
  161 + /TrimBox [
  162 + 30
  163 + 60
  164 + 522
  165 + 672
  166 + ]
  167 + /Type /Page
  168 +>>
  169 +endobj
  170 +
  171 +%% Contents for page 1
  172 +8 0 obj
  173 +<<
  174 + /Length 9 0 R
  175 +>>
  176 +stream
  177 +q
  178 +BT
  179 + /F1 12 Tf
  180 + 144 470 Td
  181 + (Media inherited) Tj
  182 +ET
  183 +Q
  184 +q
  185 +1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
  186 +/Fx1 Do
  187 +Q
  188 +endstream
  189 +endobj
  190 +
  191 +9 0 obj
  192 +121
  193 +endobj
  194 +
  195 +10 0 obj
  196 +<<
  197 + /BaseFont /Helvetica
  198 + /Encoding /WinAnsiEncoding
  199 + /Name /F1
  200 + /Subtype /Type1
  201 + /Type /Font
  202 +>>
  203 +endobj
  204 +
  205 +11 0 obj
  206 +[
  207 + /PDF
  208 + /Text
  209 +]
  210 +endobj
  211 +
  212 +12 0 obj
  213 +<<
  214 + /BBox [
  215 + 0
  216 + 0
  217 + 612
  218 + 792
  219 + ]
  220 + /Resources <<
  221 + /Font <<
  222 + /F1 10 0 R
  223 + >>
  224 + /ProcSet 31 0 R
  225 + >>
  226 + /Subtype /Form
  227 + /Type /XObject
  228 + /Length 13 0 R
  229 +>>
  230 +stream
  231 +BT
  232 + /F1 12 Tf
  233 + 144 600 Td
  234 + 1 0 0 rg
  235 + (red rectangle at media [0 0 612 792]) Tj
  236 + 0 -15 Td
  237 + 0 1 0 rg
  238 + (green at crop [10 20 582 752]) Tj
  239 + 0 -15 Td
  240 + 0 0 1 rg
  241 + (blue at bleed [20 40 552 712]) Tj
  242 + 0 -15 Td
  243 + 1 .5 0 rg
  244 + (orange at trim [30 60 522 672]) Tj
  245 + 0 -15 Td
  246 + 1 0 1 rg
  247 + (purple at art [40 80 452 552]) Tj
  248 + 0 -15 Td
  249 + 0 0 0 rg
  250 + (if crop is present, page is cropped) Tj
  251 +ET
  252 +5 w
  253 +1 0 0 RG
  254 +0 0 612 792 re s
  255 +0 1 0 RG
  256 +10 20 572 732 re s
  257 +0 0 1 RG
  258 +20 40 532 672 re s
  259 +1 .5 0 RG
  260 +30 60 492 612 re s
  261 +1 0 1 RG
  262 +40 80 452 552 re s
  263 +endstream
  264 +endobj
  265 +
  266 +13 0 obj
  267 +532
  268 +endobj
  269 +
  270 +%% Contents for page 2
  271 +14 0 obj
  272 +<<
  273 + /Length 15 0 R
  274 +>>
  275 +stream
  276 +q
  277 +BT
  278 + /F1 12 Tf
  279 + 144 470 Td
  280 + (Media inherited, Crop present) Tj
  281 +ET
  282 +Q
  283 +q
  284 +1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
  285 +/Fx1 Do
  286 +Q
  287 +endstream
  288 +endobj
  289 +
  290 +15 0 obj
  291 +135
  292 +endobj
  293 +
  294 +16 0 obj
  295 +<<
  296 + /BaseFont /Helvetica
  297 + /Encoding /WinAnsiEncoding
  298 + /Name /F1
  299 + /Subtype /Type1
  300 + /Type /Font
  301 +>>
  302 +endobj
  303 +
  304 +17 0 obj
  305 +[
  306 + /PDF
  307 + /Text
  308 +]
  309 +endobj
  310 +
  311 +%% Contents for page 3
  312 +18 0 obj
  313 +<<
  314 + /Length 19 0 R
  315 +>>
  316 +stream
  317 +q
  318 +BT
  319 + /F1 12 Tf
  320 + 144 470 Td
  321 + (Media, Crop present) Tj
  322 +ET
  323 +Q
  324 +q
  325 +1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
  326 +/Fx1 Do
  327 +Q
  328 +endstream
  329 +endobj
  330 +
  331 +19 0 obj
  332 +125
  333 +endobj
  334 +
  335 +20 0 obj
  336 +<<
  337 + /BaseFont /Helvetica
  338 + /Encoding /WinAnsiEncoding
  339 + /Name /F1
  340 + /Subtype /Type1
  341 + /Type /Font
  342 +>>
  343 +endobj
  344 +
  345 +21 0 obj
  346 +[
  347 + /PDF
  348 + /Text
  349 +]
  350 +endobj
  351 +
  352 +%% Contents for page 4
  353 +22 0 obj
  354 +<<
  355 + /Length 23 0 R
  356 +>>
  357 +stream
  358 +q
  359 +BT
  360 + /F1 12 Tf
  361 + 144 470 Td
  362 + (Media, Trim, Bleed present, Crop indirect) Tj
  363 +ET
  364 +Q
  365 +q
  366 +1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
  367 +/Fx1 Do
  368 +Q
  369 +endstream
  370 +endobj
  371 +
  372 +23 0 obj
  373 +147
  374 +endobj
  375 +
  376 +24 0 obj
  377 +[
  378 + 10
  379 + 20
  380 + 582
  381 + 752
  382 +]
  383 +endobj
  384 +
  385 +25 0 obj
  386 +<<
  387 + /BaseFont /Helvetica
  388 + /Encoding /WinAnsiEncoding
  389 + /Name /F1
  390 + /Subtype /Type1
  391 + /Type /Font
  392 +>>
  393 +endobj
  394 +
  395 +26 0 obj
  396 +[
  397 + /PDF
  398 + /Text
  399 +]
  400 +endobj
  401 +
  402 +%% Contents for page 5
  403 +27 0 obj
  404 +<<
  405 + /Length 28 0 R
  406 +>>
  407 +stream
  408 +q
  409 +BT
  410 + /F1 12 Tf
  411 + 144 470 Td
  412 + (Media inherited, Trim, Art present) Tj
  413 +ET
  414 +Q
  415 +q
  416 +1.00000 0.00000 0.00000 1.00000 0.00000 0.00000 cm
  417 +/Fx1 Do
  418 +Q
  419 +endstream
  420 +endobj
  421 +
  422 +28 0 obj
  423 +140
  424 +endobj
  425 +
  426 +29 0 obj
  427 +<<
  428 + /BaseFont /Helvetica
  429 + /Encoding /WinAnsiEncoding
  430 + /Name /F1
  431 + /Subtype /Type1
  432 + /Type /Font
  433 +>>
  434 +endobj
  435 +
  436 +30 0 obj
  437 +[
  438 + /PDF
  439 + /Text
  440 +]
  441 +endobj
  442 +
  443 +31 0 obj
  444 +[
  445 + /PDF
  446 + /Text
  447 +]
  448 +endobj
  449 +
  450 +xref
  451 +0 32
  452 +0000000000 65535 f
  453 +0000000025 00000 n
  454 +0000000079 00000 n
  455 +0000000247 00000 n
  456 +0000000446 00000 n
  457 +0000000693 00000 n
  458 +0000000986 00000 n
  459 +0000001345 00000 n
  460 +0000001651 00000 n
  461 +0000001827 00000 n
  462 +0000001847 00000 n
  463 +0000001966 00000 n
  464 +0000002002 00000 n
  465 +0000002745 00000 n
  466 +0000002789 00000 n
  467 +0000002981 00000 n
  468 +0000003002 00000 n
  469 +0000003121 00000 n
  470 +0000003180 00000 n
  471 +0000003362 00000 n
  472 +0000003383 00000 n
  473 +0000003502 00000 n
  474 +0000003561 00000 n
  475 +0000003765 00000 n
  476 +0000003786 00000 n
  477 +0000003829 00000 n
  478 +0000003948 00000 n
  479 +0000004007 00000 n
  480 +0000004204 00000 n
  481 +0000004225 00000 n
  482 +0000004344 00000 n
  483 +0000004380 00000 n
  484 +trailer <<
  485 + /Root 1 0 R
  486 + /Size 32
  487 + /ID [<42ed290ee4e4c51171853f92a1a7642d><4529bd7e2686f4deaa59ab2fa8e0338d>]
  488 +>>
  489 +startxref
  490 +4416
  491 +%%EOF
qpdf/test_driver.cc
@@ -3302,6 +3302,108 @@ test_93(QPDF&amp; pdf, char const* arg2) @@ -3302,6 +3302,108 @@ test_93(QPDF&amp; pdf, char const* arg2)
3302 assert(trailer.getKey("/Potato") == oh2); 3302 assert(trailer.getKey("/Potato") == oh2);
3303 } 3303 }
3304 3304
  3305 +static void
  3306 +test_94(QPDF& pdf, char const* arg2)
  3307 +{
  3308 + // Exercise methods to get page boxes. This test is built for
  3309 + // boxes2.pdf.
  3310 +
  3311 + // /MediaBox is present in the pages tree root.
  3312 + // Each page has the following boxes present directly:
  3313 + // 1. none
  3314 + // 2. crop
  3315 + // 3. media, crop
  3316 + // 4. media, crop, trim, bleed; crop is indirect
  3317 + // 5. trim, art
  3318 +
  3319 + auto pages_root = pdf.getRoot().getKey("/Pages");
  3320 + auto root_media = pages_root.getKey("/MediaBox");
  3321 + auto root_media_unparse = root_media.unparse();
  3322 + auto pages = QPDFPageDocumentHelper(pdf).getAllPages();
  3323 + assert(pages.size() == 5);
  3324 + auto& p1 = pages[0];
  3325 + auto& p2 = pages[1];
  3326 + auto& p3 = pages[2];
  3327 + auto& p4 = pages[3];
  3328 + auto& p5 = pages[4];
  3329 +
  3330 + assert(p1.getObjectHandle().getKey("/MediaBox").isNull());
  3331 + // MediaBox not present, so get inherited one
  3332 + assert(p1.getMediaBox(false) == root_media);
  3333 + // Other boxesBox not present, so fall back to MediaBox
  3334 + assert(p1.getCropBox(false, false) == root_media);
  3335 + assert(p1.getBleedBox(false, false) == root_media);
  3336 + assert(p1.getTrimBox(false, false) == root_media);
  3337 + assert(p1.getArtBox(false, false) == root_media);
  3338 + // Make copy of artbox
  3339 + auto p1_new_art = p1.getArtBox(false, true);
  3340 + assert(p1_new_art.unparse() == root_media_unparse);
  3341 + assert(p1_new_art != root_media);
  3342 + // This also copied cropbox
  3343 + auto p1_new_crop = p1.getCropBox(false, false);
  3344 + assert(p1_new_crop != root_media);
  3345 + assert(p1_new_crop != p1_new_art);
  3346 + assert(p1_new_crop.unparse() == root_media_unparse);
  3347 + // But it didn't copy Media
  3348 + assert(p1.getMediaBox(false) == root_media);
  3349 + // Now fall back to new crop
  3350 + assert(p1.getTrimBox(false, false) == p1_new_crop);
  3351 + // Request copy. The value returned has the same structure but is
  3352 + // a different object.
  3353 + auto p1_effective_media = p1.getMediaBox(true);
  3354 + assert(p1_effective_media.unparse() == root_media_unparse);
  3355 + assert(p1_effective_media != root_media);
  3356 +
  3357 + // copy_on_fallback didn't have to copy media to crop
  3358 + assert(p2.getMediaBox(false) == root_media);
  3359 + auto p2_crop = p2.getCropBox(false, false);
  3360 + auto p2_new_trim = p2.getTrimBox(false, true);
  3361 + assert(p2_new_trim.unparse() == p2_crop.unparse());
  3362 + assert(p2_new_trim != p2_crop);
  3363 + assert(p2.getMediaBox(false) == root_media);
  3364 +
  3365 + // We didn't need to copy anything
  3366 + auto p3_media = p3.getMediaBox(false);
  3367 + auto p3_crop = p3.getCropBox(false, false);
  3368 + assert(p3.getMediaBox(true) == p3_media);
  3369 + assert(p3.getCropBox(true, true) == p3_crop);
  3370 +
  3371 + // We didn't have to copy for bleed but we did for art
  3372 + auto p4_orig_crop = p4.getObjectHandle().getKey("/CropBox");
  3373 + auto p4_crop = p4.getCropBox(false, false);
  3374 + assert(p4_orig_crop == p4_crop);
  3375 + auto p4_bleed1 = p4.getBleedBox(false, false);
  3376 + auto p4_bleed2 = p4.getBleedBox(false, true);
  3377 + assert(p4_bleed1 != p4_crop);
  3378 + assert(p4_bleed1 == p4_bleed2);
  3379 + auto p4_art1 = p4.getArtBox(false, false);
  3380 + assert(p4_art1 == p4_crop);
  3381 + auto p4_art2 = p4.getArtBox(false, true);
  3382 + assert(p4_art2 != p4_crop);
  3383 + auto p4_new_crop = p4.getCropBox(true, false);
  3384 + assert(p4_new_crop != p4_orig_crop);
  3385 + assert(p4_orig_crop.isIndirect());
  3386 + assert(!p4_new_crop.isIndirect());
  3387 + assert(p4_new_crop.unparse() == p4_orig_crop.unparseResolved());
  3388 +
  3389 + // Exercise copying for inheritence and fallback
  3390 + assert(p5.getMediaBox(false) == root_media);
  3391 + assert(p5.getCropBox(false, false) == root_media);
  3392 + assert(p5.getBleedBox(false, false) == root_media);
  3393 + auto p5_new_bleed = p5.getBleedBox(true, true);
  3394 + auto p5_new_media = p5.getMediaBox(false);
  3395 + auto p5_new_crop = p5.getCropBox(false, false);
  3396 + assert(p5_new_media != root_media);
  3397 + assert(p5_new_crop != root_media);
  3398 + assert(p5_new_crop != p5_new_media);
  3399 + assert(p5_new_bleed != root_media);
  3400 + assert(p5_new_bleed != p5_new_media);
  3401 + assert(p5_new_bleed != p5_new_crop);
  3402 + assert(p5_new_media.unparse() == root_media_unparse);
  3403 + assert(p5_new_crop.unparse() == root_media_unparse);
  3404 + assert(p5_new_bleed.unparse() == root_media_unparse);
  3405 +}
  3406 +
3305 void 3407 void
3306 runtest(int n, char const* filename1, char const* arg2) 3408 runtest(int n, char const* filename1, char const* arg2)
3307 { 3409 {
@@ -3411,7 +3513,7 @@ runtest(int n, char const* filename1, char const* arg2) @@ -3411,7 +3513,7 @@ runtest(int n, char const* filename1, char const* arg2)
3411 {80, test_80}, {81, test_81}, {82, test_82}, {83, test_83}, 3513 {80, test_80}, {81, test_81}, {82, test_82}, {83, test_83},
3412 {84, test_84}, {85, test_85}, {86, test_86}, {87, test_87}, 3514 {84, test_84}, {85, test_85}, {86, test_86}, {87, test_87},
3413 {88, test_88}, {89, test_89}, {90, test_90}, {91, test_91}, 3515 {88, test_88}, {89, test_89}, {90, test_90}, {91, test_91},
3414 - {92, test_92}, {93, test_93}}; 3516 + {92, test_92}, {93, test_93}, {94, test_94}};
3415 3517
3416 auto fn = test_functions.find(n); 3518 auto fn = test_functions.find(n);
3417 if (fn == test_functions.end()) { 3519 if (fn == test_functions.end()) {