Commit d16e822d77d41c026e44bce459e5417c4e7b885d

Authored by m-holger
Committed by GitHub
2 parents 979dc51b 4eb9f113

Merge pull request #1532 from m-holger/oh_dict

Refactor private-API class Dictionary
include/qpdf/ObjectHandle.hh
... ... @@ -61,6 +61,13 @@ namespace qpdf
61 61  
62 62 // The rest of the header file is for qpdf internal use only.
63 63  
  64 + // Return true if both object handles refer to the same underlying object.
  65 + bool
  66 + operator==(BaseHandle const& other) const
  67 + {
  68 + return obj == other.obj;
  69 + }
  70 +
64 71 // For arrays, return the number of items in the array.
65 72 // For null-like objects, return 0.
66 73 // For all other objects, return 1.
... ... @@ -76,6 +83,10 @@ namespace qpdf
76 83 QPDFObjectHandle operator[](size_t n) const;
77 84 QPDFObjectHandle operator[](int n) const;
78 85  
  86 + bool contains(std::string const& key) const;
  87 + size_t erase(std::string const& key);
  88 + QPDFObjectHandle const& operator[](std::string const& key) const;
  89 +
79 90 std::shared_ptr<QPDFObject> copy(bool shallow = false) const;
80 91 // Recursively remove association with any QPDF object. This method may only be called
81 92 // during final destruction.
... ...
libqpdf/NNTree.cc
... ... @@ -57,75 +57,64 @@ NNTreeIterator::updateIValue(bool allow_invalid)
57 57 // we must call updateIValue as well. These cases are handled, and for good measure, we also
58 58 // call updateIValue in operator* and operator->.
59 59  
60   - if (item_number < 0 || !node.isDictionary()) {
  60 + Array items = node[impl.itemsKey()];
  61 + ivalue.first = items[item_number];
  62 + ivalue.second = items[item_number + 1];
  63 + if (ivalue.second) {
  64 + return;
  65 + }
  66 +
  67 + if (item_number < 0 || !node) {
61 68 if (!allow_invalid) {
62 69 throw std::logic_error(
63 70 "attempt made to dereference an invalid name/number tree iterator");
64 71 }
65   - ivalue.first = QPDFObjectHandle();
66   - ivalue.second = QPDFObjectHandle();
67 72 return;
68 73 }
69   - Array items = node.getKey(impl.itemsKey());
70   - if (!std::cmp_less(item_number + 1, items.size())) {
71   - impl.error(node, "update ivalue: items array is too short");
72   - }
73   - ivalue.first = items[item_number];
74   - ivalue.second = items[1 + item_number];
75   -}
76   -
77   -NNTreeIterator::PathElement::PathElement(QPDFObjectHandle const& node, int kid_number) :
78   - node(node),
79   - kid_number(kid_number)
80   -{
  74 + impl.error(node, "update ivalue: items array is too short");
81 75 }
82 76  
83   -QPDFObjectHandle
  77 +Dictionary
