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
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 | 2 | #include <qpdf/QPDF_private.hh> |
| 2 | 3 | |
| 4 | +#include <qpdf/QPDFAcroFormDocumentHelper.hh> | |
| 3 | 5 | #include <qpdf/QPDFExc.hh> |
| 4 | 6 | #include <qpdf/QPDFObjectHandle_private.hh> |
| 5 | 7 | #include <qpdf/QTC.hh> |
| ... | ... | @@ -565,3 +567,169 @@ Pages::find(QPDFObjGen og) |
| 565 | 567 | } |
| 566 | 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 | +} | ... | ... |