Commit 7e9c6c74bb3831b0f3ccf8fd0468d5a04cef68bb

Authored by m-holger
Committed by GitHub
2 parents db288584 e3ce9e17

Merge pull request #1523 from m-holger/oh_array

Refactor private API class Array
include/qpdf/ObjectHandle.hh
@@ -74,7 +74,6 @@ namespace qpdf @@ -74,7 +74,6 @@ namespace qpdf
74 } 74 }
75 75
76 QPDFObjectHandle operator[](size_t n) const; 76 QPDFObjectHandle operator[](size_t n) const;
77 -  
78 QPDFObjectHandle operator[](int n) const; 77 QPDFObjectHandle operator[](int n) const;
79 78
80 std::shared_ptr<QPDFObject> copy(bool shallow = false) const; 79 std::shared_ptr<QPDFObject> copy(bool shallow = false) const;
@@ -104,11 +103,18 @@ namespace qpdf @@ -104,11 +103,18 @@ namespace qpdf
104 BaseHandle& operator=(BaseHandle const&) = default; 103 BaseHandle& operator=(BaseHandle const&) = default;
105 BaseHandle(BaseHandle&&) = default; 104 BaseHandle(BaseHandle&&) = default;
106 BaseHandle& operator=(BaseHandle&&) = default; 105 BaseHandle& operator=(BaseHandle&&) = default;
  106 +
  107 + inline BaseHandle(QPDFObjectHandle const& oh);
  108 + inline BaseHandle(QPDFObjectHandle&& oh);
  109 +
107 ~BaseHandle() = default; 110 ~BaseHandle() = default;
108 111
109 template <typename T> 112 template <typename T>
110 T* as() const; 113 T* as() const;
111 114
  115 + inline void assign(qpdf_object_type_e required, BaseHandle const& other);
  116 + inline void assign(qpdf_object_type_e required, BaseHandle&& other);
  117 +
112 std::string description() const; 118 std::string description() const;
113 std::runtime_error type_error(char const* expected_type) const; 119 std::runtime_error type_error(char const* expected_type) const;
114 QPDFExc type_error(char const* expected_type, std::string const& message) const; 120 QPDFExc type_error(char const* expected_type, std::string const& message) const;
libqpdf/NNTree.cc
@@ -11,6 +11,8 @@ @@ -11,6 +11,8 @@
11 #include <exception> 11 #include <exception>
12 #include <utility> 12 #include <utility>
13 13
  14 +using namespace qpdf;
  15 +
