Coverage Report

Created: 2025-11-11 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
10.9k
{
22
10.9k
    std::string result("Name/Number tree node");
23
10.9k
    if (node.indirect()) {
24
9.87k
        result += " (object " + std::to_string(node.getObjectID()) + ")";
25
9.87k
    }
26
10.9k
    return result;
27
10.9k
}
28
29
void
30
NNTreeImpl::warn(QPDFObjectHandle const& node, std::string const& msg)
31
7.51k
{
32
7.51k
    qpdf.warn(qpdf_e_damaged_pdf, get_description(node), 0, msg);
33
7.51k
    if (++error_count > 5 && qpdf.doc().reconstructed_xref()) {
34
2.17k
        error(node, "too many errors - giving up");
35
2.17k
    }
36
7.51k
}
37
38
void
39
NNTreeImpl::error(QPDFObjectHandle const& node, std::string const& msg) const
40
3.41k
{
41
3.41k
    throw QPDFExc(qpdf_e_damaged_pdf, qpdf.getFilename(), get_description(node), 0, msg);
42
3.41k
}
43
44
void
45
NNTreeIterator::updateIValue(bool allow_invalid)
46
45.8k
{
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
45.8k
    Array items = node[impl.itemsKey()];
61
45.8k
    ivalue.first = items[item_number];
62
45.8k
    ivalue.second = items[item_number + 1];
63
45.8k
    if (ivalue.second) {
64
43.9k
        return;
65
43.9k
    }
66
67
1.89k
    if (item_number < 0 || !node) {
68
1.89k
        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.89k
        return;
73
1.89k
    }
74
4
    impl.error(node, "update ivalue: items array is too short");
75
4
}
76
77
Dictionary
78
NNTreeIterator::getNextKid(PathElement& pe, bool backward)
79
1.06k
{
80
2.06k
    while (true) {
81
1.98k
        pe.kid_number += backward ? -1 : 1;
82
1.98k
        Dictionary result = pe.node["/Kids"][pe.kid_number];
83
1.98k
        if (result.contains("/Kids") || result.contains(impl.itemsKey())) {
84
451
            return result;
85
451
        }
86
1.53k
        if (pe.kid_number < 0 || std::cmp_greater_equal(pe.kid_number, pe.node["/Kids"].size())) {
87
531
            return {};
88
531
        }
89
1.00k
        impl.warn(pe.node, "skipping over invalid kid at index " + std::to_string(pe.kid_number));
90
1.00k
    }
91
1.06k
}
92
void
93
NNTreeIterator::increment(bool backward)
94
8.36k
{
95
8.36k
    if (item_number < 0) {
96
0
        deepen(impl.tree_root, !backward, true);
97
0
        return;
98
0
    }
99
100
11.7k
    while (valid()) {
101
10.7k
        item_number += backward ? -2 : 2;
102
10.7k
        Array items = node[impl.itemsKey()];
103
10.7k
        if (item_number < 0 || std::cmp_greater_equal(item_number, items.size())) {
104
1.13k
            setItemNumber(QPDFObjectHandle(), -1);
105
1.84k
            while (!path.empty()) {
106
1.06k
                auto& element = path.back();
107
1.06k
                if (auto pe_node = getNextKid(element, backward)) {
108
451
                    if (deepen(pe_node, !backward, false)) {
109
353
                        break;
110
353
                    }
111
614
                } else {
112
614
                    path.pop_back();
113
614
                }
114
1.06k
            }
115
1.13k
        }
116
10.7k
        if (item_number >= 0) {
117
9.99k
            items = node[impl.itemsKey()];
118
9.99k
            if (std::cmp_greater_equal(item_number + 1, items.size())) {
119
233
                impl.warn(node, "items array doesn't have enough elements");
120
9.76k
            } else if (!impl.keyValid(items[item_number])) {
121
1.61k
                impl.warn(node, ("item " + std::to_string(item_number) + " has the wrong type"));
122
8.14k
            } else if (!impl.value_valid(items[item_number + 1])) {
123
739
                impl.warn(node, "item " + std::to_string(item_number + 1) + " is invalid");
124
7.40k
            } else {
125
7.40k
                return;
126
7.40k
            }
127
9.99k
        }
128
10.7k
    }
129
8.36k
}
130
131
void
132
NNTreeIterator::resetLimits(Dictionary a_node, std::list<PathElement>::iterator parent)
133
1.61k
{
134
1.61k
    while (true) {
135
1.61k
        if (parent == path.end()) {
136
1.61k
            a_node.erase("/Limits");
137
1.61k
            return;
138
1.61k
        }
139
140
0
        QPDFObjectHandle first;
141
0
        QPDFObjectHandle last;
142
0
        Array items = a_node[impl.itemsKey()];
143
0
        size_t nitems = items.size();
144
0
        if (nitems >= 2) {
145
0
            first = items[0];
146
0
            last = items[(nitems - 1u) & ~1u];
147
0
        } else {
148
0
            Array kids = a_node["/Kids"];
149
0
            size_t nkids = kids.size();
150
0
            if (nkids > 0) {
151
0
                Array first_limits = kids[0]["/Limits"];
152
0
                if (first_limits.size() >= 2) {
153
0
                    first = first_limits[0];
154
0
                    last = kids[nkids - 1u]["/Limits"][1];
155
0
                }
156
0
            }
157
0
        }
158
0
        if (!(first && last)) {
159
0
            impl.warn(a_node, "unable to determine limits");
160
0
        } else {
161
0
            Array olimits = a_node["/Limits"];
162
0
            if (olimits.size() == 2) {
163
0
                auto ofirst = olimits[0];
164
0
                auto olast = olimits[1];
165
0
                if (impl.keyValid(ofirst) && impl.keyValid(olast) &&
166
0
                    impl.compareKeys(first, ofirst) == 0 && impl.compareKeys(last, olast) == 0) {
167
0
                    return;
168
0
                }
169
0
            }
170
0
            if (a_node != path.begin()->node) {
171
0
                a_node.replaceKey("/Limits", Array({first, last}));
172
0
            }
173
0
        }
174
175
0
        if (parent == path.begin()) {
176
0
            return;
177
0
        }
178
0
        a_node = parent->node;
179
0
        --parent;
180
0
    }
181
1.61k
}
182
183
void
184
NNTreeIterator::split(Dictionary to_split, std::list<PathElement>::iterator parent)
185
1.61k
{
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
1.61k
    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
1.61k
    Array kids = to_split["/Kids"];
219
1.61k
    size_t nkids = kids.size();
220
1.61k
    Array items = to_split[impl.itemsKey()];
221
1.61k
    size_t nitems = items.size();
222
223
1.61k
    Array first_half;
224
1.61k
    size_t n = 0;
225
1.61k
    std::string key;
226
1.61k
    size_t threshold = static_cast<size_t>(impl.split_threshold);
227
1.61k
    if (nkids > 0) {
228
0
        first_half = kids;
229
0
        n = nkids;
230
0
        key = "/Kids";
231
1.61k
    } else if (nitems > 0) {
232
1.61k
        first_half = items;
233
1.61k
        n = nitems;
234
1.61k
        threshold *= 2;
235
1.61k
        key = impl.itemsKey();
236
1.61k
    } else {
237
0
        throw std::logic_error("NNTreeIterator::split called on invalid node");
238
0
    }
239
240
1.61k
    if (n <= threshold) {
241
1.61k
        return;
242
1.61k
    }
243
244
0
    bool is_root = parent == path.end();
245
0
    bool is_leaf = nitems > 0;
246
247
    // CURRENT STATE: tree is in original state; iterator is valid and unchanged.
248
249
0
    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
0
        Dictionary first_node = impl.qpdf.makeIndirectObject(Dictionary({{key, first_half}}));
265
0
        auto new_kids = Array::empty();
266
0
        new_kids.push_back(first_node);
267
0
        to_split.erase("/Limits"); // already shouldn't be there for root
268
0
        to_split.erase(impl.itemsKey());
269
0
        to_split.replaceKey("/Kids", new_kids);
270
0
        if (is_leaf) {
271
0
            node = first_node;
272
0
        } else {
273
0
            auto next = path.begin();
274
0
            next->node = first_node;
275
0
        }
276
0
        this->path.emplace_front(to_split, 0);
277
0
        parent = path.begin();
278
0
        to_split = first_node;
279
0
    }
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
0
    auto second_half = Array::empty();
