summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/qpdf/QPDFObjectHandle.hh8
-rw-r--r--libqpdf/CMakeLists.txt1
-rw-r--r--libqpdf/QPDFObjectHandle.cc178
-rw-r--r--libqpdf/QPDFParser.cc26
-rw-r--r--libqpdf/QPDF_Array.cc312
-rw-r--r--libqpdf/QPDF_Null.cc1
-rw-r--r--libqpdf/SparseOHArray.cc148
-rw-r--r--libqpdf/qpdf/QPDF_Array.hh44
-rw-r--r--libqpdf/qpdf/SparseOHArray.hh35
-rw-r--r--libtests/sparse_array.cc24
-rw-r--r--qpdf/qpdf.testcov2
-rw-r--r--qpdf/qtest/qpdf/object-types-os.out2
-rw-r--r--qpdf/qtest/qpdf/object-types.out2
-rw-r--r--qpdf/qtest/qpdf/test88.out1
-rw-r--r--qpdf/test_driver.cc3
15 files changed, 383 insertions, 404 deletions
diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh
index 77fe680f..ee424e39 100644
--- a/include/qpdf/QPDFObjectHandle.hh
+++ b/include/qpdf/QPDFObjectHandle.hh
@@ -1496,11 +1496,10 @@ class QPDFObjectHandle
{
friend class QPDF_Dictionary;
friend class QPDF_Stream;
- friend class SparseOHArray;
private:
static void
- disconnect(QPDFObjectHandle& o)
+ disconnect(QPDFObjectHandle o)
{
o.disconnect();
}
@@ -1577,6 +1576,11 @@ class QPDFObjectHandle
{
return obj;
}
+ std::shared_ptr<QPDFObject>
+ getObj() const
+ {
+ return obj;
+ }
QPDFObject*
getObjectPtr()
{
diff --git a/libqpdf/CMakeLists.txt b/libqpdf/CMakeLists.txt
index 5e3a628e..623e05d3 100644
--- a/libqpdf/CMakeLists.txt
+++ b/libqpdf/CMakeLists.txt
@@ -115,7 +115,6 @@ set(libqpdf_SOURCES
ResourceFinder.cc
SecureRandomDataProvider.cc
SF_FlateLzwDecode.cc
- SparseOHArray.cc
qpdf-c.cc
qpdfjob-c.cc
qpdflogger-c.cc)
diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc
index d474dcce..b3f208a5 100644
--- a/libqpdf/QPDFObjectHandle.cc
+++ b/libqpdf/QPDFObjectHandle.cc
@@ -23,7 +23,6 @@
#include <qpdf/QPDF_Stream.hh>
#include <qpdf/QPDF_String.hh>
#include <qpdf/QPDF_Unresolved.hh>
-#include <qpdf/SparseOHArray.hh>
#include <qpdf/QIntC.hh>
#include <qpdf/QTC.hh>
@@ -789,9 +788,8 @@ QPDFObjectHandle::aitems()
int
QPDFObjectHandle::getArrayNItems()
{
- auto array = asArray();
- if (array) {
- return array->getNItems();
+ if (auto array = asArray()) {
+ return array->size();
} else {
typeWarning("array", "treating as empty");
QTC::TC("qpdf", "QPDFObjectHandle array treating as empty");
@@ -802,104 +800,101 @@ QPDFObjectHandle::getArrayNItems()
QPDFObjectHandle
QPDFObjectHandle::getArrayItem(int n)
{
- auto array = asArray();
- if (array && (n < array->getNItems()) && (n >= 0)) {
- return array->getItem(n);
- } else {
- if (array) {
+ if (auto array = asArray()) {
+ if (auto result = array->at(n); result.obj != nullptr) {
+ return result;
+ } else {
objectWarning("returning null for out of bounds array access");
QTC::TC("qpdf", "QPDFObjectHandle array bounds");
- } else {
- typeWarning("array", "returning null");
- QTC::TC("qpdf", "QPDFObjectHandle array null for non-array");
}
- static auto constexpr msg =
- " -> null returned from invalid array access"sv;
- return QPDF_Null::create(obj, msg, "");
+ } else {
+ typeWarning("array", "returning null");
+ QTC::TC("qpdf", "QPDFObjectHandle array null for non-array");
}
+ static auto constexpr msg = " -> null returned from invalid array access"sv;
+ return QPDF_Null::create(obj, msg, "");
}
bool
QPDFObjectHandle::isRectangle()
{
- auto array = asArray();
- if ((array == nullptr) || (array->getNItems() != 4)) {
- return false;
- }
- for (int i = 0; i < 4; ++i) {
- if (!array->getItem(i).isNumber()) {
- return false;
+ if (auto array = asArray()) {
+ for (int i = 0; i < 4; ++i) {
+ if (auto item = array->at(i); !(item.obj && item.isNumber())) {
+ return false;
+ }
}
+ return array->size() == 4;
}
- return true;
+ return false;
}
bool
QPDFObjectHandle::isMatrix()
{
- auto array = asArray();
- if ((array == nullptr) || (array->getNItems() != 6)) {
- return false;
- }
- for (int i = 0; i < 6; ++i) {
- if (!array->getItem(i).isNumber()) {
- return false;
+ if (auto array = asArray()) {
+ for (int i = 0; i < 6; ++i) {
+ if (auto item = array->at(i); !(item.obj && item.isNumber())) {
+ return false;
+ }
}
+ return array->size() == 6;
}
- return true;
+ return false;
}
QPDFObjectHandle::Rectangle
QPDFObjectHandle::getArrayAsRectangle()
{
- Rectangle result;
- if (isRectangle()) {
- auto array = asArray();
- // Rectangle coordinates are always supposed to be llx, lly,
- // urx, ury, but files have been found in the wild where
- // llx > urx or lly > ury.
- double i0 = array->getItem(0).getNumericValue();
- double i1 = array->getItem(1).getNumericValue();
- double i2 = array->getItem(2).getNumericValue();
- double i3 = array->getItem(3).getNumericValue();
- result = Rectangle(
- std::min(i0, i2),
- std::min(i1, i3),
- std::max(i0, i2),
- std::max(i1, i3));
+ if (auto array = asArray()) {
+ if (array->size() != 4) {
+ return {};
+ }
+ double items[4];
+ for (int i = 0; i < 4; ++i) {
+ if (!array->at(i).getValueAsNumber(items[i])) {
+ return {};
+ }
+ }
+ return Rectangle(
+ std::min(items[0], items[2]),
+ std::min(items[1], items[3]),
+ std::max(items[0], items[2]),
+ std::max(items[1], items[3]));
}
- return result;
+ return {};
}
QPDFObjectHandle::Matrix
QPDFObjectHandle::getArrayAsMatrix()
{
- Matrix result;
- if (isMatrix()) {
- auto array = asArray();
- result = Matrix(
- array->getItem(0).getNumericValue(),
- array->getItem(1).getNumericValue(),
- array->getItem(2).getNumericValue(),
- array->getItem(3).getNumericValue(),
- array->getItem(4).getNumericValue(),
- array->getItem(5).getNumericValue());
+ if (auto array = asArray()) {
+ if (array->size() != 6) {
+ return {};
+ }
+ double items[6];
+ for (int i = 0; i < 6; ++i) {
+ if (!array->at(i).getValueAsNumber(items[i])) {
+ return {};
+ }
+ }
+ return Matrix(
+ items[0], items[1], items[2], items[3], items[4], items[5]);
}
- return result;
+ return {};
}
std::vector<QPDFObjectHandle>
QPDFObjectHandle::getArrayAsVector()
{
- std::vector<QPDFObjectHandle> result;
auto array = asArray();
if (array) {
- array->getAsVector(result);
+ return array->getAsVector();
} else {
typeWarning("array", "treating as empty");
QTC::TC("qpdf", "QPDFObjectHandle array treating as empty vector");
}
- return result;
+ return {};
}
// Array mutators
@@ -907,24 +902,20 @@ QPDFObjectHandle::getArrayAsVector()
void
QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const& item)
{
- auto array = asArray();
- if (array) {
- checkOwnership(item);
- array->setItem(n, item);
+ if (auto array = asArray()) {
+ if (!array->setAt(n, item)) {
+ objectWarning("ignoring attempt to set out of bounds array item");
+ QTC::TC("qpdf", "QPDFObjectHandle set array bounds");
+ }
} else {
typeWarning("array", "ignoring attempt to set item");
QTC::TC("qpdf", "QPDFObjectHandle array ignoring set item");
}
}
-
void
QPDFObjectHandle::setArrayFromVector(std::vector<QPDFObjectHandle> const& items)
{
- auto array = asArray();
- if (array) {
- for (auto const& item: items) {
- checkOwnership(item);
- }
+ if (auto array = asArray()) {
array->setFromVector(items);
} else {
typeWarning("array", "ignoring attempt to replace items");
@@ -935,9 +926,12 @@ QPDFObjectHandle::setArrayFromVector(std::vector<QPDFObjectHandle> const& items)
void
QPDFObjectHandle::insertItem(int at, QPDFObjectHandle const& item)
{
- auto array = asArray();
- if (array) {
- array->insertItem(at, item);
+ if (auto array = asArray()) {
+ if (!array->insert(at, item)) {
+ objectWarning(
+ "ignoring attempt to insert out of bounds array item");
+ QTC::TC("qpdf", "QPDFObjectHandle insert array bounds");
+ }
} else {
typeWarning("array", "ignoring attempt to insert item");
QTC::TC("qpdf", "QPDFObjectHandle array ignoring insert item");
@@ -954,10 +948,8 @@ QPDFObjectHandle::insertItemAndGetNew(int at, QPDFObjectHandle const& item)
void
QPDFObjectHandle::appendItem(QPDFObjectHandle const& item)
{
- auto array = asArray();
- if (array) {
- checkOwnership(item);
- array->appendItem(item);
+ if (auto array = asArray()) {
+ array->push_back(item);
} else {
typeWarning("array", "ignoring attempt to append item");
QTC::TC("qpdf", "QPDFObjectHandle array ignoring append item");
@@ -974,28 +966,23 @@ QPDFObjectHandle::appendItemAndGetNew(QPDFObjectHandle const& item)
void
QPDFObjectHandle::eraseItem(int at)
{
- auto array = asArray();
- if (array && (at < array->getNItems()) && (at >= 0)) {
- array->eraseItem(at);
- } else {
- if (array) {
+ if (auto array = asArray()) {
+ if (!array->erase(at)) {
objectWarning("ignoring attempt to erase out of bounds array item");
QTC::TC("qpdf", "QPDFObjectHandle erase array bounds");
- } else {
- typeWarning("array", "ignoring attempt to erase item");
- QTC::TC("qpdf", "QPDFObjectHandle array ignoring erase item");
}
+ } else {
+ typeWarning("array", "ignoring attempt to erase item");
+ QTC::TC("qpdf", "QPDFObjectHandle array ignoring erase item");
}
}
QPDFObjectHandle
QPDFObjectHandle::eraseItemAndGetOld(int at)
{
- auto result = QPDFObjectHandle::newNull();
auto array = asArray();
- if (array && (at < array->getNItems()) && (at >= 0)) {
- result = array->getItem(at);
- }
+ auto result =
+ (array && at < array->size() && at >= 0) ? array->at(at) : newNull();
eraseItem(at);
return result;
}
@@ -1515,11 +1502,10 @@ QPDFObjectHandle::arrayOrStreamToStreamArray(
{
all_description = description;
std::vector<QPDFObjectHandle> result;
- auto array = asArray();
- if (array) {
- int n_items = array->getNItems();
+ if (auto array = asArray()) {
+ int n_items = array->size();
for (int i = 0; i < n_items; ++i) {
- QPDFObjectHandle item = array->getItem(i);
+ QPDFObjectHandle item = array->at(i);
if (item.isStream()) {
result.push_back(item);
} else {
@@ -2217,9 +2203,9 @@ QPDFObjectHandle::makeDirect(
} else if (isArray()) {
std::vector<QPDFObjectHandle> items;
auto array = asArray();
- int n = array->getNItems();
+ int n = array->size();
for (int i = 0; i < n; ++i) {
- items.push_back(array->getItem(i));
+ items.push_back(array->at(i));
items.back().makeDirect(visited, stop_at_streams);
}
this->obj = QPDF_Array::create(items);
diff --git a/libqpdf/QPDFParser.cc b/libqpdf/QPDFParser.cc
index 09bf1601..4c43e487 100644
--- a/libqpdf/QPDFParser.cc
+++ b/libqpdf/QPDFParser.cc
@@ -27,16 +27,15 @@ namespace
struct StackFrame
{
StackFrame(std::shared_ptr<InputSource> input) :
- offset(input->tell()),
- contents_string(""),
- contents_offset(-1)
+ offset(input->tell())
{
}
std::vector<std::shared_ptr<QPDFObject>> olist;
qpdf_offset_t offset;
- std::string contents_string;
- qpdf_offset_t contents_offset;
+ std::string contents_string{""};
+ qpdf_offset_t contents_offset{-1};
+ int null_count{0};
};
} // namespace
@@ -50,6 +49,7 @@ QPDFParser::parse(bool& empty, bool content_stream)
// this, it will cause a logic error to be thrown from
// QPDF::inParse().
+ const static std::shared_ptr<QPDFObject> null_oh = QPDF_Null::create();
QPDF::ParseGuard pg(context);
empty = false;
@@ -67,7 +67,6 @@ QPDFParser::parse(bool& empty, bool content_stream)
int good_count = 0;
bool b_contents = false;
bool is_null = false;
- auto null_oh = QPDF_Null::create();
while (!done) {
bool bad = false;
@@ -156,6 +155,8 @@ QPDFParser::parse(bool& empty, bool content_stream)
case QPDFTokenizer::tt_null:
is_null = true;
+ ++frame.null_count;
+
break;
case QPDFTokenizer::tt_integer:
@@ -301,9 +302,11 @@ QPDFParser::parse(bool& empty, bool content_stream)
case st_dictionary:
case st_array:
- if (!indirect_ref && !is_null) {
- // No need to set description for direct nulls - they will
- // become implicit.
+ if (is_null) {
+ object = null_oh;
+ // No need to set description for direct nulls - they probably
+ // will become implicit.
+ } else if (!indirect_ref) {
setDescription(object, input->getLastOffset());
}
set_offset = true;
@@ -326,7 +329,8 @@ QPDFParser::parse(bool& empty, bool content_stream)
parser_state_e old_state = state_stack.back();
state_stack.pop_back();
if (old_state == st_array) {
- object = QPDF_Array::create(std::move(olist));
+ object = QPDF_Array::create(
+ std::move(olist), frame.null_count > 100);
setDescription(object, offset - 1);
// The `offset` points to the next of "[". Set the rewind
// offset to point to the beginning of "[". This has been
@@ -381,7 +385,7 @@ QPDFParser::parse(bool& empty, bool content_stream)
// Calculate value.
std::shared_ptr<QPDFObject> val;
if (iter != olist.end()) {
- val = *iter ? *iter : QPDF_Null::create();
+ val = *iter;
++iter;
} else {
QTC::TC("qpdf", "QPDFParser no val for last key");
diff --git a/libqpdf/QPDF_Array.cc b/libqpdf/QPDF_Array.cc
index de34103e..3bd139b1 100644
--- a/libqpdf/QPDF_Array.cc
+++ b/libqpdf/QPDF_Array.cc
@@ -1,66 +1,157 @@
#include <qpdf/QPDF_Array.hh>
-#include <qpdf/QIntC.hh>
+#include <qpdf/QPDFObjectHandle.hh>
#include <qpdf/QPDFObject_private.hh>
-#include <qpdf/QUtil.hh>
-#include <stdexcept>
-QPDF_Array::QPDF_Array(std::vector<QPDFObjectHandle> const& v) :
- QPDFValue(::ot_array, "array")
+static const QPDFObjectHandle null_oh = QPDFObjectHandle::newNull();
+
+inline void
+QPDF_Array::checkOwnership(QPDFObjectHandle const& item) const
{
- setFromVector(v);
+ if (auto obj = item.getObjectPtr()) {
+ if (qpdf) {
+ if (auto item_qpdf = obj->getQPDF()) {
+ if (qpdf != item_qpdf) {
+ throw std::logic_error(
+ "Attempting to add an object from a different QPDF. "
+ "Use QPDF::copyForeignObject to add objects from "
+ "another file.");
+ }
+ }
+ }
+ } else {
+ throw std::logic_error(
+ "Attempting to add an uninitialized object to a QPDF_Array.");
+ }
}
-QPDF_Array::QPDF_Array(std::vector<std::shared_ptr<QPDFObject>>&& v) :
+QPDF_Array::QPDF_Array() :
QPDFValue(::ot_array, "array")
{
- setFromVector(std::move(v));
}
-QPDF_Array::QPDF_Array(SparseOHArray const& items) :
+QPDF_Array::QPDF_Array(QPDF_Array const& other) :
QPDFValue(::ot_array, "array"),
- elements(items)
+ sparse(other.sparse),
+ sp_size(other.sp_size),
+ sp_elements(other.sp_elements),
+ elements(other.elements)
{
}
-std::shared_ptr<QPDFObject>
-QPDF_Array::create(std::vector<QPDFObjectHandle> const& items)
+QPDF_Array::QPDF_Array(std::vector<QPDFObjectHandle> const& v) :
+ QPDFValue(::ot_array, "array")
{
- return do_create(new QPDF_Array(items));
+ setFromVector(v);
}
-std::shared_ptr<QPDFObject>
-QPDF_Array::create(std::vector<std::shared_ptr<QPDFObject>>&& items)
+QPDF_Array::QPDF_Array(
+ std::vector<std::shared_ptr<QPDFObject>>&& v, bool sparse) :
+ QPDFValue(::ot_array, "array"),
+ sparse(sparse)
{
- return do_create(new QPDF_Array(std::move(items)));
+ if (sparse) {
+ for (auto&& item: v) {
+ if (item->getTypeCode() != ::ot_null ||
+ item->getObjGen().isIndirect()) {
+ sp_elements[sp_size] = std::move(item);
+ }
+ ++sp_size;
+ }
+ } else {
+ elements = std::move(v);
+ }
}
std::shared_ptr<QPDFObject>
-QPDF_Array::create(SparseOHArray const& items)
+QPDF_Array::create(std::vector<QPDFObjectHandle> const& items)
{
return do_create(new QPDF_Array(items));
}
std::shared_ptr<QPDFObject>
+QPDF_Array::create(
+ std::vector<std::shared_ptr<QPDFObject>>&& items, bool sparse)
+{
+ return do_create(new QPDF_Array(std::move(items), sparse));
+}
+
+std::shared_ptr<QPDFObject>
QPDF_Array::copy(bool shallow)
{
- return create(shallow ? elements : elements.copy());
+ if (shallow) {
+ return do_create(new QPDF_Array(*this));
+ } else {
+ if (sparse) {
+ QPDF_Array* result = new QPDF_Array();
+ result->sp_size = sp_size;
+ for (auto const& element: sp_elements) {
+ auto const& obj = element.second;
+ result->sp_elements[element.first] =
+ obj->getObjGen().isIndirect() ? obj : obj->copy();
+ }
+ return do_create(result);
+ } else {
+ std::vector<std::shared_ptr<QPDFObject>> result;
+ result.reserve(elements.size());
+ for (auto const& element: elements) {
+ result.push_back(
+ element
+ ? (element->getObjGen().isIndirect() ? element
+ : element->copy())
+ : element);
+ }
+ return create(std::move(result), false);
+ }
+ }
}
void
QPDF_Array::disconnect()
{
- elements.disconnect();
+ if (sparse) {
+ for (auto& item: sp_elements) {
+ auto& obj = item.second;
+ if (!obj->getObjGen().isIndirect()) {
+ obj->disconnect();
+ }
+ }
+ } else {
+ for (auto& obj: elements) {
+ if (!obj->getObjGen().isIndirect()) {
+ obj->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) {
+ int next = 0;
+ for (auto& item: sp_elements) {
+ int key = item.first;
+ for (int j = next; j < key; ++j) {
+ result += "null ";
+ }
+ item.second->resolve();
+ auto og = item.second->getObjGen();
+ result += og.isIndirect() ? og.unparse(' ') + " R "
+ : item.second->unparse() + " ";
+ next = ++key;
+ }
+ for (int j = next; j < sp_size; ++j) {
+ result += "null ";
+ }
+ } else {
+ for (auto const& item: elements) {
+ item->resolve();
+ auto og = item->getObjGen();
+ result += og.isIndirect() ? og.unparse(' ') + " R "
+ : item->unparse() + " ";
+ }
}
result += "]";
return result;
@@ -69,96 +160,157 @@ QPDF_Array::unparse()
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));
+ static const JSON j_null = JSON::makeNull();
+ JSON j_array = JSON::makeArray();
+ if (sparse) {
+ int next = 0;
+ for (auto& item: sp_elements) {
+ int key = item.first;
+ for (int j = next; j < key; ++j) {
+ j_array.addArrayElement(j_null);
+ }
+ auto og = item.second->getObjGen();
+ j_array.addArrayElement(
+ og.isIndirect() ? JSON::makeString(og.unparse(' ') + " R")
+ : item.second->getJSON(json_version));
+ next = ++key;
+ }
+ for (int j = next; j < sp_size; ++j) {
+ j_array.addArrayElement(j_null);
+ }
+ } else {
+ for (auto const& item: elements) {
+ auto og = item->getObjGen();
+ j_array.addArrayElement(
+ og.isIndirect() ? JSON::makeString(og.unparse(' ') + " R")
+ : item->getJSON(json_version));
+ }
}
- 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());
+ return j_array;
}
QPDFObjectHandle
-QPDF_Array::getItem(int n) const
+QPDF_Array::at(int n) const noexcept
{
- if ((n < 0) || (n >= QIntC::to_int(elements.size()))) {
- throw std::logic_error(
- "INTERNAL ERROR: bounds error accessing QPDF_Array element");
+ if (n < 0 || n >= size()) {
+ return {};
+ } else if (sparse) {
+ auto const& iter = sp_elements.find(n);
+ return iter == sp_elements.end() ? null_oh : (*iter).second;
+ } else {
+ return elements[size_t(n)];
}
- return this->elements.at(QIntC::to_size(n));
}
-void
-QPDF_Array::getAsVector(std::vector<QPDFObjectHandle>& v) const
+std::vector<QPDFObjectHandle>
+QPDF_Array::getAsVector() const
{
- size_t size = this->elements.size();
- for (size_t i = 0; i < size; ++i) {
- v.push_back(this->elements.at(i));
+ if (sparse) {
+ std::vector<QPDFObjectHandle> v;
+ v.reserve(size_t(size()));
+ for (auto const& item: sp_elements) {
+ v.resize(size_t(item.first), null_oh);
+ v.push_back(item.second);
+ }
+ v.resize(size_t(size()), null_oh);
+ return v;
+ } else {
+ return {elements.cbegin(), elements.cend()};
}
}
-void
-QPDF_Array::setItem(int n, QPDFObjectHandle const& oh)
+bool
+QPDF_Array::setAt(int at, QPDFObjectHandle const& oh)
{
- this->elements.setAt(QIntC::to_size(n), oh);
+ if (at < 0 || at >= size()) {
+ return false;
+ }
+ checkOwnership(oh);
+ if (sparse) {
+ sp_elements[at] = oh.getObj();
+ } else {
+ elements[size_t(at)] = oh.getObj();
+ }
+ return true;
}
void
QPDF_Array::setFromVector(std::vector<QPDFObjectHandle> const& v)
{
- this->elements = SparseOHArray();
- for (auto const& iter: v) {
- this->elements.append(iter);
+ elements.resize(0);
+ elements.reserve(v.size());
+ for (auto const& item: v) {
+ checkOwnership(item);
+ elements.push_back(item.getObj());
}
}
-void
-QPDF_Array::setFromVector(std::vector<std::shared_ptr<QPDFObject>>&& v)
+bool
+QPDF_Array::insert(int at, QPDFObjectHandle const& item)
{
- this->elements = SparseOHArray();
- for (auto&& item: v) {
- if (item) {
- this->elements.append(item);
+ int sz = size();
+ if (at < 0 || at > sz) {
+ // As special case, also allow insert beyond the end
+ return false;
+ } else if (at == sz) {
+ push_back(item);
+ } else {
+ checkOwnership(item);
+ if (sparse) {
+ auto iter = sp_elements.crbegin();
+ while (iter != sp_elements.crend()) {
+ auto key = (iter++)->first;
+ if (key >= at) {
+ auto nh = sp_elements.extract(key);
+ ++nh.key();
+ sp_elements.insert(std::move(nh));
+ } else {
+ break;
+ }
+ }
+ sp_elements[at] = item.getObj();
+ ++sp_size;
} else {
- ++this->elements.n_elements;
+ elements.insert(elements.cbegin() + at, item.getObj());
}
}
+ return true;
}
void
-QPDF_Array::insertItem(int at, QPDFObjectHandle const& item)
+QPDF_Array::push_back(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");
+ checkOwnership(item);
+ if (sparse) {
+ sp_elements[sp_size++] = item.getObj();
+ } else {
+ elements.push_back(item.getObj());
}
- this->elements.insert(QIntC::to_size(at), item);
}
-void
-QPDF_Array::appendItem(QPDFObjectHandle const& item)
+bool
+QPDF_Array::erase(int at)
{
- this->elements.append(item);
-}
-
-void
-QPDF_Array::eraseItem(int at)
-{
- this->elements.erase(QIntC::to_size(at));
-}
+ if (at < 0 || at >= size()) {
+ return false;
+ }
+ if (sparse) {
+ auto end = sp_elements.end();
+ if (auto iter = sp_elements.lower_bound(at); iter != end) {
+ if (iter->first == at) {
+ iter++;
+ sp_elements.erase(at);
+ }
-void
-QPDF_Array::addExplicitElementsToList(std::list<QPDFObjectHandle>& l) const
-{
- for (auto const& iter: this->elements) {
- l.push_back(iter.second);
+ while (iter != end) {
+ auto nh = sp_elements.extract(iter++);
+ --nh.key();
+ sp_elements.insert(std::move(nh));
+ }
+ }
+ --sp_size;
+ } else {
+ elements.erase(elements.cbegin() + at);
}
+ return true;
}
diff --git a/libqpdf/QPDF_Null.cc b/libqpdf/QPDF_Null.cc
index a82f23c0..0b59f5c9 100644
--- a/libqpdf/QPDF_Null.cc
+++ b/libqpdf/QPDF_Null.cc
@@ -50,5 +50,6 @@ QPDF_Null::unparse()
JSON
QPDF_Null::getJSON(int json_version)
{
+ // If this is updated, QPDF_Array::getJSON must also be updated.
return JSON::makeNull();
}
diff --git a/libqpdf/SparseOHArray.cc b/libqpdf/SparseOHArray.cc
deleted file mode 100644
index 5f64f50b..00000000
--- a/libqpdf/SparseOHArray.cc
+++ /dev/null
@@ -1,148 +0,0 @@
-#include <qpdf/SparseOHArray.hh>
-
-#include <qpdf/QPDFObjectHandle.hh>
-#include <qpdf/QPDFObject_private.hh>
-
-#include <stdexcept>
-
-SparseOHArray::SparseOHArray() :
- n_elements(0)
-{
-}
-
-size_t
-SparseOHArray::size() const
-{
- return this->n_elements;
-}
-
-void
-SparseOHArray::append(QPDFObjectHandle oh)
-{
- if (!oh.isDirectNull()) {
- this->elements[this->n_elements] = oh;
- }
- ++this->n_elements;
-}
-
-void
-SparseOHArray::append(std::shared_ptr<QPDFObject>&& obj)
-{
- if (obj->getTypeCode() != ::ot_null || !obj->getObjGen().isIndirect()) {
- this->elements[this->n_elements] = std::move(obj);
- }
- ++this->n_elements;
-}
-
-QPDFObjectHandle
-SparseOHArray::at(size_t idx) const
-{
- if (idx >= this->n_elements) {
- throw std::logic_error(
- "INTERNAL ERROR: bounds error accessing SparseOHArray element");
- }
- auto const& iter = this->elements.find(idx);
- if (iter == this->elements.end()) {
- return QPDFObjectHandle::newNull();
- } else {
- return (*iter).second;
- }
-}
-
-void
-SparseOHArray::remove_last()
-{
- if (this->n_elements == 0) {
- throw std::logic_error("INTERNAL ERROR: attempt to remove"
- " last item from empty SparseOHArray");
- }
- --this->n_elements;
- this->elements.erase(this->n_elements);
-}
-
-void
-SparseOHArray::disconnect()
-{
- for (auto& iter: this->elements) {
- QPDFObjectHandle::DisconnectAccess::disconnect(iter.second);
- }
-}
-
-void
-SparseOHArray::setAt(size_t idx, QPDFObjectHandle oh)
-{
- if (idx >= this->n_elements) {
- throw std::logic_error("bounds error setting item in SparseOHArray");
- }
- if (oh.isDirectNull()) {
- this->elements.erase(idx);
- } else {
- this->elements[idx] = oh;
- }
-}
-
-void
-SparseOHArray::erase(size_t idx)
-{
- if (idx >= this->n_elements) {
- throw std::logic_error("bounds error erasing item from SparseOHArray");
- }
- 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
-SparseOHArray::insert(size_t idx, QPDFObjectHandle oh)
-{
- if (idx > this->n_elements) {
- throw std::logic_error("bounds error inserting item to SparseOHArray");
- } 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;
- }
-}
-
-SparseOHArray
-SparseOHArray::copy()
-{
- SparseOHArray 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;
-}
-
-SparseOHArray::const_iterator
-SparseOHArray::begin() const
-{
- return this->elements.begin();
-}
-
-SparseOHArray::const_iterator
-SparseOHArray::end() const
-{
- return this->elements.end();
-}
diff --git a/libqpdf/qpdf/QPDF_Array.hh b/libqpdf/qpdf/QPDF_Array.hh
index 56c0101f..4762bb6e 100644
--- a/libqpdf/qpdf/QPDF_Array.hh
+++ b/libqpdf/qpdf/QPDF_Array.hh
@@ -3,8 +3,7 @@
#include <qpdf/QPDFValue.hh>
-#include <qpdf/SparseOHArray.hh>
-#include <list>
+#include <map>
#include <vector>
class QPDF_Array: public QPDFValue
@@ -14,34 +13,37 @@ class QPDF_Array: public QPDFValue
static std::shared_ptr<QPDFObject>
create(std::vector<QPDFObjectHandle> const& items);
static std::shared_ptr<QPDFObject>
- create(std::vector<std::shared_ptr<QPDFObject>>&& items);
- static std::shared_ptr<QPDFObject> create(SparseOHArray const& items);
+ create(std::vector<std::shared_ptr<QPDFObject>>&& items, bool sparse);
virtual std::shared_ptr<QPDFObject> copy(bool shallow = false);
virtual std::string unparse();
virtual JSON getJSON(int json_version);
virtual void disconnect();
- int getNItems() const;
- QPDFObjectHandle getItem(int n) const;
- void getAsVector(std::vector<QPDFObjectHandle>&) const;
-
- void setItem(int, QPDFObjectHandle const&);
+ int
+ size() const noexcept
+ {
+ return sparse ? sp_size : int(elements.size());
+ }
+ QPDFObjectHandle at(int n) const noexcept;
+ bool setAt(int n, QPDFObjectHandle const& oh);
+ std::vector<QPDFObjectHandle> getAsVector() const;
void setFromVector(std::vector<QPDFObjectHandle> const& items);
- void setFromVector(std::vector<std::shared_ptr<QPDFObject>>&& items);
- void insertItem(int at, QPDFObjectHandle const& item);
- void appendItem(QPDFObjectHandle const& item);
- void eraseItem(int at);
-
- // Helper methods for QPDF and QPDFObjectHandle -- these are
- // public methods since the whole class is not part of the public
- // API. Otherwise, these would be wrapped in accessor classes.
- void addExplicitElementsToList(std::list<QPDFObjectHandle>&) const;
+ bool insert(int at, QPDFObjectHandle const& item);
+ void push_back(QPDFObjectHandle const& item);
+ bool erase(int at);
private:
+ QPDF_Array();
+ QPDF_Array(QPDF_Array const&);
QPDF_Array(std::vector<QPDFObjectHandle> const& items);
- QPDF_Array(std::vector<std::shared_ptr<QPDFObject>>&& items);
- QPDF_Array(SparseOHArray const& items);
- SparseOHArray elements;
+ QPDF_Array(std::vector<std::shared_ptr<QPDFObject>>&& items, bool sparse);
+
+ void checkOwnership(QPDFObjectHandle const& item) const;
+
+ bool sparse{false};
+ int sp_size{0};
+ std::map<int, std::shared_ptr<QPDFObject>> sp_elements;
+ std::vector<std::shared_ptr<QPDFObject>> elements;
};
#endif // QPDF_ARRAY_HH
diff --git a/libqpdf/qpdf/SparseOHArray.hh b/libqpdf/qpdf/SparseOHArray.hh
deleted file mode 100644
index 26ae3dc0..00000000
--- a/libqpdf/qpdf/SparseOHArray.hh
+++ /dev/null
@@ -1,35 +0,0 @@
-#ifndef QPDF_SPARSEOHARRAY_HH
-#define QPDF_SPARSEOHARRAY_HH
-
-#include <qpdf/QPDFObjectHandle.hh>
-#include <unordered_map>
-
-class QPDF_Array;
-
-class SparseOHArray
-{
- public:
- SparseOHArray();
- size_t size() const;
- void append(QPDFObjectHandle oh);
- void append(std::shared_ptr<QPDFObject>&& 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);
- SparseOHArray copy();
- void disconnect();
-
- typedef std::unordered_map<size_t, QPDFObjectHandle>::const_iterator
- const_iterator;
- const_iterator begin() const;
- const_iterator end() const;
-
- private:
- friend class QPDF_Array;
- std::unordered_map<size_t, QPDFObjectHandle> elements;
- size_t n_elements;
-};
-
-#endif // QPDF_SPARSEOHARRAY_HH
diff --git a/libtests/sparse_array.cc b/libtests/sparse_array.cc
index 62410399..9f31c96f 100644
--- a/libtests/sparse_array.cc
+++ b/libtests/sparse_array.cc
@@ -1,19 +1,23 @@
#include <qpdf/assert_test.h>
-#include <qpdf/SparseOHArray.hh>
+#include <qpdf/QPDFObjectHandle.hh>
+#include <qpdf/QPDFObject_private.hh>
+#include <qpdf/QPDF_Array.hh>
#include <iostream>
int
main()
{
- SparseOHArray a;
+ auto obj = QPDF_Array::create({}, true);
+ QPDF_Array& a = *obj->as<QPDF_Array>();
+
assert(a.size() == 0);
- a.append(QPDFObjectHandle::parse("1"));
- a.append(QPDFObjectHandle::parse("(potato)"));
- a.append(QPDFObjectHandle::parse("null"));
- a.append(QPDFObjectHandle::parse("null"));
- a.append(QPDFObjectHandle::parse("/Quack"));
+ a.push_back(QPDFObjectHandle::parse("1"));
+ a.push_back(QPDFObjectHandle::parse("(potato)"));
+ a.push_back(QPDFObjectHandle::parse("null"));
+ a.push_back(QPDFObjectHandle::parse("null"));
+ a.push_back(QPDFObjectHandle::parse("/Quack"));
assert(a.size() == 5);
assert(a.at(0).isInteger() && (a.at(0).getIntValue() == 1));
assert(a.at(1).isString() && (a.at(1).getStringValue() == "potato"));
@@ -60,20 +64,20 @@ main()
a.setAt(4, QPDFObjectHandle::newNull());
assert(a.at(4).isNull());
- a.remove_last();
+ a.erase(a.size() - 1);
assert(a.size() == 5);
assert(a.at(0).isName() && (a.at(0).getName() == "/First"));
assert(a.at(1).isInteger() && (a.at(1).getIntValue() == 1));
assert(a.at(3).isName() && (a.at(3).getName() == "/Third"));
assert(a.at(4).isNull());
- a.remove_last();
+ a.erase(a.size() - 1);
assert(a.size() == 4);
assert(a.at(0).isName() && (a.at(0).getName() == "/First"));
assert(a.at(1).isInteger() && (a.at(1).getIntValue() == 1));
assert(a.at(3).isName() && (a.at(3).getName() == "/Third"));
- a.remove_last();
+ a.erase(a.size() - 1);
assert(a.size() == 3);
assert(a.at(0).isName() && (a.at(0).getName() == "/First"));
assert(a.at(1).isInteger() && (a.at(1).getIntValue() == 1));
diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov
index cad67565..014ea571 100644
--- a/qpdf/qpdf.testcov
+++ b/qpdf/qpdf.testcov
@@ -303,8 +303,10 @@ QPDFObjectHandle array treating as empty 0
QPDFObjectHandle array null for non-array 0
QPDFObjectHandle array treating as empty vector 0
QPDFObjectHandle array ignoring set item 0
+QPDFObjectHandle set array bounds 0
QPDFObjectHandle array ignoring replace items 0
QPDFObjectHandle array ignoring insert item 0
+QPDFObjectHandle insert array bounds 0
QPDFObjectHandle array ignoring append item 0
QPDFObjectHandle array ignoring erase item 0
QPDFObjectHandle dictionary false for hasKey 0
diff --git a/qpdf/qtest/qpdf/object-types-os.out b/qpdf/qtest/qpdf/object-types-os.out
index a95fa4d8..4b02d156 100644
--- a/qpdf/qtest/qpdf/object-types-os.out
+++ b/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
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
WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: ignoring attempt to erase out of bounds array item
WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: ignoring attempt to erase out of bounds array item
+WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: ignoring attempt to insert out of bounds array item
+WARNING: object-types-os.pdf object stream 1, object 7 0 at offset 384: ignoring attempt to set out of bounds array item
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
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
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
diff --git a/qpdf/qtest/qpdf/object-types.out b/qpdf/qtest/qpdf/object-types.out
index 718105db..b7089b6b 100644
--- a/qpdf/qtest/qpdf/object-types.out
+++ b/qpdf/qtest/qpdf/object-types.out
@@ -5,6 +5,8 @@ WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempt
WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to append item
WARNING: object-types.pdf, object 8 0 at offset 717: ignoring attempt to erase out of bounds array item
WARNING: object-types.pdf, object 8 0 at offset 717: ignoring attempt to erase out of bounds array item
+WARNING: object-types.pdf, object 8 0 at offset 717: ignoring attempt to insert out of bounds array item
+WARNING: object-types.pdf, object 8 0 at offset 717: ignoring attempt to set out of bounds array item
WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to erase item
WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to insert item
WARNING: object-types.pdf, object 8 0 at offset 669: operation for array attempted on object of type integer: ignoring attempt to replace items
diff --git a/qpdf/qtest/qpdf/test88.out b/qpdf/qtest/qpdf/test88.out
index 327212b6..9d5cf504 100644
--- a/qpdf/qtest/qpdf/test88.out
+++ b/qpdf/qtest/qpdf/test88.out
@@ -1,2 +1,3 @@
WARNING: test array: ignoring attempt to erase out of bounds array item
+WARNING: minimal.pdf, object 1 0 at offset 19: operation for array attempted on object of type dictionary: ignoring attempt to erase item
test 88 done
diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc
index 5bae8b54..4a5d0ae8 100644
--- a/qpdf/test_driver.cc
+++ b/qpdf/test_driver.cc
@@ -1506,6 +1506,8 @@ test_42(QPDF& pdf, char const* arg2)
integer.appendItem(null);
array.eraseItem(-1);
array.eraseItem(16059);
+ array.insertItem(42, "/Dontpanic"_qpdf);
+ array.setArrayItem(42, "/Dontpanic"_qpdf);
integer.eraseItem(0);
integer.insertItem(0, null);
integer.setArrayFromVector(std::vector<QPDFObjectHandle>());
@@ -3282,6 +3284,7 @@ test_88(QPDF& pdf, char const* arg2)
auto arr2 = pdf.getRoot().replaceKeyAndGetNew("/QTest", "[1 2]"_qpdf);
arr2.setObjectDescription(&pdf, "test array");
assert(arr2.eraseItemAndGetOld(50).isNull());
+ assert(pdf.getRoot().eraseItemAndGetOld(0).isNull());
}
static void