diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh index 77fe680f..b722b083 100644 --- a/include/qpdf/QPDFObjectHandle.hh +++ b/include/qpdf/QPDFObjectHandle.hh @@ -1497,6 +1497,7 @@ class QPDFObjectHandle friend class QPDF_Dictionary; friend class QPDF_Stream; friend class SparseOHArray; + friend class OHArray; private: static void diff --git a/libqpdf/CMakeLists.txt b/libqpdf/CMakeLists.txt index 5e3a628e..2d857f45 100644 --- a/libqpdf/CMakeLists.txt +++ b/libqpdf/CMakeLists.txt @@ -116,6 +116,7 @@ set(libqpdf_SOURCES SecureRandomDataProvider.cc SF_FlateLzwDecode.cc SparseOHArray.cc + OHArray.cc qpdf-c.cc qpdfjob-c.cc qpdflogger-c.cc) diff --git a/libqpdf/OHArray.cc b/libqpdf/OHArray.cc new file mode 100644 index 00000000..436fa4f2 --- /dev/null +++ b/libqpdf/OHArray.cc @@ -0,0 +1,148 @@ +#include + +#include +#include + +#include + +OHArray::OHArray() : + n_elements(0) +{ +} + +size_t +OHArray::size() const +{ + return this->n_elements; +} + +void +OHArray::append(QPDFObjectHandle oh) +{ + if (!oh.isDirectNull()) { + this->elements[this->n_elements] = oh; + } + ++this->n_elements; +} + +void +OHArray::append(std::shared_ptr&& obj) +{ + if (obj->getTypeCode() != ::ot_null || !obj->getObjGen().isIndirect()) { + this->elements[this->n_elements] = std::move(obj); + } + ++this->n_elements; +} + +QPDFObjectHandle +OHArray::at(size_t idx) const +{ + if (idx >= this->n_elements) { + throw std::logic_error( + "INTERNAL ERROR: bounds error accessing OHArray element"); + } + auto const& iter = this->elements.find(idx); + if (iter == this->elements.end()) { + return QPDFObjectHandle::newNull(); + } else { + return (*iter).second; + } +} + +void +OHArray::remove_last() +{ + if (this->n_elements == 0) { + throw std::logic_error("INTERNAL ERROR: attempt to remove" + " last item from empty OHArray"); + } + --this->n_elements; + this->elements.erase(this->n_elements); +} + +void +OHArray::disconnect() +{ + for (auto& iter: this->elements) { + QPDFObjectHandle::DisconnectAccess::disconnect(iter.second); + } +} + +void +OHArray::setAt(size_t idx, QPDFObjectHandle oh) +{ + if (idx >= this->n_elements) { + throw std::logic_error("bounds error setting item in OHArray"); + } + if (oh.isDirectNull()) { + this->elements.erase(idx); + } else { + this->elements[idx] = oh; + } +} + +void +OHArray::erase(size_t idx) +{ + if (idx >= this->n_elements) { + throw std::logic_error("bounds error erasing item from OHArray"); + } + decltype(this->elements) dest; + for (auto const& iter: this->elements) { + if (iter.first < idx) { + dest.insert(iter); + } else if (iter.first > idx) { + dest[iter.first - 1] = iter.second; + } + } + this->elements = dest; + --this->n_elements; +} + +void +OHArray::insert(size_t idx, QPDFObjectHandle oh) +{ + if (idx > this->n_elements) { + throw std::logic_error("bounds error inserting item to OHArray"); + } else if (idx == this->n_elements) { + // Allow inserting to the last position + append(oh); + } else { + decltype(this->elements) dest; + for (auto const& iter: this->elements) { + if (iter.first < idx) { + dest.insert(iter); + } else { + dest[iter.first + 1] = iter.second; + } + } + this->elements = dest; + this->elements[idx] = oh; + ++this->n_elements; + } +} + +OHArray +OHArray::copy() +{ + OHArray result; + result.n_elements = this->n_elements; + for (auto const& element: this->elements) { + auto value = element.second; + result.elements[element.first] = + value.isIndirect() ? value : value.shallowCopy(); + } + return result; +} + +OHArray::const_iterator +OHArray::begin() const +{ + return this->elements.begin(); +} + +OHArray::const_iterator +OHArray::end() const +{ + return this->elements.end(); +} diff --git a/libqpdf/QPDF_Array.cc b/libqpdf/QPDF_Array.cc index 93fbf928..ed43245f 100644 --- a/libqpdf/QPDF_Array.cc +++ b/libqpdf/QPDF_Array.cc @@ -19,6 +19,13 @@ QPDF_Array::QPDF_Array(std::vector>&& v) : QPDF_Array::QPDF_Array(SparseOHArray const& items) : QPDFValue(::ot_array, "array"), + sp_elements(items) +{ +} + +QPDF_Array::QPDF_Array(OHArray const& items) : + QPDFValue(::ot_array, "array"), + sparse(false), elements(items) { } @@ -41,93 +48,168 @@ QPDF_Array::create(SparseOHArray const& items) return do_create(new QPDF_Array(items)); } +std::shared_ptr +QPDF_Array::create(OHArray const& items) +{ + return do_create(new QPDF_Array(items)); +} + std::shared_ptr QPDF_Array::copy(bool shallow) { - return create(shallow ? elements : elements.copy()); + if (sparse) { + return create(shallow ? sp_elements : sp_elements.copy()); + } else { + return create(shallow ? elements : elements.copy()); + } } void QPDF_Array::disconnect() { - elements.disconnect(); + if (sparse) { + sp_elements.disconnect(); + } else { + elements.disconnect(); + } } std::string QPDF_Array::unparse() { - std::string result = "[ "; - size_t size = this->elements.size(); - for (size_t i = 0; i < size; ++i) { - result += this->elements.at(i).unparse(); - result += " "; + if (sparse) { + std::string result = "[ "; + size_t size = sp_elements.size(); + for (size_t i = 0; i < size; ++i) { + result += sp_elements.at(i).unparse(); + result += " "; + } + result += "]"; + return result; + } else { + std::string result = "[ "; + size_t size = elements.size(); + for (size_t i = 0; i < size; ++i) { + result += elements.at(i).unparse(); + result += " "; + } + result += "]"; + return result; } - result += "]"; - return result; } JSON QPDF_Array::getJSON(int json_version) { - JSON j = JSON::makeArray(); - size_t size = this->elements.size(); - for (size_t i = 0; i < size; ++i) { - j.addArrayElement(this->elements.at(i).getJSON(json_version)); + if (sparse) { + JSON j = JSON::makeArray(); + size_t size = sp_elements.size(); + for (size_t i = 0; i < size; ++i) { + j.addArrayElement(sp_elements.at(i).getJSON(json_version)); + } + return j; + } else { + JSON j = JSON::makeArray(); + size_t size = elements.size(); + for (size_t i = 0; i < size; ++i) { + j.addArrayElement(elements.at(i).getJSON(json_version)); + } + return j; } - return j; } int QPDF_Array::getNItems() const { - // This should really return a size_t, but changing it would break - // a lot of code. - return QIntC::to_int(this->elements.size()); + if (sparse) { + // This should really return a size_t, but changing it would break + // a lot of code. + return QIntC::to_int(sp_elements.size()); + } else { + return QIntC::to_int(elements.size()); + } } QPDFObjectHandle QPDF_Array::getItem(int n) const { - if ((n < 0) || (n >= QIntC::to_int(elements.size()))) { - throw std::logic_error( - "INTERNAL ERROR: bounds error accessing QPDF_Array element"); + if (sparse) { + if ((n < 0) || (n >= QIntC::to_int(sp_elements.size()))) { + throw std::logic_error( + "INTERNAL ERROR: bounds error accessing QPDF_Array element"); + } + return sp_elements.at(QIntC::to_size(n)); + } else { + if ((n < 0) || (n >= QIntC::to_int(elements.size()))) { + throw std::logic_error( + "INTERNAL ERROR: bounds error accessing QPDF_Array element"); + } + return elements.at(QIntC::to_size(n)); } - return this->elements.at(QIntC::to_size(n)); } void QPDF_Array::getAsVector(std::vector& v) const { - size_t size = this->elements.size(); - for (size_t i = 0; i < size; ++i) { - v.push_back(this->elements.at(i)); + if (sparse) { + size_t size = sp_elements.size(); + for (size_t i = 0; i < size; ++i) { + v.push_back(sp_elements.at(i)); + } + } else { + size_t size = elements.size(); + for (size_t i = 0; i < size; ++i) { + v.push_back(elements.at(i)); + } } } void QPDF_Array::setItem(int n, QPDFObjectHandle const& oh) { - this->elements.setAt(QIntC::to_size(n), oh); + if (sparse) { + sp_elements.setAt(QIntC::to_size(n), oh); + } else { + elements.setAt(QIntC::to_size(n), oh); + } } void QPDF_Array::setFromVector(std::vector const& v) { - this->elements = SparseOHArray(); - for (auto const& iter: v) { - this->elements.append(iter); + if (sparse) { + sp_elements = SparseOHArray(); + for (auto const& iter: v) { + sp_elements.append(iter); + } + } else { + elements = OHArray(); + for (auto const& iter: v) { + elements.append(iter); + } } } void QPDF_Array::setFromVector(std::vector>&& v) { - this->elements = SparseOHArray(); - for (auto&& item: v) { - if (item) { - this->elements.append(item); - } else { - ++this->elements.n_elements; + if (sparse) { + sp_elements = SparseOHArray(); + for (auto&& item: v) { + if (item) { + sp_elements.append(item); + } else { + ++sp_elements.n_elements; + } + } + } else { + elements = OHArray(); + for (auto&& item: v) { + if (item) { + elements.append(item); + } else { + ++elements.n_elements; + } } } } @@ -135,22 +217,39 @@ QPDF_Array::setFromVector(std::vector>&& v) void QPDF_Array::insertItem(int at, QPDFObjectHandle const& item) { - // As special case, also allow insert beyond the end - if ((at < 0) || (at > QIntC::to_int(this->elements.size()))) { - throw std::logic_error( - "INTERNAL ERROR: bounds error accessing QPDF_Array element"); + if (sparse) { + // As special case, also allow insert beyond the end + if ((at < 0) || (at > QIntC::to_int(sp_elements.size()))) { + throw std::logic_error( + "INTERNAL ERROR: bounds error accessing QPDF_Array element"); + } + sp_elements.insert(QIntC::to_size(at), item); + } else { + // As special case, also allow insert beyond the end + if ((at < 0) || (at > QIntC::to_int(elements.size()))) { + throw std::logic_error( + "INTERNAL ERROR: bounds error accessing QPDF_Array element"); + } + elements.insert(QIntC::to_size(at), item); } - this->elements.insert(QIntC::to_size(at), item); } void QPDF_Array::appendItem(QPDFObjectHandle const& item) { - this->elements.append(item); + if (sparse) { + sp_elements.append(item); + } else { + elements.append(item); + } } void QPDF_Array::eraseItem(int at) { - this->elements.erase(QIntC::to_size(at)); + if (sparse) { + sp_elements.erase(QIntC::to_size(at)); + } else { + elements.erase(QIntC::to_size(at)); + } } diff --git a/libqpdf/qpdf/OHArray.hh b/libqpdf/qpdf/OHArray.hh new file mode 100644 index 00000000..66223c4f --- /dev/null +++ b/libqpdf/qpdf/OHArray.hh @@ -0,0 +1,35 @@ +#ifndef QPDF_OHARRAY_HH +#define QPDF_OHARRAY_HH + +#include +#include + +class QPDF_Array; + +class OHArray +{ + public: + OHArray(); + size_t size() const; + void append(QPDFObjectHandle oh); + void append(std::shared_ptr&& obj); + QPDFObjectHandle at(size_t idx) const; + void remove_last(); + void setAt(size_t idx, QPDFObjectHandle oh); + void erase(size_t idx); + void insert(size_t idx, QPDFObjectHandle oh); + OHArray copy(); + void disconnect(); + + typedef std::unordered_map::const_iterator + const_iterator; + const_iterator begin() const; + const_iterator end() const; + + private: + friend class QPDF_Array; + std::unordered_map elements; + size_t n_elements; +}; + +#endif // QPDF_OHARRAY_HH diff --git a/libqpdf/qpdf/QPDF_Array.hh b/libqpdf/qpdf/QPDF_Array.hh index 88397ba7..1c4227ba 100644 --- a/libqpdf/qpdf/QPDF_Array.hh +++ b/libqpdf/qpdf/QPDF_Array.hh @@ -3,6 +3,7 @@ #include +#include #include #include #include @@ -16,6 +17,7 @@ class QPDF_Array: public QPDFValue static std::shared_ptr create(std::vector>&& items); static std::shared_ptr create(SparseOHArray const& items); + static std::shared_ptr create(OHArray const& items); virtual std::shared_ptr copy(bool shallow = false); virtual std::string unparse(); virtual JSON getJSON(int json_version); @@ -36,7 +38,10 @@ class QPDF_Array: public QPDFValue QPDF_Array(std::vector const& items); QPDF_Array(std::vector>&& items); QPDF_Array(SparseOHArray const& items); - SparseOHArray elements; + QPDF_Array(OHArray const& items); + bool sparse{false}; + SparseOHArray sp_elements; + OHArray elements; }; #endif // QPDF_ARRAY_HH