Coverage Report

Created: 2025-07-11 06:59

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