287
0
    auto start_idx = static_cast<int>((n / 2) & ~1u);
288
0
    while (std::cmp_greater(first_half.size(), start_idx)) {
289
0
        second_half.push_back(first_half[start_idx]);
290
0
        first_half.erase(start_idx);
291
0
    }
292
0
    resetLimits(to_split, parent);
293
294
    // Create a new node to contain the second half
295
0
    Dictionary second_node = impl.qpdf.makeIndirectObject(Dictionary({{key, second_half}}));
296
0
    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
0
    Array parent_kids = parent->node["/Kids"];
307
0
    if (!parent_kids) {
308
0
        impl.error(parent->node, "parent node has no /Kids array");
309
0
    }
310
0
    parent_kids.insert(parent->kid_number + 1, second_node);
311
0
    auto cur_elem = parent;
312
0
    ++cur_elem; // points to end() for leaf nodes
313
0
    int old_idx = (is_leaf ? item_number : cur_elem->kid_number);
314
0
    if (old_idx >= start_idx) {
315
0
        ++parent->kid_number;
316
0
        if (is_leaf) {
317
0
            setItemNumber(second_node, item_number - start_idx);
318
0
        } else {
319
0
            cur_elem->node = second_node;
320
0
            cur_elem->kid_number -= start_idx;
321
0
        }
322
0
    }
