Coverage Report

Created: 2026-03-07 06:28

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