/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 | } |