Coverage Report

Created: 2026-04-12 07:01

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
27.5k
{
23
27.5k
    std::string result("Name/Number tree node");
24
27.5k
    if (node.indirect()) {
25
24.3k
        result += " (object " + std::to_string(node.getObjectID()) + ")";
26
24.3k
    }
27
27.5k
    return result;
28
27.5k
}
29
30
void
31
NNTreeImpl::warn(QPDFObjectHandle const& node, std::string const& msg)
32
19.9k
{
33
19.9k
    qpdf.warn(qpdf_e_damaged_pdf, get_description(node), 0, msg);
34
19.9k
    if (++error_count > 5 && qpdf.doc().reconstructed_xref()) {
35
4.26k
        error(node, "too many errors - giving up");
36
4.26k
    }
37
19.9k
}
38
39
void
40
NNTreeImpl::error(QPDFObjectHandle const& node, std::string const& msg) const
41
7.59k
{
42
7.59k
    throw QPDFExc(qpdf_e_damaged_pdf, qpdf.getFilename(), get_description(node), 0, msg);
43
7.59k
}
44
45
void
46
NNTreeIterator::updateIValue(bool allow_invalid)
47
232k
{
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
232k
    Array items = node[impl.itemsKey()];
62
232k
    ivalue.first = items[item_number];
63
232k
    ivalue.second = items[item_number + 1];
64
232k
    if (ivalue.second) {
65
223k
        return;
66
223k
    }
67
68
8.44k
    if (item_number < 0 || !node) {
69
8.34k
        util::assertion(
70
8.34k
            allow_invalid, "attempt made to dereference an invalid name/number tree iterator");
71
8.34k
        return;
72
8.34k
    }
73
98
    impl.error(node, "update ivalue: items array is too short");
74
98
}
75
76
Dictionary
77
NNTreeIterator::getNextKid(PathElement& pe, bool backward)
78
4.79k
{
79
7.09k
    while (true) {
80
6.85k
        pe.kid_number += backward ? -1 : 1;
81
6.85k
        Dictionary result = pe.node["/Kids"][pe.kid_number];
82
6.85k
        if (result.contains("/Kids") || result.contains(impl.itemsKey())) {
83
2.70k
            return result;
84
2.70k
        }
85
4.15k
        if (pe.kid_number < 0 || std::cmp_greater_equal(pe.kid_number, pe.node["/Kids"].size())) {
86
1.85k
            return {};
87
1.85k
        }
88
2.29k
        impl.warn(pe.node, "skipping over invalid kid at index " + std::to_string(pe.kid_number));
89
2.29k
    }
90
4.79k
}
91
void
92
NNTreeIterator::increment(bool backward)
93
78.3k
{
94
78.3k
    if (item_number < 0) {
95
0
        deepen(impl.tree_root, !backward, true);
96
0
        return;
97
0
    }
98
99
87.4k
    while (valid()) {
100
84.5k
        item_number += backward ? -2 : 2;
101
84.5k
        Array items = node[impl.itemsKey()];
102
84.5k
        if (item_number < 0 || std::cmp_greater_equal(item_number, items.size())) {
103
4.78k
            setItemNumber(QPDFObjectHandle(), -1);
104
7.30k
            while (!path.empty()) {
105
4.79k
                auto& element = path.back();
106
4.79k
                if (auto pe_node = getNextKid(element, backward)) {
107
2.70k
                    if (deepen(pe_node, !backward, false)) {
108
2.27k
                        break;
109
2.27k
                    }
110
2.70k
                } else {
111
2.09k
                    path.pop_back();
112
2.09k
                }
113
4.79k
            }
114
4.78k
        }
115
84.5k
        if (item_number >= 0) {
116
81.9k
            items = node[impl.itemsKey()];
117
81.9k
            if (std::cmp_greater_equal(item_number + 1, items.size())) {
118
1.05k
                impl.warn(node, "items array doesn't have enough elements");
119
80.9k
            } else if (!impl.keyValid(items[item_number])) {
120
3.12k
                impl.warn(node, ("item " + std::to_string(item_number) + " has the wrong type"));
121
77.8k
            } else if (!impl.value_valid(items[item_number + 1])) {
122
2.40k
                impl.warn(node, "item " + std::to_string(item_number + 1) + " is invalid");
123
75.4k
            } else {
124
75.4k
                return;
125
75.4k
            }
126
81.9k
        }
127
84.5k
    }
128
78.3k
}
129
130
void
131
NNTreeIterator::resetLimits(Dictionary a_node, std::list<PathElement>::iterator parent)
132
20.8k
{
133
20.8k
    while (true) {
134
20.8k
        if (parent == path.end()) {
135
13.8k
            a_node.erase("/Limits");
136
13.8k
            return;
137
13.8k
        }
138
139
7.01k
        QPDFObjectHandle first;
140
7.01k
        QPDFObjectHandle last;
141
7.01k
        Array items = a_node[impl.itemsKey()];
142
7.01k
        size_t nitems = items.size();
143
7.01k
        if (nitems >= 2) {
144
6.72k
            first = items[0];
145
6.72k
            last = items[(nitems - 1u) & ~1u];
146
6.72k
        } else {
147
289
            Array kids = a_node["/Kids"];
148
289
            size_t nkids = kids.size();
149
289
            if (nkids > 0) {
150
289
                Array first_limits = kids[0]["/Limits"];
151
289
                if (first_limits.size() >= 2) {
152
289
                    first = first_limits[0];
153
289
                    last = kids[nkids - 1u]["/Limits"][1];
154
289
                }
155
289
            }
156
289
        }
157
7.01k
        if (!(first && last)) {
158
0
            impl.warn(a_node, "unable to determine limits");
159
7.01k
        } else {
160
7.01k
            Array olimits = a_node["/Limits"];
161
7.01k
            if (olimits.size() == 2) {
162
5.94k
                auto ofirst = olimits[0];
163
5.94k
                auto olast = olimits[1];
164
5.94k
                if (impl.keyValid(ofirst) && impl.keyValid(olast) &&
165
5.94k
                    impl.compareKeys(first, ofirst) == 0 && impl.compareKeys(last, olast) == 0) {
166
0
                    return;
167
0
                }
168
5.94k
            }
169
7.01k
            if (a_node != path.begin()->node) {
170
6.72k
                a_node.replace("/Limits", Array({first, last}));
171
6.72k
            }
172
7.01k
        }
173
174
7.01k
        if (parent == path.begin()) {
175
7.01k
            return;
176
7.01k
        }
177
0
        a_node = parent->node;
178
0
        --parent;
179
0
    }
180
20.8k
}
181
182
void
183
NNTreeIterator::split(Dictionary to_split, std::list<PathElement>::iterator parent)
184
19.7k
{
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
19.7k
    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
19.7k
    Array kids = to_split["/Kids"];
216
19.7k
    size_t nkids = kids.size();
217
19.7k
    Array items = to_split[impl.itemsKey()];
218
19.7k
    size_t nitems = items.size();
219
220
19.7k
    Array first_half;
221
19.7k
    size_t n = 0;
222
19.7k
    std::string key;
223
19.7k
    size_t threshold = static_cast<size_t>(impl.split_threshold);
224
19.7k
    if (nkids > 0) {
225
289
        first_half = kids;
226
289
        n = nkids;
227
289
        key = "/Kids";
228
19.4k
    } else {
229
19.4k
        util::assertion(nitems > 0, "NNTreeIterator::split called on invalid node");
230
19.4k
        first_half = items;
231
19.4k
        n = nitems;
232
19.4k
        threshold *= 2;
233
19.4k
        key = impl.itemsKey();
234
19.4k
    }
235
236
19.7k
    if (n <= threshold) {
237
19.2k
        return;
238
19.2k
    }
239
240
549
    bool is_root = parent == path.end();
241
549
    bool is_leaf = nitems > 0;
242
243
    // CURRENT STATE: tree is in original state; iterator is valid and unchanged.
244
245
549
    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
259
        Dictionary first_node = impl.qpdf.makeIndirectObject(Dictionary({{key, first_half}}));
261
259
        auto new_kids = Array::empty();
262
259
        new_kids.push_back(first_node);
263
259
        to_split.erase("/Limits"); // already shouldn't be there for root
264
259
        to_split.erase(impl.itemsKey());
265
259
        to_split.replace("/Kids", new_kids);
266
259
        if (is_leaf) {
267
245
            node = first_node;
268
245
        } else {
269
14
            auto next = path.begin();
270
14
            next->node = first_node;
271
14
        }
272
259
        this->path.emplace_front(to_split, 0);
273
259
        parent = path.begin();
274
259
        to_split = first_node;
275
259
    }
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
549
    auto second_half = Array::empty();
