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