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