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