283
549
    auto start_idx = static_cast<int>((n / 2) & ~1u);
284
18.7k
    while (std::cmp_greater(first_half.size(), start_idx)) {
285
18.1k
        second_half.push_back(first_half[start_idx]);
286
18.1k
        first_half.erase(start_idx);
287
18.1k
    }
288
549
    resetLimits(to_split, parent);
289
290
    // Create a new node to contain the second half
291
549
    Dictionary second_node = impl.qpdf.makeIndirectObject(Dictionary({{key, second_half}}));
292
549
    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
549
    Array parent_kids = parent->node["/Kids"];
303
549
    if (!parent_kids) {
304
0
        impl.error(parent->node, "parent node has no /Kids array");
305
0
    }
306
549
    parent_kids.insert(parent->kid_number + 1, second_node);
307
549
    auto cur_elem = parent;
308
549
    ++cur_elem; // points to end() for leaf nodes
309
549
    int old_idx = (is_leaf ? item_number : cur_elem->kid_number);
310
549
    if (old_idx >= start_idx) {
311
534
        ++parent->kid_number;
312
534
        if (is_leaf) {
313
534
            setItemNumber(second_node, item_number - start_idx);
314
534
        } else {
315
0
            cur_elem->node = second_node;
316
0
            cur_elem->kid_number -= start_idx;
317
0
        }
318
534
    }