323
0
    if (!is_root) {
324
0
        auto next = parent->node;
325
0
        resetLimits(next, parent);
326
0
        --parent;
327
0
        split(next, parent);
328
0
    }
329
0
}
330
331
std::list<NNTreeIterator::PathElement>::iterator
332
NNTreeIterator::lastPathElement()
333
3.22k
{
334
3.22k
    return path.empty() ? path.end() : std::prev(path.end());
335
3.22k
}
336
337
void
338
NNTreeIterator::insertAfter(QPDFObjectHandle const& key, QPDFObjectHandle const& value)
339
1.32k
{
340
1.32k
    if (!valid()) {
341
0
        impl.insertFirst(key, value);
342
0
        deepen(impl.tree_root, true, false);
343
0
        return;
344
0
    }
345
346
1.32k
    Array items = node[impl.itemsKey()];
347
1.32k
    if (!items) {
348
0
        impl.error(node, "node contains no items array");
349
0
    }
350
351
1.32k
    if (std::cmp_less(items.size(), item_number + 2)) {
352
0
        impl.error(node, "insert: items array is too short");
353
0
    }
354
1.32k
    if (!(key && value)) {
355
0
        impl.error(node, "insert: key or value is null");
356
0
    }
357
1.32k
    if (!impl.value_valid(value)) {
358
0
        impl.error(node, "insert: value is invalid");
359
0
    }
360
1.32k
    items.insert(item_number + 2, key);
361
1.32k
    items.insert(item_number + 3, value);
362
1.32k
    resetLimits(node, lastPathElement());
363
1.32k
    split(node, lastPathElement());
364
1.32k
    increment(false);
365
1.32k
}
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
12.1k
{
463
12.1k
    if (item_number == -1 && other.item_number == -1) {
464
2.67k
        return true;
465
2.67k
    }
466
9.43k
    if (path.size() != other.path.size()) {
467
5.06k
        return false;
468
5.06k
    }
469
4.36k
    auto tpi = path.begin();
470
4.36k
    auto opi = other.path.begin();
471
4.36k
    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
4.36k
    return item_number == other.item_number;
479
4.36k
}
480
481
bool
482
NNTreeIterator::deepen(Dictionary a_node, bool first, bool allow_empty)
483
9.24k
{
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
9.24k
    auto opath = path;
488
489
9.24k
    auto fail = [this, &opath](Dictionary const& failed_node, std::string const& msg) {
490
1.90k
        impl.warn(failed_node, msg);
491
1.90k
        path = opath;
492
1.90k
        return false;
493
1.90k
    };
494
495
9.24k
    QPDFObjGen::set seen;
496
9.24k
    for (auto const& i: path) {
497
679
        seen.add(i.node);
498
679
    }
499
14.2k
    while (true) {
500
14.2k
        if (!seen.add(a_node)) {
501
256
            return fail(a_node, "loop detected while traversing name/number tree");
502
256
        }
503
504
13.9k
        if (!a_node) {
505
0
            return fail(a_node, "non-dictionary node while traversing name/number tree");
506
0
        }
507
508
13.9k
        Array items = a_node[impl.itemsKey()];
509
13.9k
        int nitems = static_cast<int>(items.size());
510
13.9k
        if (nitems > 1) {
511
6.55k
            setItemNumber(a_node, first ? 0 : nitems - 2);
512
6.55k
            return true;
513
6.55k
        }
514
515
7.41k
        Array kids = a_node["/Kids"];
516
7.41k
        int nkids = static_cast<int>(kids.size());
517
7.41k
        if (nkids == 0) {
518
1.63k
            if (allow_empty && items) {
519
771
                setItemNumber(a_node, -1);
520
771
                return true;
521
771
            }
522
863
            return fail(
523
863
                a_node,
524
863
                "name/number tree node has neither non-empty " + impl.itemsKey() + " nor /Kids");
525
1.63k
        }
526
527
5.77k
        int kid_number = first ? 0 : nkids - 1;
528
5.77k
        addPathElement(a_node, kid_number);
529
5.77k
        Dictionary next = kids[kid_number];
530
5.77k
        if (!next) {
531
782
            return fail(a_node, "kid number " + std::to_string(kid_number) + " is invalid");
532
782
        }
533
4.99k
        if (!next.indirect()) {
534
9
            if (impl.auto_repair) {
535
9
                impl.warn(
536
9
                    a_node,
537
9
                    "converting kid number " + std::to_string(kid_number) +
538
9
                        " to an indirect object");
539
9
                next = impl.qpdf.makeIndirectObject(next);
540
9
                kids.set(kid_number, next);
541
9
            } 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
9
        }
547
548
4.99k
        a_node = next;
549
4.99k
    }
550
9.24k
}
551
552
NNTreeImpl::iterator
553
NNTreeImpl::begin()
554
8.78k
{
555
8.78k
    iterator result(*this);
556
8.78k
    result.deepen(tree_root, true, true);
557
8.78k
    return result;
558
8.78k
}
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
22.6k
{
571
    // We don't call this without calling keyValid first
572
22.6k
    qpdf_assert_debug(keyValid(a));
573
22.6k
    qpdf_assert_debug(keyValid(b));
574
22.6k
    if (key_type == ::ot_string) {
575
7.01k
        auto as = a.getUTF8Value();
576
7.01k
        auto bs = b.getUTF8Value();
577
7.01k
        return as < bs ? -1 : (as > bs ? 1 : 0);
578
7.01k
    }
579
15.6k
    auto as = a.getIntValue();
580
15.6k
    auto bs = b.getIntValue();
581
15.6k
    return as < bs ? -1 : (as > bs ? 1 : 0);
582
22.6k
}
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
6.08k
{
592
6.08k
    size_t max_idx = std::bit_ceil(num_items);
593
594
6.08k
    int step = static_cast<int>(max_idx / 2);
595
6.08k
    int checks = static_cast<int>(std::bit_width(max_idx)); // AppImage gcc version returns size_t
596
6.08k
    int idx = step;
597
6.08k
    int found_idx = -1;
598
599
20.6k
    for (int i = 0; i < checks; ++i) {
600
16.3k
        int status = -1;
601
16.3k
        if (std::cmp_less(idx, num_items)) {
602
13.3k
            status = search_kids ? compareKeyKid(key, items, idx) : compareKeyItem(key, items, idx);
603
13.3k
            if (status == 0) {
604
1.79k
                return idx;
605
1.79k
            }
606
11.5k
            if (status > 0) {
607
6.52k
                found_idx = idx;
608
6.52k
            }
609
11.5k
        }
610
14.5k
        step = std::max(step / 2, 1);
611
14.5k
        idx += status * step;
612
14.5k
    }
613
4.28k
    return return_prev_if_not_found ? found_idx : -1;
614
6.08k
}
615
616
int
617
NNTreeImpl::compareKeyItem(QPDFObjectHandle const& key, Array const& items, int idx) const
618
9.76k
{
619
9.76k
    if (!keyValid(items[2 * idx])) {
620
154
        error(tree_root, ("item at index " + std::to_string(2 * idx) + " is not the right type"));
621
154
    }
622
9.76k
    return compareKeys(key, items[2 * idx]);
623
9.76k
}
624
625
int
626
NNTreeImpl::compareKeyKid(QPDFObjectHandle const& key, Array const& kids, int idx) const
627
3.57k
{
628
3.57k
    Dictionary kid = kids[idx];
629
3.57k
    if (!kid) {
630
134
        error(tree_root, "invalid kid at index " + std::to_string(idx));
631
134
    }
632
3.57k
    Array limits = kid["/Limits"];
633
3.57k
    if (!(keyValid(limits[0]) && keyValid(limits[1]))) {
634
238
        error(kids[idx], "node is missing /Limits");
635
238
    }
636
3.57k
    if (compareKeys(key, limits[0]) < 0) {
637
1.18k
        return -1;
638
1.18k
    }
639
2.39k
    if (compareKeys(key, limits[1]) > 0) {
640
919
        return 1;
641
919
    }
642
1.47k
    return 0;
643
2.39k
}
644
645
namespace
646
{
647
    struct Cmp
648
    {
649
        bool
650
        operator()(const QPDFObjectHandle& lhs, const QPDFObjectHandle& rhs) const
651
20.4k
        {
652
20.4k
            Integer l = lhs;
653
20.4k
            Integer r = rhs;
654
20.4k
            if (l && r) {
655
8.26k
                return l.value() < r.value();
656
8.26k
            }
657
12.1k
            return lhs.getUTF8Value() < rhs.getUTF8Value();
658
20.4k
        }
659
    };
660
} // namespace
661
662
void
663
NNTreeImpl::repair()
664
525
{
665
525
    auto new_node = Dictionary({{itemsKey(), Array::empty()}});
666
525
    NNTreeImpl repl(qpdf, new_node, key_type, value_valid, false);
667
525
    std::map<QPDFObjectHandle, QPDFObjectHandle, Cmp> items;
668
4.83k
    for (auto const& [key, value]: *this) {
669
4.83k
        if (key && value && repl.keyValid(key) && repl.value_valid(value)) {
670
4.69k
            items.insert_or_assign(key, value);
671
4.69k
        }
672
4.83k
    }
673
1.61k
    for (auto const& [key, value]: items) {
674
1.61k
        repl.insert(key, value);
675
1.61k
    }
676
525
    tree_root.replaceKey("/Kids", new_node["/Kids"]);
677
525
    tree_root.replaceKey(itemsKey(), new_node[itemsKey()]);
678
525
}
679
680
NNTreeImpl::iterator
681
NNTreeImpl::find(QPDFObjectHandle const& key, bool return_prev_if_not_found)
682
6.71k
{
683
6.71k
    try {
684
6.71k
        return findInternal(key, return_prev_if_not_found);
685
6.71k
    } catch (QPDFExc& e) {
686
1.53k
        if (auto_repair) {
687
1.53k
            warn(tree_root, std::string("attempting to repair after error: ") + e.what());
688
1.53k
            repair();
689
1.53k
            return findInternal(key, return_prev_if_not_found);
690
1.53k
        } else {
691
0
            throw;
692
0
        }
693
1.53k
    }
694
6.71k
}
695
696
NNTreeImpl::iterator
697
NNTreeImpl::findInternal(QPDFObjectHandle const& key, bool return_prev_if_not_found)
698
6.78k
{
699
6.78k
    auto first_item = begin();
700
6.78k
    if (!first_item.valid()) {
701
1.25k
        return end();
702
1.25k
    }
703
5.52k
    if (!keyValid(first_item->first)) {
704
18
        error(tree_root, "encountered invalid key in find");
705
18
    }
706
5.52k
    if (!value_valid(first_item->second)) {
707
68
        error(tree_root, "encountered invalid value in find");
708
68
    }
709
5.52k
    if (compareKeys(key, first_item->first) < 0) {
710
        // Before the first key
711
159
        return end();
712
159
    }
713
714
5.36k
    QPDFObjGen::set seen;
715
5.36k
    auto node = tree_root;
716
5.36k
    iterator result(*this);
717
718
7.51k
    while (true) {
719
6.17k
        if (!seen.add(node)) {
720
34
            error(node, "loop detected in find");
721
34
        }
722
723
6.17k
        Array items = node[itemsKey()];
724
6.17k
        size_t nitems = items.size();
725
6.17k
        if (nitems > 1) {
726
4.03k
            int idx = binarySearch(key, items, nitems / 2, return_prev_if_not_found, false);
727
4.03k
            if (idx >= 0) {
728
3.36k
                result.setItemNumber(node, 2 * idx);
729
3.36k
                if (!result.impl.keyValid(result.ivalue.first)) {
730
0
                    error(node, "encountered invalid key in find");
731
0
                }
732
3.36k
                if (!result.impl.value_valid(result.ivalue.second)) {
733
44
                    error(tree_root, "encountered invalid value in find");
734
44
                }
735
3.36k
            }
736
4.03k
            return result;
737
4.03k
        }
738
739
2.14k
        Array kids = node["/Kids"];
740
2.14k
        size_t nkids = kids.size();
741
2.14k
        if (nkids == 0) {
742
62
            error(node, "bad node during find");
743
62
        }
744
2.14k
        int idx = binarySearch(key, kids, nkids, true, true);
745
2.14k
        if (idx == -1) {
746
76
            error(node, "unexpected -1 from binary search of kids; limits may by wrong");
747
76
        }
748
2.14k
        result.addPathElement(node, idx);
749
2.14k
        node = kids[idx];
750
2.14k
    }
751
5.36k
}
752
753
NNTreeImpl::iterator
754
NNTreeImpl::insertFirst(QPDFObjectHandle const& key, QPDFObjectHandle const& value)
755
286
{
756
286
    auto iter = begin();
757
286
    Array items = iter.node[items_key];
758
286
    if (!items) {
759
0
        error(tree_root, "unable to find a valid items node");
760
0
    }
761
286
    if (!(key && value)) {
762
0
        error(tree_root, "unable to insert null key or value");
763
0
    }
764
286
    if (!value_valid(value)) {
765
0
        error(tree_root, "attempting to insert an invalid value");
766
0
    }
767
286
    items.insert(0, key);
768
286
    items.insert(1, value);
769
286
    iter.setItemNumber(iter.node, 0);
770
286
    iter.resetLimits(iter.node, iter.lastPathElement());
771
286
    iter.split(iter.node, iter.lastPathElement());
772
286
    return iter;
773
286
}
774
775
NNTreeImpl::iterator
776
NNTreeImpl::insert(QPDFObjectHandle const& key, QPDFObjectHandle const& value)
777
1.61k
{
778
1.61k
    auto iter = find(key, true);
779
1.61k
    if (!iter.valid()) {
780
286
        return insertFirst(key, value);
781
1.32k
    } 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
1.32k
    } else {
786
1.32k
        iter.insertAfter(key, value);
787
1.32k
    }
