Coverage Report

Created: 2026-02-26 06:36

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
9.90k
    Members() = default;
11
    Members(Members const&) = delete;
12
9.90k
    ~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
4.88k
{
24
4.88k
    return !dh.m->seen.add(og);
25
4.88k
}
26
27
QPDFOutlineDocumentHelper::QPDFOutlineDocumentHelper(QPDF& qpdf) :
28
9.90k
    QPDFDocumentHelper(qpdf),
29
9.90k
    m(std::make_shared<Members>())
30
9.90k
{
31
9.90k
    validate();
32
9.90k
}
33
34
QPDFOutlineDocumentHelper&
35
QPDFOutlineDocumentHelper::get(QPDF& qpdf)
36
0
{
37
0
    return qpdf.doc().outlines();
38
0
}
39
40
void
41
QPDFOutlineDocumentHelper::validate(bool repair)
42
9.90k
{
43
9.90k
    m->outlines.clear();
44
9.90k
    m->names_dest = nullptr;
45
46
9.90k
    QPDFObjectHandle root = qpdf.getRoot();
47
9.90k
    if (!root.hasKey("/Outlines")) {
48
8.97k
        return;
49
8.97k
    }
50
926
    auto outlines = root.getKey("/Outlines");
51
926
    if (!(outlines.isDictionary() && outlines.hasKey("/First"))) {
52
30
        return;
53
30
    }
54
896
    QPDFObjectHandle cur = outlines.getKey("/First");
55
896
    QPDFObjGen::set seen;
56
2.61k
    while (!cur.null()) {
57
1.77k
        if (!seen.add(cur)) {
58
57
            cur.warn("Loop detected loop in /Outlines tree");
59
57
            return;
60
57
        }
61
1.71k
        m->outlines.emplace_back(QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1));
62
1.71k
        cur = cur.getKey("/Next");
63
1.71k
    }
64
896
}
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
0
{
75
0
    return m->outlines;
76
0
}
77
78
void
79
QPDFOutlineDocumentHelper::initializeByPage()
80
15.4k
{
81
15.4k
    std::list<QPDFOutlineObjectHelper> queue;
82
15.4k
    queue.insert(queue.end(), m->outlines.begin(), m->outlines.end());
83
84
20.2k
    while (!queue.empty()) {
85
4.81k
        QPDFOutlineObjectHelper oh = queue.front();
86
4.81k
        queue.pop_front();
87
4.81k
        m->by_page[oh.getDestPage().getObjGen()].push_back(oh);
88
4.81k
        std::vector<QPDFOutlineObjectHelper> kids = oh.getKids();
89
4.81k
        queue.insert(queue.end(), kids.begin(), kids.end());
90
4.81k
    }
91
15.4k
}
92
93
std::vector<QPDFOutlineObjectHelper>
94
QPDFOutlineDocumentHelper::getOutlinesForPage(QPDFObjGen og)
95
15.9k
{
96
15.9k
    if (m->by_page.empty()) {
97
15.4k
        initializeByPage();
98
15.4k
    }
99
15.9k
    if (m->by_page.contains(og)) {
100
153
        return m->by_page[og];
101
153
    }
102
15.8k
    return {};
103
15.9k
}
104
105
QPDFObjectHandle
106
QPDFOutlineDocumentHelper::resolveNamedDest(QPDFObjectHandle name)
107
1.62k
{
108
1.62k
    QPDFObjectHandle result;
109
1.62k
    if (name.isName()) {
110
188
        if (!m->dest_dict) {
111
76
            m->dest_dict = qpdf.getRoot().getKey("/Dests");
112
76
        }
113
188
        result = m->dest_dict.getKeyIfDict(name.getName());
114
1.43k
    } else if (name.isString()) {
115
1.43k
        if (!m->names_dest) {
116
780
            auto dests = qpdf.getRoot().getKey("/Names").getKeyIfDict("/Dests");
117
780
            if (dests.isDictionary()) {
118
621
                m->names_dest = std::make_unique<QPDFNameTreeObjectHelper>(
119
621
                    dests,
120
621
                    qpdf,
121
18.8k
                    [](QPDFObjectHandle const& o) -> bool {
122
18.8k
                        return o.isArray() || o.contains("/D");
123
18.8k
                    },
124
621
                    true);
125
621
                m->names_dest->validate();
126
621
            }
127
780
        }
128
1.43k
        if (m->names_dest) {
129
1.14k
            if (m->names_dest->findObject(name.getUTF8Value(), result)) {
130
225
                QTC::TC("qpdf", "QPDFOutlineDocumentHelper string named dest");
131
225
            }
132
1.14k
        }
133
1.43k
    }
134
1.62k
    if (!result) {
135
840
        return QPDFObjectHandle::newNull();
136
840
    }
137
787
    if (result.isDictionary()) {
138
153
        return result.getKey("/D");
139
153
    }
140
634
    return result;
141
787
}