Coverage Report

Created: 2024-05-20 06:28

/src/qpdf/libqpdf/QPDFPageDocumentHelper.cc
Line
Count
Source (jump to first uncovered line)
1
#include <qpdf/QPDFPageDocumentHelper.hh>
2
3
#include <qpdf/QPDFAcroFormDocumentHelper.hh>
4
#include <qpdf/QTC.hh>
5
#include <qpdf/QUtil.hh>
6
7
QPDFPageDocumentHelper::QPDFPageDocumentHelper(QPDF& qpdf) :
8
    QPDFDocumentHelper(qpdf)
9
7.02k
{
10
7.02k
}
11
12
std::vector<QPDFPageObjectHelper>
13
QPDFPageDocumentHelper::getAllPages()
14
11.6k
{
15
11.6k
    std::vector<QPDFPageObjectHelper> pages;
16
183k
    for (auto const& iter: this->qpdf.getAllPages()) {
17
183k
        pages.emplace_back(iter);
18
183k
    }
19
11.6k
    return pages;
20
11.6k
}
21
22
void
23
QPDFPageDocumentHelper::pushInheritedAttributesToPage()
24
0
{
25
0
    this->qpdf.pushInheritedAttributesToPage();
26
0
}
27
28
void
29
QPDFPageDocumentHelper::removeUnreferencedResources()
30
0
{
31
0
    for (auto& ph: getAllPages()) {
32
0
        ph.removeUnreferencedResources();
33
0
    }
34
0
}
35
36
void
37
QPDFPageDocumentHelper::addPage(QPDFPageObjectHelper newpage, bool first)
38
0
{
39
0
    this->qpdf.addPage(newpage.getObjectHandle(), first);
40
0
}
41
42
void
43
QPDFPageDocumentHelper::addPageAt(
44
    QPDFPageObjectHelper newpage, bool before, QPDFPageObjectHelper refpage)
45
0
{
46
0
    this->qpdf.addPageAt(newpage.getObjectHandle(), before, refpage.getObjectHandle());
47
0
}
48
49
void
50
QPDFPageDocumentHelper::removePage(QPDFPageObjectHelper page)
51
0
{
52
0
    this->qpdf.removePage(page.getObjectHandle());
53
0
}
54
55
void
56
QPDFPageDocumentHelper::flattenAnnotations(int required_flags, int forbidden_flags)
57
4.71k
{
58
4.71k
    QPDFAcroFormDocumentHelper afdh(this->qpdf);
59
4.71k
    if (afdh.getNeedAppearances()) {
60
0
        this->qpdf.getRoot()
61
0
            .getKey("/AcroForm")
62
0
            .warnIfPossible("document does not have updated appearance streams, so form fields "
63
0
                            "will not be flattened");
64
0
    }
65
89.4k
    for (auto& ph: getAllPages()) {
66
89.4k
        QPDFObjectHandle resources = ph.getAttribute("/Resources", true);
67
89.4k
        if (!resources.isDictionary()) {
68
78.2k
            QTC::TC("qpdf", "QPDFPageDocumentHelper flatten resources missing or invalid");
69
78.2k
            resources = ph.getObjectHandle().replaceKeyAndGetNew(
70
78.2k
                "/Resources", QPDFObjectHandle::newDictionary());
71
78.2k
        }
72
89.4k
        flattenAnnotationsForPage(ph, resources, afdh, required_flags, forbidden_flags);
73
89.4k
    }
74
4.71k
    if (!afdh.getNeedAppearances()) {
75
4.59k
        this->qpdf.getRoot().removeKey("/AcroForm");
76
4.59k
    }
77
4.71k
}
78
79
void
80
QPDFPageDocumentHelper::flattenAnnotationsForPage(
81
    QPDFPageObjectHelper& page,
82
    QPDFObjectHandle& resources,
83
    QPDFAcroFormDocumentHelper& afdh,
84
    int required_flags,
85
    int forbidden_flags)