788
1.32k
    return iter;
789
1.61k
}
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.19k
{
808
1.19k
    bool first = true;
809
1.19k
    QPDFObjectHandle last_key;
810
1.19k
    try {
811
2.60k
        for (auto const& [key, value]: *this) {
812
2.60k
            if (!keyValid(key)) {
813
54
                error(tree_root, "invalid key in validate");
814
54
            }
815
2.60k
            if (!value_valid(value)) {
816
90
                error(tree_root, "invalid value in validate");
817
90
            }
818
2.60k
            if (first) {
819
696
                first = false;
820
1.90k
            } else if (last_key && compareKeys(last_key, key) != -1) {
821
263
                error(tree_root, "keys are not sorted in validate");
822
263
            }
823
2.60k
            last_key = key;
824
2.60k
        }
825
1.19k
    } catch (QPDFExc& e) {
826
479
        if (a_repair) {
827
479
            warn(tree_root, std::string("attempting to repair after error: ") + e.what());
828
479
            repair();
829
479
        }
830
479
        return false;
831
479
    }
832
717
    return true;
833
1.19k
}
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
431
        impl(q, oh, ::ot_string, value_validator, auto_repair)
844
431
    {
845
431
    }
846
    Members(Members const&) = delete;
847
431
    ~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
431
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
431
    QPDFObjectHelper(oh),
