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 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 11 * Add == equality for QPDFObjectHandle. Two QPDFObjectHandle
4 12 objects are equal if they point to the same underlying object,
5 13 meaning changes to one will be reflected in the other.
... ...
include/qpdf/QPDFPageObjectHelper.hh
... ... @@ -45,29 +45,151 @@ class QPDFPageObjectHelper: public QPDFObjectHelper
45 45 QPDF_DLL
46 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 71 QPDF_DLL
58 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 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 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 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 194 // Iterate through XObjects, possibly recursing into form
73 195 // XObjects. This works with pages or form XObjects. Call action
... ... @@ -373,6 +495,11 @@ class QPDFPageObjectHelper: public QPDFObjectHelper
373 495 QPDFAcroFormDocumentHelper* from_afdh = nullptr);
374 496  
375 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 503 static bool removeUnreferencedResourcesHelper(
377 504 QPDFPageObjectHelper ph, std::set<std::string>& unresolved);
378 505  
... ...
libqpdf/QPDFPageObjectHelper.cc
... ... @@ -238,6 +238,16 @@ QPDFPageObjectHelper::QPDFPageObjectHelper(QPDFObjectHandle oh) :
238 238 QPDFObjectHandle
239 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 251 QPDFObjectHandle result;
242 252 QPDFObjectHandle dict;
243 253 bool is_form_xobject = this->oh.isFormXObject();
... ... @@ -272,36 +282,71 @@ QPDFPageObjectHelper::getAttribute(std::string const&amp; name, bool copy_if_shared)
272 282 "qpdf",
273 283 "QPDFPageObjectHelper copy shared attribute",
274 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 297 return result;
279 298 }
280 299  
281 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 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 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 352 void
... ...
manual/release-notes.rst
... ... @@ -261,6 +261,11 @@ For a detailed list of changes, please see the file
261 261 generation parameters. The old versions will continue to be
262 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 269 - Add a move constructor to the ``Buffer`` class.
265 270  
266 271 - Other changes
... ...
qpdf/qpdf.testcov
... ... @@ -676,3 +676,5 @@ QPDF_json missing json version 0
676 676 QPDF_json bad json version 0
677 677 QPDF_json bad calledgetallpages 0
678 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 14  
15 15 my $td = new TestDriver('page-api');
16 16  
17   -my $n_tests = 11;
18   -
19 17 $td->runtest("basic page API",
20 18 {$td->COMMAND => "test_driver 15 page_api_1.pdf"},
21 19 {$td->STRING => "test 15 done\n", $td->EXIT_STATUS => 0},
... ... @@ -58,5 +56,10 @@ $td-&gt;runtest(&quot;flatten rotation&quot;,
58 56 $td->runtest("check output",
59 57 {$td->FILE => "a.pdf"},
60 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 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 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 3407 void
3306 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 3513 {80, test_80}, {81, test_81}, {82, test_82}, {83, test_83},
3412 3514 {84, test_84}, {85, test_85}, {86, test_86}, {87, test_87},
3413 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 3518 auto fn = test_functions.find(n);
3417 3519 if (fn == test_functions.end()) {
... ...