Commit b5614f611d3057359dfd7ba63418c62787af5511

Authored by Jay Berkenbilt
1 parent 04edfe9f

Implement repair and insert for name/number trees

ChangeLog
1 1 2021-01-23 Jay Berkenbilt <ejb@ql.org>
2 2  
  3 + * Add an insert method to QPDFNameTreeObjectHelper and
  4 + QPDFNumberTreeObjectHelper.
  5 +
  6 + * QPDFNameTreeObjectHelper and QPDFNumberTreeObjectHelper will
  7 + automatically repair broken name and number trees by default. This
  8 + behavior can be turned off.
  9 +
3 10 * Change behavior of QPDFObjectHandle::newUnicodeString so that it
4 11 encodes ASCII or PDFDocEncoding if those encodings will support
5 12 all the characters in the string, resorting to UTF-16 only if the
... ...
... ... @@ -261,8 +261,6 @@ I find it useful to make reference to them in this list.
261 261 dictionary may need to be changed -- create test cases with lots of
262 262 duplicated/overlapping keys.
263 263  
264   - * Add support for writing name and number trees
265   -
266 264 * Figure out how to render Gajić correctly in the PDF version of the
267 265 qpdf manual.
268 266  
... ...
include/qpdf/QPDFNameTreeObjectHelper.hh
... ... @@ -127,12 +127,21 @@ class QPDFNameTreeObjectHelper: public QPDFObjectHelper
127 127 iterator find(std::string const& key,
128 128 bool return_prev_if_not_found = false);
129 129  
  130 + // Insert a new item. If the key already exists, it is replaced.
  131 + QPDF_DLL
  132 + iterator insert(std::string const& key, QPDFObjectHandle value);
  133 +
130 134 // Return the contents of the name tree as a map. Note that name
131 135 // trees may be very large, so this may use a lot of RAM. It is
132 136 // more efficient to use QPDFNameTreeObjectHelper's iterator.
133 137 QPDF_DLL
134 138 std::map<std::string, QPDFObjectHandle> getAsMap() const;
135 139  
  140 + // Split a node if the number of items exceeds this value. There's
  141 + // no real reason to ever set this except for testing.
  142 + QPDF_DLL
  143 + void setSplitThreshold(int);
  144 +
136 145 private:
137 146 class Members
138 147 {
... ...
include/qpdf/QPDFNumberTreeObjectHelper.hh
... ... @@ -145,6 +145,10 @@ class QPDFNumberTreeObjectHelper: public QPDFObjectHelper
145 145 QPDF_DLL
146 146 iterator find(numtree_number key, bool return_prev_if_not_found = false);
147 147  
  148 + // Insert a new item. If the key already exists, it is replaced.
  149 + QPDF_DLL
  150 + iterator insert(numtree_number key, QPDFObjectHandle value);
  151 +
148 152 // Return the contents of the number tree as a map. Note that
149 153 // number trees may be very large, so this may use a lot of RAM.
150 154 // It is more efficient to use QPDFNumberTreeObjectHelper's
... ... @@ -153,6 +157,11 @@ class QPDFNumberTreeObjectHelper: public QPDFObjectHelper
153 157 QPDF_DLL
154 158 idx_map getAsMap() const;
155 159  
  160 + // Split a node if the number of items exceeds this value. There's
  161 + // no real reason to ever set this except for testing.
  162 + QPDF_DLL
  163 + void setSplitThreshold(int);
  164 +
156 165 private:
157 166 class Members
158 167 {
... ...
libqpdf/NNTree.cc
... ... @@ -44,6 +44,12 @@ error(QPDF* qpdf, QPDFObjectHandle&amp; node, std::string const&amp; msg)
44 44 }
45 45 }
46 46  
  47 +NNTreeIterator::NNTreeIterator(NNTreeImpl& impl) :
  48 + impl(impl),
  49 + item_number(-1)
  50 +{
  51 +}
  52 +
47 53 NNTreeIterator::PathElement::PathElement(
48 54 QPDFObjectHandle const& node, int kid_number) :
49 55 node(node),
... ... @@ -52,18 +58,36 @@ NNTreeIterator::PathElement::PathElement(
52 58 }
53 59  
54 60 QPDFObjectHandle
55   -NNTreeIterator::PathElement::getNextKid(bool backward)
  61 +NNTreeIterator::getNextKid(PathElement& pe, bool backward)
56 62 {
57   - kid_number += backward ? -1 : 1;
58   - auto kids = node.getKey("/Kids");
59 63 QPDFObjectHandle result;
60   - if ((kid_number >= 0) && (kid_number < kids.getArrayNItems()))
61   - {
62   - result = kids.getArrayItem(kid_number);
63   - }
64   - else
  64 + bool found = false;
  65 + while (! found)
65 66 {
66   - result = QPDFObjectHandle::newNull();
  67 + pe.kid_number += backward ? -1 : 1;
  68 + auto kids = pe.node.getKey("/Kids");
  69 + if ((pe.kid_number >= 0) && (pe.kid_number < kids.getArrayNItems()))
  70 + {
  71 + result = kids.getArrayItem(pe.kid_number);
  72 + if (result.isDictionary() &&
  73 + (result.hasKey("/Kids") ||
  74 + result.hasKey(impl.details.itemsKey())))
  75 + {
  76 + found = true;
  77 + }
  78 + else
  79 + {
  80 + QTC::TC("qpdf", "NNTree skip invalid kid");
  81 + warn(impl.qpdf, pe.node,
  82 + "skipping over invalid kid at index " +
  83 + QUtil::int_to_string(pe.kid_number));
  84 + }
  85 + }
  86 + else
  87 + {
  88 + result = QPDFObjectHandle::newNull();
  89 + found = true;
  90 + }
67 91 }
68 92 return result;
69 93 }
... ... @@ -83,30 +107,358 @@ NNTreeIterator::increment(bool backward)
83 107 "attempt made to increment or decrement an invalid"
84 108 " name/number tree iterator");
85 109 }
86   - this->item_number += backward ? -2 : 2;
87   - auto items = this->node.getKey(details.itemsKey());
88   - if ((this->item_number < 0) ||
89   - (this->item_number >= items.getArrayNItems()))
  110 + bool found_valid_key = false;
  111 + while (valid() && (! found_valid_key))
