Coverage Report

Created: 2025-12-05 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qpdf/libqpdf/QPDFOutlineDocumentHelper.cc
Line
Count
Source
1
#include <qpdf/QPDFOutlineDocumentHelper.hh>
2
3
#include <qpdf/QPDFObjectHandle_private.hh>
4
#include <qpdf/QPDF_private.hh>
5
#include <qpdf/QTC.hh>
6
7
class QPDFOutlineDocumentHelper::Members
8
{
9
  public:
10
19.2k
    Members() = default;
11
    Members(Members const&) = delete;
12
19.2k
    ~Members() = default;
13
14
    std::vector<QPDFOutlineObjectHelper> outlines;
15
    QPDFObjGen::set seen;
16
    QPDFObjectHandle dest_dict;
17
    std::unique_ptr<QPDFNameTreeObjectHelper> names_dest;
18
    std::map<QPDFObjGen, std::vector<QPDFOutlineObjectHelper>> by_page;
19
};
20
21
bool
22
QPDFOutlineDocumentHelper::Accessor::checkSeen(QPDFOutlineDocumentHelper& dh, QPDFObjGen og)
23
24.5k
{
24
24.5k
    return !dh.m->seen.add(og);
25
24.5k
}
26
27
QPDFOutlineDocumentHelper::QPDFOutlineDocumentHelper(QPDF& qpdf) :
28
19.2k
    QPDFDocumentHelper(qpdf),
29
19.2k
    m(std::make_shared<Members>())
30
19.2k
{
31
19.2k
    validate();
32
19.2k
}
33
34
QPDFOutlineDocumentHelper&
35
QPDFOutlineDocumentHelper::get(QPDF& qpdf)
36
3.38k
{
37
3.38k
    return qpdf.doc().outlines();
38
3.38k
}
39
40
void
41
QPDFOutlineDocumentHelper::validate(bool repair)
42
19.2k
{
43
19.2k
    m->outlines.clear();
44
19.2k
    m->names_dest = nullptr;
45
46
19.2k
    QPDFObjectHandle root = qpdf.getRoot();
47
19.2k
    if (!root.hasKey("/Outlines")) {
48
14.5k
        return;
49
14.5k
    }
50
4.72k
    auto outlines = root.getKey("/Outlines");
51
4.72k
    if (!(outlines.isDictionary() && outlines.hasKey("/First"))) {
52
105
        return;
53
105
    }
54
4.62k
    QPDFObjectHandle cur = outlines.getKey("/First");
55
4.62k
    QPDFObjGen::set seen;
56
11.2k
    while (!cur.null()) {
57
6.77k
        if (!seen.add(cur)) {
58
174
            cur.warn("Loop detected loop in /Outlines tree");
59
174
            return;
60
174
        }
61
6.59k
        m->outlines.emplace_back(QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1));
62
6.59k
        cur = cur.getKey("/Next");
63
6.59k
    }
64
4.62k
}
65
66
bool
67
QPDFOutlineDocumentHelper::hasOutlines()
68
0
{
69
0
    return !m->outlines.empty();
70
0
}
71
72
std::vector<QPDFOutlineObjectHelper>
73
QPDFOutlineDocumentHelper::getTopLevelOutlines()
74
2.92k
{
75
2.92k
    return m->outlines;
76
2.92k
}
77
78
void
79
QPDFOutlineDocumentHelper::initializeByPage()
80
29.2k
{
81
29.2k
    std::list<QPDFOutlineObjectHelper> queue;
82
29.2k
    queue.insert(queue.end(), m->outlines.begin(), m->outlines.end());
83
84
38.8k
    while (!queue.empty()) {
85
9.62k
        QPDFOutlineObjectHelper oh = queue.front();
86
9.62k
        queue.pop_front();
87
9.62k
        m->by_page[oh.getDestPage().getObjGen()].push_back(oh);
88
9.62k
        std::vector<QPDFOutlineObjectHelper> kids = oh.getKids();
89
9.62k
        queue.insert(queue.end(), kids.begin(), kids.end());
90
9.62k
    }
91
29.2k
}
92
93
std::vector<QPDFOutlineObjectHelper>
94
QPDFOutlineDocumentHelper::getOutlinesForPage(QPDFObjGen og)
95
30.2k
{
96
30.2k
    if (m->by_page.empty()) {
97
29.2k
        initializeByPage();
98
29.2k
    }
99
30.2k
    if (m->by_page.contains(og)) {
100
273
        return m->by_page[og];
101
273
    }
102
30.0k
    return {};
103
30.2k
}
104
105
QPDFObjectHandle
106
QPDFOutlineDocumentHelper::resolveNamedDest(QPDFObjectHandle name)
107
8.86k
{
108
8.86k
    QPDFObjectHandle result;
109
8.86k
    if (name.isName()) {
110
758
        if (!m->dest_dict) {
111
275
            m->dest_dict = qpdf.getRoot().getKey("/Dests");
112
275
        }
113
758
        result = m->dest_dict.getKeyIfDict(name.getName());
114
8.10k
    } else if (name.isString()) {
115
8.10k
        if (!m->names_dest) {
116
4.12k
            auto dests = qpdf.getRoot().getKey("/Names").getKeyIfDict("/Dests");
117
4.12k
            if (dests.isDictionary()) {
118
3.11k
                m->names_dest = std::make_unique<QPDFNameTreeObjectHelper>(
119
3.11k
                    dests,
120
3.11k
                    qpdf,
121
217k
                    [](QPDFObjectHandle const& o) -> bool {
122
217k
                        return o.isArray() || o.isDictionary();
123
217k
                    },
124
3.11k
                    true);
125
3.11k
                m->names_dest->validate();
126
3.11k
            }
127
4.12k
        }
128
8.10k
        if (m->names_dest) {
129
6.42k
            if (m->names_dest->findObject(name.getUTF8Value(), result)) {
130
776
                QTC::TC("qpdf", "QPDFOutlineDocumentHelper string named dest");
131
776
            }
132
6.42k
        }
133
8.10k
    }
134
8.86k
    if (!result) {
135
6.05k
        return QPDFObjectHandle::newNull();
136
6.05k
    }
137
2.80k
    if (result.isDictionary()) {
138
645
        return result.getKey("/D");
139
645
    }
140
2.16k
    return result;
141
2.80k
}