84 78 NNTreeIterator::getNextKid(PathElement& pe, bool backward)
85 79 {
86 80 while (true) {
87 81 pe.kid_number += backward ? -1 : 1;
88   - Array kids = pe.node.getKey("/Kids");
89   - if (pe.kid_number >= 0 && std::cmp_less(pe.kid_number, kids.size())) {
90   - auto result = kids[pe.kid_number];
91   - if (result.isDictionary() &&
92   - (result.hasKey("/Kids") || result.hasKey(impl.itemsKey()))) {
93   - return result;
94   - } else {
95   - impl.warn(
96   - pe.node, "skipping over invalid kid at index " + std::to_string(pe.kid_number));
97   - }
98   - } else {
99   - return QPDFObjectHandle::newNull();
  82 + Dictionary result = pe.node["/Kids"][pe.kid_number];
  83 + if (result.contains("/Kids") || result.contains(impl.itemsKey())) {
  84 + return result;
  85 + }
  86 + if (pe.kid_number < 0 || std::cmp_greater_equal(pe.kid_number, pe.node["/Kids"].size())) {
  87 + return {};
100 88 }
  89 + impl.warn(pe.node, "skipping over invalid kid at index " + std::to_string(pe.kid_number));
101 90 }
102 91 }
103 92 void
104 93 NNTreeIterator::increment(bool backward)
105 94 {
106 95 if (item_number < 0) {
107   - deepen(impl.oh, !backward, true);
  96 + deepen(impl.tree_root, !backward, true);
108 97 return;
109 98 }
110 99  
111 100 while (valid()) {
112 101 item_number += backward ? -2 : 2;
113   - Array items = node.getKey(impl.itemsKey());
  102 + Array items = node[impl.itemsKey()];
114 103 if (item_number < 0 || std::cmp_greater_equal(item_number, items.size())) {
115   - bool found = false;
116 104 setItemNumber(QPDFObjectHandle(), -1);
117   - while (!(found || path.empty())) {
  105 + while (!path.empty()) {
118 106 auto& element = path.back();
119   - auto pe_node = getNextKid(element, backward);
120   - if (pe_node.null()) {
121   - path.pop_back();
  107 + if (auto pe_node = getNextKid(element, backward)) {
  108 + if (deepen(pe_node, !backward, false)) {
  109 + break;
  110 + }
122 111 } else {
123   - found = deepen(pe_node, !backward, false);
  112 + path.pop_back();
124 113 }
125 114 }
126 115 }
127 116 if (item_number >= 0) {
128   - items = node.getKey(impl.itemsKey());
  117 + items = node[impl.itemsKey()];
129 118 if (std::cmp_greater_equal(item_number + 1, items.size())) {
130 119 impl.warn(node, "items array doesn't have enough elements");
131 120 } else if (!impl.keyValid(items[item_number])) {
... ... @@ -140,65 +129,59 @@ NNTreeIterator::increment(bool backward)
140 129 }
141 130  
142 131 void
143   -NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list<PathElement>::iterator parent)
  132 +NNTreeIterator::resetLimits(Dictionary a_node, std::list<PathElement>::iterator parent)
144 133 {
145 134 while (true) {
146 135 if (parent == path.end()) {
147   - a_node.removeKey("/Limits");
148   - break;
  136 + a_node.erase("/Limits");
  137 + return;
149 138 }
150   - Array kids = a_node.getKey("/Kids");
151   - size_t nkids = kids.size();
152   - Array items = a_node.getKey(impl.itemsKey());
153   - size_t nitems = items.size();
154 139  
155   - bool changed = true;
156 140 QPDFObjectHandle first;
157 141 QPDFObjectHandle last;
  142 + Array items = a_node[impl.itemsKey()];
  143 + size_t nitems = items.size();
158 144 if (nitems >= 2) {
159 145 first = items[0];
160 146 last = items[(nitems - 1u) & ~1u];
161   - } else if (nkids > 0) {
162   - auto first_kid = kids[0];
163   - auto last_kid = kids[nkids - 1u];
164   - if (first_kid.isDictionary() && last_kid.isDictionary()) {
165   - Array first_limits = first_kid.getKey("/Limits");
166   - Array last_limits = last_kid.getKey("/Limits");
167   - if (first_limits.size() >= 2 && last_limits.size() >= 2) {
  147 + } else {
  148 + Array kids = a_node["/Kids"];
  149 + size_t nkids = kids.size();
  150 + if (nkids > 0) {
  151 + Array first_limits = kids[0]["/Limits"];
  152 + if (first_limits.size() >= 2) {
168 153 first = first_limits[0];
169   - last = last_limits[1];
  154 + last = kids[nkids - 1u]["/Limits"][1];
170 155 }
171 156 }
172 157 }
173   - if (first && last) {
174   - Array limits({first, last});
175   - Array olimits = a_node.getKey("/Limits");
  158 + if (!(first && last)) {
  159 + impl.warn(a_node, "unable to determine limits");
  160 + } else {
  161 + Array olimits = a_node["/Limits"];
176 162 if (olimits.size() == 2) {
177 163 auto ofirst = olimits[0];
178 164 auto olast = olimits[1];
179 165 if (impl.keyValid(ofirst) && impl.keyValid(olast) &&
180 166 impl.compareKeys(first, ofirst) == 0 && impl.compareKeys(last, olast) == 0) {
181   - changed = false;
  167 + return;
182 168 }
183 169 }
184   - if (changed && !a_node.isSameObjectAs(path.begin()->node)) {
185   - a_node.replaceKey("/Limits", limits);
  170 + if (a_node != path.begin()->node) {
  171 + a_node.replaceKey("/Limits", Array({first, last}));
186 172 }
187   - } else {
188   - impl.warn(a_node, "unable to determine limits");
189 173 }
190 174  
191   - if (!changed || parent == path.begin()) {
192   - break;
193   - } else {
194   - a_node = parent->node;
195   - --parent;
  175 + if (parent == path.begin()) {
  176 + return;
196 177 }
  178 + a_node = parent->node;
  179 + --parent;
197 180 }
198 181 }
199 182  
200 183 void
201   -NNTreeIterator::split(QPDFObjectHandle to_split, std::list<PathElement>::iterator parent)
  184 +NNTreeIterator::split(Dictionary to_split, std::list<PathElement>::iterator parent)
202 185 {
203 186 // Split some node along the path to the item pointed to by this iterator, and adjust the
204 187 // iterator so it points to the same item.
... ... @@ -232,9 +215,9 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
232 215 }
233 216  
234 217 // Find the array we actually need to split, which is either this node's kids or items.
235   - Array kids = to_split.getKey("/Kids");
  218 + Array kids = to_split["/Kids"];
236 219 size_t nkids = kids.size();
237   - Array items = to_split.getKey(impl.itemsKey());
  220 + Array items = to_split[impl.itemsKey()];
238 221 size_t nitems = items.size();
239 222  
240 223 Array first_half;
... ... @@ -278,12 +261,11 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
278 261 // is the new first half. In this way, we make the root case identical to the non-root case
279 262 // so remaining logic can handle them in the same way.
280 263  
281   - auto first_node = impl.qpdf.makeIndirectObject(QPDFObjectHandle::newDictionary());
282   - first_node.replaceKey(key, first_half);
283   - Array new_kids;
  264 + Dictionary first_node = impl.qpdf.makeIndirectObject(Dictionary({{key, first_half}}));
  265 + auto new_kids = Array::empty();
284 266 new_kids.push_back(first_node);
285   - to_split.removeKey("/Limits"); // already shouldn't be there for root
286   - to_split.removeKey(impl.itemsKey());
  267 + to_split.erase("/Limits"); // already shouldn't be there for root
  268 + to_split.erase(impl.itemsKey());
287 269 to_split.replaceKey("/Kids", new_kids);
288 270 if (is_leaf) {
289 271 node = first_node;
... ... @@ -301,7 +283,7 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
301 283  
302 284 // Create a second half array, and transfer the second half of the items into the second half
303 285 // array.
304   - Array second_half;
  286 + auto second_half = Array::empty();
305 287 auto start_idx = static_cast<int>((n / 2) & ~1u);
306 288 while (std::cmp_greater(first_half.size(), start_idx)) {
307 289 second_half.push_back(first_half[start_idx]);
... ... @@ -310,8 +292,7 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
310 292 resetLimits(to_split, parent);
311 293  
312 294 // Create a new node to contain the second half
313   - QPDFObjectHandle second_node = impl.qpdf.makeIndirectObject(QPDFObjectHandle::newDictionary());
314   - second_node.replaceKey(key, second_half);
  295 + Dictionary second_node = impl.qpdf.makeIndirectObject(Dictionary({{key, second_half}}));
315 296 resetLimits(second_node, parent);
316 297  
317 298 // CURRENT STATE: half the items from the kids or items array in the node being split have been
... ... @@ -322,7 +303,10 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
322 303 // kid_number to traverse through it. We need to update to_split's path element, or the node if
323 304 // this is a leaf, so that the kid/item number points to the right place.
324 305  
325   - Array parent_kids = parent->node.getKey("/Kids");
  306 + Array parent_kids = parent->node["/Kids"];
  307 + if (!parent_kids) {
  308 + impl.error(parent->node, "parent node has no /Kids array");
  309 + }
326 310 parent_kids.insert(parent->kid_number + 1, second_node);
327 311 auto cur_elem = parent;
328 312 ++cur_elem; // points to end() for leaf nodes
... ... @@ -347,11 +331,7 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
347 331 std::list<NNTreeIterator::PathElement>::iterator
348 332 NNTreeIterator::lastPathElement()
349 333 {
350   - auto result = path.end();
351   - if (!path.empty()) {
352   - --result;
353   - }
354   - return result;
  334 + return path.empty() ? path.end() : std::prev(path.end());
355 335 }
356 336  
357 337 void
... ... @@ -359,11 +339,11 @@ NNTreeIterator::insertAfter(QPDFObjectHandle const&amp; key, QPDFObjectHandle const&amp;
359 339 {
360 340 if (!valid()) {
361 341 impl.insertFirst(key, value);
362   - deepen(impl.oh, true, false);
  342 + deepen(impl.tree_root, true, false);
363 343 return;
364 344 }
365 345  
366   - Array items = node.getKey(impl.itemsKey());
  346 + Array items = node[impl.itemsKey()];
367 347 if (!items) {
368 348 impl.error(node, "node contains no items array");
369 349 }
... ... @@ -392,7 +372,7 @@ NNTreeIterator::remove()
392 372 if (!valid()) {
393 373 throw std::logic_error("attempt made to remove an invalid iterator");
394 374 }
395   - Array items = node.getKey(impl.itemsKey());
  375 + Array items = node[impl.itemsKey()];
396 376 int nitems = static_cast<int>(items.size());
397 377 if (std::cmp_greater(item_number + 2, nitems)) {
398 378 impl.error(node, "found short items array while removing an item");
... ... @@ -429,7 +409,7 @@ NNTreeIterator::remove()
429 409  
430 410 if (path.empty()) {
431 411 // Special case: if this is the root node, we can leave it empty.
432   - setItemNumber(impl.oh, -1);
  412 + setItemNumber(impl.tree_root, -1);
433 413 return;
434 414 }
435 415  
... ... @@ -439,7 +419,7 @@ NNTreeIterator::remove()
439 419 auto element = lastPathElement();
440 420 auto parent = element;
441 421 --parent;
442   - Array kids = element->node.getKey("/Kids");
  422 + Array kids = element->node["/Kids"];
443 423 kids.erase(element->kid_number);
444 424 auto nkids = kids.size();
445 425 if (nkids > 0) {
... ... @@ -465,10 +445,10 @@ NNTreeIterator::remove()
465 445  
466 446 if (parent == path.end()) {
467 447 // We erased the very last item. Convert the root to an empty items array.
468   - element->node.removeKey("/Kids");
469   - element->node.replaceKey(impl.itemsKey(), Array());
  448 + element->node.erase("/Kids");
  449 + element->node.replaceKey(impl.itemsKey(), Array::empty());
470 450 path.clear();
471   - setItemNumber(impl.oh, -1);
  451 + setItemNumber(impl.tree_root, -1);
472 452 return;
473 453 }
474 454  
... ... @@ -499,14 +479,14 @@ NNTreeIterator::operator==(NNTreeIterator const&amp; other) const
499 479 }
500 480  
501 481 bool
502   -NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty)
  482 +NNTreeIterator::deepen(Dictionary a_node, bool first, bool allow_empty)
503 483 {
504 484 // Starting at this node, descend through the first or last kid until we reach a node with
505 485 // items. If we succeed, return true; otherwise return false and leave path alone.
506 486  
507 487 auto opath = path;
508 488  
509   - auto fail = [this, &opath](QPDFObjectHandle const& failed_node, std::string const& msg) {
  489 + auto fail = [this, &opath](Dictionary const& failed_node, std::string const& msg) {
510 490 impl.warn(failed_node, msg);
511 491 path = opath;
512 492 return false;
... ... @@ -521,58 +501,59 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty)
521 501 return fail(a_node, "loop detected while traversing name/number tree");
522 502 }
523 503  
524   - if (!a_node.isDictionary()) {
  504 + if (!a_node) {
525 505 return fail(a_node, "non-dictionary node while traversing name/number tree");
526 506 }
527 507  
528   - Array items = a_node.getKey(impl.itemsKey());
  508 + Array items = a_node[impl.itemsKey()];
529 509 int nitems = static_cast<int>(items.size());
530 510 if (nitems > 1) {
531 511 setItemNumber(a_node, first ? 0 : nitems - 2);
532   - break;
  512 + return true;
533 513 }
534 514  
535   - Array kids = a_node.getKey("/Kids");
  515 + Array kids = a_node["/Kids"];
536 516 int nkids = static_cast<int>(kids.size());
537   - if (nkids > 0) {
538   - int kid_number = first ? 0 : nkids - 1;
539   - addPathElement(a_node, kid_number);
540   - auto next = kids[kid_number];
541   - if (!next) {
542   - return fail(a_node, "kid number " + std::to_string(kid_number) + " is invalid");
543   - }
544   - if (!next.indirect()) {
545   - if (impl.auto_repair) {
546   - impl.warn(
547   - a_node,
548   - "converting kid number " + std::to_string(kid_number) +
549   - " to an indirect object");
550   - next = impl.qpdf.makeIndirectObject(next);
551   - kids.set(kid_number, next);
552   - } else {
553   - impl.warn(
554   - a_node,
555   - "kid number " + std::to_string(kid_number) + " is not an indirect object");
556   - }
  517 + if (nkids == 0) {
  518 + if (allow_empty && items) {
  519 + setItemNumber(a_node, -1);
  520 + return true;
557 521 }
558   - a_node = next;
559   - } else if (allow_empty && items) {
560   - setItemNumber(a_node, -1);
561   - break;
562   - } else {
563 522 return fail(
564 523 a_node,
565 524 "name/number tree node has neither non-empty " + impl.itemsKey() + " nor /Kids");
566 525 }
  526 +
  527 + int kid_number = first ? 0 : nkids - 1;
  528 + addPathElement(a_node, kid_number);
  529 + Dictionary next = kids[kid_number];
  530 + if (!next) {
  531 + return fail(a_node, "kid number " + std::to_string(kid_number) + " is invalid");
  532 + }
  533 + if (!next.indirect()) {
  534 + if (impl.auto_repair) {
  535 + impl.warn(
  536 + a_node,
  537 + "converting kid number " + std::to_string(kid_number) +
  538 + " to an indirect object");
  539 + next = impl.qpdf.makeIndirectObject(next);
  540 + kids.set(kid_number, next);
  541 + } else {
  542 + impl.warn(
  543 + a_node,
  544 + "kid number " + std::to_string(kid_number) + " is not an indirect object");
  545 + }
  546 + }
  547 +
  548 + a_node = next;
567 549 }
568   - return true;
569 550 }
570 551  
571 552 NNTreeImpl::iterator
572 553 NNTreeImpl::begin()
573 554 {
574 555 iterator result(*this);
575   - result.deepen(oh, true, true);
  556 + result.deepen(tree_root, true, true);
576 557 return result;
577 558 }
578 559  
... ... @@ -580,7 +561,7 @@ NNTreeImpl::iterator
580 561 NNTreeImpl::last()
581 562 {
582 563 iterator result(*this);
583   - result.deepen(oh, false, true);
  564 + result.deepen(tree_root, false, true);
584 565 return result;
585 566 }
586 567  
... ... @@ -606,19 +587,19 @@ NNTreeImpl::binarySearch(
606 587 Array const& items,
607 588 size_t num_items,
608 589 bool return_prev_if_not_found,
609   - int (NNTreeImpl::*compare)(QPDFObjectHandle const& key, Array const& arr, int item) const) const
  590 + bool search_kids) const
610 591 {
611 592 size_t max_idx = std::bit_ceil(num_items);
612 593  
613 594 int step = static_cast<int>(max_idx / 2);
614   - size_t checks = max_idx;
  595 + int checks = static_cast<int>(std::bit_width(max_idx)); // AppImage gcc version returns size_t
615 596 int idx = step;
616 597 int found_idx = -1;
617 598  
618   - while (checks > 0) {
  599 + for (int i = 0; i < checks; ++i) {
619 600 int status = -1;
620 601 if (std::cmp_less(idx, num_items)) {
621   - status = (this->*compare)(key, items, idx);
  602 + status = search_kids ? compareKeyKid(key, items, idx) : compareKeyItem(key, items, idx);
622 603 if (status == 0) {
623 604 return idx;
624 605 }
... ... @@ -626,21 +607,9 @@ NNTreeImpl::binarySearch(
626 607 found_idx = idx;
627 608 }
628 609 }
629   - checks >>= 1;
630   - if (checks > 0) {
631   - step >>= 1;
632   - if (step == 0) {
633   - step = 1;
634   - }
635   -
636   - if (status < 0) {
637   - idx -= step;
638   - } else {
639   - idx += step;
640   - }
641   - }
  610 + step = std::max(step / 2, 1);
  611 + idx += status * step;
642 612 }
643   -
644 613 return return_prev_if_not_found ? found_idx : -1;
645 614 }
646 615  
... ... @@ -648,7 +617,7 @@ int
648 617 NNTreeImpl::compareKeyItem(QPDFObjectHandle const& key, Array const& items, int idx) const
649 618 {
650 619 if (!keyValid(items[2 * idx])) {
651   - error(oh, ("item at index " + std::to_string(2 * idx) + " is not the right type"));
  620 + error(tree_root, ("item at index " + std::to_string(2 * idx) + " is not the right type"));
652 621 }
653 622 return compareKeys(key, items[2 * idx]);
654 623 }
... ... @@ -656,10 +625,11 @@ NNTreeImpl::compareKeyItem(QPDFObjectHandle const&amp; key, Array const&amp; items, int
656 625 int
657 626 NNTreeImpl::compareKeyKid(QPDFObjectHandle const& key, Array const& kids, int idx) const
658 627 {
659   - if (!kids[idx].isDictionary()) {
660   - error(oh, "invalid kid at index " + std::to_string(idx));
  628 + Dictionary kid = kids[idx];
  629 + if (!kid) {
  630 + error(tree_root, "invalid kid at index " + std::to_string(idx));
661 631 }
662   - Array limits = kids[idx].getKey("/Limits");
  632 + Array limits = kid["/Limits"];
663 633 if (!(keyValid(limits[0]) && keyValid(limits[1]))) {
664 634 error(kids[idx], "node is missing /Limits");
665 635 }
... ... @@ -675,26 +645,25 @@ NNTreeImpl::compareKeyKid(QPDFObjectHandle const&amp; key, Array const&amp; kids, int id
675 645 void
676 646 NNTreeImpl::repair()
677 647 {
678   - auto new_node = QPDFObjectHandle::newDictionary();
679   - new_node.replaceKey(itemsKey(), Array());
  648 + auto new_node = Dictionary({{itemsKey(), Array::empty()}});
680 649 NNTreeImpl repl(qpdf, new_node, key_type, value_valid, false);
681 650 for (auto const& [key, value]: *this) {
682 651 if (key && value) {
683 652 repl.insert(key, value);
684 653 }
685 654 }
686   - oh.replaceKey("/Kids", new_node.getKey("/Kids"));
687   - oh.replaceKey(itemsKey(), new_node.getKey(itemsKey()));
  655 + tree_root.replaceKey("/Kids", new_node["/Kids"]);
  656 + tree_root.replaceKey(itemsKey(), new_node[itemsKey()]);
688 657 }
689 658  
690 659 NNTreeImpl::iterator
691   -NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found)
  660 +NNTreeImpl::find(QPDFObjectHandle const& key, bool return_prev_if_not_found)
692 661 {
693 662 try {
694 663 return findInternal(key, return_prev_if_not_found);
695 664 } catch (QPDFExc& e) {
696 665 if (auto_repair) {
697   - warn(oh, std::string("attempting to repair after error: ") + e.what());
  666 + warn(tree_root, std::string("attempting to repair after error: ") + e.what());
698 667 repair();
699 668 return findInternal(key, return_prev_if_not_found);
700 669 } else {
... ... @@ -707,26 +676,22 @@ NNTreeImpl::iterator
707 676 NNTreeImpl::findInternal(QPDFObjectHandle const& key, bool return_prev_if_not_found)
708 677 {
709 678 auto first_item = begin();
710   - auto last_item = end();
711   - if (first_item == end()) {
  679 + if (!first_item.valid()) {
712 680 return end();
713 681 }
714   - if (first_item.valid()) {
715   - if (!keyValid(first_item->first)) {
716   - error(oh, "encountered invalid key in find");
717   - }
718   - if (!value_valid(first_item->second)) {
719   - error(oh, "encountered invalid value in find");
720   - }
721   - if (compareKeys(key, first_item->first) < 0) {
722   - // Before the first key
723   - return end();
724   - }
  682 + if (!keyValid(first_item->first)) {
  683 + error(tree_root, "encountered invalid key in find");
  684 + }
  685 + if (!value_valid(first_item->second)) {
  686 + error(tree_root, "encountered invalid value in find");
  687 + }
  688 + if (compareKeys(key, first_item->first) < 0) {
  689 + // Before the first key
  690 + return end();
725 691 }
726   - qpdf_assert_debug(!last_item.valid());
727 692  
728 693 QPDFObjGen::set seen;
729   - auto node = oh;
  694 + auto node = tree_root;
730 695 iterator result(*this);
731 696  
732 697 while (true) {
... ... @@ -734,35 +699,33 @@ NNTreeImpl::findInternal(QPDFObjectHandle const&amp; key, bool return_prev_if_not_fo
734 699 error(node, "loop detected in find");
735 700 }
736 701  
737   - Array items = node.getKey(itemsKey());
  702 + Array items = node[itemsKey()];
738 703 size_t nitems = items.size();
739 704 if (nitems > 1) {
740   - int idx = binarySearch(
741   - key, items, nitems / 2, return_prev_if_not_found, &NNTreeImpl::compareKeyItem);
  705 + int idx = binarySearch(key, items, nitems / 2, return_prev_if_not_found, false);
742 706 if (idx >= 0) {
743 707 result.setItemNumber(node, 2 * idx);
744 708 if (!result.impl.keyValid(result.ivalue.first)) {
745 709 error(node, "encountered invalid key in find");
746 710 }
747 711 if (!result.impl.value_valid(result.ivalue.second)) {
748   - error(oh, "encountered invalid value in find");
  712 + error(tree_root, "encountered invalid value in find");
749 713 }
750 714 }
751 715 return result;
752 716 }
753 717  
754   - Array kids = node.getKey("/Kids");
  718 + Array kids = node["/Kids"];
755 719 size_t nkids = kids.size();
756   - if (nkids > 0) {
757   - int idx = binarySearch(key, kids, nkids, true, &NNTreeImpl::compareKeyKid);
758   - if (idx == -1) {
759   - error(node, "unexpected -1 from binary search of kids; limits may by wrong");
760   - }
761   - result.addPathElement(node, idx);
762   - node = kids[idx];
763   - } else {
  720 + if (nkids == 0) {
764 721 error(node, "bad node during find");
765 722 }
  723 + int idx = binarySearch(key, kids, nkids, true, true);
  724 + if (idx == -1) {
  725 + error(node, "unexpected -1 from binary search of kids; limits may by wrong");
  726 + }
  727 + result.addPathElement(node, idx);
  728 + node = kids[idx];
766 729 }
767 730 }
768 731  
... ... @@ -770,18 +733,15 @@ NNTreeImpl::iterator
770 733 NNTreeImpl::insertFirst(QPDFObjectHandle const& key, QPDFObjectHandle const& value)
771 734 {
772 735 auto iter = begin();
773   - Array items(nullptr);
774   - if (iter.node.isDictionary()) {
775   - items = iter.node.getKey(itemsKey());
776   - }
  736 + Array items = iter.node[items_key];
777 737 if (!items) {
778   - error(oh, "unable to find a valid items node");
  738 + error(tree_root, "unable to find a valid items node");
779 739 }
780 740 if (!(key && value)) {
781   - error(oh, "unable to insert null key or value");
  741 + error(tree_root, "unable to insert null key or value");
782 742 }
783 743 if (!value_valid(value)) {
784   - error(oh, "attempting to insert an invalid value");
  744 + error(tree_root, "attempting to insert an invalid value");
785 745 }
786 746 items.insert(0, key);
787 747 items.insert(1, value);
... ... @@ -798,7 +758,7 @@ NNTreeImpl::insert(QPDFObjectHandle const&amp; key, QPDFObjectHandle const&amp; value)
798 758 if (!iter.valid()) {
799 759 return insertFirst(key, value);
800 760 } else if (compareKeys(key, iter->first) == 0) {
801   - Array items = iter.node.getKey(itemsKey());
  761 + Array items = iter.node[itemsKey()];
802 762 items.set(iter.item_number + 1, value);
803 763 iter.updateIValue();
804 764 } else {
... ... @@ -829,21 +789,21 @@ NNTreeImpl::validate(bool a_repair)
829 789 try {
830 790 for (auto const& [key, value]: *this) {
831 791 if (!keyValid(key)) {
832   - error(oh, "invalid key in validate");
  792 + error(tree_root, "invalid key in validate");
833 793 }
834 794 if (!value_valid(value)) {
835   - error(oh, "invalid value in validate");
  795 + error(tree_root, "invalid value in validate");
836 796 }
837 797 if (first) {
838 798 first = false;
839 799 } else if (last_key && compareKeys(last_key, key) != -1) {
840   - error(oh, "keys are not sorted in validate");
  800 + error(tree_root, "keys are not sorted in validate");
841 801 }
842 802 last_key = key;
843 803 }
844 804 } catch (QPDFExc& e) {
845 805 if (a_repair) {
846   - warn(oh, std::string("attempting to repair after error: ") + e.what());
  806 + warn(tree_root, std::string("attempting to repair after error: ") + e.what());
847 807 repair();
848 808 }
849 809 return false;
... ...
libqpdf/QPDF_Array.cc
... ... @@ -61,11 +61,6 @@ Array::array() const
61 61 return nullptr; // unreachable
62 62 }
63 63  
64   -Array::Array(bool empty) :
65   - BaseHandle(empty ? QPDFObject::create<QPDF_Array>() : nullptr)
66   -{
67   -}
68   -
69 64 Array::Array(std::vector<QPDFObjectHandle> const& items) :
70 65 BaseHandle(QPDFObject::create<QPDF_Array>(items))
71 66 {
... ... @@ -76,6 +71,12 @@ Array::Array(std::vector&lt;QPDFObjectHandle&gt;&amp;&amp; items) :
76 71 {
77 72 }
78 73  
  74 +Array
  75 +Array::empty()
  76 +{
  77 + return Array(std::vector<QPDFObjectHandle>());
  78 +}
  79 +
79 80 Array::iterator
80 81 Array::begin()
81 82 {
... ... @@ -399,7 +400,6 @@ QPDFObjectHandle::getArrayItem(int n) const
399 400 return newNull();
400 401 }
401 402 objectWarning("returning null for out of bounds array access");
402   -
403 403 } else {
404 404 typeWarning("array", "returning null");
405 405 }
... ...
libqpdf/QPDF_Dictionary.cc
... ... @@ -16,26 +16,23 @@ BaseDictionary::dict() const
16 16 return nullptr; // unreachable
17 17 }
18 18  
19   -bool
20   -BaseDictionary::hasKey(std::string const& key) const
  19 +QPDFObjectHandle const&
  20 +BaseHandle::operator[](std::string const& key) const
21 21 {
22   - auto d = dict();
23   - return d->items.contains(key) && !d->items[key].null();
  22 + if (auto d = as<QPDF_Dictionary>()) {
  23 + auto it = d->items.find(key);
  24 + if (it != d->items.end()) {
  25 + return it->second;
  26 + }
  27 + }
  28 + static const QPDFObjectHandle null_obj;
  29 + return null_obj;
24 30 }
25 31  
26   -QPDFObjectHandle
27   -BaseDictionary::getKey(std::string const& key) const
  32 +bool
  33 +BaseHandle::contains(std::string const& key) const
28 34 {
29   - auto d = dict();
30   -
31   - // PDF spec says fetching a non-existent key from a dictionary returns the null object.
32   - auto item = d->items.find(key);
33   - if (item != d->items.end()) {
34   - // May be a null object
35   - return item->second;
36   - }
37   - static auto constexpr msg = " -> dictionary key $VD"sv;
38   - return QPDF_Null::create(obj, msg, key);
  35 + return !(*this)[key].null();
39 36 }
40 37  
41 38 std::set<std::string>
... ... @@ -56,11 +53,14 @@ BaseDictionary::getAsMap() const
56 53 return dict()->items;
57 54 }
58 55  
59   -void
60   -BaseDictionary::removeKey(std::string const& key)
  56 +size_t
  57 +BaseHandle::erase(const std::string& key)
61 58 {
62 59 // no-op if key does not exist
63   - dict()->items.erase(key);
  60 + if (auto d = as<QPDF_Dictionary>()) {
  61 + return d->items.erase(key);
  62 + }
  63 + return 0;
64 64 }
65 65  
66 66 void
... ... @@ -78,13 +78,28 @@ BaseDictionary::replaceKey(std::string const&amp; key, QPDFObjectHandle value)
78 78 }
79 79 }
80 80  
  81 +Dictionary::Dictionary(std::map<std::string, QPDFObjectHandle>&& dict) :
  82 + BaseDictionary(std::move(dict))
  83 +{
  84 +}
  85 +
  86 +Dictionary::Dictionary(std::shared_ptr<QPDFObject> const& obj) :
  87 + BaseDictionary(obj)
  88 +{
  89 +}
  90 +
  91 +Dictionary
  92 +Dictionary::empty()
  93 +{
  94 + return Dictionary(std::map<std::string, QPDFObjectHandle>());
  95 +}
  96 +
81 97 void
82 98 QPDFObjectHandle::checkOwnership(QPDFObjectHandle const& item) const
83 99 {
84 100 auto qpdf = getOwningQPDF();
85 101 auto item_qpdf = item.getOwningQPDF();
86 102 if (qpdf && item_qpdf && qpdf != item_qpdf) {
87   - QTC::TC("qpdf", "QPDFObjectHandle check ownership");
88 103 throw std::logic_error(
89 104 "Attempting to add an object from a different QPDF. Use "
90 105 "QPDF::copyForeignObject to add objects from another file.");
... ... @@ -94,9 +109,8 @@ QPDFObjectHandle::checkOwnership(QPDFObjectHandle const&amp; item) const
94 109 bool
95 110 QPDFObjectHandle::hasKey(std::string const& key) const
96 111 {
97   - auto dict = as_dictionary(strict);
98   - if (dict) {
99   - return dict.hasKey(key);
  112 + if (Dictionary dict = *this) {
  113 + return dict.contains(key);
100 114 } else {
101 115 typeWarning("dictionary", "returning false for a key containment request");
102 116 QTC::TC("qpdf", "QPDFObjectHandle dictionary false for hasKey");
... ... @@ -107,11 +121,14 @@ QPDFObjectHandle::hasKey(std::string const&amp; key) const
107 121 QPDFObjectHandle
108 122 QPDFObjectHandle::getKey(std::string const& key) const
109 123 {
110   - if (auto dict = as_dictionary(strict)) {
111   - return dict.getKey(key);
  124 + if (auto result = (*this)[key]) {
  125 + return result;
  126 + }
  127 + if (isDictionary()) {
  128 + static auto constexpr msg = " -> dictionary key $VD"sv;
  129 + return QPDF_Null::create(obj, msg, key);
112 130 }
113 131 typeWarning("dictionary", "returning null for attempted key retrieval");
114   - QTC::TC("qpdf", "QPDFObjectHandle dictionary null for getKey");
115 132 static auto constexpr msg = " -> null returned from getting key $VD from non-Dictionary"sv;
116 133 return QPDF_Null::create(obj, msg, "");
117 134 }
... ... @@ -174,21 +191,16 @@ QPDFObjectHandle::replaceKeyAndGetOld(std::string const&amp; key, QPDFObjectHandle c
174 191 void
175 192 QPDFObjectHandle::removeKey(std::string const& key)
176 193 {
177   - if (auto dict = as_dictionary(strict)) {
178   - dict.removeKey(key);
  194 + if (erase(key) || isDictionary()) {
179 195 return;
180 196 }
181 197 typeWarning("dictionary", "ignoring key removal request");
182   - QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring removeKey");
183 198 }
184 199  
185 200 QPDFObjectHandle
186 201 QPDFObjectHandle::removeKeyAndGetOld(std::string const& key)
187 202 {
188   - auto result = QPDFObjectHandle::newNull();
189   - if (auto dict = as_dictionary(strict)) {
190   - result = dict.getKey(key);
191   - }
192   - removeKey(key);
193   - return result;
  203 + auto result = (*this)[key];
  204 + erase(key);
  205 + return result ? result : newNull();
194 206 }
... ...
libqpdf/qpdf/NNTree.hh
... ... @@ -81,9 +81,13 @@ class NNTreeIterator final
81 81 class PathElement
82 82 {
83 83 public:
84   - PathElement(QPDFObjectHandle const& node, int kid_number);
  84 + PathElement(qpdf::Dictionary const& node, int kid_number) :
  85 + node(node),
  86 + kid_number(kid_number)
  87 + {
  88 + }
85 89  
86   - QPDFObjectHandle node;
  90 + qpdf::Dictionary node;
87 91 int kid_number;
88 92 };
89 93  
... ... @@ -92,7 +96,7 @@ class NNTreeIterator final
92 96 {
93 97 }
94 98 void updateIValue(bool allow_invalid = true);
95   - bool deepen(QPDFObjectHandle node, bool first, bool allow_empty);
  99 + bool deepen(qpdf::Dictionary node, bool first, bool allow_empty);
96 100 void
97 101 setItemNumber(QPDFObjectHandle const& a_node, int n)
98 102 {
... ... @@ -105,16 +109,16 @@ class NNTreeIterator final
105 109 {
106 110 path.emplace_back(a_node, kid_number);
107 111 }
108   - QPDFObjectHandle getNextKid(PathElement& element, bool backward);
  112 + qpdf::Dictionary getNextKid(PathElement& element, bool backward);
109 113 void increment(bool backward);
110   - void resetLimits(QPDFObjectHandle node, std::list<PathElement>::iterator parent);
  114 + void resetLimits(qpdf::Dictionary node, std::list<PathElement>::iterator parent);
111 115  
112   - void split(QPDFObjectHandle to_split, std::list<PathElement>::iterator parent);
  116 + void split(qpdf::Dictionary to_split, std::list<PathElement>::iterator parent);
113 117 std::list<PathElement>::iterator lastPathElement();
114 118  
115 119 NNTreeImpl& impl;
116 120 std::list<PathElement> path;
117   - QPDFObjectHandle node;
  121 + qpdf::Dictionary node;
118 122 int item_number{-1};
119 123 value_type ivalue;
120 124 };
... ... @@ -128,12 +132,12 @@ class NNTreeImpl final
128 132  
129 133 NNTreeImpl(
130 134 QPDF& qpdf,
131   - QPDFObjectHandle& oh,
  135 + qpdf::Dictionary tree_root,
132 136 qpdf_object_type_e key_type,
133 137 std::function<bool(QPDFObjectHandle const&)> value_validator,
134 138 bool auto_repair) :
135 139 qpdf(qpdf),
136   - oh(oh),
  140 + tree_root(std::move(tree_root)),
137 141 key_type(key_type),
138 142 items_key(key_type == ::ot_string ? "/Names" : "/Nums"),
139 143 value_valid(value_validator),
... ... @@ -147,7 +151,7 @@ class NNTreeImpl final
147 151 return {*this};
148 152 }
149 153 iterator last();
150   - iterator find(QPDFObjectHandle key, bool return_prev_if_not_found = false);
  154 + iterator find(QPDFObjectHandle const& key, bool return_prev_if_not_found = false);
151 155 iterator insertFirst(QPDFObjectHandle const& key, QPDFObjectHandle const& value);
152 156 iterator insert(QPDFObjectHandle const& key, QPDFObjectHandle const& value);
153 157 bool remove(QPDFObjectHandle const& key, QPDFObjectHandle* value = nullptr);
... ... @@ -170,8 +174,7 @@ class NNTreeImpl final
170 174 qpdf::Array const& items,
171 175 size_t num_items,
172 176 bool return_prev_if_not_found,
173   - int (NNTreeImpl::*compare)(QPDFObjectHandle const& key, qpdf::Array const& arr, int item)
174   - const) const;
  177 + bool search_kids) const;
175 178 int compareKeyItem(QPDFObjectHandle const& key, qpdf::Array const& items, int idx) const;
176 179 int compareKeyKid(QPDFObjectHandle const& key, qpdf::Array const& items, int idx) const;
177 180 void warn(QPDFObjectHandle const& node, std::string const& msg);
... ... @@ -191,7 +194,7 @@ class NNTreeImpl final
191 194  
192 195 QPDF& qpdf;
193 196 int split_threshold{32};
194   - QPDFObjectHandle oh;
  197 + qpdf::Dictionary tree_root;
195 198 const qpdf_object_type_e key_type;
196 199 const std::string items_key;
197 200 const std::function<bool(QPDFObjectHandle const&)> value_valid;
... ...
libqpdf/qpdf/QPDFObjectHandle_private.hh
... ... @@ -12,18 +12,19 @@ namespace qpdf
12 12 class Array final: public BaseHandle
13 13 {
14 14 public:
15   - // Create an empty Array. If 'empty' is false, create a null Array.
16   - Array(bool empty = true);
  15 + Array() = default;
17 16  
18   - Array(std::vector<QPDFObjectHandle> const& items);
  17 + explicit Array(std::vector<QPDFObjectHandle> const& items);
19 18  
20   - Array(std::vector<QPDFObjectHandle>&& items);
  19 + explicit Array(std::vector<QPDFObjectHandle>&& items);
21 20  
22 21 Array(Array const& other) :
23 22 BaseHandle(other.obj)
24 23 {
25 24 }
26 25  
  26 + static Array empty();
  27 +
27 28 Array&
28 29 operator=(Array const& other)
29 30 {
... ... @@ -118,6 +119,11 @@ namespace qpdf
118 119 class BaseDictionary: public BaseHandle
119 120 {
120 121 public:
  122 + // The following methods are not part of the public API.
  123 + std::set<std::string> getKeys();
  124 + std::map<std::string, QPDFObjectHandle> const& getAsMap() const;
  125 + void replaceKey(std::string const& key, QPDFObjectHandle value);
  126 +
121 127 using iterator = std::map<std::string, QPDFObjectHandle>::iterator;
122 128 using const_iterator = std::map<std::string, QPDFObjectHandle>::const_iterator;
123 129 using reverse_iterator = std::map<std::string, QPDFObjectHandle>::reverse_iterator;
... ... @@ -196,41 +202,87 @@ namespace qpdf
196 202 return {};
197 203 }
198 204  
199   - // The following methods are not part of the public API.
200   - bool hasKey(std::string const& key) const;
201   - QPDFObjectHandle getKey(std::string const& key) const;
202   - std::set<std::string> getKeys();
203   - std::map<std::string, QPDFObjectHandle> const& getAsMap() const;
204   - void removeKey(std::string const& key);
205   - void replaceKey(std::string const& key, QPDFObjectHandle value);
206   -
207 205 protected:
208 206 BaseDictionary() = default;
209   - BaseDictionary(std::shared_ptr<QPDFObject> const& obj) :
210   - BaseHandle(obj) {};
211   - BaseDictionary(std::shared_ptr<QPDFObject>&& obj) :
212   - BaseHandle(std::move(obj)) {};
  207 +
  208 + explicit BaseDictionary(std::map<std::string, QPDFObjectHandle> const& dict) :
  209 + BaseHandle(QPDFObject::create<QPDF_Dictionary>(dict))
  210 + {
  211 + }
  212 +
  213 + explicit BaseDictionary(std::map<std::string, QPDFObjectHandle>&& dict) :
  214 + BaseHandle(QPDFObject::create<QPDF_Dictionary>(std::move(dict)))
  215 + {
  216 + }
  217 +
  218 + explicit BaseDictionary(std::shared_ptr<QPDFObject> const& obj) :
  219 + BaseHandle(obj)
  220 + {
  221 + }
  222 + explicit BaseDictionary(std::shared_ptr<QPDFObject>&& obj) :
  223 + BaseHandle(std::move(obj))
  224 + {
  225 + }
213 226 BaseDictionary(BaseDictionary const&) = default;
214 227 BaseDictionary& operator=(BaseDictionary const&) = default;
215 228 BaseDictionary(BaseDictionary&&) = default;
216 229 BaseDictionary& operator=(BaseDictionary&&) = default;
  230 +
  231 + explicit BaseDictionary(QPDFObjectHandle const& oh) :
  232 + BaseHandle(oh.resolved_type_code() == ::ot_dictionary ? oh : QPDFObjectHandle())
  233 + {
  234 + }
  235 +
  236 + explicit BaseDictionary(QPDFObjectHandle&& oh) :
  237 + BaseHandle(
  238 + oh.resolved_type_code() == ::ot_dictionary ? std::move(oh) : QPDFObjectHandle())
  239 + {
  240 + }
217 241 ~BaseDictionary() = default;
218 242  
219 243 QPDF_Dictionary* dict() const;
220 244 };
221 245  
  246 + // Dictionary only defines con/destructors. All other methods are inherited from BaseDictionary.
222 247 class Dictionary final: public BaseDictionary
223 248 {
224 249 public:
225   - explicit Dictionary(std::shared_ptr<QPDFObject> const& obj) :
226   - BaseDictionary(obj)
  250 + Dictionary() = default;
  251 + explicit Dictionary(std::map<std::string, QPDFObjectHandle>&& dict);
  252 + explicit Dictionary(std::shared_ptr<QPDFObject> const& obj);
  253 +
  254 + static Dictionary empty();
  255 +
  256 + Dictionary(Dictionary const&) = default;
  257 + Dictionary& operator=(Dictionary const&) = default;
  258 + Dictionary(Dictionary&&) = default;
  259 + Dictionary& operator=(Dictionary&&) = default;
  260 +
  261 + Dictionary(QPDFObjectHandle const& oh) :
  262 + BaseDictionary(oh)
227 263 {
228 264 }
229 265  
230   - explicit Dictionary(std::shared_ptr<QPDFObject>&& obj) :
231   - BaseDictionary(std::move(obj))
  266 + Dictionary&
  267 + operator=(QPDFObjectHandle const& oh)
  268 + {
  269 + assign(::ot_dictionary, oh);
  270 + return *this;
  271 + }
  272 +
  273 + Dictionary(QPDFObjectHandle&& oh) :
  274 + BaseDictionary(std::move(oh))
232 275 {
233 276 }
  277 +
  278 + Dictionary&
  279 + operator=(QPDFObjectHandle&& oh)
  280 + {
  281 + assign(::ot_dictionary, std::move(oh));
  282 + return *this;
  283 + }
  284 +
  285 + ~Dictionary() = default;
234 286 };
235 287  
236 288 class Name final: public BaseHandle
... ...
qpdf/qpdf.testcov
... ... @@ -304,11 +304,9 @@ QPDFObjectHandle insert array bounds 0
304 304 QPDFObjectHandle array ignoring append item 0
305 305 QPDFObjectHandle array ignoring erase item 0
306 306 QPDFObjectHandle dictionary false for hasKey 0
307   -QPDFObjectHandle dictionary null for getKey 0
308 307 QPDFObjectHandle dictionary empty set for getKeys 0
309 308 QPDFObjectHandle dictionary empty map for asMap 0
310 309 QPDFObjectHandle dictionary ignoring replaceKey 0
311   -QPDFObjectHandle dictionary ignoring removeKey 0
312 310 QPDFObjectHandle numeric non-numeric 0
313 311 QPDFObjectHandle erase array bounds 0
314 312 qpdf-c called qpdf_check_pdf 0
... ... @@ -537,7 +535,6 @@ QPDFWriter preserve object streams 0
537 535 QPDFWriter preserve object streams preserve unreferenced 0
538 536 QPDFWriter exclude from object stream 0
539 537 QPDF_pages findPage not found 0
540   -QPDFObjectHandle check ownership 0
541 538 QPDFJob weak crypto error 0
542 539 qpdf-c called qpdf_oh_is_initialized 0
543 540 qpdf-c registered progress reporter 0
... ...
qpdf/qtest/qpdf/name-tree.out
... ... @@ -33,7 +33,7 @@ WARNING: name-tree.pdf (Name/Number tree node (object 17)): skipping over invali
33 33 B
34 34 W
35 35 /Bad3 -- invalid kid
36   -WARNING: name-tree.pdf (Name/Number tree node (object 25)): non-dictionary node while traversing name/number tree
  36 +WARNING: name-tree.pdf (Name/Number tree node (object 22)): kid number 0 is invalid
37 37 /Bad4 -- invalid kid
38 38 WARNING: name-tree.pdf (Name/Number tree node (object 23)): attempting to repair after error: name-tree.pdf (Name/Number tree node (object 23)): invalid kid at index 1
39 39 WARNING: name-tree.pdf (Name/Number tree node (object 23)): skipping over invalid kid at index 1
... ...