90 112 {
91   - bool found = false;
92   - setItemNumber(QPDFObjectHandle(), -1);
93   - while (! (found || this->path.empty()))
  113 + this->item_number += backward ? -2 : 2;
  114 + auto items = this->node.getKey(impl.details.itemsKey());
  115 + if ((this->item_number < 0) ||
  116 + (this->item_number >= items.getArrayNItems()))
  117 + {
  118 + bool found = false;
  119 + setItemNumber(QPDFObjectHandle(), -1);
  120 + while (! (found || this->path.empty()))
  121 + {
  122 + auto& element = this->path.back();
  123 + auto pe_node = getNextKid(element, backward);
  124 + if (pe_node.isNull())
  125 + {
  126 + this->path.pop_back();
  127 + }
  128 + else
  129 + {
  130 + found = deepen(pe_node, ! backward, false);
  131 + }
  132 + }
  133 + }
  134 + if (this->item_number >= 0)
94 135 {
95   - auto& element = this->path.back();
96   - auto node = element.getNextKid(backward);
97   - if (node.isNull())
  136 + items = this->node.getKey(impl.details.itemsKey());
  137 + if (this->item_number + 1 >= items.getArrayNItems())
98 138 {
99   - this->path.pop_back();
  139 + QTC::TC("qpdf", "NNTree skip item at end of short items");
  140 + warn(impl.qpdf, this->node,
  141 + "items array doesn't have enough elements");
  142 + }
  143 + else if (! impl.details.keyValid(
  144 + items.getArrayItem(this->item_number)))
  145 + {
  146 + QTC::TC("qpdf", "NNTree skip invalid key");
  147 + warn(impl.qpdf, this->node,
  148 + "item " + QUtil::int_to_string(this->item_number) +
  149 + " has the wrong type");
100 150 }
101 151 else
102 152 {
103   - deepen(node, ! backward);
104   - found = true;
  153 + found_valid_key = true;
  154 + }
  155 + }
  156 + }
  157 +}
  158 +
  159 +void
  160 +NNTreeIterator::resetLimits(QPDFObjectHandle node,
  161 + std::list<PathElement>::iterator parent)
  162 +{
  163 + bool done = false;
  164 + while (! done)
  165 + {
  166 + auto kids = node.getKey("/Kids");
  167 + int nkids = kids.isArray() ? kids.getArrayNItems() : 0;
  168 + auto items = node.getKey(impl.details.itemsKey());
  169 + int nitems = items.isArray() ? items.getArrayNItems() : 0;
  170 +
  171 + bool changed = true;
  172 + QPDFObjectHandle first;
  173 + QPDFObjectHandle last;
  174 + if (nitems >= 2)
  175 + {
  176 + first = items.getArrayItem(0);
  177 + last = items.getArrayItem((nitems - 1) & ~1);
  178 + }
  179 + else if (nkids > 0)
  180 + {
  181 + auto first_kid = kids.getArrayItem(0);
  182 + auto last_kid = kids.getArrayItem(nkids - 1);
  183 + if (first_kid.isDictionary() && last_kid.isDictionary())
  184 + {
  185 + auto first_limits = first_kid.getKey("/Limits");
  186 + auto last_limits = last_kid.getKey("/Limits");
  187 + if (first_limits.isArray() &&
  188 + (first_limits.getArrayNItems() >= 2) &&
  189 + last_limits.isArray() &&
  190 + (last_limits.getArrayNItems() >= 2))
  191 + {
  192 + first = first_limits.getArrayItem(0);
  193 + last = last_limits.getArrayItem(1);
  194 + }
105 195 }
106 196 }
  197 + if (first.isInitialized() && last.isInitialized())
  198 + {
  199 + auto limits = QPDFObjectHandle::newArray();
  200 + limits.appendItem(first);
  201 + limits.appendItem(last);
  202 + auto olimits = node.getKey("/Limits");
  203 + if (olimits.isArray() && (olimits.getArrayNItems() == 2))
  204 + {
  205 + auto ofirst = olimits.getArrayItem(0);
  206 + auto olast = olimits.getArrayItem(1);
  207 + if (impl.details.keyValid(ofirst) &&
  208 + impl.details.keyValid(olast) &&
  209 + (impl.details.compareKeys(first, ofirst) == 0) &&
  210 + (impl.details.compareKeys(last, olast) == 0))
  211 + {
  212 + QTC::TC("qpdf", "NNTree limits didn't change");
  213 + changed = false;
  214 + }
  215 + }
  216 + if (changed)
  217 + {
  218 + node.replaceKey("/Limits", limits);
  219 + }
  220 + }
  221 + else
  222 + {
  223 + QTC::TC("qpdf", "NNTree unable to determine limits");
  224 + warn(impl.qpdf, node, "unable to determine limits");
  225 + }
  226 +
  227 + if ((! changed) || (parent == this->path.begin()))
  228 + {
  229 + done = true;
  230 + }
  231 + else
  232 + {
  233 + node = parent->node;
  234 + --parent;
  235 + }
107 236 }
108 237 }
109 238  
  239 +void
  240 +NNTreeIterator::split(QPDFObjectHandle to_split,
  241 + std::list<PathElement>::iterator parent)
  242 +{
  243 + // Split some node along the path to the item pointed to by this
  244 + // iterator, and adjust the iterator so it points to the same
  245 + // item.
  246 +
  247 + // In examples, for simplicity, /Nums is show to just contain
  248 + // numbers instead of pairs. Imagine this tre:
  249 + //
  250 + // root: << /Kids [ A B C D ] >>
  251 + // A: << /Nums [ 1 2 3 4 ] >>
  252 + // B: << /Nums [ 5 6 7 8 ] >>
  253 + // C: << /Nums [ 9 10 11 12 ] >>
  254 + // D: << /Kids [ E F ]
  255 + // E: << /Nums [ 13 14 15 16 ] >>
  256 + // F: << /Nums [ 17 18 19 20 ] >>
  257 +
  258 + // iter1 (points to 19)
  259 + // path:
  260 + // - { node: root: kid_number: 3 }
  261 + // - { node: D, kid_number: 1 }
  262 + // node: F
  263 + // item_number: 2
  264 +
  265 + // iter2 (points to 1)
  266 + // path:
  267 + // - { node: root, kid_number: 0}
  268 + // node: A
  269 + // item_number: 0
  270 +
  271 + if (! this->impl.qpdf)
  272 + {
  273 + throw std::logic_error(
  274 + "NNTreeIterator::split called with null qpdf");
  275 + }
  276 + if (! valid())
  277 + {
  278 + throw std::logic_error(
  279 + "NNTreeIterator::split called an invalid iterator");
  280 + }
  281 +
  282 + // Find the array we actually need to split, which is either this
  283 + // node's kids or items.
  284 + auto kids = to_split.getKey("/Kids");
  285 + int nkids = kids.isArray() ? kids.getArrayNItems() : 0;
  286 + auto items = to_split.getKey(impl.details.itemsKey());
  287 + int nitems = items.isArray() ? items.getArrayNItems() : 0;
  288 +
  289 + QPDFObjectHandle first_half;
  290 + int n = 0;
  291 + std::string key;
  292 + int threshold = 0;
  293 + if (nkids > 0)
  294 + {
  295 + QTC::TC("qpdf", "NNTree split kids");
  296 + first_half = kids;
  297 + n = nkids;
  298 + threshold = impl.split_threshold;
  299 + key = "/Kids";
  300 + }
  301 + else if (nitems > 0)
  302 + {
  303 + QTC::TC("qpdf", "NNTree split items");
  304 + first_half = items;
  305 + n = nitems;
  306 + threshold = 2 * impl.split_threshold;
  307 + key = impl.details.itemsKey();
  308 + }
  309 + else
  310 + {
  311 + throw std::logic_error("NNTreeIterator::split called on invalid node");
  312 + }
  313 +
  314 + if (n <= threshold)
  315 + {
  316 + return;
  317 + }
  318 +
  319 + bool is_root = (parent == this->path.end());
  320 + bool is_leaf = (nitems > 0);
  321 +
  322 + // CURRENT STATE: tree is in original state; iterator is valid and
  323 + // unchanged.
  324 +
  325 + if (is_root)
  326 + {
  327 + // What we want to do is to create a new node for the second
  328 + // half of the items and put it in the parent's /Kids array
  329 + // right after the element that points to the current to_split
  330 + // node, but if we're splitting root, there is no parent, so
  331 + // handle that first.
  332 +
  333 + // In the non-root case, parent points to the path element
  334 + // whose /Kids contains the first half node, and the first
  335 + // half node is to_split. If we are splitting the root, we
  336 + // need to push everything down a level, but we want to keep
  337 + // the actual root object the same so that indirect references
  338 + // to it remain intact (and also in case it might be a direct
  339 + // object, which it shouldn't be but that case probably exists
  340 + // in the wild). To achieve this, we create a new node for the
  341 + // first half and then replace /Kids in the root to contain
  342 + // it. Then we adjust the path so that the first element is
  343 + // root and the second element, if any, is the new first half.
  344 + // In this way, we make the root case identical to the
  345 + // non-root case so remaining logic can handle them in the
  346 + // same way.
  347 +
  348 + auto first_node = impl.qpdf->makeIndirectObject(
  349 + QPDFObjectHandle::newDictionary());
  350 + first_node.replaceKey(key, first_half);
  351 + QPDFObjectHandle new_kids = QPDFObjectHandle::newArray();
  352 + new_kids.appendItem(first_node);
  353 + to_split.removeKey("/Limits"); // already shouldn't be there for root
  354 + to_split.removeKey(impl.details.itemsKey());
  355 + to_split.replaceKey("/Kids", new_kids);
  356 + if (is_leaf)
  357 + {
  358 + QTC::TC("qpdf", "NNTree split root + leaf");
  359 + this->node = first_node;
  360 + }
  361 + else
  362 + {
  363 + QTC::TC("qpdf", "NNTree split root + !leaf");
  364 + auto next = this->path.begin();
  365 + next->node = first_node;
  366 + }
  367 + this->path.push_front(PathElement(to_split, 0));
  368 + parent = this->path.begin();
  369 + to_split = first_node;
  370 + }
  371 +
  372 + // CURRENT STATE: parent is guaranteed to be defined, and we have
  373 + // the invariants that parent[/Kids][kid_number] == to_split and
  374 + // (++parent).node == to_split.
  375 +
  376 + // Create a second half array, and transfer the second half of the
  377 + // items into the second half array.
  378 + QPDFObjectHandle second_half = QPDFObjectHandle::newArray();
  379 + int start_idx = ((n / 2) & ~1);
  380 + while (first_half.getArrayNItems() > start_idx)
  381 + {
  382 + second_half.appendItem(first_half.getArrayItem(start_idx));
  383 + first_half.eraseItem(start_idx);
  384 + }
  385 + resetLimits(to_split, parent);
  386 +
  387 + // Create a new node to contain the second half
  388 + QPDFObjectHandle second_node = impl.qpdf->makeIndirectObject(
  389 + QPDFObjectHandle::newDictionary());
  390 + second_node.replaceKey(key, second_half);
  391 + resetLimits(second_node, parent);
  392 +
  393 + // CURRENT STATE: half the items from the kids or items array in
  394 + // the node being split have been moved into a new node. The new
  395 + // node is not yet attached to the tree. The iterator have a path
  396 + // element or leaf node that is out of bounds.
  397 +
  398 + // We need to adjust the parent to add the second node to /Kids
  399 + // and, if needed, update kid_number to traverse through it. We
  400 + // need to update to_split's path element, or the node if this is
  401 + // a leaf, so that the kid/item number points to the right place.
  402 +
  403 + auto parent_kids = parent->node.getKey("/Kids");
  404 + parent_kids.insertItem(parent->kid_number + 1, second_node);
  405 + auto cur_elem = parent;
  406 + ++cur_elem; // points to end() for leaf nodes
  407 + int old_idx = (is_leaf ? this->item_number : cur_elem->kid_number);
  408 + if (old_idx >= start_idx)
  409 + {
  410 + ++parent->kid_number;
  411 + if (is_leaf)
  412 + {
  413 + QTC::TC("qpdf", "NNTree split second half item");
  414 + setItemNumber(second_node, this->item_number - start_idx);
  415 + }
  416 + else
  417 + {
  418 + QTC::TC("qpdf", "NNTree split second half kid");
  419 + cur_elem->node = second_node;
  420 + cur_elem->kid_number -= start_idx;
  421 + }
  422 + }
  423 + if (! is_root)
  424 + {
  425 + QTC::TC("qpdf", "NNTree split parent");
  426 + auto next = parent->node;
  427 + resetLimits(next, parent);
  428 + --parent;
  429 + split(next, parent);
  430 + }
  431 +}
  432 +
  433 +std::list<NNTreeIterator::PathElement>::iterator
  434 +NNTreeIterator::lastPathElement()
  435 +{
  436 + auto result = this->path.end();
  437 + if (! this->path.empty())
  438 + {
  439 + --result;
  440 + }
  441 + return result;
  442 +}
  443 +
  444 +void
  445 +NNTreeIterator::insertAfter(QPDFObjectHandle key, QPDFObjectHandle value)
  446 +{
  447 + auto items = this->node.getKey(impl.details.itemsKey());
  448 + if (! items.isArray())
  449 + {
  450 + error(impl.qpdf, node, "node contains no items array");
  451 + }
  452 + if (items.getArrayNItems() < this->item_number + 2)
  453 + {
  454 + error(impl.qpdf, node, "items array is too short");
  455 + }
  456 + items.insertItem(this->item_number + 2, key);
  457 + items.insertItem(this->item_number + 3, value);
  458 + resetLimits(this->node, lastPathElement());
  459 + split(this->node, lastPathElement());
  460 +}
  461 +
110 462 NNTreeIterator&
111 463 NNTreeIterator::operator++()
112 464 {
... ... @@ -130,7 +482,11 @@ NNTreeIterator::operator*()
130 482 "attempt made to dereference an invalid"
131 483 " name/number tree iterator");
132 484 }
133   - auto items = this->node.getKey(details.itemsKey());
  485 + auto items = this->node.getKey(impl.details.itemsKey());
  486 + if (items.getArrayNItems() < this->item_number + 2)
  487 + {
  488 + error(impl.qpdf, node, "items array is too short");
  489 + }
134 490 return std::make_pair(items.getArrayItem(this->item_number),
135 491 items.getArrayItem(1+this->item_number));
136 492 }
... ... @@ -178,18 +534,18 @@ NNTreeIterator::addPathElement(QPDFObjectHandle const&amp; node,
178 534 this->path.push_back(PathElement(node, kid_number));
179 535 }
180 536  
181   -void
182   -NNTreeIterator::reset()
  537 +bool
  538 +NNTreeIterator::deepen(QPDFObjectHandle node, bool first, bool allow_empty)
183 539 {
184   - this->path.clear();
185   - this->item_number = -1;
186   -}
  540 + // Starting at this node, descend through the first or last kid
  541 + // until we reach a node with items. If we succeed, return true;
  542 + // otherwise return false and leave path alone.
  543 +
  544 + auto opath = this->path;
  545 + bool failed = false;