86
89.4k
{
87
89.4k
    bool need_appearances = afdh.getNeedAppearances();
88
89.4k
    std::vector<QPDFAnnotationObjectHelper> annots = page.getAnnotations();
89
89.4k
    std::vector<QPDFObjectHandle> new_annots;
90
89.4k
    std::string new_content;
91
89.4k
    int rotate = 0;
92
89.4k
    QPDFObjectHandle rotate_obj = page.getObjectHandle().getKey("/Rotate");
93
89.4k
    if (rotate_obj.isInteger() && rotate_obj.getIntValue()) {
94
16
        rotate = rotate_obj.getIntValueAsInt();
95
16
    }
96
89.4k
    int next_fx = 1;
97
89.4k
    for (auto& aoh: annots) {
98
30.1k
        QPDFObjectHandle as = aoh.getAppearanceStream("/N");
99
30.1k
        bool is_widget = (aoh.getSubtype() == "/Widget");
100
30.1k
        bool process = true;
101
30.1k
        if (need_appearances && is_widget) {
102
0
            QTC::TC("qpdf", "QPDFPageDocumentHelper skip widget need appearances");
103
0
            process = false;
104
0
        }
105
30.1k
        if (process && as.isStream()) {
106
11.4k
            if (is_widget) {
107
10.2k
                QTC::TC("qpdf", "QPDFPageDocumentHelper merge DR");
108
10.2k
                QPDFFormFieldObjectHelper ff = afdh.getFieldForAnnotation(aoh);
109
10.2k
                QPDFObjectHandle as_resources = as.getDict().getKey("/Resources");
110
10.2k
                if (as_resources.isIndirect()) {
111
1.06k
                    QTC::TC("qpdf", "QPDFPageDocumentHelper indirect as resources");
112
1.06k
                    as.getDict().replaceKey("/Resources", as_resources.shallowCopy());
113
1.06k
                    as_resources = as.getDict().getKey("/Resources");
114
1.06k
                }
115
10.2k
                as_resources.mergeResources(ff.getDefaultResources());
116
10.2k
            } else {
117
1.25k
                QTC::TC("qpdf", "QPDFPageDocumentHelper non-widget annotation");
118
1.25k
            }
119
11.4k
            std::string name = resources.getUniqueResourceName("/Fxo", next_fx);
120
11.4k
            std::string content =
121
11.4k
                aoh.getPageContentForAppearance(name, rotate, required_flags, forbidden_flags);
122
11.4k
            if (!content.empty()) {
123
9.03k
                resources.mergeResources("<< /XObject << >> >>"_qpdf);
124
9.03k
                resources.getKey("/XObject").replaceKey(name, as);
125
9.03k
                ++next_fx;
126
9.03k
            }
127
11.4k
            new_content += content;
128
18.6k
        } else if (process && !aoh.getAppearanceDictionary().isNull()) {
129
            // If an annotation has no selected appearance stream, just drop the annotation when
130
            // flattening. This can happen for unchecked checkboxes and radio buttons, popup windows
131
            // associated with comments that aren't visible, and other types of annotations that
132
            // aren't visible. Annotations that have no appearance streams at all, such as Link,
133
            // Popup, and Projection, should be preserved.
134
5.69k
            QTC::TC("qpdf", "QPDFPageDocumentHelper ignore annotation with no appearance");
135
12.9k
        } else {
136
12.9k
            new_annots.push_back(aoh.getObjectHandle());
137
12.9k
        }
138
30.1k
    }
139
89.4k
    if (new_annots.size() != annots.size()) {
140
1.25k
        QPDFObjectHandle page_oh = page.getObjectHandle();
141
1.25k
        if (new_annots.empty()) {
142
522
            QTC::TC("qpdf", "QPDFPageDocumentHelper remove annots");
143
522
            page_oh.removeKey("/Annots");
144
732
        } else {
145
732
            QPDFObjectHandle old_annots = page_oh.getKey("/Annots");
146
732
            QPDFObjectHandle new_annots_oh = QPDFObjectHandle::newArray(new_annots);
147
732
            if (old_annots.isIndirect()) {
148
130
                QTC::TC("qpdf", "QPDFPageDocumentHelper replace indirect annots");
149
130
                this->qpdf.replaceObject(old_annots.getObjGen(), new_annots_oh);
150
602
            } else {
151
602
                QTC::TC("qpdf", "QPDFPageDocumentHelper replace direct annots");
152
602
                page_oh.replaceKey("/Annots", new_annots_oh);
153
602
            }
154
732
        }
155
1.25k
        page.addPageContents(qpdf.newStream("q\n"), true);
156
1.25k
        page.addPageContents(qpdf.newStream("\nQ\n" + new_content), false);
157
1.25k
    }
158
89.4k
}