319
549
    if (!is_root) {
320
289
        auto next = parent->node;
321
289
        resetLimits(next, parent);
322
289
        --parent;
323
289
        split(next, parent);
324
289
    }
325
549
}
326
327
std::list<NNTreeIterator::PathElement>::iterator
328
NNTreeIterator::lastPathElement()
329
38.9k
{
330
38.9k
    return path.empty() ? path.end() : std::prev(path.end());
331
38.9k
}
332
333
void
334
NNTreeIterator::insertAfter(QPDFObjectHandle const& key, QPDFObjectHandle const& value)
335
18.2k
{
336
18.2k
    if (!valid()) {
337
0
        impl.insertFirst(key, value);
338
0
        deepen(impl.tree_root, true, false);
339
0
        return;
340
0
    }
341
342
18.2k
    Array items = node[impl.itemsKey()];
343
18.2k
    if (!items) {
344
0
        impl.error(node, "node contains no items array");
345
0
    }
346
347
18.2k
    if (std::cmp_less(items.size(), item_number + 2)) {
348
0
        impl.error(node, "insert: items array is too short");
349
0
    }
350
18.2k
    if (!(key && value)) {
351
0
        impl.error(node, "insert: key or value is null");
352
0
    }
353
18.2k
    if (!impl.value_valid(value)) {
354
0
        impl.error(node, "insert: value is invalid");
355
0
    }
356
18.2k
    items.insert(item_number + 2, key);
357
18.2k
    items.insert(item_number + 3, value);
358
18.2k
    resetLimits(node, lastPathElement());
359
18.2k
    split(node, lastPathElement());
360
18.2k
    increment(false);
361
18.2k
}
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
74.5k
{
456
74.5k
    if (item_number == -1 && other.item_number == -1) {
457
9.97k
        return true;
458
9.97k
    }
459
64.5k
    if (path.size() != other.path.size()) {
460
28.2k
        return false;
461
28.2k
    }
462
36.3k
    auto tpi = path.begin();
463
36.3k
    auto opi = other.path.begin();
464
36.3k
    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
36.3k
    return item_number == other.item_number;
472
36.3k
}
473
474
bool
475
NNTreeIterator::deepen(Dictionary a_node, bool first, bool allow_empty)
476
41.8k
{
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
41.8k
    auto opath = path;
481
482
41.8k
    auto fail = [this, &opath](Dictionary const& failed_node, std::string const& msg) {
483
5.95k
        impl.warn(failed_node, msg);
484
5.95k
        path = opath;
485
5.95k
        return false;
486
5.95k
    };
487
488
41.8k
    QPDFObjGen::set seen;
489
41.8k
    for (auto const& i: path) {
490
4.85k
        seen.add(i.node);
491
4.85k
    }
492
59.4k
    while (true) {
493
59.3k
        if (!seen.add(a_node)) {
494
497
            return fail(a_node, "loop detected while traversing name/number tree");
495
497
        }
496
497
58.8k
        if (!a_node) {
498
0
            return fail(a_node, "non-dictionary node while traversing name/number tree");
499
0
        }
500
501
58.8k
        Array items = a_node[impl.itemsKey()];
502
58.8k
        int nitems = static_cast<int>(items.size());
503
58.8k
        if (nitems > 1) {
504
32.1k
            setItemNumber(a_node, first ? 0 : nitems - 2);
505
32.1k
            return true;
506
32.1k
        }
507
508
26.7k
        Array kids = a_node["/Kids"];
509
26.7k
        int nkids = static_cast<int>(kids.size());
510
26.7k
        if (nkids == 0) {
511
7.44k
            if (allow_empty && items) {
512
3.68k
                setItemNumber(a_node, -1);
513
3.68k
                return true;
514
3.68k
            }
515
3.76k
            return fail(
516
3.76k
                a_node,
517
3.76k
                "name/number tree node has neither non-empty " + impl.itemsKey() + " nor /Kids");
518
7.44k
        }
519
520
19.2k
        int kid_number = first ? 0 : nkids - 1;
521
19.2k
        addPathElement(a_node, kid_number);
522
19.2k
        Dictionary next = kids[kid_number];
523
19.2k
        if (!next) {
524
1.69k
            return fail(a_node, "kid number " + std::to_string(kid_number) + " is invalid");
525
1.69k
        }
526
17.5k
        if (!next.indirect()) {
527
202
            if (impl.auto_repair) {
528
202
                impl.warn(
529
202
                    a_node,
530
202
                    "converting kid number " + std::to_string(kid_number) +
531
202
                        " to an indirect object");
532
202
                next = impl.qpdf.makeIndirectObject(next);
533
202
                kids.set(kid_number, next);
534
202
            } 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
202
        }
540
541
17.5k
        a_node = next;
542
17.5k
    }
543
41.8k
}
544
545
NNTreeImpl::iterator
546
NNTreeImpl::begin()
547
39.1k
{
548
39.1k
    iterator result(*this);
549
39.1k
    result.deepen(tree_root, true, true);
550
39.1k
    return result;
551
39.1k
}
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
151k
{
564
    // We don't call this without calling keyValid first
565
151k
    qpdf_assert_debug(keyValid(a));
566
151k
    qpdf_assert_debug(keyValid(b));
567
151k
    if (key_type == ::ot_string) {
568
135k
        auto as = a.getUTF8Value();
569
135k
        auto bs = b.getUTF8Value();
570
135k
        return as < bs ? -1 : (as > bs ? 1 : 0);
571
135k
    }
572
16.2k
    auto as = a.getIntValue();
573
16.2k
    auto bs = b.getIntValue();
574
16.2k
    return as < bs ? -1 : (as > bs ? 1 : 0);
575
151k
}
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
32.2k
{
585
32.2k
    size_t max_idx = std::bit_ceil(num_items);
586
587
32.2k
    int step = static_cast<int>(max_idx / 2);
588
32.2k
    int checks = static_cast<int>(std::bit_width(max_idx)); // AppImage gcc version returns size_t
589
32.2k
    int idx = step;
590
32.2k
    int found_idx = -1;
591
592
157k
    for (int i = 0; i < checks; ++i) {
593
127k
        int status = -1;
594
127k
        if (std::cmp_less(idx, num_items)) {
595
81.4k
            status = search_kids ? compareKeyKid(key, items, idx) : compareKeyItem(key, items, idx);
596
81.4k
            if (status == 0) {
597
2.75k
                return idx;
598
2.75k
            }
599
78.6k
            if (status > 0) {
600
70.3k
                found_idx = idx;
601
70.3k
            }
602
78.6k
        }
603
125k
        step = std::max(step / 2, 1);
604
125k
        idx += status * step;
605
125k
    }
606
29.4k
    return return_prev_if_not_found ? found_idx : -1;
607
32.2k
}
608
609
int
610
NNTreeImpl::compareKeyItem(QPDFObjectHandle const& key, Array const& items, int idx) const
611
66.3k
{
612
66.3k
    if (!keyValid(items[2 * idx])) {
613
298
        error(tree_root, ("item at index " + std::to_string(2 * idx) + " is not the right type"));
614
298
    }
615
66.3k
    return compareKeys(key, items[2 * idx]);
616
66.3k
}
617
618
int
619
NNTreeImpl::compareKeyKid(QPDFObjectHandle const& key, Array const& kids, int idx) const
620
15.0k
{
621
15.0k
    Dictionary kid = kids[idx];
622
15.0k
    if (!kid) {
623
233
        error(tree_root, "invalid kid at index " + std::to_string(idx));
624
233
    }
625
15.0k
    Array limits = kid["/Limits"];
626
15.0k
    if (!(keyValid(limits[0]) && keyValid(limits[1]))) {
627
364
        error(kids[idx], "node is missing /Limits");
628
364
    }
629
15.0k
    if (compareKeys(key, limits[0]) < 0) {
630
1.61k
        return -1;
631
1.61k
    }
632
13.4k
    if (compareKeys(key, limits[1]) > 0) {
633
11.2k
        return 1;
634
11.2k
    }
635
2.22k
    return 0;
636
13.4k
}
637
638
namespace
639
{
640
    struct Cmp
641
    {
642
        bool
643
        operator()(const QPDFObjectHandle& lhs, const QPDFObjectHandle& rhs) const
644
344k
        {
645
344k
            Integer l = lhs;
646
344k
            Integer r = rhs;
647
344k
            if (l && r) {
648
13.9k
                return l.value() < r.value();
649
13.9k
            }
650
330k
            return lhs.getUTF8Value() < rhs.getUTF8Value();
651
344k
        }
652
    };
653
} // namespace
654
655
void
656
NNTreeImpl::repair()
657
1.99k
{
658
1.99k
    auto new_node = Dictionary({{itemsKey(), Array::empty()}});
659
1.99k
    NNTreeImpl repl(qpdf, new_node, key_type, value_valid, false);
660
1.99k
    std::map<QPDFObjectHandle, QPDFObjectHandle, Cmp> items;
661
55.7k
    for (auto const& [key, value]: *this) {
662
55.7k
        if (key && value && repl.keyValid(key) && repl.value_valid(value)) {
663
55.0k
            items.insert_or_assign(key, value);
664
55.0k
        }
665
55.7k
    }
666
19.4k
    for (auto const& [key, value]: items) {
667
19.4k
        repl.insert(key, value);
668
19.4k
    }
669
1.99k
    tree_root.replace("/Kids", new_node["/Kids"]);
670
1.99k
    tree_root.replace(itemsKey(), new_node[itemsKey()]);
671
1.99k
}
672
673
NNTreeImpl::iterator
674
NNTreeImpl::find(QPDFObjectHandle const& key, bool return_prev_if_not_found)
675
31.5k
{
676
31.5k
    try {
677
31.5k
        return findInternal(key, return_prev_if_not_found);
678
31.5k
    } catch (QPDFExc& e) {
679
3.12k
        if (auto_repair) {
680
3.12k
            warn(tree_root, std::string("attempting to repair after error: ") + e.what());
681
3.12k
            repair();
682
3.12k
            return findInternal(key, return_prev_if_not_found);
683
3.12k
        } else {
684
0
            throw;
685
0
        }
686
3.12k
    }
687
31.5k
}
688
689
NNTreeImpl::iterator
690
NNTreeImpl::findInternal(QPDFObjectHandle const& key, bool return_prev_if_not_found)
691
31.7k
{
692
31.7k
    auto first_item = begin();
693
31.7k
    if (!first_item.valid()) {
694
5.13k
        return end();
695
5.13k
    }
696
26.6k
    if (!keyValid(first_item->first)) {
697
95
        error(tree_root, "encountered invalid key in find");
698
95
    }
699
26.6k
    if (!value_valid(first_item->second)) {
700
273
        error(tree_root, "encountered invalid value in find");
701
273
    }
702
26.6k
    if (compareKeys(key, first_item->first) < 0) {
703
        // Before the first key
704
399
        return end();
705
399
    }
706
707
26.2k
    QPDFObjGen::set seen;
708
26.2k
    auto node = tree_root;
709
26.2k
    iterator result(*this);
710
711
35.1k
    while (true) {
712
32.4k
        if (!seen.add(node)) {
713
74
            error(node, "loop detected in find");
714
74
        }
715
716
32.4k
        Array items = node[itemsKey()];
717
32.4k
        size_t nitems = items.size();
718
32.4k
        if (nitems > 1) {
719
23.5k
            int idx = binarySearch(key, items, nitems / 2, return_prev_if_not_found, false);
720
23.5k
            if (idx >= 0) {
721
21.2k
                result.setItemNumber(node, 2 * idx);
722
21.2k
                if (!result.impl.keyValid(result.ivalue.first)) {
723
0
                    error(node, "encountered invalid key in find");
724
0
                }
725
21.2k
                if (!result.impl.value_valid(result.ivalue.second)) {
726
79
                    error(tree_root, "encountered invalid value in find");
727
79
                }
728
21.2k
            }
729
23.5k
            return result;
730
23.5k
        }
731
732
8.91k
        Array kids = node["/Kids"];
733
8.91k
        size_t nkids = kids.size();
734
8.91k
        if (nkids == 0) {
735
87
            error(node, "bad node during find");
736
87
        }
737
8.91k
        int idx = binarySearch(key, kids, nkids, true, true);
738
8.91k
        if (idx == -1) {
739
85
            error(node, "unexpected -1 from binary search of kids; limits may by wrong");
740
85
        }
741
8.91k
        result.addPathElement(node, idx);
742
8.91k
        node = kids[idx];
743
8.91k
    }
744
26.2k
}
745
746
NNTreeImpl::iterator
747
NNTreeImpl::insertFirst(QPDFObjectHandle const& key, QPDFObjectHandle const& value)
748
1.20k
{
749
1.20k
    auto iter = begin();
750
1.20k
    Array items = iter.node[items_key];
751
1.20k
    if (!items) {
752
0
        error(tree_root, "unable to find a valid items node");
753
0
    }
754
1.20k
    if (!(key && value)) {
755
0
        error(tree_root, "unable to insert null key or value");
756
0
    }
757
1.20k
    if (!value_valid(value)) {
758
0
        error(tree_root, "attempting to insert an invalid value");
759
0
    }
760
1.20k
    items.insert(0, key);
761
1.20k
    items.insert(1, value);
762
1.20k
    iter.setItemNumber(iter.node, 0);
763
1.20k
    iter.resetLimits(iter.node, iter.lastPathElement());
764
1.20k
    iter.split(iter.node, iter.lastPathElement());
765
1.20k
    return iter;
766
1.20k
}
767
768
NNTreeImpl::iterator
769
NNTreeImpl::insert(QPDFObjectHandle const& key, QPDFObjectHandle const& value)
770
19.4k
{
771
19.4k
    auto iter = find(key, true);
772
19.4k
    if (!iter.valid()) {
773
1.20k
        return insertFirst(key, value);
774
18.2k
    } 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
18.2k
    } else {
779
18.2k
        iter.insertAfter(key, value);
780
18.2k
    }
