Coverage Report

Created: 2026-06-15 06:21

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