14 static std::string 16 static std::string
15 get_description(QPDFObjectHandle const& node) 17 get_description(QPDFObjectHandle const& node)
16 { 18 {
@@ -66,7 +68,7 @@ NNTreeIterator::updateIValue(bool allow_invalid) @@ -66,7 +68,7 @@ NNTreeIterator::updateIValue(bool allow_invalid)
66 ivalue.second = QPDFObjectHandle(); 68 ivalue.second = QPDFObjectHandle();
67 return; 69 return;
68 } 70 }
69 - auto items = node.getKey(impl.details.itemsKey()); 71 + Array items = node.getKey(impl.details.itemsKey());
70 if (!std::cmp_less(item_number + 1, items.size())) { 72 if (!std::cmp_less(item_number + 1, items.size())) {
71 impl.error(node, "update ivalue: items array is too short"); 73 impl.error(node, "update ivalue: items array is too short");
72 } 74 }
@@ -85,7 +87,7 @@ NNTreeIterator::getNextKid(PathElement&amp; pe, bool backward) @@ -85,7 +87,7 @@ NNTreeIterator::getNextKid(PathElement&amp; pe, bool backward)
85 { 87 {
86 while (true) { 88 while (true) {
87 pe.kid_number += backward ? -1 : 1; 89 pe.kid_number += backward ? -1 : 1;
88 - auto kids = pe.node.getKey("/Kids"); 90 + Array kids = pe.node.getKey("/Kids");
89 if (pe.kid_number >= 0 && std::cmp_less(pe.kid_number, kids.size())) { 91 if (pe.kid_number >= 0 && std::cmp_less(pe.kid_number, kids.size())) {
90 auto result = kids[pe.kid_number]; 92 auto result = kids[pe.kid_number];
91 if (result.isDictionary() && 93 if (result.isDictionary() &&
@@ -117,7 +119,7 @@ NNTreeIterator::increment(bool backward) @@ -117,7 +119,7 @@ NNTreeIterator::increment(bool backward)
117 119
118 while (valid()) { 120 while (valid()) {
119 item_number += backward ? -2 : 2; 121 item_number += backward ? -2 : 2;
120 - auto items = node.getKey(impl.details.itemsKey()); 122 + Array items = node.getKey(impl.details.itemsKey());
121 if (item_number < 0 || std::cmp_greater_equal(item_number, items.size())) { 123 if (item_number < 0 || std::cmp_greater_equal(item_number, items.size())) {
122 bool found = false; 124 bool found = false;
123 setItemNumber(QPDFObjectHandle(), -1); 125 setItemNumber(QPDFObjectHandle(), -1);
@@ -152,9 +154,9 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list&lt;PathElement&gt;::ite @@ -152,9 +154,9 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list&lt;PathElement&gt;::ite
152 a_node.removeKey("/Limits"); 154 a_node.removeKey("/Limits");
153 break; 155 break;
154 } 156 }
155 - auto kids = a_node.getKey("/Kids");  
156 - size_t nkids = kids.isArray() ? kids.size() : 0;  
157 - auto items = a_node.getKey(impl.details.itemsKey()); 157 + Array kids = a_node.getKey("/Kids");
  158 + size_t nkids = kids.size();
  159 + Array items = a_node.getKey(impl.details.itemsKey());
158 size_t nitems = items.size(); 160 size_t nitems = items.size();
159 161
160 bool changed = true; 162 bool changed = true;
@@ -167,8 +169,8 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list&lt;PathElement&gt;::ite @@ -167,8 +169,8 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list&lt;PathElement&gt;::ite
167 auto first_kid = kids[0]; 169 auto first_kid = kids[0];
168 auto last_kid = kids[nkids - 1u]; 170 auto last_kid = kids[nkids - 1u];
169 if (first_kid.isDictionary() && last_kid.isDictionary()) { 171 if (first_kid.isDictionary() && last_kid.isDictionary()) {
170 - auto first_limits = first_kid.getKey("/Limits");  
171 - auto last_limits = last_kid.getKey("/Limits"); 172 + Array first_limits = first_kid.getKey("/Limits");
  173 + Array last_limits = last_kid.getKey("/Limits");
172 if (first_limits.size() >= 2 && last_limits.size() >= 2) { 174 if (first_limits.size() >= 2 && last_limits.size() >= 2) {
173 first = first_limits[0]; 175 first = first_limits[0];
174 last = last_limits[1]; 176 last = last_limits[1];
@@ -176,16 +178,14 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list&lt;PathElement&gt;::ite @@ -176,16 +178,14 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list&lt;PathElement&gt;::ite
176 } 178 }
177 } 179 }
178 if (first && last) { 180 if (first && last) {
179 - auto limits = QPDFObjectHandle::newArray();  
180 - limits.appendItem(first);  
181 - limits.appendItem(last);  
182 - auto olimits = a_node.getKey("/Limits"); 181 + Array limits({first, last});
  182 + Array olimits = a_node.getKey("/Limits");
183 if (olimits.size() == 2) { 183 if (olimits.size() == 2) {
184 auto ofirst = olimits[0]; 184 auto ofirst = olimits[0];
185 auto olast = olimits[1]; 185 auto olast = olimits[1];
186 if (impl.details.keyValid(ofirst) && impl.details.keyValid(olast) && 186 if (impl.details.keyValid(ofirst) && impl.details.keyValid(olast) &&
187 - (impl.details.compareKeys(first, ofirst) == 0) &&  
188 - (impl.details.compareKeys(last, olast) == 0)) { 187 + impl.details.compareKeys(first, ofirst) == 0 &&
  188 + impl.details.compareKeys(last, olast) == 0) {
189 changed = false; 189 changed = false;
190 } 190 }
191 } 191 }
@@ -240,24 +240,23 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato @@ -240,24 +240,23 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
240 } 240 }
241 241
242 // Find the array we actually need to split, which is either this node's kids or items. 242 // Find the array we actually need to split, which is either this node's kids or items.
243 - auto kids = to_split.getKey("/Kids");  
244 - int nkids = kids.isArray() ? static_cast<int>(kids.size()) : 0;  
245 - auto items = to_split.getKey(impl.details.itemsKey());  
246 - int nitems = items.isArray() ? static_cast<int>(items.size()) : 0; 243 + Array kids = to_split.getKey("/Kids");
  244 + size_t nkids = kids.size();
  245 + Array items = to_split.getKey(impl.details.itemsKey());
  246 + size_t nitems = items.size();
247 247
248 - QPDFObjectHandle first_half;  
249 - int n = 0; 248 + Array first_half;
  249 + size_t n = 0;
250 std::string key; 250 std::string key;
251 - int threshold = 0; 251 + size_t threshold = static_cast<size_t>(impl.split_threshold);
252 if (nkids > 0) { 252 if (nkids > 0) {
253 first_half = kids; 253 first_half = kids;
254 n = nkids; 254 n = nkids;
255 - threshold = impl.split_threshold;  
256 key = "/Kids"; 255 key = "/Kids";
257 } else if (nitems > 0) { 256 } else if (nitems > 0) {
258 first_half = items; 257 first_half = items;
259 n = nitems; 258 n = nitems;
260 - threshold = 2 * impl.split_threshold; 259 + threshold *= 2;
261 key = impl.details.itemsKey(); 260 key = impl.details.itemsKey();
262 } else { 261 } else {
263 throw std::logic_error("NNTreeIterator::split called on invalid node"); 262 throw std::logic_error("NNTreeIterator::split called on invalid node");
@@ -289,8 +288,8 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato @@ -289,8 +288,8 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
289 288
290 auto first_node = impl.qpdf.makeIndirectObject(QPDFObjectHandle::newDictionary()); 289 auto first_node = impl.qpdf.makeIndirectObject(QPDFObjectHandle::newDictionary());
291 first_node.replaceKey(key, first_half); 290 first_node.replaceKey(key, first_half);
292 - QPDFObjectHandle new_kids = QPDFObjectHandle::newArray();  
293 - new_kids.appendItem(first_node); 291 + Array new_kids;
  292 + new_kids.push_back(first_node);
294 to_split.removeKey("/Limits"); // already shouldn't be there for root 293 to_split.removeKey("/Limits"); // already shouldn't be there for root
295 to_split.removeKey(impl.details.itemsKey()); 294 to_split.removeKey(impl.details.itemsKey());
296 to_split.replaceKey("/Kids", new_kids); 295 to_split.replaceKey("/Kids", new_kids);
@@ -310,11 +309,11 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato @@ -310,11 +309,11 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
310 309
311 // Create a second half array, and transfer the second half of the items into the second half 310 // Create a second half array, and transfer the second half of the items into the second half
312 // array. 311 // array.
313 - QPDFObjectHandle second_half = QPDFObjectHandle::newArray();  
314 - int start_idx = ((n / 2) & ~1); 312 + Array second_half;
  313 + auto start_idx = static_cast<int>((n / 2) & ~1u);
315 while (std::cmp_greater(first_half.size(), start_idx)) { 314 while (std::cmp_greater(first_half.size(), start_idx)) {
316 - second_half.appendItem(first_half[start_idx]);  
317 - first_half.eraseItem(start_idx); 315 + second_half.push_back(first_half[start_idx]);
  316 + first_half.erase(start_idx);
318 } 317 }
319 resetLimits(to_split, parent); 318 resetLimits(to_split, parent);
320 319
@@ -331,8 +330,8 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato @@ -331,8 +330,8 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
331 // kid_number to traverse through it. We need to update to_split's path element, or the node if 330 // kid_number to traverse through it. We need to update to_split's path element, or the node if
332 // this is a leaf, so that the kid/item number points to the right place. 331 // this is a leaf, so that the kid/item number points to the right place.
333 332
334 - auto parent_kids = parent->node.getKey("/Kids");  
335 - parent_kids.insertItem(parent->kid_number + 1, second_node); 333 + Array parent_kids = parent->node.getKey("/Kids");
  334 + parent_kids.insert(parent->kid_number + 1, second_node);
336 auto cur_elem = parent; 335 auto cur_elem = parent;
337 ++cur_elem; // points to end() for leaf nodes 336 ++cur_elem; // points to end() for leaf nodes
338 int old_idx = (is_leaf ? item_number : cur_elem->kid_number); 337 int old_idx = (is_leaf ? item_number : cur_elem->kid_number);
@@ -367,22 +366,21 @@ void @@ -367,22 +366,21 @@ void
367 NNTreeIterator::insertAfter(QPDFObjectHandle const& key, QPDFObjectHandle const& value) 366 NNTreeIterator::insertAfter(QPDFObjectHandle const& key, QPDFObjectHandle const& value)
368 { 367 {
369 if (!valid()) { 368 if (!valid()) {
370 - QTC::TC("qpdf", "NNTree insertAfter inserts first");  
371 impl.insertFirst(key, value); 369 impl.insertFirst(key, value);
372 deepen(impl.oh, true, false); 370 deepen(impl.oh, true, false);
373 return; 371 return;
374 } 372 }
375 373
376 - auto items = node.getKey(impl.details.itemsKey()); 374 + Array items = node.getKey(impl.details.itemsKey());
  375 + if (!items) {
  376 + impl.error(node, "node contains no items array");
  377 + }
377 378
378 if (std::cmp_less(items.size(), item_number + 2)) { 379 if (std::cmp_less(items.size(), item_number + 2)) {
379 - if (!items.isArray()) {  
380 - impl.error(node, "node contains no items array");  
381 - }  
382 impl.error(node, "insert: items array is too short"); 380 impl.error(node, "insert: items array is too short");
383 } 381 }
384 - items.insertItem(item_number + 2, key);  
385 - items.insertItem(item_number + 3, value); 382 + items.insert(item_number + 2, key);
  383 + items.insert(item_number + 3, value);
386 resetLimits(node, lastPathElement()); 384 resetLimits(node, lastPathElement());
387 split(node, lastPathElement()); 385 split(node, lastPathElement());
388 increment(false); 386 increment(false);
@@ -396,14 +394,14 @@ NNTreeIterator::remove() @@ -396,14 +394,14 @@ NNTreeIterator::remove()
396 if (!valid()) { 394 if (!valid()) {
397 throw std::logic_error("attempt made to remove an invalid iterator"); 395 throw std::logic_error("attempt made to remove an invalid iterator");
398 } 396 }
399 - auto items = node.getKey(impl.details.itemsKey()); 397 + Array items = node.getKey(impl.details.itemsKey());
400 int nitems = static_cast<int>(items.size()); 398 int nitems = static_cast<int>(items.size());
401 if (std::cmp_greater(item_number + 2, nitems)) { 399 if (std::cmp_greater(item_number + 2, nitems)) {
402 impl.error(node, "found short items array while removing an item"); 400 impl.error(node, "found short items array while removing an item");
403 } 401 }
404 402
405 - items.eraseItem(item_number);  
406 - items.eraseItem(item_number); 403 + items.erase(item_number);
  404 + items.erase(item_number);
407 nitems -= 2; 405 nitems -= 2;
408 406
409 if (nitems > 0) { 407 if (nitems > 0) {
@@ -443,8 +441,8 @@ NNTreeIterator::remove() @@ -443,8 +441,8 @@ NNTreeIterator::remove()
443 auto element = lastPathElement(); 441 auto element = lastPathElement();
444 auto parent = element; 442 auto parent = element;
445 --parent; 443 --parent;
446 - auto kids = element->node.getKey("/Kids");  
447 - kids.eraseItem(element->kid_number); 444 + Array kids = element->node.getKey("/Kids");
  445 + kids.erase(element->kid_number);
448 auto nkids = kids.size(); 446 auto nkids = kids.size();
449 if (nkids > 0) { 447 if (nkids > 0) {
450 // The logic here is similar to the items case. 448 // The logic here is similar to the items case.
@@ -462,7 +460,7 @@ NNTreeIterator::remove() @@ -462,7 +460,7 @@ NNTreeIterator::remove()
462 } 460 }
463 } else { 461 } else {
464 // Next kid is in deleted kid's position 462 // Next kid is in deleted kid's position
465 - deepen(kids.getArrayItem(element->kid_number), true, true); 463 + deepen(kids.get(element->kid_number), true, true);
466 } 464 }
467 return; 465 return;
468 } 466 }
@@ -470,7 +468,7 @@ NNTreeIterator::remove() @@ -470,7 +468,7 @@ NNTreeIterator::remove()
470 if (parent == path.end()) { 468 if (parent == path.end()) {
471 // We erased the very last item. Convert the root to an empty items array. 469 // We erased the very last item. Convert the root to an empty items array.
472 element->node.removeKey("/Kids"); 470 element->node.removeKey("/Kids");
473 - element->node.replaceKey(impl.details.itemsKey(), QPDFObjectHandle::newArray()); 471 + element->node.replaceKey(impl.details.itemsKey(), Array());
474 path.clear(); 472 path.clear();
475 setItemNumber(impl.oh, -1); 473 setItemNumber(impl.oh, -1);
476 return; 474 return;
@@ -527,10 +525,7 @@ NNTreeIterator::operator==(NNTreeIterator const&amp; other) const @@ -527,10 +525,7 @@ NNTreeIterator::operator==(NNTreeIterator const&amp; other) const
527 ++tpi; 525 ++tpi;
528 ++opi; 526 ++opi;
529 } 527 }
530 - if (item_number != other.item_number) {  
531 - return false;  
532 - }  
533 - return true; 528 + return item_number == other.item_number;
534 } 529 }
535 530
536 void 531 void
@@ -574,27 +569,30 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) @@ -574,27 +569,30 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty)
574 return fail(a_node, "non-dictionary node while traversing name/number tree"); 569 return fail(a_node, "non-dictionary node while traversing name/number tree");
575 } 570 }
576 571
577 - auto items = a_node.getKey(impl.details.itemsKey()); 572 + Array items = a_node.getKey(impl.details.itemsKey());
578 int nitems = static_cast<int>(items.size()); 573 int nitems = static_cast<int>(items.size());
579 if (nitems > 1) { 574 if (nitems > 1) {
580 setItemNumber(a_node, first ? 0 : nitems - 2); 575 setItemNumber(a_node, first ? 0 : nitems - 2);
581 break; 576 break;
582 } 577 }
583 578
584 - auto kids = a_node.getKey("/Kids");  
585 - int nkids = kids.isArray() ? static_cast<int>(kids.size()) : 0; 579 + Array kids = a_node.getKey("/Kids");
  580 + int nkids = static_cast<int>(kids.size());
586 if (nkids > 0) { 581 if (nkids > 0) {
587 int kid_number = first ? 0 : nkids - 1; 582 int kid_number = first ? 0 : nkids - 1;
588 addPathElement(a_node, kid_number); 583 addPathElement(a_node, kid_number);
589 auto next = kids[kid_number]; 584 auto next = kids[kid_number];
590 - if (!next.isIndirect()) { 585 + if (!next) {
  586 + return fail(a_node, "kid number " + std::to_string(kid_number) + " is invalid");
  587 + }
  588 + if (!next.indirect()) {
591 if (impl.auto_repair) { 589 if (impl.auto_repair) {
592 impl.warn( 590 impl.warn(
593 a_node, 591 a_node,
594 "converting kid number " + std::to_string(kid_number) + 592 "converting kid number " + std::to_string(kid_number) +
595 " to an indirect object"); 593 " to an indirect object");
596 next = impl.qpdf.makeIndirectObject(next); 594 next = impl.qpdf.makeIndirectObject(next);
597 - kids.setArrayItem(kid_number, next); 595 + kids.set(kid_number, next);
598 } else { 596 } else {
599 impl.warn( 597 impl.warn(
600 a_node, 598 a_node,
@@ -602,7 +600,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) @@ -602,7 +600,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty)
602 } 600 }
603 } 601 }
604 a_node = next; 602 a_node = next;
605 - } else if (allow_empty && items.isArray()) { 603 + } else if (allow_empty && items) {
606 setItemNumber(a_node, -1); 604 setItemNumber(a_node, -1);
607 break; 605 break;
608 } else { 606 } else {
@@ -655,8 +653,8 @@ NNTreeImpl::last() @@ -655,8 +653,8 @@ NNTreeImpl::last()
655 int 653 int
656 NNTreeImpl::withinLimits(QPDFObjectHandle const& key, QPDFObjectHandle const& node) 654 NNTreeImpl::withinLimits(QPDFObjectHandle const& key, QPDFObjectHandle const& node)
657 { 655 {
658 - auto limits = node.getKey("/Limits");  
659 - if (!(limits.size() >= 2 && details.keyValid(limits[0]) && details.keyValid(limits[1]))) { 656 + Array limits = node.getKey("/Limits");
  657 + if (!(details.keyValid(limits[0]) && details.keyValid(limits[1]))) {
660 error(node, "node is missing /Limits"); 658 error(node, "node is missing /Limits");
661 } 659 }
662 if (details.compareKeys(key, limits[0]) < 0) { 660 if (details.compareKeys(key, limits[0]) < 0) {
@@ -734,7 +732,7 @@ void @@ -734,7 +732,7 @@ void
734 NNTreeImpl::repair() 732 NNTreeImpl::repair()
735 { 733 {
736 auto new_node = QPDFObjectHandle::newDictionary(); 734 auto new_node = QPDFObjectHandle::newDictionary();
737 - new_node.replaceKey(details.itemsKey(), QPDFObjectHandle::newArray()); 735 + new_node.replaceKey(details.itemsKey(), Array());
738 NNTreeImpl repl(details, qpdf, new_node, false); 736 NNTreeImpl repl(details, qpdf, new_node, false);
739 for (auto const& i: *this) { 737 for (auto const& i: *this) {
740 repl.insert(i.first, i.second); 738 repl.insert(i.first, i.second);
@@ -750,7 +748,6 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found) @@ -750,7 +748,6 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found)
750 return findInternal(key, return_prev_if_not_found); 748 return findInternal(key, return_prev_if_not_found);
751 } catch (QPDFExc& e) { 749 } catch (QPDFExc& e) {
752 if (auto_repair) { 750 if (auto_repair) {
753 - QTC::TC("qpdf", "NNTree repair");  
754 warn(oh, std::string("attempting to repair after error: ") + e.what()); 751 warn(oh, std::string("attempting to repair after error: ") + e.what());
755 repair(); 752 repair();
756 return findInternal(key, return_prev_if_not_found); 753 return findInternal(key, return_prev_if_not_found);
@@ -784,7 +781,7 @@ NNTreeImpl::findInternal(QPDFObjectHandle const&amp; key, bool return_prev_if_not_fo @@ -784,7 +781,7 @@ NNTreeImpl::findInternal(QPDFObjectHandle const&amp; key, bool return_prev_if_not_fo
784 error(node, "loop detected in find"); 781 error(node, "loop detected in find");
785 } 782 }
786 783
787 - auto items = node.getKey(details.itemsKey()); 784 + Array items = node.getKey(details.itemsKey());
788 size_t nitems = items.size(); 785 size_t nitems = items.size();
789 if (nitems > 1) { 786 if (nitems > 1) {
790 int idx = binarySearch( 787 int idx = binarySearch(
@@ -795,8 +792,8 @@ NNTreeImpl::findInternal(QPDFObjectHandle const&amp; key, bool return_prev_if_not_fo @@ -795,8 +792,8 @@ NNTreeImpl::findInternal(QPDFObjectHandle const&amp; key, bool return_prev_if_not_fo
795 return result; 792 return result;
796 } 793 }
797 794
798 - auto kids = node.getKey("/Kids");  
799 - size_t nkids = kids.isArray() ? kids.size() : 0; 795 + Array kids = node.getKey("/Kids");
  796 + size_t nkids = kids.size();
800 if (nkids > 0) { 797 if (nkids > 0) {
801 int idx = binarySearch(key, kids, nkids, true, &NNTreeImpl::compareKeyKid); 798 int idx = binarySearch(key, kids, nkids, true, &NNTreeImpl::compareKeyKid);
802 if (idx == -1) { 799 if (idx == -1) {
@@ -814,15 +811,15 @@ NNTreeImpl::iterator @@ -814,15 +811,15 @@ NNTreeImpl::iterator
814 NNTreeImpl::insertFirst(QPDFObjectHandle const& key, QPDFObjectHandle const& value) 811 NNTreeImpl::insertFirst(QPDFObjectHandle const& key, QPDFObjectHandle const& value)
815 { 812 {
816 auto iter = begin(); 813 auto iter = begin();
817 - QPDFObjectHandle items; 814 + Array items(nullptr);
818 if (iter.node.isDictionary()) { 815 if (iter.node.isDictionary()) {
819 items = iter.node.getKey(details.itemsKey()); 816 items = iter.node.getKey(details.itemsKey());
820 } 817 }
821 - if (!items.isArray()) { 818 + if (!items) {
822 error(oh, "unable to find a valid items node"); 819 error(oh, "unable to find a valid items node");
823 } 820 }
824 - items.insertItem(0, key);  
825 - items.insertItem(1, value); 821 + items.insert(0, key);
  822 + items.insert(1, value);
826 iter.setItemNumber(iter.node, 0); 823 iter.setItemNumber(iter.node, 0);
827 iter.resetLimits(iter.node, iter.lastPathElement()); 824 iter.resetLimits(iter.node, iter.lastPathElement());
828 iter.split(iter.node, iter.lastPathElement()); 825 iter.split(iter.node, iter.lastPathElement());
@@ -836,8 +833,8 @@ NNTreeImpl::insert(QPDFObjectHandle const&amp; key, QPDFObjectHandle const&amp; value) @@ -836,8 +833,8 @@ NNTreeImpl::insert(QPDFObjectHandle const&amp; key, QPDFObjectHandle const&amp; value)
836 if (!iter.valid()) { 833 if (!iter.valid()) {
837 return insertFirst(key, value); 834 return insertFirst(key, value);
838 } else if (details.compareKeys(key, iter->first) == 0) { 835 } else if (details.compareKeys(key, iter->first) == 0) {
839 - auto items = iter.node.getKey(details.itemsKey());  
840 - items.setArrayItem(iter.item_number + 1, value); 836 + Array items = iter.node.getKey(details.itemsKey());
  837 + items.set(iter.item_number + 1, value);
841 iter.updateIValue(); 838 iter.updateIValue();
842 } else { 839 } else {
843 iter.insertAfter(key, value); 840 iter.insertAfter(key, value);
libqpdf/QPDFObjectHandle.cc
@@ -486,7 +486,7 @@ BaseHandle::write_json(int json_version, JSON::Writer&amp; p) const @@ -486,7 +486,7 @@ BaseHandle::write_json(int json_version, JSON::Writer&amp; p) const
486 } else { 486 } else {
487 for (auto const& item: a.elements) { 487 for (auto const& item: a.elements) {
488 p.writeNext(); 488 p.writeNext();
489 - auto item_og = item.getObj()->getObjGen(); 489 + auto item_og = item.id_gen();
490 if (item_og.isIndirect()) { 490 if (item_og.isIndirect()) {
491 p << "\"" << item_og.unparse(' ') << " R\""; 491 p << "\"" << item_og.unparse(' ') << " R\"";
492 } else { 492 } else {
@@ -1234,11 +1234,10 @@ QPDFObjectHandle::arrayOrStreamToStreamArray( @@ -1234,11 +1234,10 @@ QPDFObjectHandle::arrayOrStreamToStreamArray(
1234 if (auto array = as_array(strict)) { 1234 if (auto array = as_array(strict)) {
1235 int n_items = static_cast<int>(array.size()); 1235 int n_items = static_cast<int>(array.size());
1236 for (int i = 0; i < n_items; ++i) { 1236 for (int i = 0; i < n_items; ++i) {
1237 - QPDFObjectHandle item = array.at(i).second; 1237 + QPDFObjectHandle item = array[i];
1238 if (item.isStream()) { 1238 if (item.isStream()) {
1239 result.emplace_back(item); 1239 result.emplace_back(item);
1240 } else { 1240 } else {
1241 - QTC::TC("qpdf", "QPDFObjectHandle non-stream in stream array");  
1242 item.warn( 1241 item.warn(
1243 {qpdf_e_damaged_pdf, 1242 {qpdf_e_damaged_pdf,
1244 "", 1243 "",
libqpdf/QPDF_Array.cc
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 2
3 #include <qpdf/QTC.hh> 3 #include <qpdf/QTC.hh>
4 4
  5 +#include <array>
5 #include <utility> 6 #include <utility>
6 7
7 using namespace std::literals; 8 using namespace std::literals;
@@ -60,6 +61,21 @@ Array::array() const @@ -60,6 +61,21 @@ Array::array() const
60 return nullptr; // unreachable 61 return nullptr; // unreachable
61 } 62 }
62 63
  64 +Array::Array(bool empty) :
  65 + BaseHandle(empty ? QPDFObject::create<QPDF_Array>() : nullptr)
  66 +{
  67 +}
  68 +
  69 +Array::Array(std::vector<QPDFObjectHandle> const& items) :
  70 + BaseHandle(QPDFObject::create<QPDF_Array>(items))
  71 +{
  72 +}
  73 +
  74 +Array::Array(std::vector<QPDFObjectHandle>&& items) :
  75 + BaseHandle(QPDFObject::create<QPDF_Array>(std::move(items)))
  76 +{
  77 +}
  78 +
63 Array::iterator 79 Array::iterator
64 Array::begin() 80 Array::begin()
65 { 81 {
@@ -159,22 +175,58 @@ Array::null() const @@ -159,22 +175,58 @@ Array::null() const
159 size_t 175 size_t
160 Array::size() const 176 Array::size() const
161 { 177 {
162 - auto a = array();  
163 - return a->sp ? a->sp->size : a->elements.size(); 178 + if (auto a = as<QPDF_Array>()) {
  179 + return a->sp ? a->sp->size : a->elements.size();
  180 + }
  181 + return 0;
164 } 182 }
165 183
166 -std::pair<bool, QPDFObjectHandle>  
167 -Array::at(int n) const 184 +QPDFObjectHandle const&
  185 +Array::operator[](size_t n) const
168 { 186 {
169 - auto a = array();  
170 - if (n < 0 || std::cmp_greater_equal(n, size())) {  
171 - return {false, {}}; 187 + static const QPDFObjectHandle null_obj;
  188 + auto a = as<QPDF_Array>();
  189 + if (!a) {
  190 + return null_obj;
  191 + }
  192 + if (a->sp) {
  193 + auto const& iter = a->sp->elements.find(n);
  194 + return iter == a->sp->elements.end() ? null_obj : iter->second;
172 } 195 }
  196 + return n >= a->elements.size() ? null_obj : a->elements[n];
  197 +}
  198 +
  199 +QPDFObjectHandle const&
  200 +Array::operator[](int n) const
  201 +{
  202 + static const QPDFObjectHandle null_obj;
  203 + if (n < 0) {
  204 + return null_obj;
  205 + }
  206 + return (*this)[static_cast<size_t>(n)];
  207 +}
  208 +
  209 +QPDFObjectHandle
  210 +Array::get(size_t n) const
  211 +{
  212 + if (n >= size()) {
  213 + return {};
  214 + }
  215 + auto a = array();
173 if (!a->sp) { 216 if (!a->sp) {
174 - return {true, a->elements[to_s(n)]}; 217 + return a->elements[n];
175 } 218 }
176 - auto const& iter = a->sp->elements.find(to_s(n));  
177 - return {true, iter == a->sp->elements.end() ? null() : iter->second}; 219 + auto const& iter = a->sp->elements.find(n);
  220 + return iter == a->sp->elements.end() ? null() : iter->second;
  221 +}
  222 +
  223 +QPDFObjectHandle
  224 +Array::get(int n) const
  225 +{
  226 + if (n < 0) {
  227 + return {};
  228 + }
  229 + return get(to_s(n));
178 } 230 }
179 231
180 std::vector<QPDFObjectHandle> 232 std::vector<QPDFObjectHandle>
@@ -196,21 +248,30 @@ Array::getAsVector() const @@ -196,21 +248,30 @@ Array::getAsVector() const
196 } 248 }
197 249
198 bool 250 bool
199 -Array::setAt(int at, QPDFObjectHandle const& oh) 251 +Array::set(size_t at, QPDFObjectHandle const& oh)
200 { 252 {
201 - if (at < 0 || std::cmp_greater_equal(at, size())) { 253 + if (at >= size()) {
202 return false; 254 return false;
203 } 255 }
204 auto a = array(); 256 auto a = array();
205 checkOwnership(oh); 257 checkOwnership(oh);
206 if (a->sp) { 258 if (a->sp) {
207 - a->sp->elements[to_s(at)] = oh; 259 + a->sp->elements[at] = oh;
208 } else { 260 } else {
209 - a->elements[to_s(at)] = oh; 261 + a->elements[at] = oh;
210 } 262 }
211 return true; 263 return true;
212 } 264 }
213 265
  266 +bool
  267 +Array::set(int at, QPDFObjectHandle const& oh)
  268 +{
  269 + if (at < 0) {
  270 + return false;
  271 + }
  272 + return set(to_s(at), oh);
  273 +}
  274 +
214 void 275 void
215 Array::setFromVector(std::vector<QPDFObjectHandle> const& v) 276 Array::setFromVector(std::vector<QPDFObjectHandle> const& v)
216 { 277 {
@@ -224,43 +285,48 @@ Array::setFromVector(std::vector&lt;QPDFObjectHandle&gt; const&amp; v) @@ -224,43 +285,48 @@ Array::setFromVector(std::vector&lt;QPDFObjectHandle&gt; const&amp; v)
224 } 285 }
225 286
226 bool 287 bool
227 -Array::insert(int at_i, QPDFObjectHandle const& item) 288 +Array::insert(size_t at, QPDFObjectHandle const& item)
228 { 289 {
229 auto a = array(); 290 auto a = array();
230 size_t sz = size(); 291 size_t sz = size();
231 - if (at_i < 0) {  
232 - return false;  
233 - }  
234 - size_t at = to_s(at_i);  
235 if (at > sz) { 292 if (at > sz) {
236 return false; 293 return false;
237 } 294 }
  295 + checkOwnership(item);
238 if (at == sz) { 296 if (at == sz) {
239 // As special case, also allow insert beyond the end 297 // As special case, also allow insert beyond the end
240 push_back(item); 298 push_back(item);
241 return true; 299 return true;
242 } 300 }
243 - checkOwnership(item);  
244 - if (a->sp) {  
245 - auto iter = a->sp->elements.crbegin();  
246 - while (iter != a->sp->elements.crend()) {  
247 - auto key = (iter++)->first;  
248 - if (key >= at) {  
249 - auto nh = a->sp->elements.extract(key);  
250 - ++nh.key();  
251 - a->sp->elements.insert(std::move(nh));  
252 - } else {  
253 - break;  
254 - } 301 + if (!a->sp) {
  302 + a->elements.insert(a->elements.cbegin() + to_i(at), item);
  303 + return true;
  304 + }
  305 + auto iter = a->sp->elements.crbegin();
  306 + while (iter != a->sp->elements.crend()) {
  307 + auto key = (iter++)->first;
  308 + if (key >= at) {
  309 + auto nh = a->sp->elements.extract(key);
  310 + ++nh.key();
  311 + a->sp->elements.insert(std::move(nh));
  312 + } else {
  313 + break;
255 } 314 }
256 - a->sp->elements[at] = item.getObj();  
257 - ++a->sp->size;  
258 - } else {  
259 - a->elements.insert(a->elements.cbegin() + at_i, item.getObj());  
260 } 315 }
  316 + a->sp->elements[at] = item;
  317 + ++a->sp->size;
261 return true; 318 return true;
262 } 319 }
263 320
  321 +bool
  322 +Array::insert(int at_i, QPDFObjectHandle const& item)
  323 +{
  324 + if (at_i < 0) {
  325 + return false;
  326 + }
  327 + return insert(to_s(at_i), item);
  328 +}
  329 +
264 void 330 void
265 Array::push_back(QPDFObjectHandle const& item) 331 Array::push_back(QPDFObjectHandle const& item)
266 { 332 {
@@ -274,61 +340,68 @@ Array::push_back(QPDFObjectHandle const&amp; item) @@ -274,61 +340,68 @@ Array::push_back(QPDFObjectHandle const&amp; item)
274 } 340 }
275 341
276 bool 342 bool
277 -Array::erase(int at_i) 343 +Array::erase(size_t at)
278 { 344 {
279 auto a = array(); 345 auto a = array();
280 - if (at_i < 0) {  
281 - return false;  
282 - }  
283 - size_t at = to_s(at_i);  
284 if (at >= size()) { 346 if (at >= size()) {
285 return false; 347 return false;
286 } 348 }
287 - if (a->sp) {  
288 - auto end = a->sp->elements.end();  
289 - if (auto iter = a->sp->elements.lower_bound(at); iter != end) {  
290 - if (iter->first == at) {  
291 - iter++;  
292 - a->sp->elements.erase(at);  
293 - } 349 + if (!a->sp) {
  350 + a->elements.erase(a->elements.cbegin() + to_i(at));
  351 + return true;
  352 + }
  353 + auto end = a->sp->elements.end();
  354 + if (auto iter = a->sp->elements.lower_bound(at); iter != end) {
  355 + if (iter->first == at) {
  356 + iter++;
  357 + a->sp->elements.erase(at);
  358 + }
294 359
295 - while (iter != end) {  
296 - auto nh = a->sp->elements.extract(iter++);  
297 - --nh.key();  
298 - a->sp->elements.insert(std::move(nh));  
299 - } 360 + while (iter != end) {
  361 + auto nh = a->sp->elements.extract(iter++);
  362 + --nh.key();
  363 + a->sp->elements.insert(std::move(nh));
300 } 364 }
301 - --(a->sp->size);  
302 - } else {  
303 - a->elements.erase(a->elements.cbegin() + at_i);  
304 } 365 }
  366 + --(a->sp->size);
305 return true; 367 return true;
306 } 368 }
307 369
  370 +bool
  371 +Array::erase(int at_i)
  372 +{
  373 + if (at_i < 0) {
  374 + return false;
  375 + }
  376 + return erase(to_s(at_i));
  377 +}
  378 +
308 int 379 int
309 QPDFObjectHandle::getArrayNItems() const 380 QPDFObjectHandle::getArrayNItems() const
310 { 381 {
311 - if (auto array = as_array(strict)) {  
312 - return to_i(array.size()); 382 + auto s = size();
  383 + if (s > 1 || isArray()) {
  384 + return to_i(s);
313 } 385 }
314 typeWarning("array", "treating as empty"); 386 typeWarning("array", "treating as empty");
315 - QTC::TC("qpdf", "QPDFObjectHandle array treating as empty");  
316 return 0; 387 return 0;
317 } 388 }
318 389
319 QPDFObjectHandle 390 QPDFObjectHandle
320 QPDFObjectHandle::getArrayItem(int n) const 391 QPDFObjectHandle::getArrayItem(int n) const
321 { 392 {
322 - if (auto array = as_array(strict)) {  
323 - if (auto const [success, oh] = array.at(n); success) {  
324 - return oh;  
325 - } else {  
326 - objectWarning("returning null for out of bounds array access");  
327 - QTC::TC("qpdf", "QPDFObjectHandle array bounds"); 393 + if (auto array = Array(*this)) {
  394 + if (auto result = array[n]) {
  395 + return result;
328 } 396 }
  397 + if (n >= 0 && std::cmp_less(n, array.size())) {
  398 + // sparse array null
  399 + return newNull();
  400 + }
  401 + objectWarning("returning null for out of bounds array access");
  402 +
329 } else { 403 } else {
330 typeWarning("array", "returning null"); 404 typeWarning("array", "returning null");
331 - QTC::TC("qpdf", "QPDFObjectHandle array null for non-array");  
332 } 405 }
333 static auto constexpr msg = " -> null returned from invalid array access"sv; 406 static auto constexpr msg = " -> null returned from invalid array access"sv;
334 return QPDF_Null::create(obj, msg, ""); 407 return QPDF_Null::create(obj, msg, "");
@@ -337,69 +410,61 @@ QPDFObjectHandle::getArrayItem(int n) const @@ -337,69 +410,61 @@ QPDFObjectHandle::getArrayItem(int n) const
337 bool 410 bool
338 QPDFObjectHandle::isRectangle() const 411 QPDFObjectHandle::isRectangle() const
339 { 412 {
340 - if (auto array = as_array(strict)) {  
341 - for (int i = 0; i < 4; ++i) {  
342 - if (auto item = array.at(i).second; !item.isNumber()) {  
343 - return false;  
344 - } 413 + Array array(*this);
  414 + for (auto const& oh: array) {
  415 + if (!oh.isNumber()) {
  416 + return false;
345 } 417 }
346 - return array.size() == 4;  
347 } 418 }
348 - return false; 419 + return array.size() == 4;
349 } 420 }
350 421
351 bool 422 bool
352 QPDFObjectHandle::isMatrix() const 423 QPDFObjectHandle::isMatrix() const
353 { 424 {
354 - if (auto array = as_array(strict)) {  
355 - for (int i = 0; i < 6; ++i) {  
356 - if (auto item = array.at(i).second; !item.isNumber()) {  
357 - return false;  
358 - } 425 + Array array(*this);
  426 + for (auto const& oh: array) {
  427 + if (!oh.isNumber()) {
  428 + return false;
359 } 429 }
360 - return array.size() == 6;  
361 } 430 }
362 - return false; 431 + return array.size() == 6;
363 } 432 }
364 433
365 QPDFObjectHandle::Rectangle 434 QPDFObjectHandle::Rectangle
366 QPDFObjectHandle::getArrayAsRectangle() const 435 QPDFObjectHandle::getArrayAsRectangle() const
367 { 436 {
368 - if (auto array = as_array(strict)) {  
369 - if (array.size() != 4) { 437 + Array array(*this);
  438 + if (array.size() != 4) {
  439 + return {};
  440 + }
  441 + std::array<double, 4> items;
  442 + for (size_t i = 0; i < 4; ++i) {
  443 + if (!array[i].getValueAsNumber(items[i])) {
370 return {}; 444 return {};
371 } 445 }
372 - double items[4];  
373 - for (int i = 0; i < 4; ++i) {  
374 - if (auto item = array.at(i).second; !item.getValueAsNumber(items[i])) {  
375 - return {};  
376 - }  
377 - }  
378 - return {  
379 - std::min(items[0], items[2]),  
380 - std::min(items[1], items[3]),  
381 - std::max(items[0], items[2]),  
382 - std::max(items[1], items[3])};  
383 } 446 }
384 - return {}; 447 + return {
  448 + std::min(items[0], items[2]),
  449 + std::min(items[1], items[3]),
  450 + std::max(items[0], items[2]),
  451 + std::max(items[1], items[3])};
385 } 452 }
386 453
387 QPDFObjectHandle::Matrix 454 QPDFObjectHandle::Matrix
388 QPDFObjectHandle::getArrayAsMatrix() const 455 QPDFObjectHandle::getArrayAsMatrix() const
389 { 456 {
390 - if (auto array = as_array(strict)) {  
391 - if (array.size() != 6) { 457 + Array array(*this);
  458 + if (array.size() != 6) {
  459 + return {};
  460 + }
  461 + std::array<double, 6> items;
  462 + for (size_t i = 0; i < 6; ++i) {
  463 + if (!array[i].getValueAsNumber(items[i])) {
392 return {}; 464 return {};
393 } 465 }
394 - double items[6];  
395 - for (int i = 0; i < 6; ++i) {  
396 - if (auto item = array.at(i).second; !item.getValueAsNumber(items[i])) {  
397 - return {};  
398 - }  
399 - }  
400 - return {items[0], items[1], items[2], items[3], items[4], items[5]};  
401 } 466 }
402 - return {}; 467 + return {items[0], items[1], items[2], items[3], items[4], items[5]};
403 } 468 }
404 469
405 std::vector<QPDFObjectHandle> 470 std::vector<QPDFObjectHandle>
@@ -417,13 +482,11 @@ void @@ -417,13 +482,11 @@ void
417 QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const& item) 482 QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const& item)
418 { 483 {
419 if (auto array = as_array(strict)) { 484 if (auto array = as_array(strict)) {
420 - if (!array.setAt(n, item)) { 485 + if (!array.set(n, item)) {
421 objectWarning("ignoring attempt to set out of bounds array item"); 486 objectWarning("ignoring attempt to set out of bounds array item");
422 - QTC::TC("qpdf", "QPDFObjectHandle set array bounds");  
423 } 487 }
424 } else { 488 } else {
425 typeWarning("array", "ignoring attempt to set item"); 489 typeWarning("array", "ignoring attempt to set item");
426 - QTC::TC("qpdf", "QPDFObjectHandle array ignoring set item");  
427 } 490 }
428 } 491 }
429 void 492 void
@@ -493,11 +556,9 @@ QPDFObjectHandle::eraseItem(int at) @@ -493,11 +556,9 @@ QPDFObjectHandle::eraseItem(int at)
493 QPDFObjectHandle 556 QPDFObjectHandle
494 QPDFObjectHandle::eraseItemAndGetOld(int at) 557 QPDFObjectHandle::eraseItemAndGetOld(int at)
495 { 558 {
496 - auto array = as_array(strict);  
497 - auto result =  
498 - (array && std::cmp_less(at, array.size()) && at >= 0) ? array.at(at).second : newNull(); 559 + auto result = Array(*this)[at];
499 eraseItem(at); 560 eraseItem(at);
500 - return result; 561 + return result ? result : newNull();
501 } 562 }
502 563
503 size_t 564 size_t
@@ -532,36 +593,13 @@ BaseHandle::size() const @@ -532,36 +593,13 @@ BaseHandle::size() const
532 QPDFObjectHandle 593 QPDFObjectHandle
533 BaseHandle::operator[](size_t n) const 594 BaseHandle::operator[](size_t n) const
534 { 595 {
535 - switch (resolved_type_code()) {  
536 - case ::ot_array:  
537 - {  
538 - auto a = as<QPDF_Array>();  
539 - if (n >= a->size()) {  
540 - return {};  
541 - }  
542 - return Array(obj).at(static_cast<int>(n)).second;  
543 - }  
544 - case ::ot_uninitialized:  
545 - case ::ot_reserved:  
546 - case ::ot_null:  
547 - case ::ot_destroyed:  
548 - case ::ot_unresolved:  
549 - case ::ot_reference:  
550 - return {};  
551 - case ::ot_boolean:  
552 - case ::ot_integer:  
553 - case ::ot_real:  
554 - case ::ot_string:  
555 - case ::ot_name:  
556 - case ::ot_dictionary:  
557 - case ::ot_stream:  
558 - case ::ot_inlineimage:  
559 - case ::ot_operator:  
560 - return {obj};  
561 - default:  
562 - throw std::logic_error("Unexpected type code in size"); // unreachable  
563 - return {}; // unreachable 596 + if (resolved_type_code() == ::ot_array) {
  597 + return Array(obj)[n];
564 } 598 }
  599 + if (n < size()) {
  600 + return *this;
  601 + }
  602 + return {};
565 } 603 }
566 604
567 QPDFObjectHandle 605 QPDFObjectHandle
libqpdf/QPDF_Stream.cc
@@ -442,7 +442,7 @@ Stream::filterable( @@ -442,7 +442,7 @@ Stream::filterable(
442 442
443 int i = -1; 443 int i = -1;
444 for (auto& filter: filters) { 444 for (auto& filter: filters) {
445 - auto d_obj = decode_array.at(++i).second; 445 + auto d_obj = decode_array.get(++i);
446 if (!can_filter(decode_level, *filter, d_obj)) { 446 if (!can_filter(decode_level, *filter, d_obj)) {
447 return false; 447 return false;
448 } 448 }
libqpdf/qpdf/QPDFObjectHandle_private.hh
@@ -12,6 +12,30 @@ namespace qpdf @@ -12,6 +12,30 @@ namespace qpdf
12 class Array final: public BaseHandle 12 class Array final: public BaseHandle
13 { 13 {
14 public: 14 public:
  15 + // Create an empty Array. If 'empty' is false, create a null Array.
  16 + Array(bool empty = true);
  17 +
  18 + Array(std::vector<QPDFObjectHandle> const& items);
  19 +
  20 + Array(std::vector<QPDFObjectHandle>&& items);
  21 +
  22 + Array(Array const& other) :
  23 + BaseHandle(other.obj)
  24 + {
  25 + }
  26 +
  27 + Array&
  28 + operator=(Array const& other)
  29 + {
  30 + if (obj != other.obj) {
  31 + obj = other.obj;
  32 + }
  33 + return *this;
  34 + }
  35 +
  36 + Array(Array&&) = default;
  37 + Array& operator=(Array&&) = default;
  38 +
15 explicit Array(std::shared_ptr<QPDFObject> const& obj) : 39 explicit Array(std::shared_ptr<QPDFObject> const& obj) :
16 BaseHandle(obj) 40 BaseHandle(obj)
17 { 41 {
@@ -22,6 +46,34 @@ namespace qpdf @@ -22,6 +46,34 @@ namespace qpdf
22 { 46 {
23 } 47 }
24 48
  49 + Array(QPDFObjectHandle const& oh) :
  50 + BaseHandle(oh.resolved_type_code() == ::ot_array ? oh : QPDFObjectHandle())
  51 + {
  52 + }
  53 +
  54 + Array&
  55 + operator=(QPDFObjectHandle const& oh)
  56 + {
  57 + assign(::ot_array, oh);
  58 + return *this;
  59 + }
  60 +
  61 + Array(QPDFObjectHandle&& oh) :
  62 + BaseHandle(oh.resolved_type_code() == ::ot_array ? std::move(oh) : QPDFObjectHandle())
  63 + {
  64 + }
  65 +
  66 + Array&
  67 + operator=(QPDFObjectHandle&& oh)
  68 + {
  69 + assign(::ot_array, std::move(oh));
  70 + return *this;
  71 + }
  72 +
  73 + QPDFObjectHandle const& operator[](size_t n) const;
  74 +
  75 + QPDFObjectHandle const& operator[](int n) const;
  76 +
25 using iterator = std::vector<QPDFObjectHandle>::iterator; 77 using iterator = std::vector<QPDFObjectHandle>::iterator;
26 using const_iterator = std::vector<QPDFObjectHandle>::const_iterator; 78 using const_iterator = std::vector<QPDFObjectHandle>::const_iterator;
27 using const_reverse_iterator = std::vector<QPDFObjectHandle>::const_reverse_iterator; 79 using const_reverse_iterator = std::vector<QPDFObjectHandle>::const_reverse_iterator;
@@ -38,11 +90,16 @@ namespace qpdf @@ -38,11 +90,16 @@ namespace qpdf
38 90
39 const_reverse_iterator crend(); 91 const_reverse_iterator crend();
40 92
  93 + // Return the number of elements in the array. Return 0 if the object is not an array.
41 size_t size() const; 94 size_t size() const;
42 - std::pair<bool, QPDFObjectHandle> at(int n) const;  
43 - bool setAt(int at, QPDFObjectHandle const& oh); 95 + QPDFObjectHandle get(size_t n) const;
  96 + QPDFObjectHandle get(int n) const;
  97 + bool set(size_t at, QPDFObjectHandle const& oh);
  98 + bool set(int at, QPDFObjectHandle const& oh);
  99 + bool insert(size_t at, QPDFObjectHandle const& item);
44 bool insert(int at, QPDFObjectHandle const& item); 100 bool insert(int at, QPDFObjectHandle const& item);
45 void push_back(QPDFObjectHandle const& item); 101 void push_back(QPDFObjectHandle const& item);
  102 + bool erase(size_t at);
46 bool erase(int at); 103 bool erase(int at);
47 104
48 std::vector<QPDFObjectHandle> getAsVector() const; 105 std::vector<QPDFObjectHandle> getAsVector() const;
@@ -342,6 +399,32 @@ namespace qpdf @@ -342,6 +399,32 @@ namespace qpdf
342 return nullptr; 399 return nullptr;
343 } 400 }
344 401
  402 + inline BaseHandle::BaseHandle(QPDFObjectHandle const& oh) :
  403 + obj(oh.obj)
  404 + {
  405 + }
  406 +
  407 + inline BaseHandle::BaseHandle(QPDFObjectHandle&& oh) :
  408 + obj(std::move(oh.obj))
  409 + {
  410 + }
  411 +
  412 + inline void
  413 + BaseHandle::assign(qpdf_object_type_e required, BaseHandle const& other)
  414 + {
  415 + if (obj != other.obj) {
  416 + obj = other.resolved_type_code() == required ? other.obj : nullptr;
  417 + }
  418 + }
  419 +
  420 + inline void
  421 + BaseHandle::assign(qpdf_object_type_e required, BaseHandle&& other)
  422 + {
  423 + if (obj != other.obj) {
  424 + obj = other.resolved_type_code() == required ? std::move(other.obj) : nullptr;
  425 + }
  426 + }
  427 +
345 inline QPDFObjGen 428 inline QPDFObjGen
346 BaseHandle::id_gen() const 429 BaseHandle::id_gen() const
347 { 430 {
libtests/sparse_array.cc
@@ -26,69 +26,69 @@ main() @@ -26,69 +26,69 @@ main()
26 a.push_back(QPDFObjectHandle::parse("null")); 26 a.push_back(QPDFObjectHandle::parse("null"));
27 a.push_back(QPDFObjectHandle::parse("/Quack")); 27 a.push_back(QPDFObjectHandle::parse("/Quack"));
28 assert(a.size() == 5); 28 assert(a.size() == 5);
29 - assert(a.at(0).second.isInteger() && (a.at(0).second.getIntValue() == 1));  
30 - assert(a.at(1).second.isString() && (a.at(1).second.getStringValue() == "potato"));  
31 - assert(a.at(2).second.isNull());  
32 - assert(a.at(3).second.isNull());  
33 - assert(a.at(4).second.isName() && (a.at(4).second.getName() == "/Quack")); 29 + assert(a[0].isInteger() && (a[0].getIntValue() == 1));
  30 + assert(a[1].isString() && (a[1].getStringValue() == "potato"));
  31 + assert(a[2].isNull());
  32 + assert(a[3].isNull());
  33 + assert(a[4].isName() && (a[4].getName() == "/Quack"));
34 34
35 a.insert(4, QPDFObjectHandle::parse("/BeforeQuack")); 35 a.insert(4, QPDFObjectHandle::parse("/BeforeQuack"));
36 assert(a.size() == 6); 36 assert(a.size() == 6);
37 - assert(a.at(0).second.isInteger() && (a.at(0).second.getIntValue() == 1));  
38 - assert(a.at(4).second.isName() && (a.at(4).second.getName() == "/BeforeQuack"));  
39 - assert(a.at(5).second.isName() && (a.at(5).second.getName() == "/Quack")); 37 + assert(a[0].isInteger() && (a[0].getIntValue() == 1));
  38 + assert(a[4].isName() && (a[4].getName() == "/BeforeQuack"));
  39 + assert(a[5].isName() && (a[5].getName() == "/Quack"));
40 40
41 a.insert(2, QPDFObjectHandle::parse("/Third")); 41 a.insert(2, QPDFObjectHandle::parse("/Third"));
42 assert(a.size() == 7); 42 assert(a.size() == 7);
43 - assert(a.at(1).second.isString() && (a.at(1).second.getStringValue() == "potato"));  
44 - assert(a.at(2).second.isName() && (a.at(2).second.getName() == "/Third"));  
45 - assert(a.at(3).second.isNull());  
46 - assert(a.at(6).second.isName() && (a.at(6).second.getName() == "/Quack")); 43 + assert(a[1].isString() && (a[1].getStringValue() == "potato"));
  44 + assert(a[2].isName() && (a[2].getName() == "/Third"));
  45 + assert(a[3].isNull());
  46 + assert(a[6].isName() && (a[6].getName() == "/Quack"));
47 47
48 a.insert(0, QPDFObjectHandle::parse("/First")); 48 a.insert(0, QPDFObjectHandle::parse("/First"));
49 assert(a.size() == 8); 49 assert(a.size() == 8);
50 - assert(a.at(0).second.isName() && (a.at(0).second.getName() == "/First"));  
51 - assert(a.at(1).second.isInteger() && (a.at(1).second.getIntValue() == 1));  
52 - assert(a.at(7).second.isName() && (a.at(7).second.getName() == "/Quack")); 50 + assert(a[0].isName() && (a[0].getName() == "/First"));
  51 + assert(a[1].isInteger() && (a[1].getIntValue() == 1));
  52 + assert(a[7].isName() && (a[7].getName() == "/Quack"));
53 53
54 a.erase(6); 54 a.erase(6);
55 assert(a.size() == 7); 55 assert(a.size() == 7);
56 - assert(a.at(0).second.isName() && (a.at(0).second.getName() == "/First"));  
57 - assert(a.at(1).second.isInteger() && (a.at(1).second.getIntValue() == 1));  
58 - assert(a.at(5).second.isNull());  
59 - assert(a.at(6).second.isName() && (a.at(6).second.getName() == "/Quack")); 56 + assert(a[0].isName() && (a[0].getName() == "/First"));
  57 + assert(a[1].isInteger() && (a[1].getIntValue() == 1));
  58 + assert(a[5].isNull());
  59 + assert(a[6].isName() && (a[6].getName() == "/Quack"));
60 60
61 a.erase(6); 61 a.erase(6);
62 assert(a.size() == 6); 62 assert(a.size() == 6);
63 - assert(a.at(0).second.isName() && (a.at(0).second.getName() == "/First"));  
64 - assert(a.at(1).second.isInteger() && (a.at(1).second.getIntValue() == 1));  
65 - assert(a.at(3).second.isName() && (a.at(3).second.getName() == "/Third"));  
66 - assert(a.at(4).second.isNull());  
67 - assert(a.at(5).second.isNull()); 63 + assert(a[0].isName() && (a[0].getName() == "/First"));
  64 + assert(a[1].isInteger() && (a[1].getIntValue() == 1));
  65 + assert(a[3].isName() && (a[3].getName() == "/Third"));
  66 + assert(a[4].isNull());
  67 + assert(a[5].isNull());
68 68
69 - a.setAt(4, QPDFObjectHandle::parse("12"));  
70 - assert(a.at(4).second.isInteger() && (a.at(4).second.getIntValue() == 12));  
71 - a.setAt(4, QPDFObjectHandle::newNull());  
72 - assert(a.at(4).second.isNull()); 69 + a.set(4, QPDFObjectHandle::parse("12"));
  70 + assert(a[4].isInteger() && (a[4].getIntValue() == 12));
  71 + a.set(4, QPDFObjectHandle::newNull());
  72 + assert(a[4].isNull());
73 73
74 a.erase(to_i(a.size()) - 1); 74 a.erase(to_i(a.size()) - 1);
75 assert(a.size() == 5); 75 assert(a.size() == 5);
76 - assert(a.at(0).second.isName() && (a.at(0).second.getName() == "/First"));  
77 - assert(a.at(1).second.isInteger() && (a.at(1).second.getIntValue() == 1));  
78 - assert(a.at(3).second.isName() && (a.at(3).second.getName() == "/Third"));  
79 - assert(a.at(4).second.isNull()); 76 + assert(a[0].isName() && (a[0].getName() == "/First"));
  77 + assert(a[1].isInteger() && (a[1].getIntValue() == 1));
  78 + assert(a[3].isName() && (a[3].getName() == "/Third"));
  79 + assert(a[4].isNull());
80 80
81 a.erase(to_i(a.size()) - 1); 81 a.erase(to_i(a.size()) - 1);
82 assert(a.size() == 4); 82 assert(a.size() == 4);
83 - assert(a.at(0).second.isName() && (a.at(0).second.getName() == "/First"));  
84 - assert(a.at(1).second.isInteger() && (a.at(1).second.getIntValue() == 1));  
85 - assert(a.at(3).second.isName() && (a.at(3).second.getName() == "/Third")); 83 + assert(a[0].isName() && (a[0].getName() == "/First"));
  84 + assert(a[1].isInteger() && (a[1].getIntValue() == 1));
  85 + assert(a[3].isName() && (a[3].getName() == "/Third"));
86 86
87 a.erase(to_i(a.size()) - 1); 87 a.erase(to_i(a.size()) - 1);
88 assert(a.size() == 3); 88 assert(a.size() == 3);
89 - assert(a.at(0).second.isName() && (a.at(0).second.getName() == "/First"));  
90 - assert(a.at(1).second.isInteger() && (a.at(1).second.getIntValue() == 1));  
91 - assert(a.at(2).second.isString() && (a.at(2).second.getStringValue() == "potato")); 89 + assert(a[0].isName() && (a[0].getName() == "/First"));
  90 + assert(a[1].isInteger() && (a[1].getIntValue() == 1));
  91 + assert(a[2].isString() && (a[2].getStringValue() == "potato"));
92 92
93 QPDF pdf; 93 QPDF pdf;
94 pdf.emptyPDF(); 94 pdf.emptyPDF();
@@ -96,22 +96,22 @@ main() @@ -96,22 +96,22 @@ main()
96 obj = QPDFObject::create<QPDF_Array>( 96 obj = QPDFObject::create<QPDF_Array>(
97 std::vector<QPDFObjectHandle>{10, "null"_qpdf.getObj()}, true); 97 std::vector<QPDFObjectHandle>{10, "null"_qpdf.getObj()}, true);
98 auto b = qpdf::Array(obj); 98 auto b = qpdf::Array(obj);
99 - b.setAt(5, pdf.newIndirectNull());  
100 - b.setAt(7, "[0 1 2 3]"_qpdf);  
101 - assert(b.at(3).second.isNull());  
102 - assert(b.at(8).second.isNull());  
103 - assert(b.at(5).second.isIndirect()); 99 + b.set(5, pdf.newIndirectNull());
  100 + b.set(7, "[0 1 2 3]"_qpdf);
  101 + assert(b[3].null());
  102 + assert(b[8].null());
  103 + assert(b[5].indirect());
104 assert( 104 assert(
105 QPDFObjectHandle(obj).unparse() == 105 QPDFObjectHandle(obj).unparse() ==
106 "[ null null null null null 3 0 R null [ 0 1 2 3 ] null null ]"); 106 "[ null null null null null 3 0 R null [ 0 1 2 3 ] null null ]");
107 auto c = QPDFObjectHandle(obj).unsafeShallowCopy(); 107 auto c = QPDFObjectHandle(obj).unsafeShallowCopy();
108 auto d = QPDFObjectHandle(obj).shallowCopy(); 108 auto d = QPDFObjectHandle(obj).shallowCopy();
109 - b.at(7).second.setArrayItem(2, "42"_qpdf); 109 + b.get(7).setArrayItem(2, "42"_qpdf);
110 assert(c.unparse() == "[ null null null null null 3 0 R null [ 0 1 42 3 ] null null ]"); 110 assert(c.unparse() == "[ null null null null null 3 0 R null [ 0 1 42 3 ] null null ]");
111 assert(d.unparse() == "[ null null null null null 3 0 R null [ 0 1 2 3 ] null null ]"); 111 assert(d.unparse() == "[ null null null null null 3 0 R null [ 0 1 2 3 ] null null ]");
112 112
113 try { 113 try {
114 - b.setAt(3, {}); 114 + b.set(3, {});
115 std::cout << "inserted uninitialized object\n"; 115 std::cout << "inserted uninitialized object\n";
116 } catch (std::logic_error&) { 116 } catch (std::logic_error&) {
117 } 117 }
@@ -119,7 +119,7 @@ main() @@ -119,7 +119,7 @@ main()
119 pdf2.emptyPDF(); 119 pdf2.emptyPDF();
120 try { 120 try {
121 pdf.makeIndirectObject(obj); 121 pdf.makeIndirectObject(obj);
122 - b.setAt(3, pdf2.getObject(1, 0)); 122 + b.set(3, pdf2.getObject(1, 0));
123 std::cout << "inserted uninitialized object\n"; 123 std::cout << "inserted uninitialized object\n";
124 } catch (std::logic_error&) { 124 } catch (std::logic_error&) {
125 } 125 }
qpdf/qpdf.testcov
@@ -282,7 +282,6 @@ qpdf-c called qpdf_set_newline_before_endstream 0 @@ -282,7 +282,6 @@ qpdf-c called qpdf_set_newline_before_endstream 0
282 SF_FlateLzwDecode TIFF predictor 0 282 SF_FlateLzwDecode TIFF predictor 0
283 QPDFTokenizer inline image at EOF 0 283 QPDFTokenizer inline image at EOF 0
284 Pl_QPDFTokenizer found ID 0 284 Pl_QPDFTokenizer found ID 0
285 -QPDFObjectHandle non-stream in stream array 0  
286 QPDFObjectHandle coalesce called on stream 0 285 QPDFObjectHandle coalesce called on stream 0
287 QPDFObjectHandle coalesce provide stream data 0 286 QPDFObjectHandle coalesce provide stream data 0
288 QPDF_Stream bad token at end during normalize 0 287 QPDF_Stream bad token at end during normalize 0
@@ -290,7 +289,6 @@ QPDFParser bad token in parse 0 @@ -290,7 +289,6 @@ QPDFParser bad token in parse 0
290 QPDFParser bad token in parseRemainder 0 289 QPDFParser bad token in parseRemainder 0
291 QPDFParser eof in parse 0 290 QPDFParser eof in parse 0
292 QPDFParser eof in parseRemainder 0 291 QPDFParser eof in parseRemainder 0
293 -QPDFObjectHandle array bounds 0  
294 QPDFObjectHandle boolean returning false 0 292 QPDFObjectHandle boolean returning false 0
295 QPDFObjectHandle integer returning 0 0 293 QPDFObjectHandle integer returning 0 0
296 QPDFObjectHandle real returning 0.0 0 294 QPDFObjectHandle real returning 0.0 0
@@ -299,11 +297,7 @@ QPDFObjectHandle string returning empty string 0 @@ -299,11 +297,7 @@ QPDFObjectHandle string returning empty string 0
299 QPDFObjectHandle string returning empty utf8 0 297 QPDFObjectHandle string returning empty utf8 0
300 QPDFObjectHandle operator returning fake value 0 298 QPDFObjectHandle operator returning fake value 0
301 QPDFObjectHandle inlineimage returning empty data 0 299 QPDFObjectHandle inlineimage returning empty data 0
302 -QPDFObjectHandle array treating as empty 0  
303 -QPDFObjectHandle array null for non-array 0  
304 QPDFObjectHandle array treating as empty vector 0 300 QPDFObjectHandle array treating as empty vector 0
305 -QPDFObjectHandle array ignoring set item 0  
306 -QPDFObjectHandle set array bounds 0  
307 QPDFObjectHandle array ignoring replace items 0 301 QPDFObjectHandle array ignoring replace items 0
308 QPDFObjectHandle array ignoring insert item 0 302 QPDFObjectHandle array ignoring insert item 0
309 QPDFObjectHandle insert array bounds 0 303 QPDFObjectHandle insert array bounds 0
@@ -519,8 +513,6 @@ qpdf-c called qpdf_oh_unparse_resolved 0 @@ -519,8 +513,6 @@ qpdf-c called qpdf_oh_unparse_resolved 0
519 qpdf-c called qpdf_oh_unparse_binary 0 513 qpdf-c called qpdf_oh_unparse_binary 0
520 QPDFWriter getFilterOnWrite false 0 514 QPDFWriter getFilterOnWrite false 0
521 QPDFPageObjectHelper::forEachXObject 3 515 QPDFPageObjectHelper::forEachXObject 3
522 -NNTree repair 0  
523 -NNTree insertAfter inserts first 0  
524 NNTree erased last kid/item in tree 1 516 NNTree erased last kid/item in tree 1
525 QPDFPageObjectHelper unresolved names 0 517 QPDFPageObjectHelper unresolved names 0
526 QPDFPageObjectHelper resolving unresolved 0 518 QPDFPageObjectHelper resolving unresolved 0