187 546  
188   -void
189   -NNTreeIterator::deepen(QPDFObjectHandle node, bool first)
190   -{
191 547 std::set<QPDFObjGen> seen;
192   - while (true)
  548 + while (! failed)
193 549 {
194 550 if (node.isIndirect())
195 551 {
... ... @@ -197,16 +553,25 @@ NNTreeIterator::deepen(QPDFObjectHandle node, bool first)
197 553 if (seen.count(og))
198 554 {
199 555 QTC::TC("qpdf", "NNTree deepen: loop");
200   - warn(qpdf, node,
  556 + warn(impl.qpdf, node,
201 557 "loop detected while traversing name/number tree");
202   - reset();
203   - return;
  558 + failed = true;
  559 + break;
204 560 }
205 561 seen.insert(og);
206 562 }
  563 + if (! node.isDictionary())
  564 + {
  565 + QTC::TC("qpdf", "NNTree node is not a dictionary");
  566 + warn(impl.qpdf, node,
  567 + "non-dictionary node while traversing name/number tree");
  568 + failed = true;
  569 + break;
  570 + }
  571 +
207 572 auto kids = node.getKey("/Kids");
208 573 int nkids = kids.isArray() ? kids.getArrayNItems() : 0;
209   - auto items = node.getKey(details.itemsKey());
  574 + auto items = node.getKey(impl.details.itemsKey());
210 575 int nitems = items.isArray() ? items.getArrayNItems() : 0;
211 576 if (nitems > 0)
212 577 {
... ... @@ -217,17 +582,51 @@ NNTreeIterator::deepen(QPDFObjectHandle node, bool first)
217 582 {
218 583 int kid_number = first ? 0 : nkids - 1;
219 584 addPathElement(node, kid_number);
220   - node = kids.getArrayItem(kid_number);
  585 + auto next = kids.getArrayItem(kid_number);
  586 + if (! next.isIndirect())
  587 + {
  588 + if (impl.qpdf && impl.auto_repair)
  589 + {
  590 + QTC::TC("qpdf", "NNTree fix indirect kid");
  591 + warn(impl.qpdf, node,
  592 + "converting kid number " +
  593 + QUtil::int_to_string(kid_number) +
  594 + " to an indirect object");
  595 + next = impl.qpdf->makeIndirectObject(next);
  596 + kids.setArrayItem(kid_number, next);
  597 + }
  598 + else
  599 + {
  600 + QTC::TC("qpdf", "NNTree warn indirect kid");
  601 + warn(impl.qpdf, node,
  602 + "kid number " + QUtil::int_to_string(kid_number) +
  603 + " is not an indirect object");
  604 + }
  605 + }
  606 + node = next;
  607 + }
  608 + else if (allow_empty && items.isArray())
  609 + {
  610 + QTC::TC("qpdf", "NNTree deepen found empty");
  611 + setItemNumber(node, -1);
  612 + break;
221 613 }
222 614 else
223 615 {
224 616 QTC::TC("qpdf", "NNTree deepen: invalid node");
225   - warn(qpdf, node,
226   - "name/number tree node has neither /Kids nor /Names");
227   - reset();
228   - return;
  617 + warn(impl.qpdf, node,
  618 + "name/number tree node has neither non-empty " +
  619 + impl.details.itemsKey() + " nor /Kids");
  620 + failed = true;
  621 + break;
229 622 }
230 623 }
  624 + if (failed)
  625 + {
  626 + this->path = opath;
  627 + return false;
  628 + }
  629 + return true;
231 630 }
232 631  
233 632 NNTreeImpl::NNTreeImpl(NNTreeDetails const& details,
... ... @@ -236,29 +635,37 @@ NNTreeImpl::NNTreeImpl(NNTreeDetails const&amp; details,
236 635 bool auto_repair) :
237 636 details(details),
238 637 qpdf(qpdf),
239   - oh(oh)
  638 + split_threshold(32),
  639 + oh(oh),
  640 + auto_repair(auto_repair)
240 641 {
241 642 }
242 643  
  644 +void
  645 +NNTreeImpl::setSplitThreshold(int split_threshold)
  646 +{
  647 + this->split_threshold = split_threshold;
  648 +}
  649 +
243 650 NNTreeImpl::iterator
244 651 NNTreeImpl::begin()
245 652 {
246   - iterator result(details, this->qpdf);
247   - result.deepen(this->oh, true);
  653 + iterator result(*this);
  654 + result.deepen(this->oh, true, true);
248 655 return result;
249 656 }
250 657  
251 658 NNTreeImpl::iterator
252 659 NNTreeImpl::end()
253 660 {
254   - return iterator(details, this->qpdf);
  661 + return iterator(*this);
255 662 }
256 663  
257 664 NNTreeImpl::iterator
258 665 NNTreeImpl::last()
259 666 {
260   - iterator result(details, this->qpdf);
261   - result.deepen(this->oh, false);
  667 + iterator result(*this);
  668 + result.deepen(this->oh, false, true);
262 669 return result;
263 670 }
264 671  
... ... @@ -282,9 +689,8 @@ NNTreeImpl::withinLimits(QPDFObjectHandle key, QPDFObjectHandle node)
282 689 }
283 690 else
284 691 {
285   - // The root node has no limits, so consider the item to be in
286   - // here if there are no limits. This will cause checking lower
287   - // items.
  692 + QTC::TC("qpdf", "NNTree missing limits");
  693 + error(qpdf, node, "node is missing /Limits");
288 694 }
289 695 return result;
290 696 }
... ... @@ -294,7 +700,7 @@ NNTreeImpl::binarySearch(
294 700 QPDFObjectHandle key, QPDFObjectHandle items,
295 701 int num_items, bool return_prev_if_not_found,
296 702 int (NNTreeImpl::*compare)(QPDFObjectHandle& key,
297   - QPDFObjectHandle& node,
  703 + QPDFObjectHandle& arr,
298 704 int item))
299 705 {
300 706 int max_idx = 1;
... ... @@ -372,6 +778,7 @@ NNTreeImpl::compareKeyItem(
372 778 if (! ((items.isArray() && (items.getArrayNItems() > (2 * idx)) &&
373 779 details.keyValid(items.getArrayItem(2 * idx)))))
374 780 {
  781 + QTC::TC("qpdf", "NNTree item is wrong type");
375 782 error(qpdf, this->oh,
376 783 "item at index " + QUtil::int_to_string(2 * idx) +
377 784 " is not the right type");
... ... @@ -386,6 +793,7 @@ NNTreeImpl::compareKeyKid(
386 793 if (! (kids.isArray() && (idx < kids.getArrayNItems()) &&
387 794 kids.getArrayItem(idx).isDictionary()))
388 795 {
  796 + QTC::TC("qpdf", "NNTree kid is invalid");
389 797 error(qpdf, this->oh,
390 798 "invalid kid at index " + QUtil::int_to_string(idx));
391 799 }
... ... @@ -393,12 +801,56 @@ NNTreeImpl::compareKeyKid(
393 801 }
394 802  
395 803  
  804 +void
  805 +NNTreeImpl::repair()
  806 +{
  807 + auto new_node = QPDFObjectHandle::newDictionary();
  808 + new_node.replaceKey(details.itemsKey(), QPDFObjectHandle::newArray());
  809 + NNTreeImpl repl(details, qpdf, new_node, false);
  810 + for (auto i: *this)
  811 + {
  812 + repl.insert(i.first, i.second);
  813 + }
  814 + this->oh.replaceKey("/Kids", new_node.getKey("/Kids"));
  815 + this->oh.replaceKey(
  816 + details.itemsKey(), new_node.getKey(details.itemsKey()));
  817 +}
  818 +
396 819 NNTreeImpl::iterator
397 820 NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found)
398 821 {
  822 + try
  823 + {
  824 + return findInternal(key, return_prev_if_not_found);
  825 + }
  826 + catch (QPDFExc& e)
  827 + {
  828 + if (this->auto_repair)
  829 + {
  830 + QTC::TC("qpdf", "NNTree repair");
  831 + warn(qpdf, this->oh,
  832 + std::string("attempting to repair after error: ") + e.what());
  833 + repair();
  834 + return findInternal(key, return_prev_if_not_found);
  835 + }
  836 + else
  837 + {
  838 + throw e;
  839 + }
  840 + }
  841 +}
  842 +
  843 +NNTreeImpl::iterator
  844 +NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found)
  845 +{
399 846 auto first_item = begin();
400 847 auto last_item = end();
401   - if (first_item.valid() &&
  848 + if (first_item == end())
  849 + {
  850 + // Empty
  851 + return end();
  852 + }
  853 + else if (first_item.valid() &&
402 854 details.keyValid((*first_item).first) &&
403 855 details.compareKeys(key, (*first_item).first) < 0)
404 856 {
... ... @@ -422,13 +874,14 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found)
422 874  
423 875 std::set<QPDFObjGen> seen;
424 876 auto node = this->oh;
425   - iterator result(details, this->qpdf);
  877 + iterator result(*this);
426 878  
427 879 while (true)
428 880 {
429 881 auto og = node.getObjGen();
430 882 if (seen.count(og))
431 883 {
  884 + QTC::TC("qpdf", "NNTree loop in find");
432 885 error(qpdf, node, "loop detected in find");
433 886 }
434 887 seen.insert(og);
... ... @@ -455,18 +908,67 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found)
455 908 &NNTreeImpl::compareKeyKid);
456 909 if (idx == -1)
457 910 {
  911 + QTC::TC("qpdf", "NNTree -1 in binary search");
458 912 error(qpdf, node,
459 913 "unexpected -1 from binary search of kids;"
460   - " tree may not be sorted");
  914 + " limits may by wrong");
461 915 }
462 916 result.addPathElement(node, idx);
463 917 node = kids.getArrayItem(idx);
464 918 }
465 919 else
466 920 {
  921 + QTC::TC("qpdf", "NNTree bad node during find");
467 922 error(qpdf, node, "bad node during find");
468 923 }
469 924 }
470 925  
471 926 return result;
472 927 }
  928 +
  929 +NNTreeImpl::iterator
  930 +NNTreeImpl::insertFirst(QPDFObjectHandle key, QPDFObjectHandle value)
  931 +{
  932 + auto iter = begin();
  933 + QPDFObjectHandle items;
  934 + if (iter.node.isInitialized() &&
  935 + iter.node.isDictionary())
  936 + {
  937 + items = iter.node.getKey(details.itemsKey());
  938 + }
  939 + if (! (items.isInitialized() && items.isArray()))
  940 + {
  941 + QTC::TC("qpdf", "NNTree no valid items node in insertFirst");
  942 + error(qpdf, this->oh, "unable to find a valid items node");
  943 + }
  944 + items.insertItem(0, key);
  945 + items.insertItem(1, value);
  946 + iter.item_number = 0;
  947 + iter.resetLimits(iter.node, iter.lastPathElement());
  948 + iter.split(iter.node, iter.lastPathElement());
  949 + return begin();
  950 +}
  951 +
  952 +NNTreeImpl::iterator
  953 +NNTreeImpl::insert(QPDFObjectHandle key, QPDFObjectHandle value)
  954 +{
  955 + auto iter = find(key, true);
  956 + if (! iter.valid())
  957 + {
  958 + QTC::TC("qpdf", "NNTree insert inserts first");
  959 + return insertFirst(key, value);
  960 + }
  961 + else if (details.compareKeys(key, (*iter).first) == 0)
  962 + {
  963 + QTC::TC("qpdf", "NNTree insert replaces");
  964 + auto items = iter.node.getKey(details.itemsKey());
  965 + items.setArrayItem(iter.item_number + 1, value);
  966 + }
  967 + else
  968 + {
  969 + QTC::TC("qpdf", "NNTree insert inserts after");
  970 + iter.insertAfter(key, value);
  971 + ++iter;
  972 + }
  973 + return iter;
  974 +}
