Commit f8e39253be9d1806ccdb88d8117b7418f2045da1
Committed by
GitHub
Merge pull request #863 from m-holger/array
Refactor QPDF_Array
Showing
15 changed files
with
383 additions
and
404 deletions
include/qpdf/QPDFObjectHandle.hh
| @@ -1496,11 +1496,10 @@ class QPDFObjectHandle | @@ -1496,11 +1496,10 @@ class QPDFObjectHandle | ||
| 1496 | { | 1496 | { |
| 1497 | friend class QPDF_Dictionary; | 1497 | friend class QPDF_Dictionary; |
| 1498 | friend class QPDF_Stream; | 1498 | friend class QPDF_Stream; |
| 1499 | - friend class SparseOHArray; | ||
| 1500 | 1499 | ||
| 1501 | private: | 1500 | private: |
| 1502 | static void | 1501 | static void |
| 1503 | - disconnect(QPDFObjectHandle& o) | 1502 | + disconnect(QPDFObjectHandle o) |
| 1504 | { | 1503 | { |
| 1505 | o.disconnect(); | 1504 | o.disconnect(); |
| 1506 | } | 1505 | } |
| @@ -1577,6 +1576,11 @@ class QPDFObjectHandle | @@ -1577,6 +1576,11 @@ class QPDFObjectHandle | ||
| 1577 | { | 1576 | { |
| 1578 | return obj; | 1577 | return obj; |
| 1579 | } | 1578 | } |
| 1579 | + std::shared_ptr<QPDFObject> | ||
| 1580 | + getObj() const | ||
| 1581 | + { | ||
| 1582 | + return obj; | ||
| 1583 | + } | ||
| 1580 | QPDFObject* | 1584 | QPDFObject* |
| 1581 | getObjectPtr() | 1585 | getObjectPtr() |
| 1582 | { | 1586 | { |
libqpdf/CMakeLists.txt
| @@ -115,7 +115,6 @@ set(libqpdf_SOURCES | @@ -115,7 +115,6 @@ set(libqpdf_SOURCES | ||
| 115 | ResourceFinder.cc | 115 | ResourceFinder.cc |
| 116 | SecureRandomDataProvider.cc | 116 | SecureRandomDataProvider.cc |
| 117 | SF_FlateLzwDecode.cc | 117 | SF_FlateLzwDecode.cc |
| 118 | - SparseOHArray.cc | ||
| 119 | qpdf-c.cc | 118 | qpdf-c.cc |
| 120 | qpdfjob-c.cc | 119 | qpdfjob-c.cc |
| 121 | qpdflogger-c.cc) | 120 | qpdflogger-c.cc) |
libqpdf/QPDFObjectHandle.cc
| @@ -23,7 +23,6 @@ | @@ -23,7 +23,6 @@ | ||
| 23 | #include <qpdf/QPDF_Stream.hh> | 23 | #include <qpdf/QPDF_Stream.hh> |
| 24 | #include <qpdf/QPDF_String.hh> | 24 | #include <qpdf/QPDF_String.hh> |
| 25 | #include <qpdf/QPDF_Unresolved.hh> | 25 | #include <qpdf/QPDF_Unresolved.hh> |
| 26 | -#include <qpdf/SparseOHArray.hh> | ||
| 27 | 26 | ||
| 28 | #include <qpdf/QIntC.hh> | 27 | #include <qpdf/QIntC.hh> |
| 29 | #include <qpdf/QTC.hh> | 28 | #include <qpdf/QTC.hh> |
| @@ -789,9 +788,8 @@ QPDFObjectHandle::aitems() | @@ -789,9 +788,8 @@ QPDFObjectHandle::aitems() | ||
| 789 | int | 788 | int |
| 790 | QPDFObjectHandle::getArrayNItems() | 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 | } else { | 793 | } else { |
| 796 | typeWarning("array", "treating as empty"); | 794 | typeWarning("array", "treating as empty"); |
| 797 | QTC::TC("qpdf", "QPDFObjectHandle array treating as empty"); | 795 | QTC::TC("qpdf", "QPDFObjectHandle array treating as empty"); |
| @@ -802,104 +800,101 @@ QPDFObjectHandle::getArrayNItems() | @@ -802,104 +800,101 @@ QPDFObjectHandle::getArrayNItems() | ||
| 802 | QPDFObjectHandle | 800 | QPDFObjectHandle |
| 803 | QPDFObjectHandle::getArrayItem(int n) | 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 | objectWarning("returning null for out of bounds array access"); | 807 | objectWarning("returning null for out of bounds array access"); |
| 811 | QTC::TC("qpdf", "QPDFObjectHandle array bounds"); | 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 | bool | 818 | bool |
| 823 | QPDFObjectHandle::isRectangle() | 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 | bool | 832 | bool |
| 838 | QPDFObjectHandle::isMatrix() | 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 | QPDFObjectHandle::Rectangle | 846 | QPDFObjectHandle::Rectangle |
| 853 | QPDFObjectHandle::getArrayAsRectangle() | 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 | QPDFObjectHandle::Matrix | 868 | QPDFObjectHandle::Matrix |
| 875 | QPDFObjectHandle::getArrayAsMatrix() | 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 | std::vector<QPDFObjectHandle> | 887 | std::vector<QPDFObjectHandle> |
| 892 | QPDFObjectHandle::getArrayAsVector() | 888 | QPDFObjectHandle::getArrayAsVector() |
| 893 | { | 889 | { |
| 894 | - std::vector<QPDFObjectHandle> result; | ||
| 895 | auto array = asArray(); | 890 | auto array = asArray(); |
| 896 | if (array) { | 891 | if (array) { |
| 897 | - array->getAsVector(result); | 892 | + return array->getAsVector(); |
| 898 | } else { | 893 | } else { |
| 899 | typeWarning("array", "treating as empty"); | 894 | typeWarning("array", "treating as empty"); |
| 900 | QTC::TC("qpdf", "QPDFObjectHandle array treating as empty vector"); | 895 | QTC::TC("qpdf", "QPDFObjectHandle array treating as empty vector"); |
| 901 | } | 896 | } |
| 902 | - return result; | 897 | + return {}; |
| 903 | } | 898 | } |
| 904 | 899 | ||
| 905 | // Array mutators | 900 | // Array mutators |
| @@ -907,24 +902,20 @@ QPDFObjectHandle::getArrayAsVector() | @@ -907,24 +902,20 @@ QPDFObjectHandle::getArrayAsVector() | ||
| 907 | void | 902 | void |
| 908 | QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const& item) | 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 | } else { | 910 | } else { |
| 915 | typeWarning("array", "ignoring attempt to set item"); | 911 | typeWarning("array", "ignoring attempt to set item"); |
| 916 | QTC::TC("qpdf", "QPDFObjectHandle array ignoring set item"); | 912 | QTC::TC("qpdf", "QPDFObjectHandle array ignoring set item"); |
| 917 | } | 913 | } |
| 918 | } | 914 | } |
| 919 | - | ||
| 920 | void | 915 | void |
| 921 | QPDFObjectHandle::setArrayFromVector(std::vector<QPDFObjectHandle> const& items) | 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 | array->setFromVector(items); | 919 | array->setFromVector(items); |
| 929 | } else { | 920 | } else { |
| 930 | typeWarning("array", "ignoring attempt to replace items"); | 921 | typeWarning("array", "ignoring attempt to replace items"); |
| @@ -935,9 +926,12 @@ QPDFObjectHandle::setArrayFromVector(std::vector<QPDFObjectHandle> const& items) | @@ -935,9 +926,12 @@ QPDFObjectHandle::setArrayFromVector(std::vector<QPDFObjectHandle> const& items) | ||
| 935 | void | 926 | void |
| 936 | QPDFObjectHandle::insertItem(int at, QPDFObjectHandle const& item) | 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 | } else { | 935 | } else { |
| 942 | typeWarning("array", "ignoring attempt to insert item"); | 936 | typeWarning("array", "ignoring attempt to insert item"); |
| 943 | QTC::TC("qpdf", "QPDFObjectHandle array ignoring insert item"); | 937 | QTC::TC("qpdf", "QPDFObjectHandle array ignoring insert item"); |
| @@ -954,10 +948,8 @@ QPDFObjectHandle::insertItemAndGetNew(int at, QPDFObjectHandle const& item) | @@ -954,10 +948,8 @@ QPDFObjectHandle::insertItemAndGetNew(int at, QPDFObjectHandle const& item) | ||
| 954 | void | 948 | void |
| 955 | QPDFObjectHandle::appendItem(QPDFObjectHandle const& item) | 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 | } else { | 953 | } else { |
| 962 | typeWarning("array", "ignoring attempt to append item"); | 954 | typeWarning("array", "ignoring attempt to append item"); |
| 963 | QTC::TC("qpdf", "QPDFObjectHandle array ignoring append item"); | 955 | QTC::TC("qpdf", "QPDFObjectHandle array ignoring append item"); |
| @@ -974,28 +966,23 @@ QPDFObjectHandle::appendItemAndGetNew(QPDFObjectHandle const& item) | @@ -974,28 +966,23 @@ QPDFObjectHandle::appendItemAndGetNew(QPDFObjectHandle const& item) | ||
| 974 | void | 966 | void |
| 975 | QPDFObjectHandle::eraseItem(int at) | 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 | objectWarning("ignoring attempt to erase out of bounds array item"); | 971 | objectWarning("ignoring attempt to erase out of bounds array item"); |
| 983 | QTC::TC("qpdf", "QPDFObjectHandle erase array bounds"); | 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 | QPDFObjectHandle | 980 | QPDFObjectHandle |
| 992 | QPDFObjectHandle::eraseItemAndGetOld(int at) | 981 | QPDFObjectHandle::eraseItemAndGetOld(int at) |
| 993 | { | 982 | { |
| 994 | - auto result = QPDFObjectHandle::newNull(); | ||
| 995 | auto array = asArray(); | 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 | eraseItem(at); | 986 | eraseItem(at); |
| 1000 | return result; | 987 | return result; |
| 1001 | } | 988 | } |
| @@ -1515,11 +1502,10 @@ QPDFObjectHandle::arrayOrStreamToStreamArray( | @@ -1515,11 +1502,10 @@ QPDFObjectHandle::arrayOrStreamToStreamArray( | ||
| 1515 | { | 1502 | { |
| 1516 | all_description = description; | 1503 | all_description = description; |
| 1517 | std::vector<QPDFObjectHandle> result; | 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 | for (int i = 0; i < n_items; ++i) { | 1507 | for (int i = 0; i < n_items; ++i) { |
| 1522 | - QPDFObjectHandle item = array->getItem(i); | 1508 | + QPDFObjectHandle item = array->at(i); |
| 1523 | if (item.isStream()) { | 1509 | if (item.isStream()) { |
| 1524 | result.push_back(item); | 1510 | result.push_back(item); |
| 1525 | } else { | 1511 | } else { |
| @@ -2217,9 +2203,9 @@ QPDFObjectHandle::makeDirect( | @@ -2217,9 +2203,9 @@ QPDFObjectHandle::makeDirect( | ||
| 2217 | } else if (isArray()) { | 2203 | } else if (isArray()) { |
| 2218 | std::vector<QPDFObjectHandle> items; | 2204 | std::vector<QPDFObjectHandle> items; |
| 2219 | auto array = asArray(); | 2205 | auto array = asArray(); |
| 2220 | - int n = array->getNItems(); | 2206 | + int n = array->size(); |
| 2221 | for (int i = 0; i < n; ++i) { | 2207 | for (int i = 0; i < n; ++i) { |
| 2222 | - items.push_back(array->getItem(i)); | 2208 | + items.push_back(array->at(i)); |
| 2223 | items.back().makeDirect(visited, stop_at_streams); | 2209 | items.back().makeDirect(visited, stop_at_streams); |
| 2224 | } | 2210 | } |
| 2225 | this->obj = QPDF_Array::create(items); | 2211 | this->obj = QPDF_Array::create(items); |
libqpdf/QPDFParser.cc
| @@ -27,16 +27,15 @@ namespace | @@ -27,16 +27,15 @@ namespace | ||
| 27 | struct StackFrame | 27 | struct StackFrame |
| 28 | { | 28 | { |
| 29 | StackFrame(std::shared_ptr<InputSource> input) : | 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 | std::vector<std::shared_ptr<QPDFObject>> olist; | 34 | std::vector<std::shared_ptr<QPDFObject>> olist; |
| 37 | qpdf_offset_t offset; | 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 | } // namespace | 40 | } // namespace |
| 42 | 41 | ||
| @@ -50,6 +49,7 @@ QPDFParser::parse(bool& empty, bool content_stream) | @@ -50,6 +49,7 @@ QPDFParser::parse(bool& empty, bool content_stream) | ||
| 50 | // this, it will cause a logic error to be thrown from | 49 | // this, it will cause a logic error to be thrown from |
| 51 | // QPDF::inParse(). | 50 | // QPDF::inParse(). |
| 52 | 51 | ||
| 52 | + const static std::shared_ptr<QPDFObject> null_oh = QPDF_Null::create(); | ||
| 53 | QPDF::ParseGuard pg(context); | 53 | QPDF::ParseGuard pg(context); |
| 54 | 54 | ||
| 55 | empty = false; | 55 | empty = false; |
| @@ -67,7 +67,6 @@ QPDFParser::parse(bool& empty, bool content_stream) | @@ -67,7 +67,6 @@ QPDFParser::parse(bool& empty, bool content_stream) | ||
| 67 | int good_count = 0; | 67 | int good_count = 0; |
| 68 | bool b_contents = false; | 68 | bool b_contents = false; |
| 69 | bool is_null = false; | 69 | bool is_null = false; |
| 70 | - auto null_oh = QPDF_Null::create(); | ||
| 71 | 70 | ||
| 72 | while (!done) { | 71 | while (!done) { |
| 73 | bool bad = false; | 72 | bool bad = false; |
| @@ -156,6 +155,8 @@ QPDFParser::parse(bool& empty, bool content_stream) | @@ -156,6 +155,8 @@ QPDFParser::parse(bool& empty, bool content_stream) | ||
| 156 | 155 | ||
| 157 | case QPDFTokenizer::tt_null: | 156 | case QPDFTokenizer::tt_null: |
| 158 | is_null = true; | 157 | is_null = true; |
| 158 | + ++frame.null_count; | ||
| 159 | + | ||
| 159 | break; | 160 | break; |
| 160 | 161 | ||
| 161 | case QPDFTokenizer::tt_integer: | 162 | case QPDFTokenizer::tt_integer: |
| @@ -301,9 +302,11 @@ QPDFParser::parse(bool& empty, bool content_stream) | @@ -301,9 +302,11 @@ QPDFParser::parse(bool& empty, bool content_stream) | ||
| 301 | 302 | ||
| 302 | case st_dictionary: | 303 | case st_dictionary: |
| 303 | case st_array: | 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 | setDescription(object, input->getLastOffset()); | 310 | setDescription(object, input->getLastOffset()); |
| 308 | } | 311 | } |
| 309 | set_offset = true; | 312 | set_offset = true; |
| @@ -326,7 +329,8 @@ QPDFParser::parse(bool& empty, bool content_stream) | @@ -326,7 +329,8 @@ QPDFParser::parse(bool& empty, bool content_stream) | ||
| 326 | parser_state_e old_state = state_stack.back(); | 329 | parser_state_e old_state = state_stack.back(); |
| 327 | state_stack.pop_back(); | 330 | state_stack.pop_back(); |
| 328 | if (old_state == st_array) { | 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 | setDescription(object, offset - 1); | 334 | setDescription(object, offset - 1); |
| 331 | // The `offset` points to the next of "[". Set the rewind | 335 | // The `offset` points to the next of "[". Set the rewind |
| 332 | // offset to point to the beginning of "[". This has been | 336 | // offset to point to the beginning of "[". This has been |
| @@ -381,7 +385,7 @@ QPDFParser::parse(bool& empty, bool content_stream) | @@ -381,7 +385,7 @@ QPDFParser::parse(bool& empty, bool content_stream) | ||
| 381 | // Calculate value. | 385 | // Calculate value. |
| 382 | std::shared_ptr<QPDFObject> val; | 386 | std::shared_ptr<QPDFObject> val; |
| 383 | if (iter != olist.end()) { | 387 | if (iter != olist.end()) { |
| 384 | - val = *iter ? *iter : QPDF_Null::create(); | 388 | + val = *iter; |
| 385 | ++iter; | 389 | ++iter; |
| 386 | } else { | 390 | } else { |
| 387 | QTC::TC("qpdf", "QPDFParser no val for last key"); | 391 | QTC::TC("qpdf", "QPDFParser no val for last key"); |
libqpdf/QPDF_Array.cc
| 1 | #include <qpdf/QPDF_Array.hh> | 1 | #include <qpdf/QPDF_Array.hh> |
| 2 | 2 | ||
| 3 | -#include <qpdf/QIntC.hh> | 3 | +#include <qpdf/QPDFObjectHandle.hh> |
| 4 | #include <qpdf/QPDFObject_private.hh> | 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 | QPDFValue(::ot_array, "array") | 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 | QPDFValue(::ot_array, "array"), | 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 | std::shared_ptr<QPDFObject> | 66 | std::shared_ptr<QPDFObject> |
| 39 | -QPDF_Array::create(SparseOHArray const& items) | 67 | +QPDF_Array::create(std::vector<QPDFObjectHandle> const& items) |
| 40 | { | 68 | { |
| 41 | return do_create(new QPDF_Array(items)); | 69 | return do_create(new QPDF_Array(items)); |
| 42 | } | 70 | } |
| 43 | 71 | ||
| 44 | std::shared_ptr<QPDFObject> | 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 | QPDF_Array::copy(bool shallow) | 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 | void | 109 | void |
| 51 | QPDF_Array::disconnect() | 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 | std::string | 128 | std::string |
| 57 | QPDF_Array::unparse() | 129 | QPDF_Array::unparse() |
| 58 | { | 130 | { |
| 59 | std::string result = "[ "; | 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 | result += "]"; | 156 | result += "]"; |
| 66 | return result; | 157 | return result; |
| @@ -69,96 +160,157 @@ QPDF_Array::unparse() | @@ -69,96 +160,157 @@ QPDF_Array::unparse() | ||
| 69 | JSON | 160 | JSON |
| 70 | QPDF_Array::getJSON(int json_version) | 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 | QPDFObjectHandle | 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 | void | 237 | void |
| 114 | QPDF_Array::setFromVector(std::vector<QPDFObjectHandle> const& v) | 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 | } else { | 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 | void | 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,5 +50,6 @@ QPDF_Null::unparse() | ||
| 50 | JSON | 50 | JSON |
| 51 | QPDF_Null::getJSON(int json_version) | 51 | QPDF_Null::getJSON(int json_version) |
| 52 | { | 52 | { |
| 53 | + // If this is updated, QPDF_Array::getJSON must also be updated. | ||
| 53 | return JSON::makeNull(); | 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,8 +3,7 @@ | ||
| 3 | 3 | ||
| 4 | #include <qpdf/QPDFValue.hh> | 4 | #include <qpdf/QPDFValue.hh> |
| 5 | 5 | ||
| 6 | -#include <qpdf/SparseOHArray.hh> | ||
| 7 | -#include <list> | 6 | +#include <map> |
| 8 | #include <vector> | 7 | #include <vector> |
| 9 | 8 | ||
| 10 | class QPDF_Array: public QPDFValue | 9 | class QPDF_Array: public QPDFValue |
| @@ -14,34 +13,37 @@ class QPDF_Array: public QPDFValue | @@ -14,34 +13,37 @@ class QPDF_Array: public QPDFValue | ||
| 14 | static std::shared_ptr<QPDFObject> | 13 | static std::shared_ptr<QPDFObject> |
| 15 | create(std::vector<QPDFObjectHandle> const& items); | 14 | create(std::vector<QPDFObjectHandle> const& items); |
| 16 | static std::shared_ptr<QPDFObject> | 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 | virtual std::shared_ptr<QPDFObject> copy(bool shallow = false); | 17 | virtual std::shared_ptr<QPDFObject> copy(bool shallow = false); |
| 20 | virtual std::string unparse(); | 18 | virtual std::string unparse(); |
| 21 | virtual JSON getJSON(int json_version); | 19 | virtual JSON getJSON(int json_version); |
| 22 | virtual void disconnect(); | 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 | void setFromVector(std::vector<QPDFObjectHandle> const& items); | 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 | private: | 35 | private: |
| 36 | + QPDF_Array(); | ||
| 37 | + QPDF_Array(QPDF_Array const&); | ||
| 41 | QPDF_Array(std::vector<QPDFObjectHandle> const& items); | 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 | #endif // QPDF_ARRAY_HH | 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 | #include <qpdf/assert_test.h> | 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 | #include <iostream> | 6 | #include <iostream> |
| 5 | 7 | ||
| 6 | int | 8 | int |
| 7 | main() | 9 | main() |
| 8 | { | 10 | { |
| 9 | - SparseOHArray a; | 11 | + auto obj = QPDF_Array::create({}, true); |
| 12 | + QPDF_Array& a = *obj->as<QPDF_Array>(); | ||
| 13 | + | ||
| 10 | assert(a.size() == 0); | 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 | assert(a.size() == 5); | 21 | assert(a.size() == 5); |
| 18 | assert(a.at(0).isInteger() && (a.at(0).getIntValue() == 1)); | 22 | assert(a.at(0).isInteger() && (a.at(0).getIntValue() == 1)); |
| 19 | assert(a.at(1).isString() && (a.at(1).getStringValue() == "potato")); | 23 | assert(a.at(1).isString() && (a.at(1).getStringValue() == "potato")); |
| @@ -60,20 +64,20 @@ main() | @@ -60,20 +64,20 @@ main() | ||
| 60 | a.setAt(4, QPDFObjectHandle::newNull()); | 64 | a.setAt(4, QPDFObjectHandle::newNull()); |
| 61 | assert(a.at(4).isNull()); | 65 | assert(a.at(4).isNull()); |
| 62 | 66 | ||
| 63 | - a.remove_last(); | 67 | + a.erase(a.size() - 1); |
| 64 | assert(a.size() == 5); | 68 | assert(a.size() == 5); |
| 65 | assert(a.at(0).isName() && (a.at(0).getName() == "/First")); | 69 | assert(a.at(0).isName() && (a.at(0).getName() == "/First")); |
| 66 | assert(a.at(1).isInteger() && (a.at(1).getIntValue() == 1)); | 70 | assert(a.at(1).isInteger() && (a.at(1).getIntValue() == 1)); |
| 67 | assert(a.at(3).isName() && (a.at(3).getName() == "/Third")); | 71 | assert(a.at(3).isName() && (a.at(3).getName() == "/Third")); |
| 68 | assert(a.at(4).isNull()); | 72 | assert(a.at(4).isNull()); |
| 69 | 73 | ||
| 70 | - a.remove_last(); | 74 | + a.erase(a.size() - 1); |
| 71 | assert(a.size() == 4); | 75 | assert(a.size() == 4); |
| 72 | assert(a.at(0).isName() && (a.at(0).getName() == "/First")); | 76 | assert(a.at(0).isName() && (a.at(0).getName() == "/First")); |
| 73 | assert(a.at(1).isInteger() && (a.at(1).getIntValue() == 1)); | 77 | assert(a.at(1).isInteger() && (a.at(1).getIntValue() == 1)); |
| 74 | assert(a.at(3).isName() && (a.at(3).getName() == "/Third")); | 78 | assert(a.at(3).isName() && (a.at(3).getName() == "/Third")); |
| 75 | 79 | ||
| 76 | - a.remove_last(); | 80 | + a.erase(a.size() - 1); |
| 77 | assert(a.size() == 3); | 81 | assert(a.size() == 3); |
| 78 | assert(a.at(0).isName() && (a.at(0).getName() == "/First")); | 82 | assert(a.at(0).isName() && (a.at(0).getName() == "/First")); |
| 79 | assert(a.at(1).isInteger() && (a.at(1).getIntValue() == 1)); | 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,8 +303,10 @@ QPDFObjectHandle array treating as empty 0 | ||
| 303 | QPDFObjectHandle array null for non-array 0 | 303 | QPDFObjectHandle array null for non-array 0 |
| 304 | QPDFObjectHandle array treating as empty vector 0 | 304 | QPDFObjectHandle array treating as empty vector 0 |
| 305 | QPDFObjectHandle array ignoring set item 0 | 305 | QPDFObjectHandle array ignoring set item 0 |
| 306 | +QPDFObjectHandle set array bounds 0 | ||
| 306 | QPDFObjectHandle array ignoring replace items 0 | 307 | QPDFObjectHandle array ignoring replace items 0 |
| 307 | QPDFObjectHandle array ignoring insert item 0 | 308 | QPDFObjectHandle array ignoring insert item 0 |
| 309 | +QPDFObjectHandle insert array bounds 0 | ||
| 308 | QPDFObjectHandle array ignoring append item 0 | 310 | QPDFObjectHandle array ignoring append item 0 |
| 309 | QPDFObjectHandle array ignoring erase item 0 | 311 | QPDFObjectHandle array ignoring erase item 0 |
| 310 | QPDFObjectHandle dictionary false for hasKey 0 | 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,6 +5,8 @@ WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 429: operatio | ||
| 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 | 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 | WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: ignoring attempt to erase out of bounds array item | 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 | WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: ignoring attempt to erase out of bounds array item | 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 | 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 | 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 | 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 | 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 | 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 | 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,6 +5,8 @@ WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempt | ||
| 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 | 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 | WARNING: object-types.pdf, object 8 0 at offset 717: ignoring attempt to erase out of bounds array item | 6 | WARNING: object-types.pdf, object 8 0 at offset 717: ignoring attempt to erase out of bounds array item |
| 7 | WARNING: object-types.pdf, object 8 0 at offset 717: ignoring attempt to erase out of bounds array item | 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 | WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to erase item | 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 | WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to insert item | 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 | WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to replace items | 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 | WARNING: test array: ignoring attempt to erase out of bounds array item | 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 | test 88 done | 3 | test 88 done |
qpdf/test_driver.cc
| @@ -1506,6 +1506,8 @@ test_42(QPDF& pdf, char const* arg2) | @@ -1506,6 +1506,8 @@ test_42(QPDF& pdf, char const* arg2) | ||
| 1506 | integer.appendItem(null); | 1506 | integer.appendItem(null); |
| 1507 | array.eraseItem(-1); | 1507 | array.eraseItem(-1); |
| 1508 | array.eraseItem(16059); | 1508 | array.eraseItem(16059); |
| 1509 | + array.insertItem(42, "/Dontpanic"_qpdf); | ||
| 1510 | + array.setArrayItem(42, "/Dontpanic"_qpdf); | ||
| 1509 | integer.eraseItem(0); | 1511 | integer.eraseItem(0); |
| 1510 | integer.insertItem(0, null); | 1512 | integer.insertItem(0, null); |
| 1511 | integer.setArrayFromVector(std::vector<QPDFObjectHandle>()); | 1513 | integer.setArrayFromVector(std::vector<QPDFObjectHandle>()); |
| @@ -3282,6 +3284,7 @@ test_88(QPDF& pdf, char const* arg2) | @@ -3282,6 +3284,7 @@ test_88(QPDF& pdf, char const* arg2) | ||
| 3282 | auto arr2 = pdf.getRoot().replaceKeyAndGetNew("/QTest", "[1 2]"_qpdf); | 3284 | auto arr2 = pdf.getRoot().replaceKeyAndGetNew("/QTest", "[1 2]"_qpdf); |
| 3283 | arr2.setObjectDescription(&pdf, "test array"); | 3285 | arr2.setObjectDescription(&pdf, "test array"); |
| 3284 | assert(arr2.eraseItemAndGetOld(50).isNull()); | 3286 | assert(arr2.eraseItemAndGetOld(50).isNull()); |
| 3287 | + assert(pdf.getRoot().eraseItemAndGetOld(0).isNull()); | ||
| 3285 | } | 3288 | } |
| 3286 | 3289 | ||
| 3287 | static void | 3290 | static void |