781
18.2k
    return iter;
782
19.4k
}
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.16k
{
801
4.16k
    bool first = true;
802
4.16k
    QPDFObjectHandle last_key;
803
4.16k
    try {
804
5.96k
        for (auto const& [key, value]: *this) {
805
5.96k
            if (!keyValid(key)) {
806
288
                error(tree_root, "invalid key in validate");
807
288
            }
808
5.96k
            if (!value_valid(value)) {
809
418
                error(tree_root, "invalid value in validate");
810
418
            }
811
5.96k
            if (first) {
812
1.92k
                first = false;
813
4.03k
            } else if (last_key && compareKeys(last_key, key) != -1) {
814
932
                error(tree_root, "keys are not sorted in validate");
815
932
            }
816
5.96k
            last_key = key;
817
5.96k
        }
818
4.16k
    } catch (QPDFExc& e) {
819
1.83k
        if (a_repair) {
820
1.83k
            warn(tree_root, std::string("attempting to repair after error: ") + e.what());
821
1.83k
            repair();
822
1.83k
        }
823
1.83k
        return false;
824
1.83k
    }
825
2.33k
    return true;
826
4.16k
}
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
2.95k
        impl(q, oh, ::ot_string, value_validator, auto_repair)
837
2.95k
    {
838
2.95k
    }
