Commit e7e20772ed29f3eb9756b31fe0bd9bc29a445891
1 parent
5816fb44
name/number trees: remove
Showing
14 changed files
with
795 additions
and
4 deletions
ChangeLog
| 1 | +2021-01-24 Jay Berkenbilt <ejb@ql.org> | |
| 2 | + | |
| 3 | + * Implement remove for name and number trees as well as exposing | |
| 4 | + remove and insertAfter methods for iterators. With this addition, | |
| 5 | + qpdf now has robust read/write support for name and number trees. | |
| 6 | + | |
| 1 | 7 | 2021-01-23 Jay Berkenbilt <ejb@ql.org> |
| 2 | 8 | |
| 3 | 9 | * Add an insert method to QPDFNameTreeObjectHelper and | ... | ... |
include/qpdf/QPDFNameTreeObjectHelper.hh
| ... | ... | @@ -125,6 +125,11 @@ class QPDFNameTreeObjectHelper: public QPDFObjectHelper |
| 125 | 125 | QPDF_DLL |
| 126 | 126 | void insertAfter(std::string const& key, QPDFObjectHandle value); |
| 127 | 127 | |
| 128 | + // Remove the current item and advance the iterator to the | |
| 129 | + // next item. | |
| 130 | + QPDF_DLL | |
| 131 | + void remove(); | |
| 132 | + | |
| 128 | 133 | private: |
| 129 | 134 | iterator(std::shared_ptr<NNTreeIterator> const&); |
| 130 | 135 | std::shared_ptr<NNTreeIterator> impl; |
| ... | ... | @@ -152,6 +157,12 @@ class QPDFNameTreeObjectHelper: public QPDFObjectHelper |
| 152 | 157 | QPDF_DLL |
| 153 | 158 | iterator insert(std::string const& key, QPDFObjectHandle value); |
| 154 | 159 | |
| 160 | + // Remove an item. Return true if the item was found and removed; | |
| 161 | + // otherwise return false. If value is not null, initialize it to | |
| 162 | + // the value that was removed. | |
| 163 | + QPDF_DLL | |
| 164 | + bool remove(std::string const& key, QPDFObjectHandle* value = nullptr); | |
| 165 | + | |
| 155 | 166 | // Return the contents of the name tree as a map. Note that name |
| 156 | 167 | // trees may be very large, so this may use a lot of RAM. It is |
| 157 | 168 | // more efficient to use QPDFNameTreeObjectHelper's iterator. | ... | ... |
include/qpdf/QPDFNumberTreeObjectHelper.hh
| ... | ... | @@ -144,6 +144,11 @@ class QPDFNumberTreeObjectHelper: public QPDFObjectHelper |
| 144 | 144 | QPDF_DLL |
| 145 | 145 | void insertAfter(numtree_number key, QPDFObjectHandle value); |
| 146 | 146 | |
| 147 | + // Remove the current item and advance the iterator to the | |
| 148 | + // next item. | |
| 149 | + QPDF_DLL | |
| 150 | + void remove(); | |
| 151 | + | |
| 147 | 152 | private: |
| 148 | 153 | iterator(std::shared_ptr<NNTreeIterator> const&); |
| 149 | 154 | std::shared_ptr<NNTreeIterator> impl; |
| ... | ... | @@ -170,6 +175,12 @@ class QPDFNumberTreeObjectHelper: public QPDFObjectHelper |
| 170 | 175 | QPDF_DLL |
| 171 | 176 | iterator insert(numtree_number key, QPDFObjectHandle value); |
| 172 | 177 | |
| 178 | + // Remove an item. Return true if the item was found and removed; | |
| 179 | + // otherwise return false. If value is not null, initialize it to | |
| 180 | + // the value that was removed. | |
| 181 | + QPDF_DLL | |
| 182 | + bool remove(numtree_number key, QPDFObjectHandle* value = nullptr); | |
| 183 | + | |
| 173 | 184 | // Return the contents of the number tree as a map. Note that |
| 174 | 185 | // number trees may be very large, so this may use a lot of RAM. |
| 175 | 186 | // It is more efficient to use QPDFNumberTreeObjectHelper's | ... | ... |
libqpdf/NNTree.cc
| ... | ... | @@ -163,6 +163,13 @@ NNTreeIterator::resetLimits(QPDFObjectHandle node, |
| 163 | 163 | bool done = false; |
| 164 | 164 | while (! done) |
| 165 | 165 | { |
| 166 | + if (parent == this->path.end()) | |
| 167 | + { | |
| 168 | + QTC::TC("qpdf", "NNTree remove limits from root"); | |
| 169 | + node.removeKey("/Limits"); | |
| 170 | + done = true; | |
| 171 | + break; | |
| 172 | + } | |
| 166 | 173 | auto kids = node.getKey("/Kids"); |
| 167 | 174 | int nkids = kids.isArray() ? kids.getArrayNItems() : 0; |
| 168 | 175 | auto items = node.getKey(impl.details.itemsKey()); |
| ... | ... | @@ -459,7 +466,7 @@ NNTreeIterator::insertAfter(QPDFObjectHandle key, QPDFObjectHandle value) |
| 459 | 466 | } |
| 460 | 467 | if (items.getArrayNItems() < this->item_number + 2) |
| 461 | 468 | { |
| 462 | - error(impl.qpdf, node, "items array is too short"); | |
| 469 | + error(impl.qpdf, node, "insert: items array is too short"); | |
| 463 | 470 | } |
| 464 | 471 | items.insertItem(this->item_number + 2, key); |
| 465 | 472 | items.insertItem(this->item_number + 3, value); |
| ... | ... | @@ -468,6 +475,144 @@ NNTreeIterator::insertAfter(QPDFObjectHandle key, QPDFObjectHandle value) |
| 468 | 475 | increment(false); |
| 469 | 476 | } |
| 470 | 477 | |
| 478 | +void | |
| 479 | +NNTreeIterator::remove() | |
| 480 | +{ | |
| 481 | + // Remove this item, leaving the tree valid and this iterator | |
| 482 | + // pointing to the next item. | |
| 483 | + | |
| 484 | + if (! valid()) | |
| 485 | + { | |
| 486 | + throw std::logic_error("attempt made to remove an invalid iterator"); | |
| 487 | + } | |
| 488 | + auto items = this->node.getKey(impl.details.itemsKey()); | |
| 489 | + int nitems = items.getArrayNItems(); | |
| 490 | + if (this->item_number + 2 > nitems) | |
| 491 | + { | |
| 492 | + error(impl.qpdf, this->node, | |
| 493 | + "found short items array while removing an item"); | |
| 494 | + } | |
| 495 | + | |
| 496 | + items.eraseItem(this->item_number); | |
| 497 | + items.eraseItem(this->item_number); | |
| 498 | + nitems -= 2; | |
| 499 | + | |
| 500 | + if (nitems > 0) | |
| 501 | + { | |
| 502 | + // There are still items left | |
| 503 | + | |
| 504 | + if ((this->item_number == 0) || (this->item_number == nitems)) | |
| 505 | + { | |
| 506 | + // We removed either the first or last item of an items array | |
| 507 | + // that remains non-empty, so we have to adjust limits. | |
| 508 | + QTC::TC("qpdf", "NNTree remove reset limits"); | |
| 509 | + resetLimits(this->node, lastPathElement()); | |
| 510 | + } | |
| 511 | + | |
| 512 | + if (this->item_number == nitems) | |
| 513 | + { | |
| 514 | + // We removed the last item of a non-empty items array, so | |
| 515 | + // advance to the successor of the previous item. | |
| 516 | + QTC::TC("qpdf", "NNTree erased last item"); | |
| 517 | + this->item_number -= 2; | |
| 518 | + increment(false); | |
| 519 | + } | |
| 520 | + else if (this->item_number < nitems) | |
| 521 | + { | |
| 522 | + // We don't have to do anything since the removed item's | |
| 523 | + // successor now occupies its former location. | |
| 524 | + QTC::TC("qpdf", "NNTree erased non-last item"); | |
| 525 | + } | |
| 526 | + else | |
| 527 | + { | |
| 528 | + // We already checked to ensure this condition would not | |
| 529 | + // happen. | |
| 530 | + throw std::logic_error( | |
| 531 | + "NNTreeIterator::remove: item_number > nitems after erase"); | |
| 532 | + } | |
| 533 | + return; | |
| 534 | + } | |
| 535 | + | |
| 536 | + if (this->path.empty()) | |
| 537 | + { | |
| 538 | + // Special case: if this is the root node, we can leave it | |
| 539 | + // empty. | |
| 540 | + QTC::TC("qpdf", "NNTree erased all items on leaf/root"); | |
| 541 | + setItemNumber(impl.oh, -1); | |
| 542 | + return; | |
| 543 | + } | |
| 544 | + | |
| 545 | + QTC::TC("qpdf", "NNTree items is empty after remove"); | |
| 546 | + | |
| 547 | + // We removed the last item from this items array, so we need to | |
| 548 | + // remove this node from the parent on up the tree. Then we need | |
| 549 | + // to position ourselves at the removed item's successor. | |
| 550 | + bool done = false; | |
| 551 | + while (! done) | |
| 552 | + { | |
| 553 | + auto element = lastPathElement(); | |
| 554 | + auto parent = element; | |
| 555 | + --parent; | |
| 556 | + auto kids = element->node.getKey("/Kids"); | |
| 557 | + kids.eraseItem(element->kid_number); | |
| 558 | + auto nkids = kids.getArrayNItems(); | |
| 559 | + if (nkids > 0) | |
| 560 | + { | |
| 561 | + // The logic here is similar to the items case. | |
| 562 | + if ((element->kid_number == 0) || (element->kid_number == nkids)) | |
| 563 | + { | |
| 564 | + QTC::TC("qpdf", "NNTree erased first or last kid"); | |
| 565 | + resetLimits(element->node, parent); | |
| 566 | + } | |
| 567 | + if (element->kid_number == nkids) | |
| 568 | + { | |
| 569 | + // Move to the successor of the last child of the | |
| 570 | + // previous kid. | |
| 571 | + setItemNumber(QPDFObjectHandle(), -1); | |
| 572 | + --element->kid_number; | |
| 573 | + deepen(kids.getArrayItem(element->kid_number), false, true); | |
| 574 | + if (valid()) | |
| 575 | + { | |
| 576 | + increment(false); | |
| 577 | + if (! valid()) | |
| 578 | + { | |
| 579 | + QTC::TC("qpdf", "NNTree erased last item in tree"); | |
| 580 | + } | |
| 581 | + else | |
| 582 | + { | |
| 583 | + QTC::TC("qpdf", "NNTree erased last kid"); | |
| 584 | + } | |
| 585 | + } | |
| 586 | + } | |
| 587 | + else | |
| 588 | + { | |
| 589 | + // Next kid is in deleted kid's position | |
| 590 | + QTC::TC("qpdf", "NNTree erased non-last kid"); | |
| 591 | + deepen(kids.getArrayItem(element->kid_number), true, true); | |
| 592 | + } | |
| 593 | + done = true; | |
| 594 | + } | |
| 595 | + else if (parent == this->path.end()) | |
| 596 | + { | |
| 597 | + // We erased the very last item. Convert the root to an | |
| 598 | + // empty items array. | |
| 599 | + QTC::TC("qpdf", "NNTree non-flat tree is empty after remove"); | |
| 600 | + element->node.removeKey("/Kids"); | |
| 601 | + element->node.replaceKey(impl.details.itemsKey(), | |
| 602 | + QPDFObjectHandle::newArray()); | |
| 603 | + this->path.clear(); | |
| 604 | + setItemNumber(impl.oh, -1); | |
| 605 | + done = true; | |
| 606 | + } | |
| 607 | + else | |
| 608 | + { | |
| 609 | + // Walk up the tree and continue | |
| 610 | + QTC::TC("qpdf", "NNTree remove walking up tree"); | |
| 611 | + this->path.pop_back(); | |
| 612 | + } | |
| 613 | + } | |
| 614 | +} | |
| 615 | + | |
| 471 | 616 | NNTreeIterator& |
| 472 | 617 | NNTreeIterator::operator++() |
| 473 | 618 | { |
| ... | ... | @@ -494,7 +639,7 @@ NNTreeIterator::operator*() |
| 494 | 639 | auto items = this->node.getKey(impl.details.itemsKey()); |
| 495 | 640 | if (items.getArrayNItems() < this->item_number + 2) |
| 496 | 641 | { |
| 497 | - error(impl.qpdf, node, "items array is too short"); | |
| 642 | + error(impl.qpdf, node, "operator*: items array is too short"); | |
| 498 | 643 | } |
| 499 | 644 | return std::make_pair(items.getArrayItem(this->item_number), |
| 500 | 645 | items.getArrayItem(1+this->item_number)); |
| ... | ... | @@ -980,3 +1125,20 @@ NNTreeImpl::insert(QPDFObjectHandle key, QPDFObjectHandle value) |
| 980 | 1125 | } |
| 981 | 1126 | return iter; |
| 982 | 1127 | } |
| 1128 | + | |
| 1129 | +bool | |
| 1130 | +NNTreeImpl::remove(QPDFObjectHandle key, QPDFObjectHandle* value) | |
| 1131 | +{ | |
| 1132 | + auto iter = find(key, false); | |
| 1133 | + if (! iter.valid()) | |
| 1134 | + { | |
| 1135 | + QTC::TC("qpdf", "NNTree remove not found"); | |
| 1136 | + return false; | |
| 1137 | + } | |
| 1138 | + if (value) | |
| 1139 | + { | |
| 1140 | + *value = (*iter).second; | |
| 1141 | + } | |
| 1142 | + iter.remove(); | |
| 1143 | + return true; | |
| 1144 | +} | ... | ... |
libqpdf/QPDFNameTreeObjectHelper.cc
| ... | ... | @@ -109,6 +109,12 @@ QPDFNameTreeObjectHelper::iterator::insertAfter( |
| 109 | 109 | impl->insertAfter(QPDFObjectHandle::newUnicodeString(key), value); |
| 110 | 110 | } |
| 111 | 111 | |
| 112 | +void | |
| 113 | +QPDFNameTreeObjectHelper::iterator::remove() | |
| 114 | +{ | |
| 115 | + impl->remove(); | |
| 116 | +} | |
| 117 | + | |
| 112 | 118 | QPDFNameTreeObjectHelper::iterator |
| 113 | 119 | QPDFNameTreeObjectHelper::begin() const |
| 114 | 120 | { |
| ... | ... | @@ -146,6 +152,14 @@ QPDFNameTreeObjectHelper::insert(std::string const& key, |
| 146 | 152 | } |
| 147 | 153 | |
| 148 | 154 | bool |
| 155 | +QPDFNameTreeObjectHelper::remove(std::string const& key, | |
| 156 | + QPDFObjectHandle* value) | |
| 157 | +{ | |
| 158 | + return this->m->impl->remove( | |
| 159 | + QPDFObjectHandle::newUnicodeString(key), value); | |
| 160 | +} | |
| 161 | + | |
| 162 | +bool | |
| 149 | 163 | QPDFNameTreeObjectHelper::hasName(std::string const& name) |
| 150 | 164 | { |
| 151 | 165 | auto i = find(name); | ... | ... |
libqpdf/QPDFNumberTreeObjectHelper.cc
| ... | ... | @@ -105,6 +105,12 @@ QPDFNumberTreeObjectHelper::iterator::insertAfter( |
| 105 | 105 | impl->insertAfter(QPDFObjectHandle::newInteger(key), value); |
| 106 | 106 | } |
| 107 | 107 | |
| 108 | +void | |
| 109 | +QPDFNumberTreeObjectHelper::iterator::remove() | |
| 110 | +{ | |
| 111 | + impl->remove(); | |
| 112 | +} | |
| 113 | + | |
| 108 | 114 | QPDFNumberTreeObjectHelper::iterator |
| 109 | 115 | QPDFNumberTreeObjectHelper::begin() const |
| 110 | 116 | { |
| ... | ... | @@ -140,6 +146,14 @@ QPDFNumberTreeObjectHelper::insert(numtree_number key, QPDFObjectHandle value) |
| 140 | 146 | return iterator(std::make_shared<NNTreeIterator>(i)); |
| 141 | 147 | } |
| 142 | 148 | |
| 149 | +bool | |
| 150 | +QPDFNumberTreeObjectHelper::remove(numtree_number key, | |
| 151 | + QPDFObjectHandle* value) | |
| 152 | +{ | |
| 153 | + return this->m->impl->remove( | |
| 154 | + QPDFObjectHandle::newInteger(key), value); | |
| 155 | +} | |
| 156 | + | |
| 143 | 157 | QPDFNumberTreeObjectHelper::numtree_number |
| 144 | 158 | QPDFNumberTreeObjectHelper::getMin() |
| 145 | 159 | { | ... | ... |
libqpdf/qpdf/NNTree.hh
| ... | ... | @@ -49,6 +49,7 @@ class NNTreeIterator: public std::iterator< |
| 49 | 49 | |
| 50 | 50 | void insertAfter( |
| 51 | 51 | QPDFObjectHandle key, QPDFObjectHandle value); |
| 52 | + void remove(); | |
| 52 | 53 | |
| 53 | 54 | private: |
| 54 | 55 | class PathElement |
| ... | ... | @@ -94,6 +95,7 @@ class NNTreeImpl |
| 94 | 95 | iterator find(QPDFObjectHandle key, bool return_prev_if_not_found = false); |
| 95 | 96 | iterator insertFirst(QPDFObjectHandle key, QPDFObjectHandle value); |
| 96 | 97 | iterator insert(QPDFObjectHandle key, QPDFObjectHandle value); |
| 98 | + bool remove(QPDFObjectHandle key, QPDFObjectHandle* value = nullptr); | |
| 97 | 99 | |
| 98 | 100 | // Change the split threshold for easier testing. There's no real |
| 99 | 101 | // reason to expose this to downstream tree helpers, but it has to | ... | ... |
manual/qpdf-manual.xml
| ... | ... | @@ -4859,7 +4859,8 @@ print "\n"; |
| 4859 | 4859 | and <classname>QPDFNumberTreeObjectHelper</classname> to be |
| 4860 | 4860 | more efficient, add an iterator-based API, give them the |
| 4861 | 4861 | capability to repair broken trees, and create methods for |
| 4862 | - modifying the trees. | |
| 4862 | + modifying the trees. With this change, qpdf has a robust | |
| 4863 | + read/write implementation of name and number trees. | |
| 4863 | 4864 | </para> |
| 4864 | 4865 | </listitem> |
| 4865 | 4866 | </itemizedlist> | ... | ... |
qpdf/qpdf.testcov
| ... | ... | @@ -553,3 +553,16 @@ NNTree node is not a dictionary 0 |
| 553 | 553 | NNTree limits didn't change 0 |
| 554 | 554 | NNTree increment end() 0 |
| 555 | 555 | NNTree insertAfter inserts first 0 |
| 556 | +NNTree remove not found 0 | |
| 557 | +NNTree remove reset limits 0 | |
| 558 | +NNTree erased last item 0 | |
| 559 | +NNTree erased non-last item 0 | |
| 560 | +NNTree items is empty after remove 0 | |
| 561 | +NNTree erased all items on leaf/root 0 | |
| 562 | +NNTree erased first or last kid 0 | |
| 563 | +NNTree erased last kid 0 | |
| 564 | +NNTree erased non-last kid 0 | |
| 565 | +NNTree non-flat tree is empty after remove 0 | |
| 566 | +NNTree remove walking up tree 0 | |
| 567 | +NNTree erased last item in tree 0 | |
| 568 | +NNTree remove limits from root 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 += 4; | |
| 586 | +$n_tests += 6; | |
| 587 | 587 | |
| 588 | 588 | $td->runtest("number trees", |
| 589 | 589 | {$td->COMMAND => "test_driver 46 number-tree.pdf"}, |
| ... | ... | @@ -600,6 +600,13 @@ $td->runtest("nntree split", |
| 600 | 600 | $td->runtest("check file", |
| 601 | 601 | {$td->FILE => "a.pdf"}, |
| 602 | 602 | {$td->FILE => "split-nntree-out.pdf"}); |
| 603 | +$td->runtest("nntree erase", | |
| 604 | + {$td->COMMAND => "test_driver 75 erase-nntree.pdf"}, | |
| 605 | + {$td->FILE => "erase-nntree.out", $td->EXIT_STATUS => 0}, | |
| 606 | + $td->NORMALIZE_NEWLINES); | |
| 607 | +$td->runtest("check file", | |
| 608 | + {$td->FILE => "a.pdf"}, | |
| 609 | + {$td->FILE => "erase-nntree-out.pdf"}); | |
| 603 | 610 | |
| 604 | 611 | show_ntests(); |
| 605 | 612 | # ---------- | ... | ... |
qpdf/qtest/qpdf/erase-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 6 0 R | |
| 9 | + /Type /Catalog | |
| 10 | +>> | |
| 11 | +endobj | |
| 12 | + | |
| 13 | +%% Original object ID: 8 0 | |
| 14 | +2 0 obj | |
| 15 | +<< | |
| 16 | + /Names [ | |
| 17 | + ] | |
| 18 | +>> | |
| 19 | +endobj | |
| 20 | + | |
| 21 | +%% Original object ID: 9 0 | |
| 22 | +3 0 obj | |
| 23 | +<< | |
| 24 | + /Kids [ | |
| 25 | + 7 0 R | |
| 26 | + 8 0 R | |
| 27 | + ] | |
| 28 | +>> | |
| 29 | +endobj | |
| 30 | + | |
| 31 | +%% Original object ID: 14 0 | |
| 32 | +4 0 obj | |
| 33 | +<< | |
| 34 | + /Nums [ | |
| 35 | + ] | |
| 36 | +>> | |
| 37 | +endobj | |
| 38 | + | |
| 39 | +%% Original object ID: 18 0 | |
| 40 | +5 0 obj | |
| 41 | +<< | |
| 42 | + /Kids [ | |
| 43 | + 9 0 R | |
| 44 | + 10 0 R | |
| 45 | + ] | |
| 46 | +>> | |
| 47 | +endobj | |
| 48 | + | |
| 49 | +%% Original object ID: 2 0 | |
| 50 | +6 0 obj | |
| 51 | +<< | |
| 52 | + /Count 1 | |
| 53 | + /Kids [ | |
| 54 | + 11 0 R | |
| 55 | + ] | |
| 56 | + /Type /Pages | |
| 57 | +>> | |
| 58 | +endobj | |
| 59 | + | |
| 60 | +%% Original object ID: 10 0 | |
| 61 | +7 0 obj | |
| 62 | +<< | |
| 63 | + /Kids [ | |
| 64 | + 12 0 R | |
| 65 | + ] | |
| 66 | + /Limits [ | |
| 67 | + 220 | |
| 68 | + 220 | |
| 69 | + ] | |
| 70 | +>> | |
| 71 | +endobj | |
| 72 | + | |
| 73 | +%% Original object ID: 11 0 | |
| 74 | +8 0 obj | |
| 75 | +<< | |
| 76 | + /Limits [ | |
| 77 | + 230 | |
| 78 | + 240 | |
| 79 | + ] | |
| 80 | + /Nums [ | |
| 81 | + 230 | |
| 82 | + (230) | |
| 83 | + 240 | |
| 84 | + (240) | |
| 85 | + ] | |
| 86 | +>> | |
| 87 | +endobj | |
| 88 | + | |
| 89 | +%% Original object ID: 19 0 | |
| 90 | +9 0 obj | |
| 91 | +<< | |
| 92 | + /Kids [ | |
| 93 | + 13 0 R | |
| 94 | + ] | |
| 95 | + /Limits [ | |
| 96 | + 410 | |
| 97 | + 410 | |
| 98 | + ] | |
| 99 | +>> | |
| 100 | +endobj | |
| 101 | + | |
| 102 | +%% Original object ID: 20 0 | |
| 103 | +10 0 obj | |
| 104 | +<< | |
| 105 | + /Limits [ | |
| 106 | + 430 | |
| 107 | + 430 | |
| 108 | + ] | |
| 109 | + /Nums [ | |
| 110 | + 430 | |
| 111 | + (430) | |
| 112 | + ] | |
| 113 | +>> | |
| 114 | +endobj | |
| 115 | + | |
| 116 | +%% Page 1 | |
| 117 | +%% Original object ID: 3 0 | |
| 118 | +11 0 obj | |
| 119 | +<< | |
| 120 | + /Contents 14 0 R | |
| 121 | + /MediaBox [ | |
| 122 | + 0 | |
| 123 | + 0 | |
| 124 | + 612 | |
| 125 | + 792 | |
| 126 | + ] | |
| 127 | + /Parent 6 0 R | |
| 128 | + /Resources << | |
| 129 | + /Font << | |
| 130 | + /F1 16 0 R | |
| 131 | + >> | |
| 132 | + /ProcSet 17 0 R | |
| 133 | + >> | |
| 134 | + /Type /Page | |
| 135 | +>> | |
| 136 | +endobj | |
| 137 | + | |
| 138 | +%% Original object ID: 13 0 | |
| 139 | +12 0 obj | |
| 140 | +<< | |
| 141 | + /Limits [ | |
| 142 | + 220 | |
| 143 | + 220 | |
| 144 | + ] | |
| 145 | + /Nums [ | |
| 146 | + 220 | |
| 147 | + (220) | |
| 148 | + ] | |
| 149 | +>> | |
| 150 | +endobj | |
| 151 | + | |
| 152 | +%% Original object ID: 21 0 | |
| 153 | +13 0 obj | |
| 154 | +<< | |
| 155 | + /Limits [ | |
| 156 | + 410 | |
| 157 | + 410 | |
| 158 | + ] | |
| 159 | + /Nums [ | |
| 160 | + 410 | |
| 161 | + (410) | |
| 162 | + ] | |
| 163 | +>> | |
| 164 | +endobj | |
| 165 | + | |
| 166 | +%% Contents for page 1 | |
| 167 | +%% Original object ID: 4 0 | |
| 168 | +14 0 obj | |
| 169 | +<< | |
| 170 | + /Length 15 0 R | |
| 171 | +>> | |
| 172 | +stream | |
| 173 | +BT | |
| 174 | + /F1 24 Tf | |
| 175 | + 72 720 Td | |
| 176 | + (Potato) Tj | |
| 177 | +ET | |
| 178 | +endstream | |
| 179 | +endobj | |
| 180 | + | |
| 181 | +15 0 obj | |
| 182 | +44 | |
| 183 | +endobj | |
| 184 | + | |
| 185 | +%% Original object ID: 6 0 | |
| 186 | +16 0 obj | |
| 187 | +<< | |
| 188 | + /BaseFont /Helvetica | |
| 189 | + /Encoding /WinAnsiEncoding | |
| 190 | + /Name /F1 | |
| 191 | + /Subtype /Type1 | |
| 192 | + /Type /Font | |
| 193 | +>> | |
| 194 | +endobj | |
| 195 | + | |
| 196 | +%% Original object ID: 7 0 | |
| 197 | +17 0 obj | |
| 198 | +[ | |
| 199 | ||
| 200 | + /Text | |
| 201 | +] | |
| 202 | +endobj | |
| 203 | + | |
| 204 | +xref | |
| 205 | +0 18 | |
| 206 | +0000000000 65535 f | |
| 207 | +0000000052 00000 n | |
| 208 | +0000000133 00000 n | |
| 209 | +0000000197 00000 n | |
| 210 | +0000000281 00000 n | |
| 211 | +0000000345 00000 n | |
| 212 | +0000000429 00000 n | |
| 213 | +0000000530 00000 n | |
| 214 | +0000000637 00000 n | |
| 215 | +0000000769 00000 n | |
| 216 | +0000000876 00000 n | |
| 217 | +0000001000 00000 n | |
| 218 | +0000001224 00000 n | |
| 219 | +0000001339 00000 n | |
| 220 | +0000001476 00000 n | |
| 221 | +0000001577 00000 n | |
| 222 | +0000001624 00000 n | |
| 223 | +0000001770 00000 n | |
| 224 | +trailer << | |
| 225 | + /Erase1 2 0 R | |
| 226 | + /Erase2 3 0 R | |
| 227 | + /Erase3 4 0 R | |
| 228 | + /Erase4 5 0 R | |
| 229 | + /Root 1 0 R | |
| 230 | + /Size 18 | |
| 231 | + /ID [<2c3b7a6ec7fc61db8a5db4eebf57f540><31415926535897932384626433832795>] | |
| 232 | +>> | |
| 233 | +startxref | |
| 234 | +1806 | |
| 235 | +%%EOF | ... | ... |
qpdf/qtest/qpdf/erase-nntree.out
0 โ 100644
| 1 | +test 75 done | ... | ... |
qpdf/qtest/qpdf/erase-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 | ||
| 74 | + /Text | |
| 75 | +] | |
| 76 | +endobj | |
| 77 | + | |
| 78 | +8 0 obj | |
| 79 | +<< | |
| 80 | + /Names [ | |
| 81 | + (1A) (a) | |
| 82 | + (1B) (b) | |
| 83 | + (1C) (c) | |
| 84 | + (1D) (d) | |
| 85 | + ] | |
| 86 | +>> | |
| 87 | +endobj | |
| 88 | + | |
| 89 | +9 0 obj | |
| 90 | +<< | |
| 91 | + /Kids [ | |
| 92 | + 10 0 R | |
| 93 | + 11 0 R | |
| 94 | + ] | |
| 95 | +>> | |
| 96 | +endobj | |
| 97 | + | |
| 98 | +10 0 obj | |
| 99 | +<< | |
| 100 | + /Limits [ 210 220 ] | |
| 101 | + /Kids [ | |
| 102 | + 12 0 R | |
| 103 | + 13 0 R | |
| 104 | + ] | |
| 105 | +>> | |
| 106 | +endobj | |
| 107 | + | |
| 108 | +11 0 obj | |
| 109 | +<< | |
| 110 | + /Limits [ 230 250 ] | |
| 111 | + /Nums [ | |
| 112 | + 230 (230) | |
| 113 | + 240 (240) | |
| 114 | + 250 (250) | |
| 115 | + ] | |
| 116 | +>> | |
| 117 | +endobj | |
| 118 | + | |
| 119 | +12 0 obj | |
| 120 | +<< | |
| 121 | + /Limits [ 210 210 ] | |
| 122 | + /Nums [ | |
| 123 | + 210 (210) | |
| 124 | + ] | |
| 125 | +>> | |
| 126 | +endobj | |
| 127 | + | |
| 128 | +13 0 obj | |
| 129 | +<< | |
| 130 | + /Limits [ 220 220 ] | |
| 131 | + /Nums [ | |
| 132 | + 220 (220) | |
| 133 | + ] | |
| 134 | +>> | |
| 135 | +endobj | |
| 136 | + | |
| 137 | +14 0 obj | |
| 138 | +<< | |
| 139 | + /Kids [ | |
| 140 | + 15 0 R | |
| 141 | + ] | |
| 142 | +>> | |
| 143 | +endobj | |
| 144 | + | |
| 145 | +15 0 obj | |
| 146 | +<< | |
| 147 | + /Limits [ 310 320 ] | |
| 148 | + /Kids [ | |
| 149 | + 16 0 R | |
| 150 | + 17 0 R | |
| 151 | + ] | |
| 152 | +>> | |
| 153 | +endobj | |
| 154 | + | |
| 155 | +16 0 obj | |
| 156 | +<< | |
| 157 | + /Limits [ 310 310 ] | |
| 158 | + /Nums [ | |
| 159 | + 310 (310) | |
| 160 | + ] | |
| 161 | +>> | |
| 162 | +endobj | |
| 163 | + | |
| 164 | +17 0 obj | |
| 165 | +<< | |
| 166 | + /Limits [ 320 320 ] | |
| 167 | + /Nums [ | |
| 168 | + 320 (320) | |
| 169 | + ] | |
| 170 | +>> | |
| 171 | +endobj | |
| 172 | + | |
| 173 | +18 0 obj | |
| 174 | +<< | |
| 175 | + /Kids [ | |
| 176 | + 19 0 R | |
| 177 | + 20 0 R | |
| 178 | + ] | |
| 179 | +>> | |
| 180 | +endobj | |
| 181 | + | |
| 182 | +19 0 obj | |
| 183 | +<< | |
| 184 | + /Limits [ 410 420 ] | |
| 185 | + /Kids [ | |
| 186 | + 21 0 R | |
| 187 | + 22 0 R | |
| 188 | + ] | |
| 189 | +>> | |
| 190 | +endobj | |
| 191 | + | |
| 192 | +20 0 obj | |
| 193 | +<< | |
| 194 | + /Limits [ 430 430 ] | |
| 195 | + /Nums [ | |
| 196 | + 430 (430) | |
| 197 | + ] | |
| 198 | +>> | |
| 199 | +endobj | |
| 200 | + | |
| 201 | +21 0 obj | |
| 202 | +<< | |
| 203 | + /Limits [ 410 410 ] | |
| 204 | + /Nums [ | |
| 205 | + 410 (410) | |
| 206 | + ] | |
| 207 | +>> | |
| 208 | +endobj | |
| 209 | + | |
| 210 | +22 0 obj | |
| 211 | +<< | |
| 212 | + /Limits [ 420 420 ] | |
| 213 | + /Nums [ | |
| 214 | + 420 (420) | |
| 215 | + ] | |
| 216 | +>> | |
| 217 | +endobj | |
| 218 | + | |
| 219 | +xref | |
| 220 | +0 23 | |
| 221 | +0000000000 65535 f | |
| 222 | +0000000025 00000 n | |
| 223 | +0000000079 00000 n | |
| 224 | +0000000161 00000 n | |
| 225 | +0000000376 00000 n | |
| 226 | +0000000475 00000 n | |
| 227 | +0000000494 00000 n | |
| 228 | +0000000612 00000 n | |
| 229 | +0000000647 00000 n | |
| 230 | +0000000736 00000 n | |
| 231 | +0000000794 00000 n | |
| 232 | +0000000875 00000 n | |
| 233 | +0000000976 00000 n | |
| 234 | +0000001049 00000 n | |
| 235 | +0000001122 00000 n | |
| 236 | +0000001170 00000 n | |
| 237 | +0000001251 00000 n | |
| 238 | +0000001324 00000 n | |
| 239 | +0000001397 00000 n | |
| 240 | +0000001456 00000 n | |
| 241 | +0000001537 00000 n | |
| 242 | +0000001610 00000 n | |
| 243 | +0000001683 00000 n | |
| 244 | +trailer << | |
| 245 | + /Root 1 0 R | |
| 246 | + /Erase1 8 0 R | |
| 247 | + /Erase2 9 0 R | |
| 248 | + /Erase3 14 0 R | |
| 249 | + /Erase4 18 0 R | |
| 250 | + /Size 23 | |
| 251 | + /ID [<2c3b7a6ec7fc61db8a5db4eebf57f540><2c3b7a6ec7fc61db8a5db4eebf57f540>] | |
| 252 | +>> | |
| 253 | +startxref | |
| 254 | +1756 | |
| 255 | +%%EOF | ... | ... |
qpdf/test_driver.cc
| ... | ... | @@ -2615,6 +2615,65 @@ void runtest(int n, char const* filename1, char const* arg2) |
| 2615 | 2615 | w.setQDFMode(true); |
| 2616 | 2616 | w.write(); |
| 2617 | 2617 | } |
| 2618 | + else if (n == 75) | |
| 2619 | + { | |
| 2620 | + // This test is crafted to work with erase-nntree.pdf | |
| 2621 | + auto erase1 = QPDFNameTreeObjectHelper( | |
| 2622 | + pdf.getTrailer().getKey("/Erase1"), pdf); | |
| 2623 | + QPDFObjectHandle value; | |
| 2624 | + assert(! erase1.remove("1X")); | |
| 2625 | + assert(erase1.remove("1C", &value)); | |
| 2626 | + assert(value.getUTF8Value() == "c"); | |
| 2627 | + auto iter1 = erase1.find("1B"); | |
| 2628 | + iter1.remove(); | |
| 2629 | + assert((*iter1).first == "1D"); | |
| 2630 | + iter1.remove(); | |
| 2631 | + assert(iter1 == erase1.end()); | |
| 2632 | + --iter1; | |
| 2633 | + assert((*iter1).first == "1A"); | |
| 2634 | + iter1.remove(); | |
| 2635 | + assert(iter1 == erase1.end()); | |
| 2636 | + | |
| 2637 | + auto erase2_oh = pdf.getTrailer().getKey("/Erase2"); | |
| 2638 | + auto erase2 = QPDFNumberTreeObjectHelper(erase2_oh, pdf); | |
| 2639 | + auto iter2 = erase2.find(250); | |
| 2640 | + iter2.remove(); | |
| 2641 | + assert(iter2 == erase2.end()); | |
| 2642 | + --iter2; | |
| 2643 | + assert((*iter2).first == 240); | |
| 2644 | + auto k1 = erase2_oh.getKey("/Kids").getArrayItem(1); | |
| 2645 | + auto l1 = k1.getKey("/Limits"); | |
| 2646 | + assert(l1.getArrayItem(0).getIntValue() == 230); | |
| 2647 | + assert(l1.getArrayItem(1).getIntValue() == 240); | |
| 2648 | + iter2 = erase2.find(210); | |
| 2649 | + iter2.remove(); | |
| 2650 | + assert((*iter2).first == 220); | |
| 2651 | + k1 = erase2_oh.getKey("/Kids").getArrayItem(0); | |
| 2652 | + l1 = k1.getKey("/Limits"); | |
| 2653 | + assert(l1.getArrayItem(0).getIntValue() == 220); | |
| 2654 | + assert(l1.getArrayItem(1).getIntValue() == 220); | |
| 2655 | + k1 = k1.getKey("/Kids"); | |
| 2656 | + assert(k1.getArrayNItems() == 1); | |
| 2657 | + | |
| 2658 | + auto erase3 = QPDFNumberTreeObjectHelper( | |
| 2659 | + pdf.getTrailer().getKey("/Erase3"), pdf); | |
| 2660 | + iter2 = erase3.find(320); | |
| 2661 | + iter2.remove(); | |
| 2662 | + assert(iter2 == erase3.end()); | |
| 2663 | + erase3.remove(310); | |
| 2664 | + assert(erase3.begin() == erase3.end()); | |
| 2665 | + | |
| 2666 | + auto erase4 = QPDFNumberTreeObjectHelper( | |
| 2667 | + pdf.getTrailer().getKey("/Erase4"), pdf); | |
| 2668 | + iter2 = erase4.find(420); | |
| 2669 | + iter2.remove(); | |
| 2670 | + assert((*iter2).first == 430); | |
| 2671 | + | |
| 2672 | + QPDFWriter w(pdf, "a.pdf"); | |
| 2673 | + w.setStaticID(true); | |
| 2674 | + w.setQDFMode(true); | |
| 2675 | + w.write(); | |
| 2676 | + } | |
| 2618 | 2677 | else |
| 2619 | 2678 | { |
| 2620 | 2679 | throw std::runtime_error(std::string("invalid test ") + | ... | ... |