868
431
    m(std::make_shared<Members>(oh, q, value_validator, auto_repair))
869
431
{
870
431
}
871
872
QPDFNameTreeObjectHelper
873
QPDFNameTreeObjectHelper::newEmpty(QPDF& qpdf, bool auto_repair)
874
0
{
875
0
    return {qpdf.makeIndirectObject(Dictionary({{"/Names", Array::empty()}})), qpdf, auto_repair};
876
0
}
877
878
QPDFNameTreeObjectHelper::iterator::iterator(std::shared_ptr<NNTreeIterator> const& i) :
879
2.35k
    impl(i)
880
2.35k
{
881
2.35k
}
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
337
{
908
337
    if (impl->valid()) {
909
337
        auto p = *impl;
910
337
        ivalue.first = p->first.getUTF8Value();
911
337
        ivalue.second = p->second;
912
337
    } else {
913
0
        ivalue.first = "";
914
0
        ivalue.second = QPDFObjectHandle();
915
0
    }
916
337
}
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
337
{
928
337
    updateIValue();
929
337
    return &ivalue;
930
337
}
931
932
bool
933
QPDFNameTreeObjectHelper::iterator::operator==(iterator const& other) const
934
1.17k
{
935
1.17k
    return *(impl) == *(other.impl);
936
1.17k
}
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.17k
{
961
1.17k
    return {std::make_shared<NNTreeIterator>(m->impl.end())};
962
1.17k
}
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.31k
{
973
1.31k
    auto i = m->impl.find(QPDFObjectHandle::newUnicodeString(key), return_prev_if_not_found);
974
1.31k
    return {std::make_shared<NNTreeIterator>(i)};
975
1.31k
}
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.31k
{
1000
1.31k
    auto i = find(name);
1001
1.31k
    if (i == end()) {
1002
839
        return false;
1003
839
    }
1004
473
    oh = i->second;
1005
473
    return true;
1006
1.31k
}
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
431
{
1025
431
    return m->impl.validate(repair);
1026
431
}
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
765
        impl(q, oh, ::ot_integer, value_validator, auto_repair)