839
    Members(Members const&) = delete;
840
2.95k
    ~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
2.95k
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
2.95k
    QPDFObjectHelper(oh),
861
2.95k
    m(std::make_shared<Members>(oh, q, value_validator, auto_repair))
862
2.95k
{
863
2.95k
}
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.5k
    impl(i)
873
11.5k
{
874
11.5k
}
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
845
{
901
845
    if (impl->valid()) {
902
845
        auto p = *impl;
903
845
        ivalue.first = p->first.getUTF8Value();
904
845
        ivalue.second = p->second;
905
845
    } else {
906
0
        ivalue.first = "";
907
0
        ivalue.second = QPDFObjectHandle();
908
0
    }
909
845
}
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
845
{
921
845
    updateIValue();
922
845
    return &ivalue;
923
845
}
924
925
bool
926
QPDFNameTreeObjectHelper::iterator::operator==(iterator const& other) const
927
5.75k
{
928
5.75k
    return *(impl) == *(other.impl);
929
5.75k
}
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.75k
{
954
5.75k
    return {std::make_shared<NNTreeIterator>(m->impl.end())};
955
5.75k
}
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.35k
{
966
6.35k
    auto i = m->impl.find(QPDFObjectHandle::newUnicodeString(key), return_prev_if_not_found);
967
6.35k
    return {std::make_shared<NNTreeIterator>(i)};
968
6.35k
}
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.35k
{
993
6.35k
    auto i = find(name);
994
6.35k
    if (i == end()) {
995
4.91k
        return false;
996
4.91k
    }
997
1.43k
    oh = i->second;
998
1.43k
    return true;
999
6.35k
}
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
2.95k
{
1018
2.95k
    return m->impl.validate(repair);
1019
2.95k
}
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.21k
        impl(q, oh, ::ot_integer, value_validator, auto_repair)
