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