1039
765
    {
1040
765
    }
1041
    Members(Members const&) = delete;
1042
765
    ~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
765
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
765
    QPDFObjectHelper(oh),
1064
765
    m(std::make_shared<Members>(oh, q, value_validator, auto_repair))
1065
765
{
1066
765
}
1067
1068
QPDFNumberTreeObjectHelper
1069
QPDFNumberTreeObjectHelper::newEmpty(QPDF& qpdf, bool auto_repair)
1070
0
{
1071
0
    return {qpdf.makeIndirectObject(Dictionary({{"/Nums", Array::empty()}})), qpdf, auto_repair};
1072
0
}
1073
1074
QPDFNumberTreeObjectHelper::iterator::iterator(std::shared_ptr<NNTreeIterator> const& i) :
1075
4.92k
    impl(i)
1076
4.92k
{
1077
4.92k
}
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
4.95k
{
1104
4.95k
    if (impl->valid()) {
1105
4.95k
        auto p = *impl;
1106
4.95k
        this->ivalue.first = p->first.getIntValue();
1107
4.95k
        this->ivalue.second = p->second;
1108
4.95k
    } else {
1109
0
        this->ivalue.first = 0;
1110
0
        this->ivalue.second = QPDFObjectHandle();
1111
0
    }
1112
4.95k
}
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
4.95k
{
1124
4.95k
    updateIValue();
1125
4.95k
    return &this->ivalue;
1126
4.95k
}
1127
1128
bool
1129
QPDFNumberTreeObjectHelper::iterator::operator==(iterator const& other) const
1130
2.46k
{
1131
2.46k
    return *(impl) == *(other.impl);
1132
2.46k
}
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
2.46k
{
1157
2.46k
    return {std::make_shared<NNTreeIterator>(m->impl.end())};
1158
2.46k
}
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
3.79k
{
1169
3.79k
    auto i = m->impl.find(QPDFObjectHandle::newInteger(key), return_prev_if_not_found);
1170
3.79k
    return {std::make_shared<NNTreeIterator>(i)};
1171
3.79k
}
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
3.79k
{
1228
3.79k
    auto i = find(idx, true);
1229
3.79k
    if (i == end()) {
1230
807
        return false;
1231
807
    }
1232
2.98k
    oh = i->second;
1233
2.98k
    QIntC::range_check_subtract(idx, i->first);
1234
2.98k
    offset = idx - i->first;
1235
2.98k
    return true;
1236
3.79k
}
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
765
{
1255
765
    return m->impl.validate(repair);
1256
765
}