Coverage Report

Created: 2025-10-10 06:17

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