Commit 76cd7ea67aee7edd1004a68a28e63a00a38239dd
1 parent
c1def4ea
Clarify and improve QPDFPageObjectHelper::get*Box methods
Add copy_if_fallback and explain how it differs from copy_if_shared.
Showing
8 changed files
with
818 additions
and
35 deletions
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& 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->runtest("flatten rotation", |
| 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 | ||
| 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 | ||
| 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 | ||
| 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 | ||
| 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 | ||
| 439 | + /Text | |
| 440 | +] | |
| 441 | +endobj | |
| 442 | + | |
| 443 | +31 0 obj | |
| 444 | +[ | |
| 445 | ||
| 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& 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()) { | ... | ... |