Commit 4f23d70b9a489f961429119275173afa862acc89
1 parent
361f147f
Refactor `QPDFPageDocumentHelper`: merge implementation into `QPDF_pages`, updat…
…e includes, and streamline page-related logic.
Showing
3 changed files
with
168 additions
and
174 deletions
libqpdf/CMakeLists.txt
| @@ -76,7 +76,6 @@ set(libqpdf_SOURCES | @@ -76,7 +76,6 @@ set(libqpdf_SOURCES | ||
| 76 | QPDFObjectHelper.cc | 76 | QPDFObjectHelper.cc |
| 77 | QPDFOutlineDocumentHelper.cc | 77 | QPDFOutlineDocumentHelper.cc |
| 78 | QPDFOutlineObjectHelper.cc | 78 | QPDFOutlineObjectHelper.cc |
| 79 | - QPDFPageDocumentHelper.cc | ||
| 80 | QPDFPageLabelDocumentHelper.cc | 79 | QPDFPageLabelDocumentHelper.cc |
| 81 | QPDFPageObjectHelper.cc | 80 | QPDFPageObjectHelper.cc |
| 82 | QPDFParser.cc | 81 | QPDFParser.cc |
libqpdf/QPDFPageDocumentHelper.cc deleted
| 1 | -#include <qpdf/QPDFPageDocumentHelper.hh> | ||
| 2 | - | ||
| 3 | -#include <qpdf/QPDFAcroFormDocumentHelper.hh> | ||
| 4 | -#include <qpdf/QPDFObjectHandle_private.hh> | ||
| 5 | -#include <qpdf/QPDF_private.hh> | ||
| 6 | -#include <qpdf/QTC.hh> | ||
| 7 | -#include <qpdf/QUtil.hh> | ||
| 8 | - | ||
| 9 | -class QPDFPageDocumentHelper::Members | ||
| 10 | -{ | ||
| 11 | -}; | ||
| 12 | - | ||
| 13 | -QPDFPageDocumentHelper::QPDFPageDocumentHelper(QPDF& qpdf) : | ||
| 14 | - QPDFDocumentHelper(qpdf) | ||
| 15 | -{ | ||
| 16 | -} | ||
| 17 | - | ||
| 18 | -QPDFPageDocumentHelper& | ||
| 19 | -QPDFPageDocumentHelper::get(QPDF& qpdf) | ||
| 20 | -{ | ||
| 21 | - return qpdf.doc().page_dh(); | ||
| 22 | -} | ||
| 23 | - | ||
| 24 | -void | ||
| 25 | -QPDFPageDocumentHelper::validate(bool repair) | ||
| 26 | -{ | ||
| 27 | -} | ||
| 28 | - | ||
| 29 | -std::vector<QPDFPageObjectHelper> | ||
| 30 | -QPDFPageDocumentHelper::getAllPages() | ||
| 31 | -{ | ||
| 32 | - auto& pp = qpdf.doc().pages(); | ||
| 33 | - return {pp.begin(), pp.end()}; | ||
| 34 | -} | ||
| 35 | - | ||
| 36 | -void | ||
| 37 | -QPDFPageDocumentHelper::pushInheritedAttributesToPage() | ||
| 38 | -{ | ||
| 39 | - qpdf.pushInheritedAttributesToPage(); | ||
| 40 | -} | ||
| 41 | - | ||
| 42 | -void | ||
| 43 | -QPDFPageDocumentHelper::removeUnreferencedResources() | ||
| 44 | -{ | ||
| 45 | - for (auto& ph: getAllPages()) { | ||
| 46 | - ph.removeUnreferencedResources(); | ||
| 47 | - } | ||
| 48 | -} | ||
| 49 | - | ||
| 50 | -void | ||
| 51 | -QPDFPageDocumentHelper::addPage(QPDFPageObjectHelper newpage, bool first) | ||
| 52 | -{ | ||
| 53 | - qpdf.addPage(newpage.getObjectHandle(), first); | ||
| 54 | -} | ||
| 55 | - | ||
| 56 | -void | ||
| 57 | -QPDFPageDocumentHelper::addPageAt( | ||
| 58 | - QPDFPageObjectHelper newpage, bool before, QPDFPageObjectHelper refpage) | ||
| 59 | -{ | ||
| 60 | - qpdf.addPageAt(newpage.getObjectHandle(), before, refpage.getObjectHandle()); | ||
| 61 | -} | ||
| 62 | - | ||
| 63 | -void | ||
| 64 | -QPDFPageDocumentHelper::removePage(QPDFPageObjectHelper page) | ||
| 65 | -{ | ||
| 66 | - qpdf.removePage(page.getObjectHandle()); | ||
| 67 | -} | ||
| 68 | - | ||
| 69 | -void | ||
| 70 | -QPDFPageDocumentHelper::flattenAnnotations(int required_flags, int forbidden_flags) | ||
| 71 | -{ | ||
| 72 | - auto& afdh = qpdf.doc().acroform(); | ||
| 73 | - if (afdh.getNeedAppearances()) { | ||
| 74 | - qpdf.getRoot() | ||
| 75 | - .getKey("/AcroForm") | ||
| 76 | - .warn( | ||
| 77 | - "document does not have updated appearance streams, so form fields " | ||
| 78 | - "will not be flattened"); | ||
| 79 | - } | ||
| 80 | - for (auto& ph: getAllPages()) { | ||
| 81 | - QPDFObjectHandle resources = ph.getAttribute("/Resources", true); | ||
| 82 | - if (!resources.isDictionary()) { | ||
| 83 | - // As of #1521, this should be impossible unless a user inserted an invalid page. | ||
| 84 | - resources = ph.getObjectHandle().replaceKeyAndGetNew( | ||
| 85 | - "/Resources", QPDFObjectHandle::newDictionary()); | ||
| 86 | - } | ||
| 87 | - flattenAnnotationsForPage(ph, resources, afdh, required_flags, forbidden_flags); | ||
| 88 | - } | ||
| 89 | - if (!afdh.getNeedAppearances()) { | ||
| 90 | - qpdf.getRoot().removeKey("/AcroForm"); | ||
| 91 | - } | ||
| 92 | -} | ||
| 93 | - | ||
| 94 | -void | ||
| 95 | -QPDFPageDocumentHelper::flattenAnnotationsForPage( | ||
| 96 | - QPDFPageObjectHelper& page, | ||
| 97 | - QPDFObjectHandle& resources, | ||
| 98 | - QPDFAcroFormDocumentHelper& afdh, | ||
| 99 | - int required_flags, | ||
| 100 | - int forbidden_flags) | ||
| 101 | -{ | ||
| 102 | - bool need_appearances = afdh.getNeedAppearances(); | ||
| 103 | - std::vector<QPDFAnnotationObjectHelper> annots = page.getAnnotations(); | ||
| 104 | - std::vector<QPDFObjectHandle> new_annots; | ||
| 105 | - std::string new_content; | ||
| 106 | - int rotate = 0; | ||
| 107 | - QPDFObjectHandle rotate_obj = page.getObjectHandle().getKey("/Rotate"); | ||
| 108 | - if (rotate_obj.isInteger() && rotate_obj.getIntValue()) { | ||
| 109 | - rotate = rotate_obj.getIntValueAsInt(); | ||
| 110 | - } | ||
| 111 | - int next_fx = 1; | ||
| 112 | - for (auto& aoh: annots) { | ||
| 113 | - QPDFObjectHandle as = aoh.getAppearanceStream("/N"); | ||
| 114 | - bool is_widget = (aoh.getSubtype() == "/Widget"); | ||
| 115 | - bool process = true; | ||
| 116 | - if (need_appearances && is_widget) { | ||
| 117 | - QTC::TC("qpdf", "QPDFPageDocumentHelper skip widget need appearances"); | ||
| 118 | - process = false; | ||
| 119 | - } | ||
| 120 | - if (process && as.isStream()) { | ||
| 121 | - if (is_widget) { | ||
| 122 | - QTC::TC("qpdf", "QPDFPageDocumentHelper merge DR"); | ||
| 123 | - QPDFFormFieldObjectHelper ff = afdh.getFieldForAnnotation(aoh); | ||
| 124 | - QPDFObjectHandle as_resources = as.getDict().getKey("/Resources"); | ||
| 125 | - if (as_resources.isIndirect()) { | ||
| 126 | - QTC::TC("qpdf", "QPDFPageDocumentHelper indirect as resources"); | ||
| 127 | - as.getDict().replaceKey("/Resources", as_resources.shallowCopy()); | ||
| 128 | - as_resources = as.getDict().getKey("/Resources"); | ||
| 129 | - } | ||
| 130 | - as_resources.mergeResources(ff.getDefaultResources()); | ||
| 131 | - } else { | ||
| 132 | - QTC::TC("qpdf", "QPDFPageDocumentHelper non-widget annotation"); | ||
| 133 | - } | ||
| 134 | - std::string name = resources.getUniqueResourceName("/Fxo", next_fx); | ||
| 135 | - std::string content = | ||
| 136 | - aoh.getPageContentForAppearance(name, rotate, required_flags, forbidden_flags); | ||
| 137 | - if (!content.empty()) { | ||
| 138 | - resources.mergeResources("<< /XObject << >> >>"_qpdf); | ||
| 139 | - resources.getKey("/XObject").replaceKey(name, as); | ||
| 140 | - ++next_fx; | ||
| 141 | - } | ||
| 142 | - new_content += content; | ||
| 143 | - } else if (process && !aoh.getAppearanceDictionary().null()) { | ||
| 144 | - // If an annotation has no selected appearance stream, just drop the annotation when | ||
| 145 | - // flattening. This can happen for unchecked checkboxes and radio buttons, popup windows | ||
| 146 | - // associated with comments that aren't visible, and other types of annotations that | ||
| 147 | - // aren't visible. Annotations that have no appearance streams at all, such as Link, | ||
| 148 | - // Popup, and Projection, should be preserved. | ||
| 149 | - QTC::TC("qpdf", "QPDFPageDocumentHelper ignore annotation with no appearance"); | ||
| 150 | - } else { | ||
| 151 | - new_annots.push_back(aoh.getObjectHandle()); | ||
| 152 | - } | ||
| 153 | - } | ||
| 154 | - if (new_annots.size() != annots.size()) { | ||
| 155 | - QPDFObjectHandle page_oh = page.getObjectHandle(); | ||
| 156 | - if (new_annots.empty()) { | ||
| 157 | - QTC::TC("qpdf", "QPDFPageDocumentHelper remove annots"); | ||
| 158 | - page_oh.removeKey("/Annots"); | ||
| 159 | - } else { | ||
| 160 | - QPDFObjectHandle old_annots = page_oh.getKey("/Annots"); | ||
| 161 | - QPDFObjectHandle new_annots_oh = QPDFObjectHandle::newArray(new_annots); | ||
| 162 | - if (old_annots.isIndirect()) { | ||
| 163 | - QTC::TC("qpdf", "QPDFPageDocumentHelper replace indirect annots"); | ||
| 164 | - qpdf.replaceObject(old_annots.getObjGen(), new_annots_oh); | ||
| 165 | - } else { | ||
| 166 | - QTC::TC("qpdf", "QPDFPageDocumentHelper replace direct annots"); | ||
| 167 | - page_oh.replaceKey("/Annots", new_annots_oh); | ||
| 168 | - } | ||
| 169 | - } | ||
| 170 | - page.addPageContents(qpdf.newStream("q\n"), true); | ||
| 171 | - page.addPageContents(qpdf.newStream("\nQ\n" + new_content), false); | ||
| 172 | - } | ||
| 173 | -} |
libqpdf/QPDF_pages.cc
| 1 | +#include <qpdf/QPDFPageDocumentHelper.hh> | ||
| 1 | #include <qpdf/QPDF_private.hh> | 2 | #include <qpdf/QPDF_private.hh> |
| 2 | 3 | ||
| 4 | +#include <qpdf/QPDFAcroFormDocumentHelper.hh> | ||
| 3 | #include <qpdf/QPDFExc.hh> | 5 | #include <qpdf/QPDFExc.hh> |
| 4 | #include <qpdf/QPDFObjectHandle_private.hh> | 6 | #include <qpdf/QPDFObjectHandle_private.hh> |
| 5 | #include <qpdf/QTC.hh> | 7 | #include <qpdf/QTC.hh> |
| @@ -565,3 +567,169 @@ Pages::find(QPDFObjGen og) | @@ -565,3 +567,169 @@ Pages::find(QPDFObjGen og) | ||
| 565 | } | 567 | } |
| 566 | return (*it).second; | 568 | return (*it).second; |
| 567 | } | 569 | } |
| 570 | + | ||
| 571 | +class QPDFPageDocumentHelper::Members | ||
| 572 | +{ | ||
| 573 | +}; | ||
| 574 | + | ||
| 575 | +QPDFPageDocumentHelper::QPDFPageDocumentHelper(QPDF& qpdf) : | ||
| 576 | + QPDFDocumentHelper(qpdf) | ||
| 577 | +{ | ||
| 578 | +} | ||
| 579 | + | ||
| 580 | +QPDFPageDocumentHelper& | ||
| 581 | +QPDFPageDocumentHelper::get(QPDF& qpdf) | ||
| 582 | +{ | ||
| 583 | + return qpdf.doc().page_dh(); | ||
| 584 | +} | ||
| 585 | + | ||
| 586 | +void | ||
| 587 | +QPDFPageDocumentHelper::validate(bool repair) | ||
| 588 | +{ | ||
| 589 | +} | ||
| 590 | + | ||
| 591 | +std::vector<QPDFPageObjectHelper> | ||
| 592 | +QPDFPageDocumentHelper::getAllPages() | ||
| 593 | +{ | ||
| 594 | + auto& pp = qpdf.doc().pages(); | ||
| 595 | + return {pp.begin(), pp.end()}; | ||
| 596 | +} | ||
| 597 | + | ||
| 598 | +void | ||
| 599 | +QPDFPageDocumentHelper::pushInheritedAttributesToPage() | ||
| 600 | +{ | ||
| 601 | + qpdf.pushInheritedAttributesToPage(); | ||
| 602 | +} | ||
| 603 | + | ||
| 604 | +void | ||
| 605 | +QPDFPageDocumentHelper::removeUnreferencedResources() | ||
| 606 | +{ | ||
| 607 | + for (auto& ph: getAllPages()) { | ||
| 608 | + ph.removeUnreferencedResources(); | ||
| 609 | + } | ||
| 610 | +} | ||
| 611 | + | ||
| 612 | +void | ||
| 613 | +QPDFPageDocumentHelper::addPage(QPDFPageObjectHelper newpage, bool first) | ||
| 614 | +{ | ||
| 615 | + qpdf.addPage(newpage.getObjectHandle(), first); | ||
| 616 | +} | ||
| 617 | + | ||
| 618 | +void | ||
| 619 | +QPDFPageDocumentHelper::addPageAt( | ||
| 620 | + QPDFPageObjectHelper newpage, bool before, QPDFPageObjectHelper refpage) | ||
| 621 | +{ | ||
| 622 | + qpdf.addPageAt(newpage.getObjectHandle(), before, refpage.getObjectHandle()); | ||
| 623 | +} | ||
| 624 | + | ||
| 625 | +void | ||
| 626 | +QPDFPageDocumentHelper::removePage(QPDFPageObjectHelper page) | ||
| 627 | +{ | ||
| 628 | + qpdf.removePage(page.getObjectHandle()); | ||
| 629 | +} | ||
| 630 | + | ||
| 631 | +void | ||
| 632 | +QPDFPageDocumentHelper::flattenAnnotations(int required_flags, int forbidden_flags) | ||
| 633 | +{ | ||
| 634 | + auto& afdh = qpdf.doc().acroform(); | ||
| 635 | + if (afdh.getNeedAppearances()) { | ||
| 636 | + qpdf.getRoot() | ||
| 637 | + .getKey("/AcroForm") | ||
| 638 | + .warn( | ||
| 639 | + "document does not have updated appearance streams, so form fields " | ||
| 640 | + "will not be flattened"); | ||
| 641 | + } | ||
| 642 | + for (auto& ph: getAllPages()) { | ||
| 643 | + QPDFObjectHandle resources = ph.getAttribute("/Resources", true); | ||
| 644 | + if (!resources.isDictionary()) { | ||
| 645 | + // As of #1521, this should be impossible unless a user inserted an invalid page. | ||
| 646 | + resources = ph.getObjectHandle().replaceKeyAndGetNew( | ||
| 647 | + "/Resources", QPDFObjectHandle::newDictionary()); | ||
| 648 | + } | ||
| 649 | + flattenAnnotationsForPage(ph, resources, afdh, required_flags, forbidden_flags); | ||
| 650 | + } | ||
| 651 | + if (!afdh.getNeedAppearances()) { | ||
| 652 | + qpdf.getRoot().removeKey("/AcroForm"); | ||
| 653 | + } | ||
| 654 | +} | ||
| 655 | + | ||
| 656 | +void | ||
| 657 | +QPDFPageDocumentHelper::flattenAnnotationsForPage( | ||
| 658 | + QPDFPageObjectHelper& page, | ||
| 659 | + QPDFObjectHandle& resources, | ||
| 660 | + QPDFAcroFormDocumentHelper& afdh, | ||
| 661 | + int required_flags, | ||
| 662 | + int forbidden_flags) | ||
| 663 | +{ | ||
| 664 | + bool need_appearances = afdh.getNeedAppearances(); | ||
| 665 | + std::vector<QPDFAnnotationObjectHelper> annots = page.getAnnotations(); | ||
| 666 | + std::vector<QPDFObjectHandle> new_annots; | ||
| 667 | + std::string new_content; | ||
| 668 | + int rotate = 0; | ||
| 669 | + QPDFObjectHandle rotate_obj = page.getObjectHandle().getKey("/Rotate"); | ||
| 670 | + if (rotate_obj.isInteger() && rotate_obj.getIntValue()) { | ||
| 671 | + rotate = rotate_obj.getIntValueAsInt(); | ||
| 672 | + } | ||
| 673 | + int next_fx = 1; | ||
| 674 | + for (auto& aoh: annots) { | ||
| 675 | + QPDFObjectHandle as = aoh.getAppearanceStream("/N"); | ||
| 676 | + bool is_widget = (aoh.getSubtype() == "/Widget"); | ||
| 677 | + bool process = true; | ||
| 678 | + if (need_appearances && is_widget) { | ||
| 679 | + QTC::TC("qpdf", "QPDFPageDocumentHelper skip widget need appearances"); | ||
| 680 | + process = false; | ||
| 681 | + } | ||
| 682 | + if (process && as.isStream()) { | ||
| 683 | + if (is_widget) { | ||
| 684 | + QTC::TC("qpdf", "QPDFPageDocumentHelper merge DR"); | ||
| 685 | + QPDFFormFieldObjectHelper ff = afdh.getFieldForAnnotation(aoh); | ||
| 686 | + QPDFObjectHandle as_resources = as.getDict().getKey("/Resources"); | ||
| 687 | + if (as_resources.isIndirect()) { | ||
| 688 | + QTC::TC("qpdf", "QPDFPageDocumentHelper indirect as resources"); | ||
| 689 | + as.getDict().replaceKey("/Resources", as_resources.shallowCopy()); | ||
| 690 | + as_resources = as.getDict().getKey("/Resources"); | ||
| 691 | + } | ||
| 692 | + as_resources.mergeResources(ff.getDefaultResources()); | ||
| 693 | + } else { | ||
| 694 | + QTC::TC("qpdf", "QPDFPageDocumentHelper non-widget annotation"); | ||
| 695 | + } | ||
| 696 | + std::string name = resources.getUniqueResourceName("/Fxo", next_fx); | ||
| 697 | + std::string content = | ||
| 698 | + aoh.getPageContentForAppearance(name, rotate, required_flags, forbidden_flags); | ||
| 699 | + if (!content.empty()) { | ||
| 700 | + resources.mergeResources("<< /XObject << >> >>"_qpdf); | ||
| 701 | + resources.getKey("/XObject").replaceKey(name, as); | ||
| 702 | + ++next_fx; | ||
| 703 | + } | ||
| 704 | + new_content += content; | ||
| 705 | + } else if (process && !aoh.getAppearanceDictionary().null()) { | ||
| 706 | + // If an annotation has no selected appearance stream, just drop the annotation when | ||
| 707 | + // flattening. This can happen for unchecked checkboxes and radio buttons, popup windows | ||
| 708 | + // associated with comments that aren't visible, and other types of annotations that | ||
| 709 | + // aren't visible. Annotations that have no appearance streams at all, such as Link, | ||
| 710 | + // Popup, and Projection, should be preserved. | ||
| 711 | + QTC::TC("qpdf", "QPDFPageDocumentHelper ignore annotation with no appearance"); | ||
| 712 | + } else { | ||
| 713 | + new_annots.push_back(aoh.getObjectHandle()); | ||
| 714 | + } | ||
| 715 | + } | ||
| 716 | + if (new_annots.size() != annots.size()) { | ||
| 717 | + QPDFObjectHandle page_oh = page.getObjectHandle(); | ||
| 718 | + if (new_annots.empty()) { | ||
| 719 | + QTC::TC("qpdf", "QPDFPageDocumentHelper remove annots"); | ||
| 720 | + page_oh.removeKey("/Annots"); | ||
| 721 | + } else { | ||
| 722 | + QPDFObjectHandle old_annots = page_oh.getKey("/Annots"); | ||
| 723 | + QPDFObjectHandle new_annots_oh = QPDFObjectHandle::newArray(new_annots); | ||
| 724 | + if (old_annots.isIndirect()) { | ||
| 725 | + QTC::TC("qpdf", "QPDFPageDocumentHelper replace indirect annots"); | ||
| 726 | + qpdf.replaceObject(old_annots.getObjGen(), new_annots_oh); | ||
| 727 | + } else { | ||
| 728 | + QTC::TC("qpdf", "QPDFPageDocumentHelper replace direct annots"); | ||
| 729 | + page_oh.replaceKey("/Annots", new_annots_oh); | ||
| 730 | + } | ||
| 731 | + } | ||
| 732 | + page.addPageContents(qpdf.newStream("q\n"), true); | ||
| 733 | + page.addPageContents(qpdf.newStream("\nQ\n" + new_content), false); | ||
| 734 | + } | ||
| 735 | +} |