/src/qpdf/libqpdf/NNTree.cc
Line | Count | Source |
1 | | #include <qpdf/assert_debug.h> |
2 | | |
3 | | #include <qpdf/NNTree.hh> |
4 | | |
5 | | #include <qpdf/QPDFNameTreeObjectHelper.hh> |
6 | | #include <qpdf/QPDFNumberTreeObjectHelper.hh> |
7 | | |
8 | | #include <qpdf/QPDFObjectHandle_private.hh> |
9 | | #include <qpdf/QPDF_private.hh> |
10 | | #include <qpdf/QTC.hh> |
11 | | #include <qpdf/QUtil.hh> |
12 | | #include <qpdf/Util.hh> |
13 | | |
14 | | #include <bit> |
15 | | #include <exception> |
16 | | #include <utility> |
17 | | |
18 | | using namespace qpdf; |
19 | | |
20 | | static std::string |
21 | | get_description(QPDFObjectHandle const& node) |
22 | 4.28k | { |
23 | 4.28k | std::string result("Name/Number tree node"); |
24 | 4.28k | if (node.indirect()) { |
25 | 4.17k | result += " (object " + std::to_string(node.getObjectID()) + ")"; |
26 | 4.17k | } |
27 | 4.28k | return result; |
28 | 4.28k | } |
29 | | |
30 | | void |
31 | | NNTreeImpl::warn(QPDFObjectHandle const& node, std::string const& msg) |
32 | 3.58k | { |
33 | 3.58k | qpdf.warn(qpdf_e_damaged_pdf, get_description(node), 0, msg); |
34 | 3.58k | if (++error_count > 5 && qpdf.doc().reconstructed_xref()) { |
35 | 193 | error(node, "too many errors - giving up"); |
36 | 193 | } |
37 | 3.58k | } |
38 | | |
39 | | void |
40 | | NNTreeImpl::error(QPDFObjectHandle const& node, std::string const& msg) const |
41 | 702 | { |
42 | 702 | throw QPDFExc(qpdf_e_damaged_pdf, qpdf.getFilename(), get_description(node), 0, msg); |
43 | 702 | } |
44 | | |
45 | | void |
46 | | NNTreeIterator::updateIValue(bool allow_invalid) |
47 | 69.3k | { |
48 | | // ivalue should never be used inside the class since we return a pointer/reference to it. Every |
49 | | // bit of code that ever changes what object the iterator points to should take care to call |
50 | | // updateIValue. Failure to do this means that any old references to *iter will point to |
51 | | // incorrect objects, though the next dereference of the iterator will fix it. This isn't |
52 | | // necessarily catastrophic, but it would be confusing. The test suite attempts to exercise |
53 | | // various cases to ensure we don't introduce that bug in the future, but sadly it's tricky to |
54 | | // verify by reasoning about the code that this constraint is always satisfied. Whenever we |
55 | | // update what the iterator points to, we should call setItemNumber, which calls this. If we |
56 | | // change what the iterator points to in some other way, such as replacing a value or removing |
57 | | // an item and making the iterator point at a different item in potentially the same position, |
58 | | // we must call updateIValue as well. These cases are handled, and for good measure, we also |
59 | | // call updateIValue in operator* and operator->. |
60 | | |
61 | 69.3k | Array items = node[impl.itemsKey()]; |
62 | 69.3k | ivalue.first = items[item_number]; |
63 | 69.3k | ivalue.second = items[item_number + 1]; |
64 | 69.3k | if (ivalue.second) { |
65 | 67.6k | return; |
66 | 67.6k | } |
67 | | |
68 | 1.76k | if (item_number < 0 || !node) { |
69 | 1.75k | util::assertion( |
70 | 1.75k | allow_invalid, "attempt made to dereference an invalid name/number tree iterator"); |
71 | 1.75k | return; |
72 | 1.75k | } |
73 | 5 | impl.error(node, "update ivalue: items array is too short"); |
74 | 5 | } |
75 | | |
76 | | Dictionary |
77 | | NNTreeIterator::getNextKid(PathElement& pe, bool backward) |
78 | 517 | { |
79 | 788 | while (true) { |
80 | 756 | pe.kid_number += backward ? -1 : 1; |
81 | 756 | Dictionary result = pe.node["/Kids"][pe.kid_number]; |
82 | 756 | if (result.contains("/Kids") || result.contains(impl.itemsKey())) { |
83 | 253 | return result; |
84 | 253 | } |
85 | 503 | if (pe.kid_number < 0 || std::cmp_greater_equal(pe.kid_number, pe.node["/Kids"].size())) { |
86 | 232 | return {}; |
87 | 232 | } |
88 | 271 | impl.warn(pe.node, "skipping over invalid kid at index " + std::to_string(pe.kid_number)); |
89 | 271 | } |
90 | 517 | } |
91 | | void |
92 | | NNTreeIterator::increment(bool backward) |
93 | 30.0k | { |
94 | 30.0k | if (item_number < 0) { |
95 | 0 | deepen(impl.tree_root, !backward, true); |
96 | 0 | return; |
97 | 0 | } |
98 | | |
99 | 31.9k | while (valid()) { |
100 | 31.2k | item_number += backward ? -2 : 2; |
101 | 31.2k | Array items = node[impl.itemsKey()]; |
102 | 31.2k | if (item_number < 0 || std::cmp_greater_equal(item_number, items.size())) { |
103 | 790 | setItemNumber(QPDFObjectHandle(), -1); |
104 | 1.09k | while (!path.empty()) { |
105 | 517 | auto& element = path.back(); |
106 | 517 | if (auto pe_node = getNextKid(element, backward)) { |
107 | 253 | if (deepen(pe_node, !backward, false)) { |
108 | 209 | break; |
109 | 209 | } |
110 | 264 | } else { |
111 | 264 | path.pop_back(); |
112 | 264 | } |
113 | 517 | } |
114 | 790 | } |
115 | 31.2k | if (item_number >= 0) { |
116 | 30.6k | items = node[impl.itemsKey()]; |
117 | 30.6k | if (std::cmp_greater_equal(item_number + 1, items.size())) { |
118 | 292 | impl.warn(node, "items array doesn't have enough elements"); |
119 | 30.3k | } else if (!impl.keyValid(items[item_number])) { |
120 | 536 | impl.warn(node, ("item " + std::to_string(item_number) + " has the wrong type")); |
121 | 29.8k | } else if (!impl.value_valid(items[item_number + 1])) { |
122 | 447 | impl.warn(node, "item " + std::to_string(item_number + 1) + " is invalid"); |
123 | 29.3k | } else { |
124 | 29.3k | return; |
125 | 29.3k | } |
126 | 30.6k | } |
127 | 31.2k | } |
128 | 30.0k | } |
129 | | |
130 | | void |
131 | | NNTreeIterator::resetLimits(Dictionary a_node, std::list<PathElement>::iterator parent) |
132 | 6.99k | { |
133 | 6.99k | while (true) { |
134 | 6.99k | if (parent == path.end()) { |
135 | 3.86k | a_node.erase("/Limits"); |
136 | 3.86k | return; |
137 | 3.86k | } |
138 | | |
139 | 3.13k | QPDFObjectHandle first; |
140 | 3.13k | QPDFObjectHandle last; |
141 | 3.13k | Array items = a_node[impl.itemsKey()]; |
142 | 3.13k | size_t nitems = items.size(); |
143 | 3.13k | if (nitems >= 2) { |
144 | 2.99k | first = items[0]; |
145 | 2.99k | last = items[(nitems - 1u) & ~1u]; |
146 | 2.99k | } else { |
147 | 138 | Array kids = a_node["/Kids"]; |
148 | 138 | size_t nkids = kids.size(); |
149 | 138 | if (nkids > 0) { |
150 | 138 | Array first_limits = kids[0]["/Limits"]; |
151 | 138 | if (first_limits.size() >= 2) { |
152 | 138 | first = first_limits[0]; |
153 | 138 | last = kids[nkids - 1u]["/Limits"][1]; |
154 | 138 | } |
155 | 138 | } |
156 | 138 | } |
157 | 3.13k | if (!(first && last)) { |
158 | 0 | impl.warn(a_node, "unable to determine limits"); |
159 | 3.13k | } else { |
160 | 3.13k | Array olimits = a_node["/Limits"]; |
161 | 3.13k | if (olimits.size() == 2) { |
162 | 2.71k | auto ofirst = olimits[0]; |
163 | 2.71k | auto olast = olimits[1]; |
164 | 2.71k | if (impl.keyValid(ofirst) && impl.keyValid(olast) && |
165 | 2.71k | impl.compareKeys(first, ofirst) == 0 && impl.compareKeys(last, olast) == 0) { |
166 | 0 | return; |
167 | 0 | } |
168 | 2.71k | } |
169 | 3.13k | if (a_node != path.begin()->node) { |
170 | 2.99k | a_node.replace("/Limits", Array({first, last})); |
171 | 2.99k | } |
172 | 3.13k | } |
173 | | |
174 | 3.13k | if (parent == path.begin()) { |
175 | 3.13k | return; |
176 | 3.13k | } |
177 | 0 | a_node = parent->node; |
178 | 0 | --parent; |
179 | 0 | } |
180 | 6.99k | } |
181 | | |
182 | | void |
183 | | NNTreeIterator::split(Dictionary to_split, std::list<PathElement>::iterator parent) |
184 | 6.58k | { |
185 | | // Split some node along the path to the item pointed to by this iterator, and adjust the |
186 | | // iterator so it points to the same item. |
187 | | |
188 | | // In examples, for simplicity, /Nums is shown to just contain numbers instead of pairs. Imagine |
189 | | // this tree: |
190 | | // |
191 | | // root: << /Kids [ A B C D ] >> |
192 | | // A: << /Nums [ 1 2 3 4 ] >> |
193 | | // B: << /Nums [ 5 6 7 8 ] >> |
194 | | // C: << /Nums [ 9 10 11 12 ] >> |
195 | | // D: << /Kids [ E F ] |
196 | | // E: << /Nums [ 13 14 15 16 ] >> |
197 | | // F: << /Nums [ 17 18 19 20 ] >> |
198 | | |
199 | | // iter1 (points to 19) |
200 | | // path: |
201 | | // - { node: root: kid_number: 3 } |
202 | | // - { node: D, kid_number: 1 } |
203 | | // node: F |
204 | | // item_number: 2 |
205 | | |
206 | | // iter2 (points to 1) |
207 | | // path: |
208 | | // - { node: root, kid_number: 0} |
209 | | // node: A |
210 | | // item_number: 0 |
211 | | |
212 | 6.58k | util::assertion(valid(), "NNTreeIterator::split called an invalid iterator"); |
213 | | |
214 | | // Find the array we actually need to split, which is either this node's kids or items. |
215 | 6.58k | Array kids = to_split["/Kids"]; |
216 | 6.58k | size_t nkids = kids.size(); |
217 | 6.58k | Array items = to_split[impl.itemsKey()]; |
218 | 6.58k | size_t nitems = items.size(); |
219 | | |
220 | 6.58k | Array first_half; |
221 | 6.58k | size_t n = 0; |
222 | 6.58k | std::string key; |
223 | 6.58k | size_t threshold = static_cast<size_t>(impl.split_threshold); |
224 | 6.58k | if (nkids > 0) { |
225 | 138 | first_half = kids; |
226 | 138 | n = nkids; |
227 | 138 | key = "/Kids"; |
228 | 6.44k | } else { |
229 | 6.44k | util::assertion(nitems > 0, "NNTreeIterator::split called on invalid node"); |
230 | 6.44k | first_half = items; |
231 | 6.44k | n = nitems; |
232 | 6.44k | threshold *= 2; |
233 | 6.44k | key = impl.itemsKey(); |
234 | 6.44k | } |
235 | | |
236 | 6.58k | if (n <= threshold) { |
237 | 6.37k | return; |
238 | 6.37k | } |
239 | | |
240 | 208 | bool is_root = parent == path.end(); |
241 | 208 | bool is_leaf = nitems > 0; |
242 | | |
243 | | // CURRENT STATE: tree is in original state; iterator is valid and unchanged. |
244 | | |
245 | 208 | if (is_root) { |
246 | | // What we want to do is to create a new node for the second half of the items and put it in |
247 | | // the parent's /Kids array right after the element that points to the current to_split |
248 | | // node, but if we're splitting root, there is no parent, so handle that first. |
249 | | |
250 | | // In the non-root case, parent points to the path element whose /Kids contains the first |
251 | | // half node, and the first half node is to_split. If we are splitting the root, we need to |
252 | | // push everything down a level, but we want to keep the actual root object the same so that |
253 | | // indirect references to it remain intact (and also in case it might be a direct object, |
254 | | // which it shouldn't be but that case probably exists in the wild). To achieve this, we |
255 | | // create a new node for the first half and then replace /Kids in the root to contain it. |
256 | | // Then we adjust the path so that the first element is root and the second element, if any, |
257 | | // is the new first half. In this way, we make the root case identical to the non-root case |
258 | | // so remaining logic can handle them in the same way. |
259 | | |
260 | 70 | Dictionary first_node = impl.qpdf.makeIndirectObject(Dictionary({{key, first_half}})); |
261 | 70 | auto new_kids = Array::empty(); |
262 | 70 | new_kids.push_back(first_node); |
263 | 70 | to_split.erase("/Limits"); // already shouldn't be there for root |
264 | 70 | to_split.erase(impl.itemsKey()); |
265 | 70 | to_split.replace("/Kids", new_kids); |
266 | 70 | if (is_leaf) { |
267 | 69 | node = first_node; |
268 | 69 | } else { |
269 | 1 | auto next = path.begin(); |
270 | 1 | next->node = first_node; |
271 | 1 | } |
272 | 70 | this->path.emplace_front(to_split, 0); |
273 | 70 | parent = path.begin(); |
274 | 70 | to_split = first_node; |
275 | 70 | } |
276 | | |
277 | | // CURRENT STATE: parent is guaranteed to be defined, and we have the invariants that |
278 | | // parent[/Kids][kid_number] == to_split and (++parent).node == to_split. |
279 | | |
280 | | // Create a second half array, and transfer the second half of the items into the second half |
281 | | // array. |
282 | 208 | auto second_half = Array::empty(); |
283 | 208 | auto start_idx = static_cast<int>((n / 2) & ~1u); |
284 | 7.24k | while (std::cmp_greater(first_half.size(), start_idx)) { |
285 | 7.03k | second_half.push_back(first_half[start_idx]); |
286 | 7.03k | first_half.erase(start_idx); |
287 | 7.03k | } |
288 | 208 | resetLimits(to_split, parent); |
289 | | |
290 | | // Create a new node to contain the second half |
291 | 208 | Dictionary second_node = impl.qpdf.makeIndirectObject(Dictionary({{key, second_half}})); |
292 | 208 | resetLimits(second_node, parent); |
293 | | |
294 | | // CURRENT STATE: half the items from the kids or items array in the node being split have been |
295 | | // moved into a new node. The new node is not yet attached to the tree. The iterator may have a |
296 | | // path element or leaf node that is out of bounds. |
297 | | |
298 | | // We need to adjust the parent to add the second node to /Kids and, if needed, update |
299 | | // kid_number to traverse through it. We need to update to_split's path element, or the node if |
300 | | // this is a leaf, so that the kid/item number points to the right place. |
301 | | |
302 | 208 | Array parent_kids = parent->node["/Kids"]; |
303 | 208 | if (!parent_kids) { |
304 | 0 | impl.error(parent->node, "parent node has no /Kids array"); |
305 | 0 | } |
306 | 208 | parent_kids.insert(parent->kid_number + 1, second_node); |
307 | 208 | auto cur_elem = parent; |
308 | 208 | ++cur_elem; // points to end() for leaf nodes |
309 | 208 | int old_idx = (is_leaf ? item_number : cur_elem->kid_number); |
310 | 208 | if (old_idx >= start_idx) { |
311 | 207 | ++parent->kid_number; |
312 | 207 | if (is_leaf) { |
313 | 207 | setItemNumber(second_node, item_number - start_idx); |
314 | 207 | } else { |
315 | 0 | cur_elem->node = second_node; |
316 | 0 | cur_elem->kid_number -= start_idx; |
317 | 0 | } |
318 | 207 | } |
319 | 208 | if (!is_root) { |
320 | 138 | auto next = parent->node; |
321 | 138 | resetLimits(next, parent); |
322 | 138 | --parent; |
323 | 138 | split(next, parent); |
324 | 138 | } |
325 | 208 | } |
326 | | |
327 | | std::list<NNTreeIterator::PathElement>::iterator |
328 | | NNTreeIterator::lastPathElement() |
329 | 12.8k | { |
330 | 12.8k | return path.empty() ? path.end() : std::prev(path.end()); |
331 | 12.8k | } |
332 | | |
333 | | void |
334 | | NNTreeIterator::insertAfter(QPDFObjectHandle const& key, QPDFObjectHandle const& value) |
335 | 6.09k | { |
336 | 6.09k | if (!valid()) { |
337 | 0 | impl.insertFirst(key, value); |
338 | 0 | deepen(impl.tree_root, true, false); |
339 | 0 | return; |
340 | 0 | } |
341 | | |
342 | 6.09k | Array items = node[impl.itemsKey()]; |
343 | 6.09k | if (!items) { |
344 | 0 | impl.error(node, "node contains no items array"); |
345 | 0 | } |
346 | | |
347 | 6.09k | if (std::cmp_less(items.size(), item_number + 2)) { |
348 | 0 | impl.error(node, "insert: items array is too short"); |
349 | 0 | } |
350 | 6.09k | if (!(key && value)) { |
351 | 0 | impl.error(node, "insert: key or value is null"); |
352 | 0 | } |
353 | 6.09k | if (!impl.value_valid(value)) { |
354 | 0 | impl.error(node, "insert: value is invalid"); |
355 | 0 | } |
356 | 6.09k | items.insert(item_number + 2, key); |
357 | 6.09k | items.insert(item_number + 3, value); |
358 | 6.09k | resetLimits(node, lastPathElement()); |
359 | 6.09k | split(node, lastPathElement()); |
360 | 6.09k | increment(false); |
361 | 6.09k | } |
362 | | |
363 | | void |
364 | | NNTreeIterator::remove() |
365 | 0 | { |
366 | | // Remove this item, leaving the tree valid and this iterator pointing to the next item. |
367 | |
|
368 | 0 | util::assertion(valid(), "attempt made to remove an invalid iterator"); |
369 | 0 | Array items = node[impl.itemsKey()]; |
370 | 0 | int nitems = static_cast<int>(items.size()); |
371 | 0 | if (std::cmp_greater(item_number + 2, nitems)) { |
372 | 0 | impl.error(node, "found short items array while removing an item"); |
373 | 0 | } |
374 | |
|
375 | 0 | items.erase(item_number); |
376 | 0 | items.erase(item_number); |
377 | 0 | nitems -= 2; |
378 | |
|
379 | 0 | if (nitems > 0) { |
380 | | // There are still items left |
381 | |
|
382 | 0 | if (item_number == 0 || item_number == nitems) { |
383 | | // We removed either the first or last item of an items array that remains non-empty, so |
384 | | // we have to adjust limits. |
385 | 0 | resetLimits(node, lastPathElement()); |
386 | 0 | } |
387 | |
|
388 | 0 | if (item_number == nitems) { |
389 | | // We removed the last item of a non-empty items array, so advance to the successor of |
390 | | // the previous item. |
391 | 0 | item_number -= 2; |
392 | 0 | increment(false); |
393 | 0 | } else { |
394 | 0 | util::assertion( |
395 | 0 | item_number < nitems, "NNTreeIterator::remove: item_number > nitems after erase"); |
396 | | // We don't have to do anything since the removed item's successor now occupies its |
397 | | // former location. |
398 | 0 | updateIValue(); |
399 | 0 | } |
400 | 0 | return; |
401 | 0 | } |
402 | | |
403 | 0 | if (path.empty()) { |
404 | | // Special case: if this is the root node, we can leave it empty. |
405 | 0 | setItemNumber(impl.tree_root, -1); |
406 | 0 | return; |
407 | 0 | } |
408 | | |
409 | | // We removed the last item from this items array, so we need to remove this node from the |
410 | | // parent on up the tree. Then we need to position ourselves at the removed item's successor. |
411 | 0 | while (true) { |
412 | 0 | auto element = lastPathElement(); |
413 | 0 | auto parent = element; |
414 | 0 | --parent; |
415 | 0 | Array kids = element->node["/Kids"]; |
416 | 0 | kids.erase(element->kid_number); |
417 | 0 | auto nkids = kids.size(); |
418 | 0 | if (nkids > 0) { |
419 | | // The logic here is similar to the items case. |
420 | 0 | if (element->kid_number == 0 || std::cmp_equal(element->kid_number, nkids)) { |
421 | 0 | resetLimits(element->node, parent); |
422 | 0 | } |
423 | 0 | if (std::cmp_equal(element->kid_number, nkids)) { |
424 | | // Move to the successor of the last child of the previous kid. |
425 | 0 | setItemNumber({}, -1); |
426 | 0 | --element->kid_number; |
427 | 0 | deepen(kids[element->kid_number], false, true); |
428 | 0 | if (valid()) { |
429 | 0 | increment(false); |
430 | 0 | QTC::TC("qpdf", "NNTree erased last kid/item in tree", valid() ? 0 : 1); |
431 | 0 | } |
432 | 0 | } else { |
433 | | // Next kid is in deleted kid's position |
434 | 0 | deepen(kids.get(element->kid_number), true, true); |
435 | 0 | } |
436 | 0 | return; |
437 | 0 | } |
438 | | |
439 | 0 | if (parent == path.end()) { |
440 | | // We erased the very last item. Convert the root to an empty items array. |
441 | 0 | element->node.erase("/Kids"); |
442 | 0 | element->node.replace(impl.itemsKey(), Array::empty()); |
443 | 0 | path.clear(); |
444 | 0 | setItemNumber(impl.tree_root, -1); |
445 | 0 | return; |
446 | 0 | } |
447 | | |
448 | | // Walk up the tree and continue |
449 | 0 | path.pop_back(); |
450 | 0 | } |
451 | 0 | } |
452 | | |
453 | | bool |
454 | | NNTreeIterator::operator==(NNTreeIterator const& other) const |
455 | 27.5k | { |
456 | 27.5k | if (item_number == -1 && other.item_number == -1) { |
457 | 3.02k | return true; |
458 | 3.02k | } |
459 | 24.5k | if (path.size() != other.path.size()) { |
460 | 1.39k | return false; |
461 | 1.39k | } |
462 | 23.1k | auto tpi = path.begin(); |
463 | 23.1k | auto opi = other.path.begin(); |
464 | 23.1k | while (tpi != path.end()) { |
465 | 0 | if (tpi->kid_number != opi->kid_number) { |
466 | 0 | return false; |
467 | 0 | } |
468 | 0 | ++tpi; |
469 | 0 | ++opi; |
470 | 0 | } |
471 | 23.1k | return item_number == other.item_number; |
472 | 23.1k | } |
473 | | |
474 | | bool |
475 | | NNTreeIterator::deepen(Dictionary a_node, bool first, bool allow_empty) |
476 | 10.9k | { |
477 | | // Starting at this node, descend through the first or last kid until we reach a node with |
478 | | // items. If we succeed, return true; otherwise return false and leave path alone. |
479 | | |
480 | 10.9k | auto opath = path; |
481 | | |
482 | 10.9k | auto fail = [this, &opath](Dictionary const& failed_node, std::string const& msg) { |
483 | 1.39k | impl.warn(failed_node, msg); |
484 | 1.39k | path = opath; |
485 | 1.39k | return false; |
486 | 1.39k | }; |
487 | | |
488 | 10.9k | QPDFObjGen::set seen; |
489 | 10.9k | for (auto const& i: path) { |
490 | 348 | seen.add(i.node); |
491 | 348 | } |
492 | 14.8k | while (true) { |
493 | 14.8k | if (!seen.add(a_node)) { |
494 | 26 | return fail(a_node, "loop detected while traversing name/number tree"); |
495 | 26 | } |
496 | | |
497 | 14.8k | if (!a_node) { |
498 | 0 | return fail(a_node, "non-dictionary node while traversing name/number tree"); |
499 | 0 | } |
500 | | |
501 | 14.8k | Array items = a_node[impl.itemsKey()]; |
502 | 14.8k | int nitems = static_cast<int>(items.size()); |
503 | 14.8k | if (nitems > 1) { |
504 | 8.47k | setItemNumber(a_node, first ? 0 : nitems - 2); |
505 | 8.47k | return true; |
506 | 8.47k | } |
507 | | |
508 | 6.33k | Array kids = a_node["/Kids"]; |
509 | 6.33k | int nkids = static_cast<int>(kids.size()); |
510 | 6.33k | if (nkids == 0) { |
511 | 1.88k | if (allow_empty && items) { |
512 | 1.02k | setItemNumber(a_node, -1); |
513 | 1.02k | return true; |
514 | 1.02k | } |
515 | 866 | return fail( |
516 | 866 | a_node, |
517 | 866 | "name/number tree node has neither non-empty " + impl.itemsKey() + " nor /Kids"); |
518 | 1.88k | } |
519 | | |
520 | 4.45k | int kid_number = first ? 0 : nkids - 1; |
521 | 4.45k | addPathElement(a_node, kid_number); |
522 | 4.45k | Dictionary next = kids[kid_number]; |
523 | 4.45k | if (!next) { |
524 | 506 | return fail(a_node, "kid number " + std::to_string(kid_number) + " is invalid"); |
525 | 506 | } |
526 | 3.94k | if (!next.indirect()) { |
527 | 74 | if (impl.auto_repair) { |
528 | 74 | impl.warn( |
529 | 74 | a_node, |
530 | 74 | "converting kid number " + std::to_string(kid_number) + |
531 | 74 | " to an indirect object"); |
532 | 74 | next = impl.qpdf.makeIndirectObject(next); |
533 | 74 | kids.set(kid_number, next); |
534 | 74 | } else { |
535 | 0 | impl.warn( |
536 | 0 | a_node, |
537 | 0 | "kid number " + std::to_string(kid_number) + " is not an indirect object"); |
538 | 0 | } |
539 | 74 | } |
540 | | |
541 | 3.94k | a_node = next; |
542 | 3.94k | } |
543 | 10.9k | } |
544 | | |
545 | | NNTreeImpl::iterator |
546 | | NNTreeImpl::begin() |
547 | 10.6k | { |
548 | 10.6k | iterator result(*this); |
549 | 10.6k | result.deepen(tree_root, true, true); |
550 | 10.6k | return result; |
551 | 10.6k | } |
552 | | |
553 | | NNTreeImpl::iterator |
554 | | NNTreeImpl::last() |
555 | 0 | { |
556 | 0 | iterator result(*this); |
557 | 0 | result.deepen(tree_root, false, true); |
558 | 0 | return result; |
559 | 0 | } |
560 | | |
561 | | int |
562 | | NNTreeImpl::compareKeys(QPDFObjectHandle a, QPDFObjectHandle b) const |
563 | 49.8k | { |
564 | | // We don't call this without calling keyValid first |
565 | 49.8k | qpdf_assert_debug(keyValid(a)); |
566 | 49.8k | qpdf_assert_debug(keyValid(b)); |
567 | 49.8k | if (key_type == ::ot_string) { |
568 | 49.8k | auto as = a.getUTF8Value(); |
569 | 49.8k | auto bs = b.getUTF8Value(); |
570 | 49.8k | return as < bs ? -1 : (as > bs ? 1 : 0); |
571 | 49.8k | } |
572 | 0 | auto as = a.getIntValue(); |
573 | 0 | auto bs = b.getIntValue(); |
574 | 0 | return as < bs ? -1 : (as > bs ? 1 : 0); |
575 | 49.8k | } |
576 | | |
577 | | int |
578 | | NNTreeImpl::binarySearch( |
579 | | QPDFObjectHandle const& key, |
580 | | Array const& items, |
581 | | size_t num_items, |
582 | | bool return_prev_if_not_found, |
583 | | bool search_kids) const |
584 | 9.96k | { |
585 | 9.96k | size_t max_idx = std::bit_ceil(num_items); |
586 | | |
587 | 9.96k | int step = static_cast<int>(max_idx / 2); |
588 | 9.96k | int checks = static_cast<int>(std::bit_width(max_idx)); // AppImage gcc version returns size_t |
589 | 9.96k | int idx = step; |
590 | 9.96k | int found_idx = -1; |
591 | | |
592 | 51.8k | for (int i = 0; i < checks; ++i) { |
593 | 42.2k | int status = -1; |
594 | 42.2k | if (std::cmp_less(idx, num_items)) { |
595 | 25.5k | status = search_kids ? compareKeyKid(key, items, idx) : compareKeyItem(key, items, idx); |
596 | 25.5k | if (status == 0) { |
597 | 331 | return idx; |
598 | 331 | } |
599 | 25.1k | if (status > 0) { |
600 | 24.2k | found_idx = idx; |
601 | 24.2k | } |
602 | 25.1k | } |
603 | 41.8k | step = std::max(step / 2, 1); |
604 | 41.8k | idx += status * step; |
605 | 41.8k | } |
606 | 9.63k | return return_prev_if_not_found ? found_idx : -1; |
607 | 9.96k | } |
608 | | |
609 | | int |
610 | | NNTreeImpl::compareKeyItem(QPDFObjectHandle const& key, Array const& items, int idx) const |
611 | 20.3k | { |
612 | 20.3k | if (!keyValid(items[2 * idx])) { |
613 | 21 | error(tree_root, ("item at index " + std::to_string(2 * idx) + " is not the right type")); |
614 | 21 | } |
615 | 20.3k | return compareKeys(key, items[2 * idx]); |
616 | 20.3k | } |
617 | | |
618 | | int |
619 | | NNTreeImpl::compareKeyKid(QPDFObjectHandle const& key, Array const& kids, int idx) const |
620 | 5.19k | { |
621 | 5.19k | Dictionary kid = kids[idx]; |
622 | 5.19k | if (!kid) { |
623 | 7 | error(tree_root, "invalid kid at index " + std::to_string(idx)); |
624 | 7 | } |
625 | 5.19k | Array limits = kid["/Limits"]; |
626 | 5.19k | if (!(keyValid(limits[0]) && keyValid(limits[1]))) { |
627 | 43 | error(kids[idx], "node is missing /Limits"); |
628 | 43 | } |
629 | 5.19k | if (compareKeys(key, limits[0]) < 0) { |
630 | 97 | return -1; |
631 | 97 | } |
632 | 5.09k | if (compareKeys(key, limits[1]) > 0) { |
633 | 4.87k | return 1; |
634 | 4.87k | } |
635 | 220 | return 0; |
636 | 5.09k | } |
637 | | |
638 | | namespace |
639 | | { |
640 | | struct Cmp |
641 | | { |
642 | | bool |
643 | | operator()(const QPDFObjectHandle& lhs, const QPDFObjectHandle& rhs) const |
644 | 156k | { |
645 | 156k | Integer l = lhs; |
646 | 156k | Integer r = rhs; |
647 | 156k | if (l && r) { |
648 | 0 | return l.value() < r.value(); |
649 | 0 | } |
650 | 156k | return lhs.getUTF8Value() < rhs.getUTF8Value(); |
651 | 156k | } |
652 | | }; |
653 | | } // namespace |
654 | | |
655 | | void |
656 | | NNTreeImpl::repair() |
657 | 493 | { |
658 | 493 | auto new_node = Dictionary({{itemsKey(), Array::empty()}}); |
659 | 493 | NNTreeImpl repl(qpdf, new_node, key_type, value_valid, false); |
660 | 493 | std::map<QPDFObjectHandle, QPDFObjectHandle, Cmp> items; |
661 | 23.0k | for (auto const& [key, value]: *this) { |
662 | 23.0k | if (key && value && repl.keyValid(key) && repl.value_valid(value)) { |
663 | 22.8k | items.insert_or_assign(key, value); |
664 | 22.8k | } |
665 | 23.0k | } |
666 | 6.44k | for (auto const& [key, value]: items) { |
667 | 6.44k | repl.insert(key, value); |
668 | 6.44k | } |
669 | 493 | tree_root.replace("/Kids", new_node["/Kids"]); |
670 | 493 | tree_root.replace(itemsKey(), new_node[itemsKey()]); |
671 | 493 | } |
672 | | |
673 | | NNTreeImpl::iterator |
674 | | NNTreeImpl::find(QPDFObjectHandle const& key, bool return_prev_if_not_found) |
675 | 8.63k | { |
676 | 8.63k | try { |
677 | 8.63k | return findInternal(key, return_prev_if_not_found); |
678 | 8.63k | } catch (QPDFExc& e) { |
679 | 102 | if (auto_repair) { |
680 | 102 | warn(tree_root, std::string("attempting to repair after error: ") + e.what()); |
681 | 102 | repair(); |
682 | 102 | return findInternal(key, return_prev_if_not_found); |
683 | 102 | } else { |
684 | 0 | throw; |
685 | 0 | } |
686 | 102 | } |
687 | 8.63k | } |
688 | | |
689 | | NNTreeImpl::iterator |
690 | | NNTreeImpl::findInternal(QPDFObjectHandle const& key, bool return_prev_if_not_found) |
691 | 8.68k | { |
692 | 8.68k | auto first_item = begin(); |
693 | 8.68k | if (!first_item.valid()) { |
694 | 1.49k | return end(); |
695 | 1.49k | } |
696 | 7.19k | if (!keyValid(first_item->first)) { |
697 | 0 | error(tree_root, "encountered invalid key in find"); |
698 | 0 | } |
699 | 7.19k | if (!value_valid(first_item->second)) { |
700 | 0 | error(tree_root, "encountered invalid value in find"); |
701 | 0 | } |
702 | 7.19k | if (compareKeys(key, first_item->first) < 0) { |
703 | | // Before the first key |
704 | 108 | return end(); |
705 | 108 | } |
706 | | |
707 | 7.08k | QPDFObjGen::set seen; |
708 | 7.08k | auto node = tree_root; |
709 | 7.08k | iterator result(*this); |
710 | | |
711 | 10.0k | while (true) { |
712 | 9.97k | if (!seen.add(node)) { |
713 | 0 | error(node, "loop detected in find"); |
714 | 0 | } |
715 | | |
716 | 9.97k | Array items = node[itemsKey()]; |
717 | 9.97k | size_t nitems = items.size(); |
718 | 9.97k | if (nitems > 1) { |
719 | 7.00k | int idx = binarySearch(key, items, nitems / 2, return_prev_if_not_found, false); |
720 | 7.00k | if (idx >= 0) { |
721 | 6.25k | result.setItemNumber(node, 2 * idx); |
722 | 6.25k | if (!result.impl.keyValid(result.ivalue.first)) { |
723 | 0 | error(node, "encountered invalid key in find"); |
724 | 0 | } |
725 | 6.25k | if (!result.impl.value_valid(result.ivalue.second)) { |
726 | 1 | error(tree_root, "encountered invalid value in find"); |
727 | 1 | } |
728 | 6.25k | } |
729 | 7.00k | return result; |
730 | 7.00k | } |
731 | | |
732 | 2.96k | Array kids = node["/Kids"]; |
733 | 2.96k | size_t nkids = kids.size(); |
734 | 2.96k | if (nkids == 0) { |
735 | 4 | error(node, "bad node during find"); |
736 | 4 | } |
737 | 2.96k | int idx = binarySearch(key, kids, nkids, true, true); |
738 | 2.96k | if (idx == -1) { |
739 | 2 | error(node, "unexpected -1 from binary search of kids; limits may by wrong"); |
740 | 2 | } |
741 | 2.96k | result.addPathElement(node, idx); |
742 | 2.96k | node = kids[idx]; |
743 | 2.96k | } |
744 | 7.08k | } |
745 | | |
746 | | NNTreeImpl::iterator |
747 | | NNTreeImpl::insertFirst(QPDFObjectHandle const& key, QPDFObjectHandle const& value) |
748 | 352 | { |
749 | 352 | auto iter = begin(); |
750 | 352 | Array items = iter.node[items_key]; |
751 | 352 | if (!items) { |
752 | 0 | error(tree_root, "unable to find a valid items node"); |
753 | 0 | } |
754 | 352 | if (!(key && value)) { |
755 | 0 | error(tree_root, "unable to insert null key or value"); |
756 | 0 | } |
757 | 352 | if (!value_valid(value)) { |
758 | 0 | error(tree_root, "attempting to insert an invalid value"); |
759 | 0 | } |
760 | 352 | items.insert(0, key); |
761 | 352 | items.insert(1, value); |
762 | 352 | iter.setItemNumber(iter.node, 0); |
763 | 352 | iter.resetLimits(iter.node, iter.lastPathElement()); |
764 | 352 | iter.split(iter.node, iter.lastPathElement()); |
765 | 352 | return iter; |
766 | 352 | } |
767 | | |
768 | | NNTreeImpl::iterator |
769 | | NNTreeImpl::insert(QPDFObjectHandle const& key, QPDFObjectHandle const& value) |
770 | 6.44k | { |
771 | 6.44k | auto iter = find(key, true); |
772 | 6.44k | if (!iter.valid()) { |
773 | 352 | return insertFirst(key, value); |
774 | 6.09k | } else if (compareKeys(key, iter->first) == 0) { |
775 | 0 | Array items = iter.node[itemsKey()]; |
776 | 0 | items.set(iter.item_number + 1, value); |
777 | 0 | iter.updateIValue(); |
778 | 6.09k | } else { |
779 | 6.09k | iter.insertAfter(key, value); |
780 | 6.09k | } |
781 | 6.09k | return iter; |
782 | 6.44k | } |
783 | | |
784 | | bool |
785 | | NNTreeImpl::remove(QPDFObjectHandle const& key, QPDFObjectHandle* value) |
786 | 0 | { |
787 | 0 | auto iter = find(key, false); |
788 | 0 | if (!iter.valid()) { |
789 | 0 | return false; |
790 | 0 | } |
791 | 0 | if (value) { |
792 | 0 | *value = iter->second; |
793 | 0 | } |
794 | 0 | iter.remove(); |
795 | 0 | return true; |
796 | 0 | } |
797 | | |
798 | | bool |
799 | | NNTreeImpl::validate(bool a_repair) |
800 | 1.12k | { |
801 | 1.12k | bool first = true; |
802 | 1.12k | QPDFObjectHandle last_key; |
803 | 1.12k | try { |
804 | 1.31k | for (auto const& [key, value]: *this) { |
805 | 1.31k | if (!keyValid(key)) { |
806 | 37 | error(tree_root, "invalid key in validate"); |
807 | 37 | } |
808 | 1.31k | if (!value_valid(value)) { |
809 | 157 | error(tree_root, "invalid value in validate"); |
810 | 157 | } |
811 | 1.31k | if (first) { |
812 | 400 | first = false; |
813 | 910 | } else if (last_key && compareKeys(last_key, key) != -1) { |
814 | 232 | error(tree_root, "keys are not sorted in validate"); |
815 | 232 | } |
816 | 1.31k | last_key = key; |
817 | 1.31k | } |
818 | 1.12k | } catch (QPDFExc& e) { |
819 | 463 | if (a_repair) { |
820 | 463 | warn(tree_root, std::string("attempting to repair after error: ") + e.what()); |
821 | 463 | repair(); |
822 | 463 | } |
823 | 463 | return false; |
824 | 463 | } |
825 | 658 | return true; |
826 | 1.12k | } |
827 | | |
828 | | class QPDFNameTreeObjectHelper::Members |
829 | | { |
830 | | public: |
831 | | Members( |
832 | | QPDFObjectHandle& oh, |
833 | | QPDF& q, |
834 | | std::function<bool(QPDFObjectHandle const&)> value_validator, |
835 | | bool auto_repair) : |
836 | 1.12k | impl(q, oh, ::ot_string, value_validator, auto_repair) |
837 | 1.12k | { |
838 | 1.12k | } |
839 | | Members(Members const&) = delete; |
840 | 1.12k | ~Members() = default; |
841 | | |
842 | | NNTreeImpl impl; |
843 | | }; |
844 | | |
845 | | // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer. For this specific |
846 | | // class, see github issue #745. |
847 | 1.12k | QPDFNameTreeObjectHelper::~QPDFNameTreeObjectHelper() = default; |
848 | | |
849 | | QPDFNameTreeObjectHelper::QPDFNameTreeObjectHelper(QPDFObjectHandle oh, QPDF& q, bool auto_repair) : |
850 | 0 | QPDFNameTreeObjectHelper( |
851 | 0 | oh, q, [](QPDFObjectHandle const& o) -> bool { return static_cast<bool>(o); }, auto_repair) |
852 | 0 | { |
853 | 0 | } |
854 | | |
855 | | QPDFNameTreeObjectHelper::QPDFNameTreeObjectHelper( |
856 | | QPDFObjectHandle oh, |
857 | | QPDF& q, |
858 | | std::function<bool(QPDFObjectHandle const&)> value_validator, |
859 | | bool auto_repair) : |
860 | 1.12k | QPDFObjectHelper(oh), |
861 | 1.12k | m(std::make_shared<Members>(oh, q, value_validator, auto_repair)) |
862 | 1.12k | { |
863 | 1.12k | } |
864 | | |
865 | | QPDFNameTreeObjectHelper |
866 | | QPDFNameTreeObjectHelper::newEmpty(QPDF& qpdf, bool auto_repair) |
867 | 0 | { |
868 | 0 | return {qpdf.makeIndirectObject(Dictionary({{"/Names", Array::empty()}})), qpdf, auto_repair}; |
869 | 0 | } |
870 | | |
871 | | QPDFNameTreeObjectHelper::iterator::iterator(std::shared_ptr<NNTreeIterator> const& i) : |
872 | 4.28k | impl(i) |
873 | 4.28k | { |
874 | 4.28k | } |
875 | | |
876 | | bool |
877 | | QPDFNameTreeObjectHelper::iterator::valid() const |
878 | 0 | { |
879 | 0 | return impl->valid(); |
880 | 0 | } |
881 | | |
882 | | QPDFNameTreeObjectHelper::iterator& |
883 | | QPDFNameTreeObjectHelper::iterator::operator++() |
884 | 0 | { |
885 | 0 | ++(*impl); |
886 | 0 | updateIValue(); |
887 | 0 | return *this; |
888 | 0 | } |
889 | | |
890 | | QPDFNameTreeObjectHelper::iterator& |
891 | | QPDFNameTreeObjectHelper::iterator::operator--() |
892 | 0 | { |
893 | 0 | --(*impl); |
894 | 0 | updateIValue(); |
895 | 0 | return *this; |
896 | 0 | } |
897 | | |
898 | | void |
899 | | QPDFNameTreeObjectHelper::iterator::updateIValue() |
900 | 160 | { |
901 | 160 | if (impl->valid()) { |
902 | 160 | auto p = *impl; |
903 | 160 | ivalue.first = p->first.getUTF8Value(); |
904 | 160 | ivalue.second = p->second; |
905 | 160 | } else { |
906 | 0 | ivalue.first = ""; |
907 | 0 | ivalue.second = QPDFObjectHandle(); |
908 | 0 | } |
909 | 160 | } |
910 | | |
911 | | QPDFNameTreeObjectHelper::iterator::reference |
912 | | QPDFNameTreeObjectHelper::iterator::operator*() |
913 | 0 | { |
914 | 0 | updateIValue(); |
915 | 0 | return ivalue; |
916 | 0 | } |
917 | | |
918 | | QPDFNameTreeObjectHelper::iterator::pointer |
919 | | QPDFNameTreeObjectHelper::iterator::operator->() |
920 | 160 | { |
921 | 160 | updateIValue(); |
922 | 160 | return &ivalue; |
923 | 160 | } |
924 | | |
925 | | bool |
926 | | QPDFNameTreeObjectHelper::iterator::operator==(iterator const& other) const |
927 | 2.14k | { |
928 | 2.14k | return *(impl) == *(other.impl); |
929 | 2.14k | } |
930 | | |
931 | | void |
932 | | QPDFNameTreeObjectHelper::iterator::insertAfter(std::string const& key, QPDFObjectHandle value) |
933 | 0 | { |
934 | 0 | impl->insertAfter(QPDFObjectHandle::newUnicodeString(key), value); |
935 | 0 | updateIValue(); |
936 | 0 | } |
937 | | |
938 | | void |
939 | | QPDFNameTreeObjectHelper::iterator::remove() |
940 | 0 | { |
941 | 0 | impl->remove(); |
942 | 0 | updateIValue(); |
943 | 0 | } |
944 | | |
945 | | QPDFNameTreeObjectHelper::iterator |
946 | | QPDFNameTreeObjectHelper::begin() const |
947 | 0 | { |
948 | 0 | return {std::make_shared<NNTreeIterator>(m->impl.begin())}; |
949 | 0 | } |
950 | | |
951 | | QPDFNameTreeObjectHelper::iterator |
952 | | QPDFNameTreeObjectHelper::end() const |
953 | 2.14k | { |
954 | 2.14k | return {std::make_shared<NNTreeIterator>(m->impl.end())}; |
955 | 2.14k | } |
956 | | |
957 | | QPDFNameTreeObjectHelper::iterator |
958 | | QPDFNameTreeObjectHelper::last() const |
959 | 0 | { |
960 | 0 | return {std::make_shared<NNTreeIterator>(m->impl.last())}; |
961 | 0 | } |
962 | | |
963 | | QPDFNameTreeObjectHelper::iterator |
964 | | QPDFNameTreeObjectHelper::find(std::string const& key, bool return_prev_if_not_found) |
965 | 2.19k | { |
966 | 2.19k | auto i = m->impl.find(QPDFObjectHandle::newUnicodeString(key), return_prev_if_not_found); |
967 | 2.19k | return {std::make_shared<NNTreeIterator>(i)}; |
968 | 2.19k | } |
969 | | |
970 | | QPDFNameTreeObjectHelper::iterator |
971 | | QPDFNameTreeObjectHelper::insert(std::string const& key, QPDFObjectHandle value) |
972 | 0 | { |
973 | 0 | auto i = m->impl.insert(QPDFObjectHandle::newUnicodeString(key), value); |
974 | 0 | return {std::make_shared<NNTreeIterator>(i)}; |
975 | 0 | } |
976 | | |
977 | | bool |
978 | | QPDFNameTreeObjectHelper::remove(std::string const& key, QPDFObjectHandle* value) |
979 | 0 | { |
980 | 0 | return m->impl.remove(QPDFObjectHandle::newUnicodeString(key), value); |
981 | 0 | } |
982 | | |
983 | | bool |
984 | | QPDFNameTreeObjectHelper::hasName(std::string const& name) |
985 | 0 | { |
986 | 0 | auto i = find(name); |
987 | 0 | return (i != end()); |
988 | 0 | } |
989 | | |
990 | | bool |
991 | | QPDFNameTreeObjectHelper::findObject(std::string const& name, QPDFObjectHandle& oh) |
992 | 2.19k | { |
993 | 2.19k | auto i = find(name); |
994 | 2.19k | if (i == end()) { |
995 | 1.98k | return false; |
996 | 1.98k | } |
997 | 213 | oh = i->second; |
998 | 213 | return true; |
999 | 2.19k | } |
1000 | | |
1001 | | void |
1002 | | QPDFNameTreeObjectHelper::setSplitThreshold(int t) |
1003 | 0 | { |
1004 | 0 | m->impl.setSplitThreshold(t); |
1005 | 0 | } |
1006 | | |
1007 | | std::map<std::string, QPDFObjectHandle> |
1008 | | QPDFNameTreeObjectHelper::getAsMap() const |
1009 | 0 | { |
1010 | 0 | std::map<std::string, QPDFObjectHandle> result; |
1011 | 0 | result.insert(begin(), end()); |
1012 | 0 | return result; |
1013 | 0 | } |
1014 | | |
1015 | | bool |
1016 | | QPDFNameTreeObjectHelper::validate(bool repair) |
1017 | 1.12k | { |
1018 | 1.12k | return m->impl.validate(repair); |
1019 | 1.12k | } |
1020 | | |
1021 | | class QPDFNumberTreeObjectHelper::Members |
1022 | | { |
1023 | | typedef QPDFNumberTreeObjectHelper::numtree_number numtree_number; |
1024 | | |
1025 | | public: |
1026 | | Members( |
1027 | | QPDFObjectHandle& oh, |
1028 | | QPDF& q, |
1029 | | std::function<bool(QPDFObjectHandle const&)> value_validator, |
1030 | | bool auto_repair) : |
1031 | 0 | impl(q, oh, ::ot_integer, value_validator, auto_repair) |
1032 | 0 | { |
1033 | 0 | } |
1034 | | Members(Members const&) = delete; |
1035 | 0 | ~Members() = default; |
1036 | | |
1037 | | NNTreeImpl impl; |
1038 | | }; |
1039 | | |
1040 | | // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer. For this specific |
1041 | | // class, see github issue #745. |
1042 | 0 | QPDFNumberTreeObjectHelper::~QPDFNumberTreeObjectHelper() = default; |
1043 | | |
1044 | | QPDFNumberTreeObjectHelper::QPDFNumberTreeObjectHelper( |
1045 | | QPDFObjectHandle oh, QPDF& q, bool auto_repair) : |
1046 | 0 | QPDFNumberTreeObjectHelper( |
1047 | 0 | oh, q, [](QPDFObjectHandle const& o) -> bool { return static_cast<bool>(o); }, auto_repair) |
1048 | 0 | { |
1049 | 0 | } |
1050 | | |
1051 | | QPDFNumberTreeObjectHelper::QPDFNumberTreeObjectHelper( |
1052 | | QPDFObjectHandle oh, |
1053 | | QPDF& q, |
1054 | | std::function<bool(QPDFObjectHandle const&)> value_validator, |
1055 | | bool auto_repair) : |
1056 | 0 | QPDFObjectHelper(oh), |
1057 | 0 | m(std::make_shared<Members>(oh, q, value_validator, auto_repair)) |
1058 | 0 | { |
1059 | 0 | } |
1060 | | |
1061 | | QPDFNumberTreeObjectHelper |
1062 | | QPDFNumberTreeObjectHelper::newEmpty(QPDF& qpdf, bool auto_repair) |
1063 | 0 | { |
1064 | 0 | return {qpdf.makeIndirectObject(Dictionary({{"/Nums", Array::empty()}})), qpdf, auto_repair}; |
1065 | 0 | } |
1066 | | |
1067 | | QPDFNumberTreeObjectHelper::iterator::iterator(std::shared_ptr<NNTreeIterator> const& i) : |
1068 | 0 | impl(i) |
1069 | 0 | { |
1070 | 0 | } |
1071 | | |
1072 | | bool |
1073 | | QPDFNumberTreeObjectHelper::iterator::valid() const |
1074 | 0 | { |
1075 | 0 | return impl->valid(); |
1076 | 0 | } |
1077 | | |
1078 | | QPDFNumberTreeObjectHelper::iterator& |
1079 | | QPDFNumberTreeObjectHelper::iterator::operator++() |
1080 | 0 | { |
1081 | 0 | ++(*impl); |
1082 | 0 | updateIValue(); |
1083 | 0 | return *this; |
1084 | 0 | } |
1085 | | |
1086 | | QPDFNumberTreeObjectHelper::iterator& |
1087 | | QPDFNumberTreeObjectHelper::iterator::operator--() |
1088 | 0 | { |
1089 | 0 | --(*impl); |
1090 | 0 | updateIValue(); |
1091 | 0 | return *this; |
1092 | 0 | } |
1093 | | |
1094 | | void |
1095 | | QPDFNumberTreeObjectHelper::iterator::updateIValue() |
1096 | 0 | { |
1097 | 0 | if (impl->valid()) { |
1098 | 0 | auto p = *impl; |
1099 | 0 | this->ivalue.first = p->first.getIntValue(); |
1100 | 0 | this->ivalue.second = p->second; |
1101 | 0 | } else { |
1102 | 0 | this->ivalue.first = 0; |
1103 | 0 | this->ivalue.second = QPDFObjectHandle(); |
1104 | 0 | } |
1105 | 0 | } |
1106 | | |
1107 | | QPDFNumberTreeObjectHelper::iterator::reference |
1108 | | QPDFNumberTreeObjectHelper::iterator::operator*() |
1109 | 0 | { |
1110 | 0 | updateIValue(); |
1111 | 0 | return this->ivalue; |
1112 | 0 | } |
1113 | | |
1114 | | QPDFNumberTreeObjectHelper::iterator::pointer |
1115 | | QPDFNumberTreeObjectHelper::iterator::operator->() |
1116 | 0 | { |
1117 | 0 | updateIValue(); |
1118 | 0 | return &this->ivalue; |
1119 | 0 | } |
1120 | | |
1121 | | bool |
1122 | | QPDFNumberTreeObjectHelper::iterator::operator==(iterator const& other) const |
1123 | 0 | { |
1124 | 0 | return *(impl) == *(other.impl); |
1125 | 0 | } |
1126 | | |
1127 | | void |
1128 | | QPDFNumberTreeObjectHelper::iterator::insertAfter(numtree_number key, QPDFObjectHandle value) |
1129 | 0 | { |
1130 | 0 | impl->insertAfter(QPDFObjectHandle::newInteger(key), value); |
1131 | 0 | updateIValue(); |
1132 | 0 | } |
1133 | | |
1134 | | void |
1135 | | QPDFNumberTreeObjectHelper::iterator::remove() |
1136 | 0 | { |
1137 | 0 | impl->remove(); |
1138 | 0 | updateIValue(); |
1139 | 0 | } |
1140 | | |
1141 | | QPDFNumberTreeObjectHelper::iterator |
1142 | | QPDFNumberTreeObjectHelper::begin() const |
1143 | 0 | { |
1144 | 0 | return {std::make_shared<NNTreeIterator>(m->impl.begin())}; |
1145 | 0 | } |
1146 | | |
1147 | | QPDFNumberTreeObjectHelper::iterator |
1148 | | QPDFNumberTreeObjectHelper::end() const |
1149 | 0 | { |
1150 | 0 | return {std::make_shared<NNTreeIterator>(m->impl.end())}; |
1151 | 0 | } |
1152 | | |
1153 | | QPDFNumberTreeObjectHelper::iterator |
1154 | | QPDFNumberTreeObjectHelper::last() const |
1155 | 0 | { |
1156 | 0 | return {std::make_shared<NNTreeIterator>(m->impl.last())}; |
1157 | 0 | } |
1158 | | |
1159 | | QPDFNumberTreeObjectHelper::iterator |
1160 | | QPDFNumberTreeObjectHelper::find(numtree_number key, bool return_prev_if_not_found) |
1161 | 0 | { |
1162 | 0 | auto i = m->impl.find(QPDFObjectHandle::newInteger(key), return_prev_if_not_found); |
1163 | 0 | return {std::make_shared<NNTreeIterator>(i)}; |
1164 | 0 | } |
1165 | | |
1166 | | QPDFNumberTreeObjectHelper::iterator |
1167 | | QPDFNumberTreeObjectHelper::insert(numtree_number key, QPDFObjectHandle value) |
1168 | 0 | { |
1169 | 0 | auto i = m->impl.insert(QPDFObjectHandle::newInteger(key), value); |
1170 | 0 | return {std::make_shared<NNTreeIterator>(i)}; |
1171 | 0 | } |
1172 | | |
1173 | | bool |
1174 | | QPDFNumberTreeObjectHelper::remove(numtree_number key, QPDFObjectHandle* value) |
1175 | 0 | { |
1176 | 0 | return m->impl.remove(QPDFObjectHandle::newInteger(key), value); |
1177 | 0 | } |
1178 | | |
1179 | | QPDFNumberTreeObjectHelper::numtree_number |
1180 | | QPDFNumberTreeObjectHelper::getMin() |
1181 | 0 | { |
1182 | 0 | auto i = begin(); |
1183 | 0 | if (i == end()) { |
1184 | 0 | return 0; |
1185 | 0 | } |
1186 | 0 | return i->first; |
1187 | 0 | } |
1188 | | |
1189 | | QPDFNumberTreeObjectHelper::numtree_number |
1190 | | QPDFNumberTreeObjectHelper::getMax() |
1191 | 0 | { |
1192 | 0 | auto i = last(); |
1193 | 0 | if (i == end()) { |
1194 | 0 | return 0; |
1195 | 0 | } |
1196 | 0 | return i->first; |
1197 | 0 | } |
1198 | | |
1199 | | bool |
1200 | | QPDFNumberTreeObjectHelper::hasIndex(numtree_number idx) |
1201 | 0 | { |
1202 | 0 | auto i = find(idx); |
1203 | 0 | return (i != this->end()); |
1204 | 0 | } |
1205 | | |
1206 | | bool |
1207 | | QPDFNumberTreeObjectHelper::findObject(numtree_number idx, QPDFObjectHandle& oh) |
1208 | 0 | { |
1209 | 0 | auto i = find(idx); |
1210 | 0 | if (i == end()) { |
1211 | 0 | return false; |
1212 | 0 | } |
1213 | 0 | oh = i->second; |
1214 | 0 | return true; |
1215 | 0 | } |
1216 | | |
1217 | | bool |
1218 | | QPDFNumberTreeObjectHelper::findObjectAtOrBelow( |
1219 | | numtree_number idx, QPDFObjectHandle& oh, numtree_number& offset) |
1220 | 0 | { |
1221 | 0 | auto i = find(idx, true); |
1222 | 0 | if (i == end()) { |
1223 | 0 | return false; |
1224 | 0 | } |
1225 | 0 | oh = i->second; |
1226 | 0 | QIntC::range_check_subtract(idx, i->first); |
1227 | 0 | offset = idx - i->first; |
1228 | 0 | return true; |
1229 | 0 | } |
1230 | | |
1231 | | void |
1232 | | QPDFNumberTreeObjectHelper::setSplitThreshold(int t) |
1233 | 0 | { |
1234 | 0 | m->impl.setSplitThreshold(t); |
1235 | 0 | } |
1236 | | |
1237 | | std::map<QPDFNumberTreeObjectHelper::numtree_number, QPDFObjectHandle> |
1238 | | QPDFNumberTreeObjectHelper::getAsMap() const |
1239 | 0 | { |
1240 | 0 | std::map<numtree_number, QPDFObjectHandle> result; |
1241 | 0 | result.insert(begin(), end()); |
1242 | 0 | return result; |
1243 | 0 | } |
1244 | | |
1245 | | bool |
1246 | | QPDFNumberTreeObjectHelper::validate(bool repair) |
1247 | 0 | { |
1248 | 0 | return m->impl.validate(repair); |
1249 | 0 | } |