1032
1.21k
    {
1033
1.21k
    }
1034
    Members(Members const&) = delete;
1035
1.21k
    ~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.21k
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.21k
    QPDFObjectHelper(oh),
1057
1.21k
    m(std::make_shared<Members>(oh, q, value_validator, auto_repair))
1058
1.21k
{
1059
1.21k
}
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.80k
    impl(i)
1069
6.80k
{
1070
6.80k
}
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.99k
{
1097
5.99k
    if (impl->valid()) {
1098
5.99k
        auto p = *impl;
1099
5.99k
        this->ivalue.first = p->first.getIntValue();
1100
5.99k
        this->ivalue.second = p->second;
1101
5.99k
    } else {
1102
0
        this->ivalue.first = 0;
1103
0
        this->ivalue.second = QPDFObjectHandle();
1104
0
    }
1105
5.99k
}
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.99k
{
1117
5.99k
    updateIValue();
1118
5.99k
    return &this->ivalue;
1119
5.99k
}
1120
1121
bool
1122
QPDFNumberTreeObjectHelper::iterator::operator==(iterator const& other) const
1123
3.40k
{
1124
3.40k
    return *(impl) == *(other.impl);
1125
3.40k
}
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.40k
{
1150
3.40k
    return {std::make_shared<NNTreeIterator>(m->impl.end())};
1151
3.40k
}
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.75k
{
1162
5.75k
    auto i = m->impl.find(QPDFObjectHandle::newInteger(key), return_prev_if_not_found);
1163
5.75k
    return {std::make_shared<NNTreeIterator>(i)};
1164
5.75k
}
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.75k
{
1221
5.75k
    auto i = find(idx, true);
1222
5.75k
    if (i == end()) {
1223
1.40k
        return false;
1224
1.40k
    }
1225
4.34k
    oh = i->second;
1226
4.34k
    QIntC::range_check_subtract(idx, i->first);
1227
4.34k
    offset = idx - i->first;
1228
4.34k
    return true;
1229
5.75k
}
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.21k
{
1248
1.21k
    return m->impl.validate(repair);
1249
1.21k
}