Coverage Report

Created: 2026-06-09 06:59

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