Coverage Report

Created: 2026-03-07 06:25

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
8.69k
    Members() = default;
11
    Members(Members const&) = delete;
12
8.69k
    ~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
5.07k
{
24
5.07k
    return !dh.m->seen.add(og);
25
5.07k
}
26
27
QPDFOutlineDocumentHelper::QPDFOutlineDocumentHelper(QPDF& qpdf) :
28
8.69k
    QPDFDocumentHelper(qpdf),
29
8.69k
    m(std::make_shared<Members>())
30
8.69k
{
31
8.69k
    validate();
32
8.69k
}
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
8.69k
{
43
8.69k
    m->outlines.clear();
44
8.69k
    m->names_dest = nullptr;
45
46
8.69k
    QPDFObjectHandle root = qpdf.getRoot();
47
8.69k
    if (!root.hasKey("/Outlines")) {
48
7.78k
        return;
49
7.78k
    }
50
915
    auto outlines = root.getKey("/Outlines");
51
915
    if (!(outlines.isDictionary() && outlines.hasKey("/First"))) {
52
28
        return;
53
28
    }
54
887
    QPDFObjectHandle cur = outlines.getKey("/First");
55
887
    QPDFObjGen::set seen;
56
2.57k
    while (!cur.null()) {
57
1.74k
        if (!seen.add(cur)) {
58
65
            cur.warn("Loop detected loop in /Outlines tree");
59
65
            return;
60
65
        }
61
1.68k
        m->outlines.emplace_back(QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1));
62
1.68k
        cur = cur.getKey("/Next");
63
1.68k
    }
64
887
}
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
17.3k
{
81
17.3k
    std::list<QPDFOutlineObjectHelper> queue;
82
17.3k
    queue.insert(queue.end(), m->outlines.begin(), m->outlines.end());
83
84
22.4k
    while (!queue.empty()) {
85
5.07k
        QPDFOutlineObjectHelper oh = queue.front();
86
5.07k
        queue.pop_front();
87
5.07k
        m->by_page[oh.getDestPage().getObjGen()].push_back(oh);
88
5.07k
        std::vector<QPDFOutlineObjectHelper> kids = oh.getKids();
89
5.07k
        queue.insert(queue.end(), kids.begin(), kids.end());
90
5.07k
    }
91
17.3k
}
92
93
std::vector<QPDFOutlineObjectHelper>
94
QPDFOutlineDocumentHelper::getOutlinesForPage(QPDFObjGen og)
95
17.7k
{
96
17.7k
    if (m->by_page.empty()) {
97
17.3k
        initializeByPage();
98
17.3k
    }
99
17.7k
    if (m->by_page.contains(og)) {
100
90
        return m->by_page[og];
101
90
    }
102
17.6k
    return {};
103
17.7k
}
104
105
QPDFObjectHandle
106
QPDFOutlineDocumentHelper::resolveNamedDest(QPDFObjectHandle name)
107
2.03k
{
108
2.03k
    QPDFObjectHandle result;
109
2.03k
    if (name.isName()) {
110
239
        if (!m->dest_dict) {
111
108
            m->dest_dict = qpdf.getRoot().getKey("/Dests");
112
108
        }
113
239
        result = m->dest_dict.getKeyIfDict(name.getName());
114
1.79k
    } else if (name.isString()) {
115
1.79k
        if (!m->names_dest) {
116
892
            auto dests = qpdf.getRoot().getKey("/Names").getKeyIfDict("/Dests");
117
892
            if (dests.isDictionary()) {
118
590
                m->names_dest = std::make_unique<QPDFNameTreeObjectHelper>(
119
590
                    dests,
120
590
                    qpdf,
121
12.7k
                    [](QPDFObjectHandle const& o) -> bool {
122
12.7k
                        return o.isArray() || o.contains("/D");
123
12.7k
                    },
124
590
                    true);
125
590
                m->names_dest->validate();
126
590
            }
127
892
        }
128
1.79k
        if (m->names_dest) {
129
1.32k
            if (m->names_dest->findObject(name.getUTF8Value(), result)) {
130
191
                QTC::TC("qpdf", "QPDFOutlineDocumentHelper string named dest");
131
191
            }
132
1.32k
        }
133
1.79k
    }
134
2.03k
    if (!result) {
135
1.01k
        return QPDFObjectHandle::newNull();
136
1.01k
    }
137
1.01k
    if (result.isDictionary()) {
138
166
        return result.getKey("/D");
139
166
    }
140
853
    return result;
141
1.01k
}