... ...
libqpdf/QPDFNameTreeObjectHelper.cc
... ... @@ -122,6 +122,15 @@ QPDFNameTreeObjectHelper::find(std::string const&amp; key,
122 122 return iterator(std::make_shared<NNTreeIterator>(i));
123 123 }
124 124  
  125 +QPDFNameTreeObjectHelper::iterator
  126 +QPDFNameTreeObjectHelper::insert(std::string const& key,
  127 + QPDFObjectHandle value)
  128 +{
  129 + auto i = this->m->impl->insert(
  130 + QPDFObjectHandle::newUnicodeString(key), value);
  131 + return iterator(std::make_shared<NNTreeIterator>(i));
  132 +}
  133 +
125 134 bool
126 135 QPDFNameTreeObjectHelper::hasName(std::string const& name)
127 136 {
... ... @@ -142,6 +151,12 @@ QPDFNameTreeObjectHelper::findObject(
142 151 return true;
143 152 }
144 153  
  154 +void
  155 +QPDFNameTreeObjectHelper::setSplitThreshold(int t)
  156 +{
  157 + this->m->impl->setSplitThreshold(t);
  158 +}
  159 +
145 160 std::map<std::string, QPDFObjectHandle>
146 161 QPDFNameTreeObjectHelper::getAsMap() const
147 162 {
... ...
libqpdf/QPDFNumberTreeObjectHelper.cc
... ... @@ -118,6 +118,14 @@ QPDFNumberTreeObjectHelper::find(numtree_number key,
118 118 return iterator(std::make_shared<NNTreeIterator>(i));
119 119 }
120 120  
  121 +QPDFNumberTreeObjectHelper::iterator
  122 +QPDFNumberTreeObjectHelper::insert(numtree_number key, QPDFObjectHandle value)
  123 +{
  124 + auto i = this->m->impl->insert(
  125 + QPDFObjectHandle::newInteger(key), value);
  126 + return iterator(std::make_shared<NNTreeIterator>(i));
  127 +}
  128 +
121 129 QPDFNumberTreeObjectHelper::numtree_number
122 130 QPDFNumberTreeObjectHelper::getMin()
123 131 {
... ... @@ -175,6 +183,12 @@ QPDFNumberTreeObjectHelper::findObjectAtOrBelow(
175 183 return true;
176 184 }
177 185  
  186 +void
  187 +QPDFNumberTreeObjectHelper::setSplitThreshold(int t)
  188 +{
  189 + this->m->impl->setSplitThreshold(t);
  190 +}
  191 +
178 192 std::map<QPDFNumberTreeObjectHelper::numtree_number, QPDFObjectHandle>
179 193 QPDFNumberTreeObjectHelper::getAsMap() const
180 194 {
... ...
libqpdf/qpdf/NNTree.hh
... ... @@ -15,6 +15,7 @@ class NNTreeDetails
15 15 virtual int compareKeys(QPDFObjectHandle, QPDFObjectHandle) const = 0;
16 16 };
17 17  
  18 +class NNTreeImpl;
18 19 class NNTreeIterator: public std::iterator<
19 20 std::bidirectional_iterator_tag,
20 21 std::pair<QPDFObjectHandle, QPDFObjectHandle>,
... ... @@ -46,32 +47,34 @@ class NNTreeIterator: public std::iterator&lt;
46 47 return ! operator==(other);
47 48 }
48 49  
  50 + void insertAfter(
  51 + QPDFObjectHandle key, QPDFObjectHandle value);
  52 +
49 53 private:
50 54 class PathElement
51 55 {
52 56 public:
53 57 PathElement(QPDFObjectHandle const& node, int kid_number);
54   - QPDFObjectHandle getNextKid(bool backward);
55 58  
56 59 QPDFObjectHandle node;
57 60 int kid_number;
58 61 };
59 62  
60 63 // ABI: for qpdf 11, make qpdf a reference
61   - NNTreeIterator(NNTreeDetails const& details, QPDF* qpdf) :
62   - details(details),
63   - qpdf(qpdf),
64   - item_number(-1)
65   - {
66   - }
67   - void reset();
68   - void deepen(QPDFObjectHandle node, bool first);
  64 + NNTreeIterator(NNTreeImpl& impl);
  65 + bool deepen(QPDFObjectHandle node, bool first, bool allow_empty);
69 66 void setItemNumber(QPDFObjectHandle const& node, int);
70 67 void addPathElement(QPDFObjectHandle const& node, int kid_number);
  68 + QPDFObjectHandle getNextKid(PathElement& element, bool backward);
71 69 void increment(bool backward);
  70 + void resetLimits(QPDFObjectHandle node,
  71 + std::list<PathElement>::iterator parent);
72 72  
73   - NNTreeDetails const& details;
74   - QPDF* qpdf;
  73 + void split(QPDFObjectHandle to_split,
  74 + std::list<PathElement>::iterator parent);
  75 + std::list<PathElement>::iterator lastPathElement();
  76 +
  77 + NNTreeImpl& impl;
75 78 std::list<PathElement> path;
76 79 QPDFObjectHandle node;
77 80 int item_number;
... ... @@ -79,6 +82,7 @@ class NNTreeIterator: public std::iterator&lt;
79 82  
80 83 class NNTreeImpl
81 84 {
  85 + friend class NNTreeIterator;
82 86 public:
83 87 typedef NNTreeIterator iterator;
84 88  
... ... @@ -88,14 +92,24 @@ class NNTreeImpl
88 92 iterator end();
89 93 iterator last();
90 94 iterator find(QPDFObjectHandle key, bool return_prev_if_not_found = false);
  95 + iterator insertFirst(QPDFObjectHandle key, QPDFObjectHandle value);
  96 + iterator insert(QPDFObjectHandle key, QPDFObjectHandle value);
  97 +
  98 + // Change the split threshold for easier testing. There's no real
  99 + // reason to expose this to downstream tree helpers, but it has to
  100 + // be public so we can call it from the test suite.
  101 + void setSplitThreshold(int split_threshold);
91 102  
92 103 private:
  104 + void repair();
  105 + iterator findInternal(
  106 + QPDFObjectHandle key, bool return_prev_if_not_found = false);
93 107 int withinLimits(QPDFObjectHandle key, QPDFObjectHandle node);
94 108 int binarySearch(
95 109 QPDFObjectHandle key, QPDFObjectHandle items,
96 110 int num_items, bool return_prev_if_not_found,
97 111 int (NNTreeImpl::*compare)(QPDFObjectHandle& key,
98   - QPDFObjectHandle& node,
  112 + QPDFObjectHandle& arr,
99 113 int item));
100 114 int compareKeyItem(
101 115 QPDFObjectHandle& key, QPDFObjectHandle& items, int idx);
... ... @@ -104,7 +118,9 @@ class NNTreeImpl
104 118  
105 119 NNTreeDetails const& details;
106 120 QPDF* qpdf;
  121 + int split_threshold;
107 122 QPDFObjectHandle oh;
  123 + bool auto_repair;
108 124 };
109 125  
110 126 #endif // NNTREE_HH
... ...
manual/qpdf-manual.xml
... ... @@ -4857,7 +4857,9 @@ print &quot;\n&quot;;
4857 4857 <para>
4858 4858 Re-implement <classname>QPDFNameTreeObjectHelper</classname>
4859 4859 and <classname>QPDFNumberTreeObjectHelper</classname> to be
4860   - more efficient, and add an iterator-based API.
  4860 + more efficient, add an iterator-based API, give them the
  4861 + capability to repair broken trees, and create methods for
  4862 + modifying the trees.
4861 4863 </para>
4862 4864 </listitem>
4863 4865 </itemizedlist>
... ...
qpdf/qpdf.testcov
... ... @@ -524,3 +524,30 @@ QPDFWriter getFilterOnWrite false 0
524 524 QPDFPageObjectHelper::forEachXObject 3
525 525 NNTree deepen: invalid node 0
526 526 NNTree deepen: loop 0
  527 +NNTree skip invalid kid 0
  528 +NNTree skip item at end of short items 0
  529 +NNTree skip invalid key 0
  530 +NNTree no valid items node in insertFirst 0
  531 +NNTree deepen found empty 0
  532 +NNTree insert inserts first 0
  533 +NNTree insert replaces 0
  534 +NNTree insert inserts after 0
  535 +NNTree unable to determine limits 0
  536 +NNTree warn indirect kid 0
  537 +NNTree fix indirect kid 0
  538 +NNTree repair 0
  539 +NNTree split root + leaf 0
  540 +NNTree split root + !leaf 0
  541 +NNTree split kids 0
  542 +NNTree split items 0
  543 +NNTree split second half item 0
  544 +NNTree split parent 0
  545 +NNTree split second half kid 0
  546 +NNTree missing limits 0
  547 +NNTree item is wrong type 0
  548 +NNTree kid is invalid 0
  549 +NNTree loop in find 0
  550 +NNTree -1 in binary search 0
  551 +NNTree bad node during find 0
  552 +NNTree node is not a dictionary 0
  553 +NNTree limits didn't change 0
... ...
qpdf/qtest/qpdf.test
... ... @@ -583,7 +583,7 @@ foreach my $input (@ext_inputs)
583 583 show_ntests();
584 584 # ----------
585 585 $td->notify("--- Number and Name Trees ---");
586   -$n_tests += 2;
  586 +$n_tests += 4;
587 587  
588 588 $td->runtest("number trees",
589 589 {$td->COMMAND => "test_driver 46 number-tree.pdf"},
... ... @@ -593,6 +593,13 @@ $td-&gt;runtest(&quot;name trees&quot;,
593 593 {$td->COMMAND => "test_driver 48 name-tree.pdf"},
594 594 {$td->FILE => "name-tree.out", $td->EXIT_STATUS => 0},
595 595 $td->NORMALIZE_NEWLINES);
  596 +$td->runtest("nntree split",
  597 + {$td->COMMAND => "test_driver 74 split-nntree.pdf"},
  598 + {$td->FILE => "split-nntree.out", $td->EXIT_STATUS => 0},
  599 + $td->NORMALIZE_NEWLINES);
  600 +$td->runtest("check file",
  601 + {$td->FILE => "a.pdf"},
  602 + {$td->FILE => "split-nntree-out.pdf"});
596 603  
597 604 show_ntests();
598 605 # ----------
... ...
qpdf/qtest/qpdf/name-tree.out
... ... @@ -16,4 +16,32 @@
16 16 20 twenty -> twenty.
17 17 22 twenty-two -> twenty-two!
18 18 29 twenty-nine -> twenty-nine!
  19 +/Empty1
  20 +/Empty2
  21 +/Bad1: deprecated API
  22 +Name/Number tree node (object 16): item at index 2 is not the right type
  23 +/Bad1 -- wrong key type
  24 +WARNING: name-tree.pdf (Name/Number tree node (object 16)): attempting to repair after error: name-tree.pdf (Name/Number tree node (object 16)): item at index 2 is not the right type
  25 +WARNING: name-tree.pdf (Name/Number tree node (object 16)): item 2 has the wrong type
  26 +A
  27 +Q
  28 +Z
  29 +/Bad2 -- invalid kid
  30 +WARNING: name-tree.pdf (Name/Number tree node (object 17)): attempting to repair after error: name-tree.pdf (Name/Number tree node (object 19)): bad node during find
  31 +WARNING: name-tree.pdf (Name/Number tree node (object 17)): skipping over invalid kid at index 1
  32 +B
  33 +W
  34 +/Bad3 -- invalid kid
  35 +WARNING: name-tree.pdf (Name/Number tree node (object 25)): non-dictionary node while traversing name/number tree
  36 +/Bad4 -- invalid kid
  37 +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
  38 +WARNING: name-tree.pdf (Name/Number tree node (object 23)): skipping over invalid kid at index 1
  39 +C
  40 +Q
  41 +Z
  42 +/Bad5 -- loop in find
  43 +WARNING: name-tree.pdf (Name/Number tree node (object 28)): attempting to repair after error: name-tree.pdf (Name/Number tree node (object 30)): loop detected in find
  44 +WARNING: name-tree.pdf (Name/Number tree node (object 30)): loop detected while traversing name/number tree
  45 +/Bad6 -- bad limits
  46 +WARNING: name-tree.pdf (Name/Number tree node (object 32)): unable to determine limits
19 47 test 48 done
... ...
qpdf/qtest/qpdf/name-tree.pdf
... ... @@ -139,9 +139,219 @@ endobj
139 139 >>
140 140 endobj
141 141  
  142 +13 0 obj
  143 +<<
  144 + /Names [
  145 + ]
  146 +>>
  147 +endobj
  148 +
  149 +14 0 obj
  150 +<<
  151 + /Kids [
  152 + 15 0 R
  153 + ]
  154 +>>
  155 +endobj
  156 +
  157 +15 0 obj
  158 +<<
  159 + /Names [
  160 + ]
  161 +>>
  162 +endobj
  163 +
  164 +16 0 obj
  165 +<<
  166 + /Names [
  167 + (A) (A)
  168 + 6 (F)
  169 + (Q) (Q)
  170 + (Z) (Z)
  171 + ]
  172 +>>
  173 +endobj
  174 +
  175 +17 0 obj
  176 +<<
  177 + /Kids [
  178 + 18 0 R
  179 + 19 0 R
  180 + 20 0 R
  181 + ]
  182 +>>
  183 +endobj
  184 +
  185 +18 0 obj
  186 +<<
  187 + /Limits [ (B) (B) ]
  188 + /Names [
  189 + (B) (B)
  190 + ]
  191 +>>
  192 +endobj
  193 +
  194 +19 0 obj
  195 +<<
  196 + /Limits [ (F) (H) ]
  197 + /X (oops)
  198 +>>
  199 +endobj
  200 +
  201 +20 0 obj
  202 +<<
  203 + /Limits [ (W) (W) ]
  204 + /Names [
  205 + (W) (W)
  206 + ]
  207 +>>
  208 +endobj
  209 +
  210 +21 0 obj
  211 +<<
  212 + /Kids [
  213 + 22 0 R
  214 + ]
  215 +>>
  216 +endobj
  217 +
  218 +22 0 obj
  219 +<<
  220 + /Limits [ (A) (Z) ]
  221 + /Kids [
  222 + 25 0 R
  223 + ]
  224 +>>
  225 +endobj
  226 +
  227 +23 0 obj
  228 +<<
  229 + /Kids [
  230 + 24 0 R
  231 + 25 0 R
  232 + 26 0 R
  233 + 27 0 R
  234 + ]
  235 +>>
  236 +endobj
  237 +
  238 +24 0 obj
  239 +<<
  240 + /Limits [ (C) (C) ]
  241 + /Names [
  242 + (C) (C)
  243 + ]
  244 +>>
  245 +endobj
  246 +
  247 +25 0 obj
  248 +(oops)
  249 +endobj
  250 +
  251 +26 0 obj
  252 +<<
  253 + /Limits [ (Q) (Q) ]
  254 + /Names [
  255 + (Q) (Q)
  256 + ]
  257 +>>
  258 +endobj
  259 +
  260 +27 0 obj
  261 +<<
  262 + /Limits [ (Z) (Z) ]
  263 + /Names [
  264 + (Z) (Z)
  265 + ]
  266 +>>
  267 +endobj
  268 +
  269 +28 0 obj
  270 +<<
  271 + /Kids [
  272 + 29 0 R
  273 + 30 0 R
  274 + ]
  275 +>>
  276 +endobj
  277 +
  278 +29 0 obj
  279 +<<
  280 + /Limits [ (D) (D) ]
  281 + /Names [
  282 + (D) (D)
  283 + ]
  284 +>>
  285 +endobj
  286 +
  287 +30 0 obj
  288 +<<
  289 + /Limits [ (E) (Z) ]
  290 + /Kids [
  291 + 30 0 R
  292 + ]
  293 +>>
  294 +endobj
  295 +
  296 +31 0 obj
  297 +<<
  298 + /Kids [
  299 + 32 0 R
  300 + ]
  301 +>>
  302 +endobj
  303 +
  304 +32 0 obj
  305 +<<
  306 + /Limits [ (E) (Z) ]
  307 + /Kids [
  308 + 33 0 R
  309 + 34 0 R
  310 + 35 0 R
  311 + 36 0 R
  312 + ]
  313 +>>
  314 +endobj
  315 +
  316 +33 0 obj
  317 +<<
  318 + /Limits [ (E) (G) ]
  319 + /Names [
  320 + (E) (E)
  321 + (G) (G)
  322 + ]
  323 +>>
  324 +endobj
  325 +
  326 +34 0 obj
  327 +<<
  328 + /Limits [ (N) (N) ]
  329 + /Names [
  330 + (N) (N)
  331 + ]
  332 +>>
  333 +endobj
  334 +
  335 +35 0 obj
  336 +<<
  337 + /Limits [ (O) (O) ]
  338 + /Names [
  339 + (O) (O)
  340 + ]
  341 +>>
  342 +endobj
  343 +
  344 +36 0 obj
  345 +<<
  346 + /Limits [ (bad) ]
  347 + /Names [
  348 + (Q) (Q)
  349 + ]
  350 +>>
  351 +endobj
142 352  
143 353 xref
144   -0 13
  354 +0 37
145 355 0000000000 65535 f
146 356 0000000025 00000 n
147 357 0000000079 00000 n
... ... @@ -155,12 +365,44 @@ xref
155 365 0000000808 00000 n
156 366 0000000995 00000 n
157 367 0000001191 00000 n
  368 +0000001364 00000 n
  369 +0000001402 00000 n
  370 +0000001450 00000 n
  371 +0000001488 00000 n
  372 +0000001572 00000 n
  373 +0000001642 00000 n
  374 +0000001714 00000 n
  375 +0000001771 00000 n
  376 +0000001843 00000 n
  377 +0000001891 00000 n
  378 +0000001961 00000 n
  379 +0000002042 00000 n
  380 +0000002114 00000 n
  381 +0000002138 00000 n
  382 +0000002210 00000 n
  383 +0000002282 00000 n
  384 +0000002341 00000 n
  385 +0000002413 00000 n
  386 +0000002483 00000 n
  387 +0000002531 00000 n
  388 +0000002634 00000 n
  389 +0000002718 00000 n
  390 +0000002790 00000 n
  391 +0000002862 00000 n
158 392 trailer <<
159 393 /Root 1 0 R
160 394 /QTest 8 0 R
161   - /Size 13
  395 + /Empty1 13 0 R
  396 + /Empty2 14 0 R
  397 + /Bad1 16 0 R
  398 + /Bad2 17 0 R
  399 + /Bad3 21 0 R
  400 + /Bad4 23 0 R
  401 + /Bad5 28 0 R
  402 + /Bad6 31 0 R
  403 + /Size 37
162 404 /ID [<2c3b7a6ec7fc61db8a5db4eebf57f540><2c3b7a6ec7fc61db8a5db4eebf57f540>]
163 405 >>
164 406 startxref
165   -1365
  407 +2932
166 408 %%EOF
... ...
qpdf/qtest/qpdf/number-tree.out
... ... @@ -26,6 +26,39 @@
26 26 22 twenty-two
27 27 23 twenty-three
28 28 29 twenty-nine
29   -WARNING: number-tree.pdf (Name/Number tree node (object 14)): name/number tree node has neither /Kids nor /Names
  29 +/Bad1: deprecated API
  30 +/Bad1
  31 +WARNING: number-tree.pdf (Name/Number tree node (object 14)): name/number tree node has neither non-empty /Nums nor /Kids
30 32 WARNING: number-tree.pdf (Name/Number tree node (object 13)): loop detected while traversing name/number tree
  33 +/Bad2
  34 +10 (10)
  35 +WARNING: number-tree.pdf (Name/Number tree node (object 16)): item 2 has the wrong type
  36 +15 (15)
  37 +WARNING: number-tree.pdf (Name/Number tree node (object 16)): items array doesn't have enough elements
  38 +WARNING: number-tree.pdf (Name/Number tree node (object 15)): skipping over invalid kid at index 1
  39 +WARNING: number-tree.pdf (Name/Number tree node (object 17)): name/number tree node has neither non-empty /Nums nor /Kids
  40 +35 (35)
  41 +38 (38)
  42 +WARNING: number-tree.pdf (Name/Number tree node (object 19)): name/number tree node has neither non-empty /Nums nor /Kids
  43 +/Empty1
  44 +/Empty2
  45 +Insert into invalid
  46 +WARNING: number-tree.pdf (Name/Number tree node): name/number tree node has neither non-empty /Nums nor /Kids
  47 +WARNING: number-tree.pdf (Name/Number tree node): name/number tree node has neither non-empty /Nums nor /Kids
  48 +number-tree.pdf (Name/Number tree node): unable to find a valid items node
  49 +/Bad3, no repair
  50 +WARNING: number-tree.pdf (Name/Number tree node (object 23)): kid number 0 is not an indirect object
  51 +0 (zero)
  52 +10 (ten)
  53 +/Bad3, repair
  54 +WARNING: number-tree.pdf (Name/Number tree node (object 23)): converting kid number 0 to an indirect object
  55 +0 (zero)
  56 +10 (ten)
  57 +/Bad4 -- missing limits
  58 +WARNING: number-tree.pdf (Name/Number tree node (object 24)): attempting to repair after error: number-tree.pdf (Name/Number tree node (object 25)): node is missing /Limits
  59 +0 (0)
  60 +5 (5)
  61 +10 (10)
  62 +/Bad5 -- limit errors
  63 +WARNING: number-tree.pdf (Name/Number tree node (object 28)): attempting to repair after error: number-tree.pdf (Name/Number tree node (object 29)): unexpected -1 from binary search of kids; limits may by wrong
31 64 test 46 done
... ...
qpdf/qtest/qpdf/number-tree.pdf
... ... @@ -158,8 +158,155 @@ endobj
158 158 >>
159 159 endobj
160 160  
  161 +15 0 obj
  162 +<<
  163 + /Kids [
  164 + 16 0 R
  165 + 14 0 R
  166 + 17 0 R
  167 + 18 0 R
  168 + 19 0 R
  169 + ]
  170 +>>
  171 +endobj
  172 +
  173 +16 0 obj
  174 +<<
  175 + /Limits [ 10 20 ]
  176 + /Nums [
  177 + 10 (10)
  178 + (12) (12)
  179 + 15 (15)
  180 + 20
  181 + ]
  182 +>>
  183 +endobj
  184 +
  185 +17 0 obj
  186 +<<
  187 + /Limits [ 25 25 ]
  188 + /Nums [
  189 + ]
  190 +>>
  191 +endobj
  192 +
  193 +18 0 obj
  194 +<<
  195 + /Limits [ 35 35 ]
  196 + /Nums [
  197 + 35 (35)
  198 + 38 (38)
  199 + ]
  200 +>>
  201 +endobj
  202 +
  203 +19 0 obj
  204 +<<
  205 + /Limits [ 40 40 ]
  206 + /Nums [
  207 + ]
  208 +>>
  209 +endobj
  210 +
  211 +20 0 obj
  212 +<<
  213 + /Nums [
  214 + ]
  215 +>>
  216 +endobj
  217 +
  218 +21 0 obj
  219 +<<
  220 + /Kids [
  221 + 22 0 R
  222 + ]
  223 +>>
  224 +endobj
  225 +
  226 +22 0 obj
  227 +<<
  228 + /Nums [
  229 + ]
  230 +>>
  231 +endobj
  232 +
  233 +23 0 obj
  234 +<<
  235 + /Kids [
  236 + <<
  237 + /Limits [ 0 10 ]
  238 + /Nums [
  239 + 0 (zero)
  240 + 10 (ten)
  241 + ]
  242 + >>
  243 + ]
  244 +>>
  245 +endobj
  246 +
  247 +24 0 obj
  248 +<<
  249 + /Kids [
  250 + 25 0 R
  251 + ]
  252 +>>
  253 +endobj
  254 +
  255 +25 0 obj
  256 +<<
  257 + /Kids [
  258 + 26 0 R
  259 + 27 0 R
  260 + ]
  261 +>>
  262 +endobj
  263 +
  264 +26 0 obj
  265 +<<
  266 + /Nums [
  267 + 0 (0)
  268 + ]
  269 +>>
  270 +endobj
  271 +
  272 +27 0 obj
  273 +<<
  274 + /Nums [
  275 + 10 (10)
  276 + ]
  277 +>>
  278 +endobj
  279 +
  280 +28 0 obj
  281 +<<
  282 + /Kids [
  283 + 29 0 R
  284 + ]
  285 +>>
  286 +endobj
  287 +
  288 +29 0 obj
  289 +<<
  290 + /Limits [ 5 15 ]
  291 + /Kids [
  292 + 30 0 R
  293 + ]
  294 +>>
  295 +endobj
  296 +
  297 +30 0 obj
  298 +<<
  299 + /Limits [ 20 30 ]
  300 + /Nums [
  301 + 2 (2)
  302 + 20 (20)
  303 + 30 (30)
  304 + ]
  305 +>>
  306 +endobj
  307 +
161 308 xref
162   -0 15
  309 +0 31
163 310 0000000000 65535 f
164 311 0000000025 00000 n
165 312 0000000079 00000 n
... ... @@ -175,13 +322,35 @@ xref
175 322 0000001078 00000 n
176 323 0000001214 00000 n
177 324 0000001273 00000 n
  325 +0000001296 00000 n
  326 +0000001388 00000 n
  327 +0000001490 00000 n
  328 +0000001547 00000 n
  329 +0000001628 00000 n
  330 +0000001685 00000 n
  331 +0000001722 00000 n
  332 +0000001770 00000 n
  333 +0000001807 00000 n
  334 +0000001937 00000 n
  335 +0000001985 00000 n
  336 +0000002044 00000 n
  337 +0000002091 00000 n
  338 +0000002140 00000 n
  339 +0000002188 00000 n
  340 +0000002255 00000 n
178 341 trailer <<
179 342 /Root 1 0 R
180 343 /QTest 8 0 R
181 344 /Bad1 13 0 R
182   - /Size 15
  345 + /Bad2 15 0 R
  346 + /Bad3 23 0 R
  347 + /Bad4 24 0 R
  348 + /Bad5 28 0 R
  349 + /Empty1 20 0 R
  350 + /Empty2 21 0 R
  351 + /Size 31
183 352 /ID [<2c3b7a6ec7fc61db8a5db4eebf57f540><2c3b7a6ec7fc61db8a5db4eebf57f540>]
184 353 >>
185 354 startxref
186   -1296
  355 +2346
187 356 %%EOF
... ...
qpdf/qtest/qpdf/split-nntree-out.pdf 0 → 100644
  1 +%PDF-1.3
  2 +%¿÷¢þ
  3 +%QDF-1.0
  4 +
  5 +%% Original object ID: 1 0
  6 +1 0 obj
  7 +<<
  8 + /Pages 5 0 R
  9 + /Type /Catalog
  10 +>>
  11 +endobj
  12 +
  13 +%% Original object ID: 8 0
  14 +2 0 obj
  15 +<<
  16 + /Kids [
  17 + 6 0 R
  18 + 7 0 R
  19 + ]
  20 +>>
  21 +endobj
  22 +
  23 +%% Original object ID: 17 0
  24 +3 0 obj
  25 +<<
  26 + /Kids [
  27 + 8 0 R
  28 + 9 0 R
  29 + ]
  30 +>>
  31 +endobj
  32 +
  33 +%% Original object ID: 18 0
  34 +4 0 obj
  35 +<<
  36 + /Kids [
  37 + 10 0 R
  38 + 11 0 R
  39 + ]
  40 +>>
  41 +endobj
  42 +
  43 +%% Original object ID: 2 0
  44 +5 0 obj
  45 +<<
  46 + /Count 1
  47 + /Kids [
  48 + 12 0 R
  49 + ]
  50 + /Type /Pages
  51 +>>
  52 +endobj
  53 +
  54 +%% Original object ID: 20 0
  55 +6 0 obj
  56 +<<
  57 + /Kids [
  58 + 13 0 R
  59 + 14 0 R
  60 + ]
  61 + /Limits [
  62 + 10
  63 + 40
  64 + ]
  65 +>>
  66 +endobj
  67 +
  68 +%% Original object ID: 21 0
  69 +7 0 obj
  70 +<<
  71 + /Kids [
  72 + 15 0 R
  73 + 16 0 R
  74 + 17 0 R
  75 + 18 0 R
  76 + ]
  77 + /Limits [
  78 + 50
  79 + 170
  80 + ]
  81 +>>
  82 +endobj
  83 +
  84 +%% Original object ID: 24 0
  85 +8 0 obj
  86 +<<
  87 + /Limits [
  88 + (A)
  89 + (C)
  90 + ]
  91 + /Names [
  92 + (A)
  93 + (A)
  94 + (C)
  95 + (C)
  96 + ]
  97 +>>
  98 +endobj
  99 +
  100 +%% Original object ID: 25 0
  101 +9 0 obj
  102 +<<
  103 + /Limits [
  104 + (F)
  105 + (Q)
  106 + ]
  107 + /Names [
  108 + (F)
  109 + (F)
  110 + (L)
  111 + (L)
  112 + (Q)
  113 + (Q)
  114 + ]
  115 +>>
  116 +endobj
  117 +
  118 +%% Original object ID: 26 0
  119 +10 0 obj
  120 +<<
  121 + /Limits [
  122 + (A)
  123 + (F)
  124 + ]
  125 + /Names [
  126 + (A)
  127 + (A)
  128 + (F)
  129 + (F)
  130 + ]
  131 +>>
  132 +endobj
  133 +
  134 +%% Original object ID: 27 0
  135 +11 0 obj
  136 +<<
  137 + /Limits [
  138 + (L)
  139 + <feff03c0>
  140 + ]
  141 + /Names [
  142 + (L)
  143 + (L)
  144 + (P)
  145 + (P)
  146 + (Q)
  147 + (Q)
  148 + <feff03c0>
  149 + <feff03c0>
  150 + ]
  151 +>>
  152 +endobj
  153 +
  154 +%% Page 1
  155 +%% Original object ID: 3 0
  156 +12 0 obj
  157 +<<
  158 + /Contents 19 0 R
  159 + /MediaBox [
  160 + 0
  161 + 0
  162 + 612
  163 + 792
  164 + ]
  165 + /Parent 5 0 R
  166 + /Resources <<
  167 + /Font <<
  168 + /F1 21 0 R
  169 + >>
  170 + /ProcSet 22 0 R
  171 + >>
  172 + /Type /Page
  173 +>>
  174 +endobj
  175 +
  176 +%% Original object ID: 9 0
  177 +13 0 obj
  178 +<<
  179 + /Limits [
  180 + 10
  181 + 15
  182 + ]
  183 + /Nums [
  184 + 10
  185 + (10)
  186 + 15
  187 + (15)
  188 + ]
  189 +>>
  190 +endobj
  191 +
  192 +%% Original object ID: 19 0
  193 +14 0 obj
  194 +<<
  195 + /Limits [
  196 + 20
  197 + 40
  198 + ]
  199 + /Nums [
  200 + 20
  201 + (20)
  202 + 30
  203 + (30)
  204 + 35
  205 + (35)
  206 + 40
  207 + (40)
  208 + ]
  209 +>>
  210 +endobj
  211 +
  212 +%% Original object ID: 10 0
  213 +15 0 obj
  214 +<<
  215 + /Limits [
  216 + 50
  217 + 80
  218 + ]
  219 + /Nums [
  220 + 50
  221 + (50)
  222 + 60
  223 + (60)
  224 + 70
  225 + (70)
  226 + 80
  227 + (80)
  228 + ]
  229 +>>
  230 +endobj
  231 +
  232 +%% Original object ID: 11 0
  233 +16 0 obj
  234 +<<
  235 + /Kids [
  236 + 23 0 R
  237 + 24 0 R
  238 + ]
  239 + /Limits [
  240 + 90
  241 + 100
  242 + ]
  243 +>>
  244 +endobj
  245 +
  246 +%% Original object ID: 23 0
  247 +17 0 obj
  248 +<<
  249 + /Kids [
  250 + 25 0 R
  251 + 26 0 R
  252 + 27 0 R
  253 + ]
  254 + /Limits [
  255 + 110
  256 + 160
  257 + ]
  258 +>>
  259 +endobj
  260 +
  261 +%% Original object ID: 16 0
  262 +18 0 obj
  263 +<<
  264 + /Limits [
  265 + 170
  266 + 170
  267 + ]
  268 + /Nums [
  269 + 170
  270 + (170)
  271 + ]
  272 +>>
  273 +endobj
  274 +
  275 +%% Contents for page 1
  276 +%% Original object ID: 4 0
  277 +19 0 obj
  278 +<<
  279 + /Length 20 0 R
  280 +>>
  281 +stream
  282 +BT
  283 + /F1 24 Tf
  284 + 72 720 Td
  285 + (Potato) Tj
  286 +ET
  287 +endstream
  288 +endobj
  289 +
  290 +20 0 obj
  291 +44
  292 +endobj
  293 +
  294 +%% Original object ID: 6 0
  295 +21 0 obj
  296 +<<
  297 + /BaseFont /Helvetica
  298 + /Encoding /WinAnsiEncoding
  299 + /Name /F1
  300 + /Subtype /Type1
  301 + /Type /Font
  302 +>>
  303 +endobj
  304 +
  305 +%% Original object ID: 7 0
  306 +22 0 obj
  307 +[
  308 + /PDF
  309 + /Text
  310 +]
  311 +endobj
  312 +
  313 +%% Original object ID: 12 0
  314 +23 0 obj
  315 +<<
  316 + /Limits [
  317 + 90
  318 + 90
  319 + ]
  320 + /Nums [
  321 + 90
  322 + (90)
  323 + ]
  324 +>>
  325 +endobj
  326 +
  327 +%% Original object ID: 13 0
  328 +24 0 obj
  329 +<<
  330 + /Limits [
  331 + 100
  332 + 100
  333 + ]
  334 + /Nums [
  335 + 100
  336 + (100)
  337 + ]
  338 +>>
  339 +endobj
  340 +
  341 +%% Original object ID: 14 0
  342 +25 0 obj
  343 +<<
  344 + /Limits [
  345 + 110
  346 + 120
  347 + ]
  348 + /Nums [
  349 + 110
  350 + (110)
  351 + 120
  352 + (120)
  353 + ]
  354 +>>
  355 +endobj
  356 +
  357 +%% Original object ID: 22 0
  358 +26 0 obj
  359 +<<
  360 + /Limits [
  361 + 125
  362 + 140
  363 + ]
  364 + /Nums [
  365 + 125
  366 + (125)
  367 + 130
  368 + (130)
  369 + 140
  370 + (140)
  371 + ]
  372 +>>
  373 +endobj
  374 +
  375 +%% Original object ID: 15 0
  376 +27 0 obj
  377 +<<
  378 + /Limits [
  379 + 150
  380 + 160
  381 + ]
  382 + /Nums [
  383 + 150
  384 + (150)
  385 + 160
  386 + (160)
  387 + ]
  388 +>>
  389 +endobj
  390 +
  391 +xref
  392 +0 28
  393 +0000000000 65535 f
  394 +0000000052 00000 n
  395 +0000000133 00000 n
  396 +0000000217 00000 n
  397 +0000000301 00000 n
  398 +0000000386 00000 n
  399 +0000000487 00000 n
  400 +0000000603 00000 n
  401 +0000000742 00000 n
  402 +0000000871 00000 n
  403 +0000001016 00000 n
  404 +0000001146 00000 n
  405 +0000001338 00000 n
  406 +0000001561 00000 n
  407 +0000001688 00000 n
  408 +0000001847 00000 n
  409 +0000002006 00000 n
  410 +0000002124 00000 n
  411 +0000002254 00000 n
  412 +0000002391 00000 n
  413 +0000002492 00000 n
  414 +0000002539 00000 n
  415 +0000002685 00000 n
  416 +0000002749 00000 n
  417 +0000002860 00000 n
  418 +0000002975 00000 n
  419 +0000003108 00000 n
  420 +0000003259 00000 n
  421 +trailer <<
  422 + /Root 1 0 R
  423 + /Size 28
  424 + /Split1 2 0 R
  425 + /Split2 3 0 R
  426 + /Split3 4 0 R
  427 + /ID [<2c3b7a6ec7fc61db8a5db4eebf57f540><31415926535897932384626433832795>]
  428 +>>
  429 +startxref
  430 +3364
  431 +%%EOF
... ...
qpdf/qtest/qpdf/split-nntree.out 0 → 100644
  1 +/Split1
  2 +10
  3 +15
  4 +20
  5 +30
  6 +35
  7 +40
  8 +50
  9 +60
  10 +70
  11 +80
  12 +90
  13 +100
  14 +110
  15 +120
  16 +125
  17 +130
  18 +140
  19 +150
  20 +160
  21 +170
  22 +/Split2
  23 +A
  24 +C
  25 +F
  26 +L
  27 +Q
  28 +/Split3
  29 +A (A)
  30 +F (F)
  31 +L (L)
  32 +P (P)
  33 +Q (Q)
  34 +π <feff03c0>
  35 +test 74 done
... ...
qpdf/qtest/qpdf/split-nntree.pdf 0 → 100644
  1 +%PDF-1.3
  2 +%¿÷¢þ
  3 +%QDF-1.0
  4 +
  5 +1 0 obj
  6 +<<
  7 + /Pages 2 0 R
  8 + /Type /Catalog
  9 +>>
  10 +endobj
  11 +
  12 +2 0 obj
  13 +<<
  14 + /Count 1
  15 + /Kids [
  16 + 3 0 R
  17 + ]
  18 + /Type /Pages
  19 +>>
  20 +endobj
  21 +
  22 +%% Page 1
  23 +3 0 obj
  24 +<<
  25 + /Contents 4 0 R
  26 + /MediaBox [
  27 + 0
  28 + 0
  29 + 612
  30 + 792
  31 + ]
  32 + /Parent 2 0 R
  33 + /Resources <<
  34 + /Font <<
  35 + /F1 6 0 R
  36 + >>
  37 + /ProcSet 7 0 R
  38 + >>
  39 + /Type /Page
  40 +>>
  41 +endobj
  42 +
  43 +%% Contents for page 1
  44 +4 0 obj
  45 +<<
  46 + /Length 5 0 R
  47 +>>
  48 +stream
  49 +BT
  50 + /F1 24 Tf
  51 + 72 720 Td
  52 + (Potato) Tj
  53 +ET
  54 +endstream
  55 +endobj
  56 +
  57 +5 0 obj
  58 +44
  59 +endobj
  60 +
  61 +6 0 obj
  62 +<<
  63 + /BaseFont /Helvetica
  64 + /Encoding /WinAnsiEncoding
  65 + /Name /F1
  66 + /Subtype /Type1
  67 + /Type /Font
  68 +>>
  69 +endobj
  70 +
  71 +7 0 obj
  72 +[
  73 + /PDF
  74 + /Text
  75 +]
  76 +endobj
  77 +
  78 +8 0 obj
  79 +<<
  80 + /Kids [
  81 + 9 0 R
  82 + 10 0 R
  83 + 11 0 R
  84 + 16 0 R
  85 + ]
  86 +>>
  87 +endobj
  88 +
  89 +9 0 obj
  90 +<<
  91 + /Limits [ 10 40 ]
  92 + /Nums [
  93 + 10 (10)
  94 + 20 (20)
  95 + 30 (30)
  96 + 40 (40)
  97 + ]
  98 +>>
  99 +endobj
  100 +
  101 +10 0 obj
  102 +<<
  103 + /Limits [ 50 80 ]
  104 + /Nums [
  105 + 50 (50)
  106 + 60 (60)
  107 + 70 (70)
  108 + 80 (80)
  109 + ]
  110 +>>
  111 +endobj
  112 +
  113 +11 0 obj
  114 +<<
  115 + /Limits [ 90 160 ]
  116 + /Kids [
  117 + 12 0 R
  118 + 13 0 R
  119 + 14 0 R
  120 + 15 0 R
  121 + ]
  122 +>>
  123 +endobj
  124 +
  125 +12 0 obj
  126 +<<
  127 + /Limits [ 90 90 ]
  128 + /Nums [
  129 + 90 (90)
  130 + ]
  131 +>>
  132 +endobj
  133 +
  134 +13 0 obj
  135 +<<
  136 + /Limits [ 100 100 ]
  137 + /Nums [
  138 + 100 (100)
  139 + ]
  140 +>>
  141 +endobj
  142 +
  143 +14 0 obj
  144 +<<
  145 + /Limits [ 110 140 ]
  146 + /Nums [
  147 + 110 (110)
  148 + 120 (120)
  149 + 130 (130)
  150 + 140 (140)
  151 + ]
  152 +>>
  153 +endobj
  154 +
  155 +15 0 obj
  156 +<<
  157 + /Limits [ 150 160 ]
  158 + /Nums [
  159 + 150 (150)
  160 + 160 (160)
  161 + ]
  162 +>>
  163 +endobj
  164 +
  165 +16 0 obj
  166 +<<
  167 + /Limits [ 170 170 ]
  168 + /Nums [
  169 + 170 (170)
  170 + ]
  171 +>>
  172 +endobj
  173 +
  174 +17 0 obj
  175 +<<
  176 + /Names [
  177 + (A) (A)
  178 + (F) (F)
  179 + (L) (L)
  180 + (Q) (Q)
  181 + ]
  182 +>>
  183 +endobj
  184 +
  185 +18 0 obj
  186 +<<
  187 + /Names [
  188 + (A) (A)
  189 + (F) (F)
  190 + (L) (L)
  191 + (Q) (Q)
  192 + ]
  193 +>>
  194 +endobj
  195 +
  196 +xref
  197 +0 19
  198 +0000000000 65535 f
  199 +0000000025 00000 n
  200 +0000000079 00000 n
  201 +0000000161 00000 n
  202 +0000000376 00000 n
  203 +0000000475 00000 n
  204 +0000000494 00000 n
  205 +0000000612 00000 n
  206 +0000000647 00000 n
  207 +0000000726 00000 n
  208 +0000000830 00000 n
  209 +0000000935 00000 n
  210 +0000001037 00000 n
  211 +0000001106 00000 n
  212 +0000001179 00000 n
  213 +0000001294 00000 n
  214 +0000001381 00000 n
  215 +0000001454 00000 n
  216 +0000001540 00000 n
  217 +trailer <<
  218 + /Root 1 0 R
  219 + /Split1 8 0 R
  220 + /Split2 17 0 R
  221 + /Split3 18 0 R
  222 + /Size 19
  223 + /ID [<2c3b7a6ec7fc61db8a5db4eebf57f540><2c3b7a6ec7fc61db8a5db4eebf57f540>]
  224 +>>
  225 +startxref
  226 +1626
  227 +%%EOF
... ...
qpdf/test_driver.cc
... ... @@ -1777,14 +1777,92 @@ void runtest(int n, char const* filename1, char const* arg2)
1777 1777 assert(2 == offset);
1778 1778  
1779 1779 // Exercise deprecated API until qpdf 11
  1780 + std::cout << "/Bad1: deprecated API" << std::endl;
1780 1781 auto bad1 = QPDFNumberTreeObjectHelper(
1781 1782 pdf.getTrailer().getKey("/Bad1"));
1782 1783 assert(bad1.begin() == bad1.end());
1783 1784  
  1785 + std::cout << "/Bad1" << std::endl;
1784 1786 bad1 = QPDFNumberTreeObjectHelper(
1785 1787 pdf.getTrailer().getKey("/Bad1"), pdf);
1786 1788 assert(bad1.begin() == bad1.end());
1787 1789 assert(bad1.last() == bad1.end());
  1790 +
  1791 + std::cout << "/Bad2" << std::endl;
  1792 + auto bad2 = QPDFNumberTreeObjectHelper(
  1793 + pdf.getTrailer().getKey("/Bad2"), pdf);
  1794 + for (auto i: bad2)
  1795 + {
  1796 + std::cout << i.first << " " << i.second.unparse() << std::endl;
  1797 + }
  1798 +
  1799 + std::vector<std::string> empties = {"/Empty1", "/Empty2"};
  1800 + for (auto const& k: empties)
  1801 + {
  1802 + std::cout << k << std::endl;
  1803 + auto empty = QPDFNumberTreeObjectHelper(
  1804 + pdf.getTrailer().getKey(k), pdf);
  1805 + assert(empty.begin() == empty.end());
  1806 + assert(empty.last() == empty.end());
  1807 + auto i = empty.insert(5, QPDFObjectHandle::newString("5"));
  1808 + assert((*i).first == 5);
  1809 + assert((*i).second.getStringValue() == "5");
  1810 + assert((*empty.begin()).first == 5);
  1811 + assert((*empty.last()).first == 5);
  1812 + assert((*empty.begin()).second.getStringValue() == "5");
  1813 + i = empty.insert(5, QPDFObjectHandle::newString("5+"));
  1814 + assert((*i).first == 5);
  1815 + assert((*i).second.getStringValue() == "5+");
  1816 + assert((*empty.begin()).second.getStringValue() == "5+");
  1817 + i = empty.insert(6, QPDFObjectHandle::newString("6"));
  1818 + assert((*i).first == 6);
  1819 + assert((*i).second.getStringValue() == "6");
  1820 + assert((*empty.begin()).second.getStringValue() == "5+");
  1821 + assert((*empty.last()).first == 6);
  1822 + assert((*empty.last()).second.getStringValue() == "6");
  1823 + }
  1824 + std::cout << "Insert into invalid" << std::endl;
  1825 + auto invalid1 = QPDFNumberTreeObjectHelper(
  1826 + QPDFObjectHandle::newDictionary(), pdf);
  1827 + try
  1828 + {
  1829 + invalid1.insert(1, QPDFObjectHandle::newNull());
  1830 + }
  1831 + catch (QPDFExc& e)
  1832 + {
  1833 + std::cout << e.what() << std::endl;
  1834 + }
  1835 +
  1836 + std::cout << "/Bad3, no repair" << std::endl;
  1837 + auto bad3_oh = pdf.getTrailer().getKey("/Bad3");
  1838 + auto bad3 = QPDFNumberTreeObjectHelper(bad3_oh, pdf, false);
  1839 + for (auto i: bad3)
  1840 + {
  1841 + std::cout << i.first << " " << i.second.unparse() << std::endl;
  1842 + }
  1843 + assert(! bad3_oh.getKey("/Kids").getArrayItem(0).isIndirect());
  1844 +
  1845 + std::cout << "/Bad3, repair" << std::endl;
  1846 + bad3 = QPDFNumberTreeObjectHelper(bad3_oh, pdf, true);
  1847 + for (auto i: bad3)
  1848 + {
  1849 + std::cout << i.first << " " << i.second.unparse() << std::endl;
  1850 + }
  1851 + assert(bad3_oh.getKey("/Kids").getArrayItem(0).isIndirect());
  1852 +
  1853 + std::cout << "/Bad4 -- missing limits" << std::endl;
  1854 + auto bad4 = QPDFNumberTreeObjectHelper(
  1855 + pdf.getTrailer().getKey("/Bad4"), pdf);
  1856 + bad4.insert(5, QPDFObjectHandle::newString("5"));
  1857 + for (auto i: bad4)
  1858 + {
  1859 + std::cout << i.first << " " << i.second.unparse() << std::endl;
  1860 + }
  1861 +
  1862 + std::cout << "/Bad5 -- limit errors" << std::endl;
  1863 + auto bad5 = QPDFNumberTreeObjectHelper(
  1864 + pdf.getTrailer().getKey("/Bad5"), pdf);
  1865 + assert(bad5.find(10) == bad5.end());
1788 1866 }
1789 1867 else if (n == 47)
1790 1868 {
... ... @@ -1830,6 +1908,88 @@ void runtest(int n, char const* filename1, char const* arg2)
1830 1908 auto last = ntoh.last();
1831 1909 assert((*last).first == "29 twenty-nine");
1832 1910 assert((*last).second.getUTF8Value() == "twenty-nine!");
  1911 +
  1912 + std::vector<std::string> empties = {"/Empty1", "/Empty2"};
  1913 + for (auto const& k: empties)
  1914 + {
  1915 + std::cout << k << std::endl;
  1916 + auto empty = QPDFNameTreeObjectHelper(
  1917 + pdf.getTrailer().getKey(k), pdf);
  1918 + assert(empty.begin() == empty.end());
  1919 + assert(empty.last() == empty.end());
  1920 + auto i = empty.insert("five", QPDFObjectHandle::newString("5"));
  1921 + assert((*i).first == "five");
  1922 + assert((*i).second.getStringValue() == "5");
  1923 + assert((*empty.begin()).first == "five");
  1924 + assert((*empty.last()).first == "five");
  1925 + assert((*empty.begin()).second.getStringValue() == "5");
  1926 + i = empty.insert("five", QPDFObjectHandle::newString("5+"));
  1927 + assert((*i).first == "five");
  1928 + assert((*i).second.getStringValue() == "5+");
  1929 + assert((*empty.begin()).second.getStringValue() == "5+");
  1930 + i = empty.insert("six", QPDFObjectHandle::newString("6"));
  1931 + assert((*i).first == "six");
  1932 + assert((*i).second.getStringValue() == "6");
  1933 + assert((*empty.begin()).second.getStringValue() == "5+");
  1934 + assert((*empty.last()).first == "six");
  1935 + assert((*empty.last()).second.getStringValue() == "6");
  1936 + }
  1937 +
  1938 + // Exercise deprecated API until qpdf 11
  1939 + std::cout << "/Bad1: deprecated API" << std::endl;
  1940 + auto bad1 = QPDFNameTreeObjectHelper(
  1941 + pdf.getTrailer().getKey("/Bad1"));
  1942 + try
  1943 + {
  1944 + bad1.find("G", true);
  1945 + assert(false);
  1946 + }
  1947 + catch (std::runtime_error& e)
  1948 + {
  1949 + std::cout << e.what() << std::endl;
  1950 + }
  1951 +
  1952 + std::cout << "/Bad1 -- wrong key type" << std::endl;
  1953 + bad1 = QPDFNameTreeObjectHelper(
  1954 + pdf.getTrailer().getKey("/Bad1"), pdf);
  1955 + assert((*bad1.find("G", true)).first == "A");
  1956 + for (auto i: bad1)
  1957 + {
  1958 + std::cout << i.first << std::endl;
  1959 + }
  1960 +
  1961 + std::cout << "/Bad2 -- invalid kid" << std::endl;
  1962 + auto bad2 = QPDFNameTreeObjectHelper(
  1963 + pdf.getTrailer().getKey("/Bad2"), pdf);
  1964 + assert((*bad2.find("G", true)).first == "B");
  1965 + for (auto i: bad2)
  1966 + {
  1967 + std::cout << i.first << std::endl;
  1968 + }
  1969 +
  1970 + std::cout << "/Bad3 -- invalid kid" << std::endl;
  1971 + auto bad3 = QPDFNameTreeObjectHelper(
  1972 + pdf.getTrailer().getKey("/Bad3"), pdf);
  1973 + assert(bad3.find("G", true) == bad3.end());
  1974 +
  1975 + std::cout << "/Bad4 -- invalid kid" << std::endl;
  1976 + auto bad4 = QPDFNameTreeObjectHelper(
  1977 + pdf.getTrailer().getKey("/Bad4"), pdf);
  1978 + assert((*bad4.find("F", true)).first == "C");
  1979 + for (auto i: bad4)
  1980 + {
  1981 + std::cout << i.first << std::endl;
  1982 + }
  1983 +
  1984 + std::cout << "/Bad5 -- loop in find" << std::endl;
  1985 + auto bad5 = QPDFNameTreeObjectHelper(
  1986 + pdf.getTrailer().getKey("/Bad5"), pdf);
  1987 + assert((*bad5.find("F", true)).first == "D");
  1988 +
  1989 + std::cout << "/Bad6 -- bad limits" << std::endl;
  1990 + auto bad6 = QPDFNameTreeObjectHelper(
  1991 + pdf.getTrailer().getKey("/Bad6"), pdf);
  1992 + assert((*bad6.insert("H", QPDFObjectHandle::newNull())).first == "H");
1833 1993 }
1834 1994 else if (n == 49)
1835 1995 {
... ... @@ -2326,6 +2486,57 @@ void runtest(int n, char const* filename1, char const* arg2)
2326 2486 pdf.closeInputSource();
2327 2487 pdf.getRoot().getKey("/Pages").unparseResolved();
2328 2488 }
  2489 + else if (n == 74)
  2490 + {
  2491 + // This test is crafted to work with split-nntree.pdf
  2492 + std::cout << "/Split1" << std::endl;
  2493 + auto split1 = QPDFNumberTreeObjectHelper(
  2494 + pdf.getTrailer().getKey("/Split1"), pdf);
  2495 + split1.setSplitThreshold(4);
  2496 + auto check_split1 = [&split1](int k) {
  2497 + auto i = split1.insert(k, QPDFObjectHandle::newString(
  2498 + QUtil::int_to_string(k)));
  2499 + assert((*i).first == k);
  2500 + };
  2501 + check_split1(15);
  2502 + check_split1(35);
  2503 + check_split1(125);
  2504 + for (auto i: split1)
  2505 + {
  2506 + std::cout << i.first << std::endl;
  2507 + }
  2508 +
  2509 + std::cout << "/Split2" << std::endl;
  2510 + auto split2 = QPDFNameTreeObjectHelper(
  2511 + pdf.getTrailer().getKey("/Split2"), pdf);
  2512 + split2.setSplitThreshold(4);
  2513 + auto check_split2 = [](QPDFNameTreeObjectHelper& noh,
  2514 + std::string const& k) {
  2515 + auto i = noh.insert(k, QPDFObjectHandle::newUnicodeString(k));
  2516 + assert((*i).first == k);
  2517 + };
  2518 + check_split2(split2, "C");
  2519 + for (auto i: split2)
  2520 + {
  2521 + std::cout << i.first << std::endl;
  2522 + }
  2523 +
  2524 + std::cout << "/Split3" << std::endl;
  2525 + auto split3 = QPDFNameTreeObjectHelper(
  2526 + pdf.getTrailer().getKey("/Split3"), pdf);
  2527 + split3.setSplitThreshold(4);
  2528 + check_split2(split3, "P");
  2529 + check_split2(split3, "\xcf\x80");
  2530 + for (auto i: split3)
  2531 + {
  2532 + std::cout << i.first << " " << i.second.unparse() << std::endl;
  2533 + }
  2534 +
  2535 + QPDFWriter w(pdf, "a.pdf");
  2536 + w.setStaticID(true);
  2537 + w.setQDFMode(true);
  2538 + w.write();
  2539 + }
2329 2540 else
2330 2541 {
2331 2542 throw std::runtime_error(std::string("invalid test ") +
... ...