Coverage Report

Created: 2025-07-01 06:10

/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
0
    QPDFDocumentHelper(qpdf)
9
0
{
10
0
}
11
12
std::vector<QPDFPageObjectHelper>
13
QPDFPageDocumentHelper::getAllPages()
14
0
{
15
0
    std::vector<QPDFPageObjectHelper> pages;
16
0
    for (auto const& iter: qpdf.getAllPages()) {
17
0
        pages.emplace_back(iter);
18
0
    }
19
0
    return pages;
20
0
}
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
0
{
58
0
    QPDFAcroFormDocumentHelper afdh(qpdf);
59
0
    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
0
    for (auto& ph: getAllPages()) {
67
0
        QPDFObjectHandle resources = ph.getAttribute("/Resources", true);
68
0
        if (!resources.isDictionary()) {
69
0
            QTC::TC("qpdf", "QPDFPageDocumentHelper flatten resources missing or invalid");
70
0
            resources = ph.getObjectHandle().replaceKeyAndGetNew(
71
0
                "/Resources", QPDFObjectHandle::newDictionary());
72
0
        }
73
0
        flattenAnnotationsForPage(ph, resources, afdh, required_flags, forbidden_flags);
74
0
    }
75
0
    if (!afdh.getNeedAppearances()) {
76
0
        qpdf.getRoot().removeKey("/AcroForm");
77
0
    }
78
0
}
79
80
void
81
QPDFPageDocumentHelper::flattenAnnotationsForPage(
82
    QPDFPageObjectHelper& page,
83
    QPDFObjectHandle& resources,
84
    QPDFAcroFormDocumentHelper& afdh,
85
    int required_flags,
86
    int forbidden_flags)
87
0
{
88
0
    bool need_appearances = afdh.getNeedAppearances();
89
0
    std::vector<QPDFAnnotationObjectHelper> annots = page.getAnnotations();
90
0
    std::vector<QPDFObjectHandle> new_annots;
91
0
    std::string new_content;
92
0
    int rotate = 0;
93
0
    QPDFObjectHandle rotate_obj = page.getObjectHandle().getKey("/Rotate");
94
0
    if (rotate_obj.isInteger() && rotate_obj.getIntValue()) {
95
0
        rotate = rotate_obj.getIntValueAsInt();
96
0
    }
97
0
    int next_fx = 1;
98
0
    for (auto& aoh: annots) {
99
0
        QPDFObjectHandle as = aoh.getAppearanceStream("/N");
100
0
        bool is_widget = (aoh.getSubtype() == "/Widget");
101
0
        bool process = true;
102
0
        if (need_appearances && is_widget) {
103
0
            QTC::TC("qpdf", "QPDFPageDocumentHelper skip widget need appearances");
104
0
            process = false;
105
0
        }
106
0
        if (process && as.isStream()) {
107
0
            if (is_widget) {
108
0
                QTC::TC("qpdf", "QPDFPageDocumentHelper merge DR");
109
0
                QPDFFormFieldObjectHelper ff = afdh.getFieldForAnnotation(aoh);
110
0
                QPDFObjectHandle as_resources = as.getDict().getKey("/Resources");
111
0
                if (as_resources.isIndirect()) {
112
0
                    QTC::TC("qpdf", "QPDFPageDocumentHelper indirect as resources");
113
0
                    as.getDict().replaceKey("/Resources", as_resources.shallowCopy());
114
0
                    as_resources = as.getDict().getKey("/Resources");
115
0
                }
116
0
                as_resources.mergeResources(ff.getDefaultResources());
117
0
            } else {
118
0
                QTC::TC("qpdf", "QPDFPageDocumentHelper non-widget annotation");
119
0
            }
120
0
            std::string name = resources.getUniqueResourceName("/Fxo", next_fx);
121
0
            std::string content =
122
0
                aoh.getPageContentForAppearance(name, rotate, required_flags, forbidden_flags);
123
0
            if (!content.empty()) {
124
0
                resources.mergeResources("<< /XObject << >> >>"_qpdf);
125
0
                resources.getKey("/XObject").replaceKey(name, as);
126
0
                ++next_fx;
127
0
            }
128
0
            new_content += content;
129
0
        } 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
0
            QTC::TC("qpdf", "QPDFPageDocumentHelper ignore annotation with no appearance");
136
0
        } else {
137
0
            new_annots.push_back(aoh.getObjectHandle());
138
0
        }
139
0
    }
140
0
    if (new_annots.size() != annots.size()) {
141
0
        QPDFObjectHandle page_oh = page.getObjectHandle();
142
0
        if (new_annots.empty()) {
143
0
            QTC::TC("qpdf", "QPDFPageDocumentHelper remove annots");
144
0
            page_oh.removeKey("/Annots");
145
0
        } else {
146
0
            QPDFObjectHandle old_annots = page_oh.getKey("/Annots");
147
0
            QPDFObjectHandle new_annots_oh = QPDFObjectHandle::newArray(new_annots);
148
0
            if (old_annots.isIndirect()) {
149
0
                QTC::TC("qpdf", "QPDFPageDocumentHelper replace indirect annots");
150
0
                qpdf.replaceObject(old_annots.getObjGen(), new_annots_oh);
151
0
            } else {
152
0
                QTC::TC("qpdf", "QPDFPageDocumentHelper replace direct annots");
153
0
                page_oh.replaceKey("/Annots", new_annots_oh);
154
0
            }
155
0
        }
156
0
        page.addPageContents(qpdf.newStream("q\n"), true);
157
0
        page.addPageContents(qpdf.newStream("\nQ\n" + new_content), false);
158
0
    }
159
0
}