Commit f8e39253be9d1806ccdb88d8117b7418f2045da1

Authored by Jay Berkenbilt
Committed by GitHub
2 parents 40e4d1f9 0b53b648

Merge pull request #863 from m-holger/array

Refactor QPDF_Array
include/qpdf/QPDFObjectHandle.hh
... ... @@ -1496,11 +1496,10 @@ class QPDFObjectHandle
1496 1496 {
1497 1497 friend class QPDF_Dictionary;
1498 1498 friend class QPDF_Stream;
1499   - friend class SparseOHArray;
1500 1499  
1501 1500 private:
1502 1501 static void
1503   - disconnect(QPDFObjectHandle& o)
  1502 + disconnect(QPDFObjectHandle o)
1504 1503 {
1505 1504 o.disconnect();
1506 1505 }
... ... @@ -1577,6 +1576,11 @@ class QPDFObjectHandle
1577 1576 {
1578 1577 return obj;
1579 1578 }
  1579 + std::shared_ptr<QPDFObject>
  1580 + getObj() const
  1581 + {
  1582 + return obj;
  1583 + }
1580 1584 QPDFObject*
1581 1585 getObjectPtr()
1582 1586 {
... ...
libqpdf/CMakeLists.txt
... ... @@ -115,7 +115,6 @@ set(libqpdf_SOURCES
115 115 ResourceFinder.cc
116 116 SecureRandomDataProvider.cc
117 117 SF_FlateLzwDecode.cc
118   - SparseOHArray.cc
119 118 qpdf-c.cc
120 119 qpdfjob-c.cc
121 120 qpdflogger-c.cc)
... ...
libqpdf/QPDFObjectHandle.cc
... ... @@ -23,7 +23,6 @@
23 23 #include <qpdf/QPDF_Stream.hh>
24 24 #include <qpdf/QPDF_String.hh>
25 25 #include <qpdf/QPDF_Unresolved.hh>
26   -#include <qpdf/SparseOHArray.hh>
27 26  
28 27 #include <qpdf/QIntC.hh>
29 28 #include <qpdf/QTC.hh>
... ... @@ -789,9 +788,8 @@ QPDFObjectHandle::aitems()
789 788 int
790 789 QPDFObjectHandle::getArrayNItems()
791 790 {
792   - auto array = asArray();
793   - if (array) {
794   - return array->getNItems();
  791 + if (auto array = asArray()) {
  792 + return array->size();
795 793 } else {
796 794 typeWarning("array", "treating as empty");
797 795 QTC::TC("qpdf", "QPDFObjectHandle array treating as empty");
... ... @@ -802,104 +800,101 @@ QPDFObjectHandle::getArrayNItems()
802 800 QPDFObjectHandle
803 801 QPDFObjectHandle::getArrayItem(int n)
804 802 {
805   - auto array = asArray();
806   - if (array && (n < array->getNItems()) && (n >= 0)) {
807   - return array->getItem(n);
808   - } else {
809   - if (array) {
  803 + if (auto array = asArray()) {
  804 + if (auto result = array->at(n); result.obj != nullptr) {
  805 + return result;
  806 + } else {
810 807 objectWarning("returning null for out of bounds array access");
811 808 QTC::TC("qpdf", "QPDFObjectHandle array bounds");
812   - } else {
813   - typeWarning("array", "returning null");
814   - QTC::TC("qpdf", "QPDFObjectHandle array null for non-array");
815 809 }
816   - static auto constexpr msg =
817   - " -> null returned from invalid array access"sv;
818   - return QPDF_Null::create(obj, msg, "");
  810 + } else {
  811 + typeWarning("array", "returning null");
  812 + QTC::TC("qpdf", "QPDFObjectHandle array null for non-array");
819 813 }
  814 + static auto constexpr msg = " -> null returned from invalid array access"sv;
  815 + return QPDF_Null::create(obj, msg, "");
820 816 }
821 817  
822 818 bool
823 819 QPDFObjectHandle::isRectangle()
824 820 {
825   - auto array = asArray();
826   - if ((array == nullptr) || (array->getNItems() != 4)) {
827   - return false;
828   - }
829   - for (int i = 0; i < 4; ++i) {
830   - if (!array->getItem(i).isNumber()) {
831   - return false;
  821 + if (auto array = asArray()) {
  822 + for (int i = 0; i < 4; ++i) {
  823 + if (auto item = array->at(i); !(item.obj && item.isNumber())) {
  824 + return false;
  825 + }
832 826 }
  827 + return array->size() == 4;
833 828 }
834   - return true;
  829 + return false;
835 830 }
836 831  
837 832 bool
838 833 QPDFObjectHandle::isMatrix()
839 834 {
840   - auto array = asArray();
841   - if ((array == nullptr) || (array->getNItems() != 6)) {
842   - return false;
843   - }
844   - for (int i = 0; i < 6; ++i) {
845   - if (!array->getItem(i).isNumber()) {
846   - return false;
  835 + if (auto array = asArray()) {
  836 + for (int i = 0; i < 6; ++i) {
  837 + if (auto item = array->at(i); !(item.obj && item.isNumber())) {
  838 + return false;
  839 + }
847 840 }
  841 + return array->size() == 6;
848 842 }
849   - return true;
  843 + return false;
850 844 }
851 845  
852 846 QPDFObjectHandle::Rectangle
853 847 QPDFObjectHandle::getArrayAsRectangle()
854 848 {
855   - Rectangle result;
856   - if (isRectangle()) {
857   - auto array = asArray();
858   - // Rectangle coordinates are always supposed to be llx, lly,
859   - // urx, ury, but files have been found in the wild where
860   - // llx > urx or lly > ury.
861   - double i0 = array->getItem(0).getNumericValue();
862   - double i1 = array->getItem(1).getNumericValue();
863   - double i2 = array->getItem(2).getNumericValue();
864   - double i3 = array->getItem(3).getNumericValue();
865   - result = Rectangle(
866   - std::min(i0, i2),
867   - std::min(i1, i3),
868   - std::max(i0, i2),
869   - std::max(i1, i3));
  849 + if (auto array = asArray()) {
  850 + if (array->size() != 4) {
  851 + return {};
  852 + }
  853 + double items[4];
  854 + for (int i = 0; i < 4; ++i) {
  855 + if (!array->at(i).getValueAsNumber(items[i])) {
  856 + return {};
  857 + }
  858 + }
  859 + return Rectangle(
  860 + std::min(items[0], items[2]),
  861 + std::min(items[1], items[3]),
  862 + std::max(items[0], items[2]),
  863 + std::max(items[1], items[3]));
870 864 }
871   - return result;
  865 + return {};
872 866 }
873 867  
874 868 QPDFObjectHandle::Matrix
875 869 QPDFObjectHandle::getArrayAsMatrix()
876 870 {
877   - Matrix result;
878   - if (isMatrix()) {
879   - auto array = asArray();
880   - result = Matrix(
881   - array->getItem(0).getNumericValue(),
882   - array->getItem(1).getNumericValue(),
883   - array->getItem(2).getNumericValue(),
884   - array->getItem(3).getNumericValue(),
885   - array->getItem(4).getNumericValue(),
886   - array->getItem(5).getNumericValue());
  871 + if (auto array = asArray()) {
  872 + if (array->size() != 6) {
  873 + return {};
  874 + }
  875 + double items[6];
  876 + for (int i = 0; i < 6; ++i) {
  877 + if (!array->at(i).getValueAsNumber(items[i])) {
  878 + return {};
  879 + }
  880 + }
  881 + return Matrix(
  882 + items[0], items[1], items[2], items[3], items[4], items[5]);
887 883 }
888   - return result;
  884 + return {};
889 885 }
890 886  
891 887 std::vector<QPDFObjectHandle>
892 888 QPDFObjectHandle::getArrayAsVector()
893 889 {
894   - std::vector<QPDFObjectHandle> result;
895 890 auto array = asArray();
896 891 if (array) {
897   - array->getAsVector(result);
  892 + return array->getAsVector();
898 893 } else {
899 894 typeWarning("array", "treating as empty");
900 895 QTC::TC("qpdf", "QPDFObjectHandle array treating as empty vector");
901 896 }
902   - return result;
  897 + return {};
903 898 }
904 899  
905 900 // Array mutators
... ... @@ -907,24 +902,20 @@ QPDFObjectHandle::getArrayAsVector()
907 902 void
908 903 QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const& item)
909 904 {
910   - auto array = asArray();
911   - if (array) {
912   - checkOwnership(item);
913   - array->setItem(n, item);
  905 + if (auto array = asArray()) {
  906 + if (!array->setAt(n, item)) {
  907 + objectWarning("ignoring attempt to set out of bounds array item");
  908 + QTC::TC("qpdf", "QPDFObjectHandle set array bounds");
  909 + }
914 910 } else {
915 911 typeWarning("array", "ignoring attempt to set item");
916 912 QTC::TC("qpdf", "QPDFObjectHandle array ignoring set item");
917 913 }
918 914 }
919   -
920 915 void
921 916 QPDFObjectHandle::setArrayFromVector(std::vector<QPDFObjectHandle> const& items)
922 917 {
923   - auto array = asArray();
924   - if (array) {
925   - for (auto const& item: items) {
926   - checkOwnership(item);
927   - }
  918 + if (auto array = asArray()) {
928 919 array->setFromVector(items);
929 920 } else {
930 921 typeWarning("array", "ignoring attempt to replace items");
... ... @@ -935,9 +926,12 @@ QPDFObjectHandle::setArrayFromVector(std::vector&lt;QPDFObjectHandle&gt; const&amp; items)
935 926 void
936 927 QPDFObjectHandle::insertItem(int at, QPDFObjectHandle const& item)
937 928 {
938   - auto array = asArray();
939   - if (array) {
940   - array->insertItem(at, item);
  929 + if (auto array = asArray()) {
  930 + if (!array->insert(at, item)) {
  931 + objectWarning(
  932 + "ignoring attempt to insert out of bounds array item");
  933 + QTC::TC("qpdf", "QPDFObjectHandle insert array bounds");
  934 + }
941 935 } else {
942 936 typeWarning("array", "ignoring attempt to insert item");
943 937 QTC::TC("qpdf", "QPDFObjectHandle array ignoring insert item");
... ... @@ -954,10 +948,8 @@ QPDFObjectHandle::insertItemAndGetNew(int at, QPDFObjectHandle const&amp; item)
954 948 void
955 949 QPDFObjectHandle::appendItem(QPDFObjectHandle const& item)
956 950 {
957   - auto array = asArray();
958   - if (array) {
959   - checkOwnership(item);
960   - array->appendItem(item);
  951 + if (auto array = asArray()) {
  952 + array->push_back(item);
961 953 } else {
962 954 typeWarning("array", "ignoring attempt to append item");
963 955 QTC::TC("qpdf", "QPDFObjectHandle array ignoring append item");
... ... @@ -974,28 +966,23 @@ QPDFObjectHandle::appendItemAndGetNew(QPDFObjectHandle const&amp; item)
974 966 void
975 967 QPDFObjectHandle::eraseItem(int at)
976 968 {
977   - auto array = asArray();
978   - if (array && (at < array->getNItems()) && (at >= 0)) {
979   - array->eraseItem(at);
980   - } else {
981   - if (array) {
  969 + if (auto array = asArray()) {
  970 + if (!array->erase(at)) {
982 971 objectWarning("ignoring attempt to erase out of bounds array item");
983 972 QTC::TC("qpdf", "QPDFObjectHandle erase array bounds");
984   - } else {
985   - typeWarning("array", "ignoring attempt to erase item");
986   - QTC::TC("qpdf", "QPDFObjectHandle array ignoring erase item");
987 973 }
  974 + } else {
  975 + typeWarning("array", "ignoring attempt to erase item");
  976 + QTC::TC("qpdf", "QPDFObjectHandle array ignoring erase item");
988 977 }
989 978 }
990 979  
991 980 QPDFObjectHandle
992 981 QPDFObjectHandle::eraseItemAndGetOld(int at)
993 982 {
994   - auto result = QPDFObjectHandle::newNull();
995 983 auto array = asArray();
996   - if (array && (at < array->getNItems()) && (at >= 0)) {
997   - result = array->getItem(at);
998   - }
  984 + auto result =
  985 + (array && at < array->size() && at >= 0) ? array->at(at) : newNull();
999 986 eraseItem(at);
1000 987 return result;
1001 988 }
... ... @@ -1515,11 +1502,10 @@ QPDFObjectHandle::arrayOrStreamToStreamArray(
1515 1502 {
1516 1503 all_description = description;
1517 1504 std::vector<QPDFObjectHandle> result;
1518   - auto array = asArray();
1519   - if (array) {
1520   - int n_items = array->getNItems();
  1505 + if (auto array = asArray()) {
  1506 + int n_items = array->size();
1521 1507 for (int i = 0; i < n_items; ++i) {
1522   - QPDFObjectHandle item = array->getItem(i);
  1508 + QPDFObjectHandle item = array->at(i);
1523 1509 if (item.isStream()) {
1524 1510 result.push_back(item);
1525 1511 } else {
... ... @@ -2217,9 +2203,9 @@ QPDFObjectHandle::makeDirect(
2217 2203 } else if (isArray()) {
2218 2204 std::vector<QPDFObjectHandle> items;
2219 2205 auto array = asArray();
2220   - int n = array->getNItems();
  2206 + int n = array->size();
2221 2207 for (int i = 0; i < n; ++i) {
2222   - items.push_back(array->getItem(i));
  2208 + items.push_back(array->at(i));
2223 2209 items.back().makeDirect(visited, stop_at_streams);
2224 2210 }
2225 2211 this->obj = QPDF_Array::create(items);
... ...
libqpdf/QPDFParser.cc
... ... @@ -27,16 +27,15 @@ namespace
27 27 struct StackFrame
28 28 {
29 29 StackFrame(std::shared_ptr<InputSource> input) :
30   - offset(input->tell()),
31   - contents_string(""),
32   - contents_offset(-1)
  30 + offset(input->tell())
33 31 {
34 32 }
35 33  
36 34 std::vector<std::shared_ptr<QPDFObject>> olist;
37 35 qpdf_offset_t offset;
38   - std::string contents_string;
39   - qpdf_offset_t contents_offset;
  36 + std::string contents_string{""};
  37 + qpdf_offset_t contents_offset{-1};
  38 + int null_count{0};
40 39 };
41 40 } // namespace
42 41  
... ... @@ -50,6 +49,7 @@ QPDFParser::parse(bool&amp; empty, bool content_stream)
50 49 // this, it will cause a logic error to be thrown from
51 50 // QPDF::inParse().
52 51  
  52 + const static std::shared_ptr<QPDFObject> null_oh = QPDF_Null::create();
53 53 QPDF::ParseGuard pg(context);
54 54  
55 55 empty = false;
... ... @@ -67,7 +67,6 @@ QPDFParser::parse(bool&amp; empty, bool content_stream)
67 67 int good_count = 0;
68 68 bool b_contents = false;
69 69 bool is_null = false;
70   - auto null_oh = QPDF_Null::create();
71 70  
72 71 while (!done) {
73 72 bool bad = false;
... ... @@ -156,6 +155,8 @@ QPDFParser::parse(bool&amp; empty, bool content_stream)
156 155  
157 156 case QPDFTokenizer::tt_null:
158 157 is_null = true;
  158 + ++frame.null_count;
  159 +
159 160 break;
160 161  
161 162 case QPDFTokenizer::tt_integer:
... ... @@ -301,9 +302,11 @@ QPDFParser::parse(bool&amp; empty, bool content_stream)
301 302  
302 303 case st_dictionary:
303 304 case st_array:
304   - if (!indirect_ref && !is_null) {
305   - // No need to set description for direct nulls - they will
306   - // become implicit.
  305 + if (is_null) {
  306 + object = null_oh;
  307 + // No need to set description for direct nulls - they probably
  308 + // will become implicit.
  309 + } else if (!indirect_ref) {
307 310 setDescription(object, input->getLastOffset());
308 311 }
309 312 set_offset = true;
... ... @@ -326,7 +329,8 @@ QPDFParser::parse(bool&amp; empty, bool content_stream)
326 329 parser_state_e old_state = state_stack.back();
327 330 state_stack.pop_back();
328 331 if (old_state == st_array) {
329   - object = QPDF_Array::create(std::move(olist));
  332 + object = QPDF_Array::create(
  333 + std::move(olist), frame.null_count > 100);
330 334 setDescription(object, offset - 1);
331 335 // The `offset` points to the next of "[". Set the rewind
332 336 // offset to point to the beginning of "[". This has been
... ... @@ -381,7 +385,7 @@ QPDFParser::parse(bool&amp; empty, bool content_stream)
381 385 // Calculate value.
382 386 std::shared_ptr<QPDFObject> val;
383 387 if (iter != olist.end()) {
384   - val = *iter ? *iter : QPDF_Null::create();
  388 + val = *iter;
385 389 ++iter;
386 390 } else {
387 391 QTC::TC("qpdf", "QPDFParser no val for last key");
... ...
libqpdf/QPDF_Array.cc
1 1 #include <qpdf/QPDF_Array.hh>
2 2  
3   -#include <qpdf/QIntC.hh>
  3 +#include <qpdf/QPDFObjectHandle.hh>
4 4 #include <qpdf/QPDFObject_private.hh>
5   -#include <qpdf/QUtil.hh>
6   -#include <stdexcept>
7 5  
8   -QPDF_Array::QPDF_Array(std::vector<QPDFObjectHandle> const& v) :
9   - QPDFValue(::ot_array, "array")
  6 +static const QPDFObjectHandle null_oh = QPDFObjectHandle::newNull();
  7 +
  8 +inline void
  9 +QPDF_Array::checkOwnership(QPDFObjectHandle const& item) const
10 10 {
11   - setFromVector(v);
  11 + if (auto obj = item.getObjectPtr()) {
  12 + if (qpdf) {
  13 + if (auto item_qpdf = obj->getQPDF()) {
  14 + if (qpdf != item_qpdf) {
  15 + throw std::logic_error(
  16 + "Attempting to add an object from a different QPDF. "
  17 + "Use QPDF::copyForeignObject to add objects from "
  18 + "another file.");
  19 + }
  20 + }
  21 + }
  22 + } else {
  23 + throw std::logic_error(
  24 + "Attempting to add an uninitialized object to a QPDF_Array.");
  25 + }
12 26 }
13 27  
14   -QPDF_Array::QPDF_Array(std::vector<std::shared_ptr<QPDFObject>>&& v) :
  28 +QPDF_Array::QPDF_Array() :
15 29 QPDFValue(::ot_array, "array")
16 30 {
17   - setFromVector(std::move(v));
18 31 }
19 32  
20   -QPDF_Array::QPDF_Array(SparseOHArray const& items) :
  33 +QPDF_Array::QPDF_Array(QPDF_Array const& other) :
21 34 QPDFValue(::ot_array, "array"),
22   - elements(items)
  35 + sparse(other.sparse),
  36 + sp_size(other.sp_size),
  37 + sp_elements(other.sp_elements),
  38 + elements(other.elements)
23 39 {
24 40 }
25 41  
26   -std::shared_ptr<QPDFObject>
27   -QPDF_Array::create(std::vector<QPDFObjectHandle> const& items)
  42 +QPDF_Array::QPDF_Array(std::vector<QPDFObjectHandle> const& v) :
  43 + QPDFValue(::ot_array, "array")
28 44 {
29   - return do_create(new QPDF_Array(items));
  45 + setFromVector(v);
30 46 }
31 47  
32   -std::shared_ptr<QPDFObject>
33   -QPDF_Array::create(std::vector<std::shared_ptr<QPDFObject>>&& items)
  48 +QPDF_Array::QPDF_Array(
  49 + std::vector<std::shared_ptr<QPDFObject>>&& v, bool sparse) :
  50 + QPDFValue(::ot_array, "array"),
  51 + sparse(sparse)
34 52 {
35   - return do_create(new QPDF_Array(std::move(items)));
  53 + if (sparse) {
  54 + for (auto&& item: v) {
  55 + if (item->getTypeCode() != ::ot_null ||
  56 + item->getObjGen().isIndirect()) {
  57 + sp_elements[sp_size] = std::move(item);
  58 + }
  59 + ++sp_size;
  60 + }
  61 + } else {
  62 + elements = std::move(v);
  63 + }
36 64 }
37 65  
38 66 std::shared_ptr<QPDFObject>
39   -QPDF_Array::create(SparseOHArray const& items)
  67 +QPDF_Array::create(std::vector<QPDFObjectHandle> const& items)
40 68 {
41 69 return do_create(new QPDF_Array(items));
42 70 }
43 71  
44 72 std::shared_ptr<QPDFObject>
  73 +QPDF_Array::create(
  74 + std::vector<std::shared_ptr<QPDFObject>>&& items, bool sparse)
  75 +{
  76 + return do_create(new QPDF_Array(std::move(items), sparse));
  77 +}
  78 +
  79 +std::shared_ptr<QPDFObject>
45 80 QPDF_Array::copy(bool shallow)
46 81 {
47   - return create(shallow ? elements : elements.copy());
  82 + if (shallow) {
  83 + return do_create(new QPDF_Array(*this));
  84 + } else {
  85 + if (sparse) {
  86 + QPDF_Array* result = new QPDF_Array();
  87 + result->sp_size = sp_size;
  88 + for (auto const& element: sp_elements) {
  89 + auto const& obj = element.second;
  90 + result->sp_elements[element.first] =
  91 + obj->getObjGen().isIndirect() ? obj : obj->copy();
  92 + }
  93 + return do_create(result);
  94 + } else {
  95 + std::vector<std::shared_ptr<QPDFObject>> result;
  96 + result.reserve(elements.size());
  97 + for (auto const& element: elements) {
  98 + result.push_back(
  99 + element
  100 + ? (element->getObjGen().isIndirect() ? element
  101 + : element->copy())
  102 + : element);
  103 + }
  104 + return create(std::move(result), false);
  105 + }
  106 + }
48 107 }
49 108  
50 109 void
51 110 QPDF_Array::disconnect()
52 111 {
53   - elements.disconnect();
  112 + if (sparse) {
  113 + for (auto& item: sp_elements) {
  114 + auto& obj = item.second;
  115 + if (!obj->getObjGen().isIndirect()) {
  116 + obj->disconnect();
  117 + }
  118 + }
  119 + } else {
  120 + for (auto& obj: elements) {
  121 + if (!obj->getObjGen().isIndirect()) {
  122 + obj->disconnect();
  123 + }
  124 + }
  125 + }
54 126 }
55 127  
56 128 std::string
57 129 QPDF_Array::unparse()
58 130 {
59 131 std::string result = "[ ";
60   - size_t size = this->elements.size();
61   - for (size_t i = 0; i < size; ++i) {
62   - result += this->elements.at(i).unparse();
63   - result += " ";
  132 + if (sparse) {
  133 + int next = 0;
  134 + for (auto& item: sp_elements) {
  135 + int key = item.first;
  136 + for (int j = next; j < key; ++j) {
  137 + result += "null ";
  138 + }
  139 + item.second->resolve();
  140 + auto og = item.second->getObjGen();
  141 + result += og.isIndirect() ? og.unparse(' ') + " R "
  142 + : item.second->unparse() + " ";
  143 + next = ++key;
  144 + }
  145 + for (int j = next; j < sp_size; ++j) {
  146 + result += "null ";
  147 + }
  148 + } else {
  149 + for (auto const& item: elements) {
  150 + item->resolve();
  151 + auto og = item->getObjGen();
  152 + result += og.isIndirect() ? og.unparse(' ') + " R "
  153 + : item->unparse() + " ";
  154 + }
64 155 }
65 156 result += "]";
66 157 return result;
... ... @@ -69,96 +160,157 @@ QPDF_Array::unparse()
69 160 JSON
70 161 QPDF_Array::getJSON(int json_version)
71 162 {
72   - JSON j = JSON::makeArray();
73   - size_t size = this->elements.size();
74   - for (size_t i = 0; i < size; ++i) {
75   - j.addArrayElement(this->elements.at(i).getJSON(json_version));
  163 + static const JSON j_null = JSON::makeNull();
  164 + JSON j_array = JSON::makeArray();
  165 + if (sparse) {
  166 + int next = 0;
  167 + for (auto& item: sp_elements) {
  168 + int key = item.first;
  169 + for (int j = next; j < key; ++j) {
  170 + j_array.addArrayElement(j_null);
  171 + }
  172 + auto og = item.second->getObjGen();
  173 + j_array.addArrayElement(
  174 + og.isIndirect() ? JSON::makeString(og.unparse(' ') + " R")
  175 + : item.second->getJSON(json_version));
  176 + next = ++key;
  177 + }
  178 + for (int j = next; j < sp_size; ++j) {
  179 + j_array.addArrayElement(j_null);
  180 + }
  181 + } else {
  182 + for (auto const& item: elements) {
  183 + auto og = item->getObjGen();
  184 + j_array.addArrayElement(
  185 + og.isIndirect() ? JSON::makeString(og.unparse(' ') + " R")
  186 + : item->getJSON(json_version));
  187 + }
76 188 }
77   - return j;
78   -}
79   -
80   -int
81   -QPDF_Array::getNItems() const
82   -{
83   - // This should really return a size_t, but changing it would break
84   - // a lot of code.
85   - return QIntC::to_int(this->elements.size());
  189 + return j_array;
86 190 }
87 191  
88 192 QPDFObjectHandle
89   -QPDF_Array::getItem(int n) const
  193 +QPDF_Array::at(int n) const noexcept
90 194 {
91   - if ((n < 0) || (n >= QIntC::to_int(elements.size()))) {
92   - throw std::logic_error(
93   - "INTERNAL ERROR: bounds error accessing QPDF_Array element");
  195 + if (n < 0 || n >= size()) {
  196 + return {};
  197 + } else if (sparse) {
  198 + auto const& iter = sp_elements.find(n);
  199 + return iter == sp_elements.end() ? null_oh : (*iter).second;
  200 + } else {
  201 + return elements[size_t(n)];
94 202 }
95   - return this->elements.at(QIntC::to_size(n));
96 203 }
97 204  
98   -void
99   -QPDF_Array::getAsVector(std::vector<QPDFObjectHandle>& v) const
  205 +std::vector<QPDFObjectHandle>
  206 +QPDF_Array::getAsVector() const
100 207 {
101   - size_t size = this->elements.size();
102   - for (size_t i = 0; i < size; ++i) {
103   - v.push_back(this->elements.at(i));
  208 + if (sparse) {
  209 + std::vector<QPDFObjectHandle> v;
  210 + v.reserve(size_t(size()));
  211 + for (auto const& item: sp_elements) {
  212 + v.resize(size_t(item.first), null_oh);
  213 + v.push_back(item.second);
  214 + }
  215 + v.resize(size_t(size()), null_oh);
  216 + return v;
  217 + } else {
  218 + return {elements.cbegin(), elements.cend()};
104 219 }
105 220 }
106 221  
107   -void
108   -QPDF_Array::setItem(int n, QPDFObjectHandle const& oh)
  222 +bool
  223 +QPDF_Array::setAt(int at, QPDFObjectHandle const& oh)
109 224 {
110   - this->elements.setAt(QIntC::to_size(n), oh);
  225 + if (at < 0 || at >= size()) {
  226 + return false;
  227 + }
  228 + checkOwnership(oh);
  229 + if (sparse) {
  230 + sp_elements[at] = oh.getObj();
  231 + } else {
  232 + elements[size_t(at)] = oh.getObj();
  233 + }
  234 + return true;
111 235 }
112 236  
113 237 void
114 238 QPDF_Array::setFromVector(std::vector<QPDFObjectHandle> const& v)
115 239 {
116   - this->elements = SparseOHArray();
117   - for (auto const& iter: v) {
118   - this->elements.append(iter);
  240 + elements.resize(0);
  241 + elements.reserve(v.size());
  242 + for (auto const& item: v) {
  243 + checkOwnership(item);
  244 + elements.push_back(item.getObj());
119 245 }
120 246 }
121 247  
122   -void
123   -QPDF_Array::setFromVector(std::vector<std::shared_ptr<QPDFObject>>&& v)
  248 +bool
  249 +QPDF_Array::insert(int at, QPDFObjectHandle const& item)
124 250 {
125   - this->elements = SparseOHArray();
126   - for (auto&& item: v) {
127   - if (item) {
128   - this->elements.append(item);
  251 + int sz = size();
  252 + if (at < 0 || at > sz) {
  253 + // As special case, also allow insert beyond the end
  254 + return false;
  255 + } else if (at == sz) {
  256 + push_back(item);
  257 + } else {
  258 + checkOwnership(item);
  259 + if (sparse) {
  260 + auto iter = sp_elements.crbegin();
  261 + while (iter != sp_elements.crend()) {
  262 + auto key = (iter++)->first;
  263 + if (key >= at) {
  264 + auto nh = sp_elements.extract(key);
  265 + ++nh.key();
  266 + sp_elements.insert(std::move(nh));
  267 + } else {
  268 + break;
  269 + }
  270 + }
  271 + sp_elements[at] = item.getObj();
  272 + ++sp_size;
129 273 } else {
130   - ++this->elements.n_elements;
  274 + elements.insert(elements.cbegin() + at, item.getObj());
131 275 }
132 276 }
  277 + return true;
133 278 }
134 279  
135 280 void
136   -QPDF_Array::insertItem(int at, QPDFObjectHandle const& item)
  281 +QPDF_Array::push_back(QPDFObjectHandle const& item)
137 282 {
138   - // As special case, also allow insert beyond the end
139   - if ((at < 0) || (at > QIntC::to_int(this->elements.size()))) {
140   - throw std::logic_error(
141   - "INTERNAL ERROR: bounds error accessing QPDF_Array element");
  283 + checkOwnership(item);
  284 + if (sparse) {
  285 + sp_elements[sp_size++] = item.getObj();
  286 + } else {
  287 + elements.push_back(item.getObj());
142 288 }
143   - this->elements.insert(QIntC::to_size(at), item);
144 289 }
145 290  
146   -void
147   -QPDF_Array::appendItem(QPDFObjectHandle const& item)
  291 +bool
  292 +QPDF_Array::erase(int at)
148 293 {
149   - this->elements.append(item);
150   -}
151   -
152   -void
153   -QPDF_Array::eraseItem(int at)
154   -{
155   - this->elements.erase(QIntC::to_size(at));
156   -}
  294 + if (at < 0 || at >= size()) {
  295 + return false;
  296 + }
  297 + if (sparse) {
  298 + auto end = sp_elements.end();
  299 + if (auto iter = sp_elements.lower_bound(at); iter != end) {
  300 + if (iter->first == at) {
  301 + iter++;
  302 + sp_elements.erase(at);
  303 + }
157 304  
158   -void
159   -QPDF_Array::addExplicitElementsToList(std::list<QPDFObjectHandle>& l) const
160   -{
161   - for (auto const& iter: this->elements) {
162   - l.push_back(iter.second);
  305 + while (iter != end) {
  306 + auto nh = sp_elements.extract(iter++);
  307 + --nh.key();
  308 + sp_elements.insert(std::move(nh));
  309 + }
  310 + }
  311 + --sp_size;
  312 + } else {
  313 + elements.erase(elements.cbegin() + at);
163 314 }
  315 + return true;
164 316 }
... ...
libqpdf/QPDF_Null.cc
... ... @@ -50,5 +50,6 @@ QPDF_Null::unparse()
50 50 JSON
51 51 QPDF_Null::getJSON(int json_version)
52 52 {
  53 + // If this is updated, QPDF_Array::getJSON must also be updated.
53 54 return JSON::makeNull();
54 55 }
... ...
libqpdf/SparseOHArray.cc deleted
1   -#include <qpdf/SparseOHArray.hh>
2   -
3   -#include <qpdf/QPDFObjectHandle.hh>
4   -#include <qpdf/QPDFObject_private.hh>
5   -
6   -#include <stdexcept>
7   -
8   -SparseOHArray::SparseOHArray() :
9   - n_elements(0)
10   -{
11   -}
12   -
13   -size_t
14   -SparseOHArray::size() const
15   -{
16   - return this->n_elements;
17   -}
18   -
19   -void
20   -SparseOHArray::append(QPDFObjectHandle oh)
21   -{
22   - if (!oh.isDirectNull()) {
23   - this->elements[this->n_elements] = oh;
24   - }
25   - ++this->n_elements;
26   -}
27   -
28   -void
29   -SparseOHArray::append(std::shared_ptr<QPDFObject>&& obj)
30   -{
31   - if (obj->getTypeCode() != ::ot_null || !obj->getObjGen().isIndirect()) {
32   - this->elements[this->n_elements] = std::move(obj);
33   - }
34   - ++this->n_elements;
35   -}
36   -
37   -QPDFObjectHandle
38   -SparseOHArray::at(size_t idx) const
39   -{
40   - if (idx >= this->n_elements) {
41   - throw std::logic_error(
42   - "INTERNAL ERROR: bounds error accessing SparseOHArray element");
43   - }
44   - auto const& iter = this->elements.find(idx);
45   - if (iter == this->elements.end()) {
46   - return QPDFObjectHandle::newNull();
47   - } else {
48   - return (*iter).second;
49   - }
50   -}
51   -
52   -void
53   -SparseOHArray::remove_last()
54   -{
55   - if (this->n_elements == 0) {
56   - throw std::logic_error("INTERNAL ERROR: attempt to remove"
57   - " last item from empty SparseOHArray");
58   - }
59   - --this->n_elements;
60   - this->elements.erase(this->n_elements);
61   -}
62   -
63   -void
64   -SparseOHArray::disconnect()
65   -{
66   - for (auto& iter: this->elements) {
67   - QPDFObjectHandle::DisconnectAccess::disconnect(iter.second);
68   - }
69   -}
70   -
71   -void
72   -SparseOHArray::setAt(size_t idx, QPDFObjectHandle oh)
73   -{
74   - if (idx >= this->n_elements) {
75   - throw std::logic_error("bounds error setting item in SparseOHArray");
76   - }
77   - if (oh.isDirectNull()) {
78   - this->elements.erase(idx);
79   - } else {
80   - this->elements[idx] = oh;
81   - }
82   -}
83   -
84   -void
85   -SparseOHArray::erase(size_t idx)
86   -{
87   - if (idx >= this->n_elements) {
88   - throw std::logic_error("bounds error erasing item from SparseOHArray");
89   - }
90   - decltype(this->elements) dest;
91   - for (auto const& iter: this->elements) {
92   - if (iter.first < idx) {
93   - dest.insert(iter);
94   - } else if (iter.first > idx) {
95   - dest[iter.first - 1] = iter.second;
96   - }
97   - }
98   - this->elements = dest;
99   - --this->n_elements;
100   -}
101   -
102   -void
103   -SparseOHArray::insert(size_t idx, QPDFObjectHandle oh)
104   -{
105   - if (idx > this->n_elements) {
106   - throw std::logic_error("bounds error inserting item to SparseOHArray");
107   - } else if (idx == this->n_elements) {
108   - // Allow inserting to the last position
109   - append(oh);
110   - } else {
111   - decltype(this->elements) dest;
112   - for (auto const& iter: this->elements) {
113   - if (iter.first < idx) {
114   - dest.insert(iter);
115   - } else {
116   - dest[iter.first + 1] = iter.second;
117   - }
118   - }
119   - this->elements = dest;
120   - this->elements[idx] = oh;
121   - ++this->n_elements;
122   - }
123   -}
124   -
125   -SparseOHArray
126   -SparseOHArray::copy()
127   -{
128   - SparseOHArray result;
129   - result.n_elements = this->n_elements;
130   - for (auto const& element: this->elements) {
131   - auto value = element.second;
132   - result.elements[element.first] =
133   - value.isIndirect() ? value : value.shallowCopy();
134   - }
135   - return result;
136   -}
137   -
138   -SparseOHArray::const_iterator
139   -SparseOHArray::begin() const
140   -{
141   - return this->elements.begin();
142   -}
143   -
144   -SparseOHArray::const_iterator
145   -SparseOHArray::end() const
146   -{
147   - return this->elements.end();
148   -}
libqpdf/qpdf/QPDF_Array.hh
... ... @@ -3,8 +3,7 @@
3 3  
4 4 #include <qpdf/QPDFValue.hh>
5 5  
6   -#include <qpdf/SparseOHArray.hh>
7   -#include <list>
  6 +#include <map>
8 7 #include <vector>
9 8  
10 9 class QPDF_Array: public QPDFValue
... ... @@ -14,34 +13,37 @@ class QPDF_Array: public QPDFValue
14 13 static std::shared_ptr<QPDFObject>
15 14 create(std::vector<QPDFObjectHandle> const& items);
16 15 static std::shared_ptr<QPDFObject>
17   - create(std::vector<std::shared_ptr<QPDFObject>>&& items);
18   - static std::shared_ptr<QPDFObject> create(SparseOHArray const& items);
  16 + create(std::vector<std::shared_ptr<QPDFObject>>&& items, bool sparse);
19 17 virtual std::shared_ptr<QPDFObject> copy(bool shallow = false);
20 18 virtual std::string unparse();
21 19 virtual JSON getJSON(int json_version);
22 20 virtual void disconnect();
23 21  
24   - int getNItems() const;
25   - QPDFObjectHandle getItem(int n) const;
26   - void getAsVector(std::vector<QPDFObjectHandle>&) const;
27   -
28   - void setItem(int, QPDFObjectHandle const&);
  22 + int
  23 + size() const noexcept
  24 + {
  25 + return sparse ? sp_size : int(elements.size());
  26 + }
  27 + QPDFObjectHandle at(int n) const noexcept;
  28 + bool setAt(int n, QPDFObjectHandle const& oh);
  29 + std::vector<QPDFObjectHandle> getAsVector() const;
29 30 void setFromVector(std::vector<QPDFObjectHandle> const& items);
30   - void setFromVector(std::vector<std::shared_ptr<QPDFObject>>&& items);
31   - void insertItem(int at, QPDFObjectHandle const& item);
32   - void appendItem(QPDFObjectHandle const& item);
33   - void eraseItem(int at);
34   -
35   - // Helper methods for QPDF and QPDFObjectHandle -- these are
36   - // public methods since the whole class is not part of the public
37   - // API. Otherwise, these would be wrapped in accessor classes.
38   - void addExplicitElementsToList(std::list<QPDFObjectHandle>&) const;
  31 + bool insert(int at, QPDFObjectHandle const& item);
  32 + void push_back(QPDFObjectHandle const& item);
  33 + bool erase(int at);
39 34  
40 35 private:
  36 + QPDF_Array();
  37 + QPDF_Array(QPDF_Array const&);
41 38 QPDF_Array(std::vector<QPDFObjectHandle> const& items);
42   - QPDF_Array(std::vector<std::shared_ptr<QPDFObject>>&& items);
43   - QPDF_Array(SparseOHArray const& items);
44   - SparseOHArray elements;
  39 + QPDF_Array(std::vector<std::shared_ptr<QPDFObject>>&& items, bool sparse);
  40 +
  41 + void checkOwnership(QPDFObjectHandle const& item) const;
  42 +
  43 + bool sparse{false};
  44 + int sp_size{0};
  45 + std::map<int, std::shared_ptr<QPDFObject>> sp_elements;
  46 + std::vector<std::shared_ptr<QPDFObject>> elements;
45 47 };
46 48  
47 49 #endif // QPDF_ARRAY_HH
... ...
libqpdf/qpdf/SparseOHArray.hh deleted
1   -#ifndef QPDF_SPARSEOHARRAY_HH
2   -#define QPDF_SPARSEOHARRAY_HH
3   -
4   -#include <qpdf/QPDFObjectHandle.hh>
5   -#include <unordered_map>
6   -
7   -class QPDF_Array;
8   -
9   -class SparseOHArray
10   -{
11   - public:
12   - SparseOHArray();
13   - size_t size() const;
14   - void append(QPDFObjectHandle oh);
15   - void append(std::shared_ptr<QPDFObject>&& obj);
16   - QPDFObjectHandle at(size_t idx) const;
17   - void remove_last();
18   - void setAt(size_t idx, QPDFObjectHandle oh);
19   - void erase(size_t idx);
20   - void insert(size_t idx, QPDFObjectHandle oh);
21   - SparseOHArray copy();
22   - void disconnect();
23   -
24   - typedef std::unordered_map<size_t, QPDFObjectHandle>::const_iterator
25   - const_iterator;
26   - const_iterator begin() const;
27   - const_iterator end() const;
28   -
29   - private:
30   - friend class QPDF_Array;
31   - std::unordered_map<size_t, QPDFObjectHandle> elements;
32   - size_t n_elements;
33   -};
34   -
35   -#endif // QPDF_SPARSEOHARRAY_HH
libtests/sparse_array.cc
1 1 #include <qpdf/assert_test.h>
2 2  
3   -#include <qpdf/SparseOHArray.hh>
  3 +#include <qpdf/QPDFObjectHandle.hh>
  4 +#include <qpdf/QPDFObject_private.hh>
  5 +#include <qpdf/QPDF_Array.hh>
4 6 #include <iostream>
5 7  
6 8 int
7 9 main()
8 10 {
9   - SparseOHArray a;
  11 + auto obj = QPDF_Array::create({}, true);
  12 + QPDF_Array& a = *obj->as<QPDF_Array>();
  13 +
10 14 assert(a.size() == 0);
11 15  
12   - a.append(QPDFObjectHandle::parse("1"));
13   - a.append(QPDFObjectHandle::parse("(potato)"));
14   - a.append(QPDFObjectHandle::parse("null"));
15   - a.append(QPDFObjectHandle::parse("null"));
16   - a.append(QPDFObjectHandle::parse("/Quack"));
  16 + a.push_back(QPDFObjectHandle::parse("1"));
  17 + a.push_back(QPDFObjectHandle::parse("(potato)"));
  18 + a.push_back(QPDFObjectHandle::parse("null"));
  19 + a.push_back(QPDFObjectHandle::parse("null"));
  20 + a.push_back(QPDFObjectHandle::parse("/Quack"));
17 21 assert(a.size() == 5);
18 22 assert(a.at(0).isInteger() && (a.at(0).getIntValue() == 1));
19 23 assert(a.at(1).isString() && (a.at(1).getStringValue() == "potato"));
... ... @@ -60,20 +64,20 @@ main()
60 64 a.setAt(4, QPDFObjectHandle::newNull());
61 65 assert(a.at(4).isNull());
62 66  
63   - a.remove_last();
  67 + a.erase(a.size() - 1);
64 68 assert(a.size() == 5);
65 69 assert(a.at(0).isName() && (a.at(0).getName() == "/First"));
66 70 assert(a.at(1).isInteger() && (a.at(1).getIntValue() == 1));
67 71 assert(a.at(3).isName() && (a.at(3).getName() == "/Third"));
68 72 assert(a.at(4).isNull());
69 73  
70   - a.remove_last();
  74 + a.erase(a.size() - 1);
71 75 assert(a.size() == 4);
72 76 assert(a.at(0).isName() && (a.at(0).getName() == "/First"));
73 77 assert(a.at(1).isInteger() && (a.at(1).getIntValue() == 1));
74 78 assert(a.at(3).isName() && (a.at(3).getName() == "/Third"));
75 79  
76   - a.remove_last();
  80 + a.erase(a.size() - 1);
77 81 assert(a.size() == 3);
78 82 assert(a.at(0).isName() && (a.at(0).getName() == "/First"));
79 83 assert(a.at(1).isInteger() && (a.at(1).getIntValue() == 1));
... ...
qpdf/qpdf.testcov
... ... @@ -303,8 +303,10 @@ QPDFObjectHandle array treating as empty 0
303 303 QPDFObjectHandle array null for non-array 0
304 304 QPDFObjectHandle array treating as empty vector 0
305 305 QPDFObjectHandle array ignoring set item 0
  306 +QPDFObjectHandle set array bounds 0
306 307 QPDFObjectHandle array ignoring replace items 0
307 308 QPDFObjectHandle array ignoring insert item 0
  309 +QPDFObjectHandle insert array bounds 0
308 310 QPDFObjectHandle array ignoring append item 0
309 311 QPDFObjectHandle array ignoring erase item 0
310 312 QPDFObjectHandle dictionary false for hasKey 0
... ...
qpdf/qtest/qpdf/object-types-os.out
... ... @@ -5,6 +5,8 @@ WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operatio
5 5 WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for array attempted on object of type integer: ignoring attempt to append item
6 6 WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: ignoring attempt to erase out of bounds array item
7 7 WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: ignoring attempt to erase out of bounds array item
  8 +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: ignoring attempt to insert out of bounds array item
  9 +WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: ignoring attempt to set out of bounds array item
8 10 WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for array attempted on object of type integer: ignoring attempt to erase item
9 11 WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for array attempted on object of type integer: ignoring attempt to insert item
10 12 WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operation for array attempted on object of type integer: ignoring attempt to replace items
... ...
qpdf/qtest/qpdf/object-types.out
... ... @@ -5,6 +5,8 @@ WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempt
5 5 WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to append item
6 6 WARNING: object-types.pdf, object 8 0 at offset 717: ignoring attempt to erase out of bounds array item
7 7 WARNING: object-types.pdf, object 8 0 at offset 717: ignoring attempt to erase out of bounds array item
  8 +WARNING: object-types.pdf, object 8 0 at offset 717: ignoring attempt to insert out of bounds array item
  9 +WARNING: object-types.pdf, object 8 0 at offset 717: ignoring attempt to set out of bounds array item
8 10 WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to erase item
9 11 WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to insert item
10 12 WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to replace items
... ...
qpdf/qtest/qpdf/test88.out
1 1 WARNING: test array: ignoring attempt to erase out of bounds array item
  2 +WARNING: minimal.pdf, object 1 0 at offset 19: operation for array attempted on object of type dictionary: ignoring attempt to erase item
2 3 test 88 done
... ...
qpdf/test_driver.cc
... ... @@ -1506,6 +1506,8 @@ test_42(QPDF&amp; pdf, char const* arg2)
1506 1506 integer.appendItem(null);
1507 1507 array.eraseItem(-1);
1508 1508 array.eraseItem(16059);
  1509 + array.insertItem(42, "/Dontpanic"_qpdf);
  1510 + array.setArrayItem(42, "/Dontpanic"_qpdf);
1509 1511 integer.eraseItem(0);
1510 1512 integer.insertItem(0, null);
1511 1513 integer.setArrayFromVector(std::vector<QPDFObjectHandle>());
... ... @@ -3282,6 +3284,7 @@ test_88(QPDF&amp; pdf, char const* arg2)
3282 3284 auto arr2 = pdf.getRoot().replaceKeyAndGetNew("/QTest", "[1 2]"_qpdf);
3283 3285 arr2.setObjectDescription(&pdf, "test array");
3284 3286 assert(arr2.eraseItemAndGetOld(50).isNull());
  3287 + assert(pdf.getRoot().eraseItemAndGetOld(0).isNull());
3285 3288 }
3286 3289  
3287 3290 static void
... ...