diff --git a/include/qpdf/ObjectHandle.hh b/include/qpdf/ObjectHandle.hh index 296da03..a69f5ae 100644 --- a/include/qpdf/ObjectHandle.hh +++ b/include/qpdf/ObjectHandle.hh @@ -74,7 +74,6 @@ namespace qpdf } QPDFObjectHandle operator[](size_t n) const; - QPDFObjectHandle operator[](int n) const; std::shared_ptr copy(bool shallow = false) const; @@ -104,11 +103,18 @@ namespace qpdf BaseHandle& operator=(BaseHandle const&) = default; BaseHandle(BaseHandle&&) = default; BaseHandle& operator=(BaseHandle&&) = default; + + inline BaseHandle(QPDFObjectHandle const& oh); + inline BaseHandle(QPDFObjectHandle&& oh); + ~BaseHandle() = default; template T* as() const; + inline void assign(qpdf_object_type_e required, BaseHandle const& other); + inline void assign(qpdf_object_type_e required, BaseHandle&& other); + std::string description() const; std::runtime_error type_error(char const* expected_type) const; QPDFExc type_error(char const* expected_type, std::string const& message) const; diff --git a/libqpdf/NNTree.cc b/libqpdf/NNTree.cc index 308cdb1..9506103 100644 --- a/libqpdf/NNTree.cc +++ b/libqpdf/NNTree.cc @@ -11,6 +11,8 @@ #include #include +using namespace qpdf; + static std::string get_description(QPDFObjectHandle const& node) { @@ -66,7 +68,7 @@ NNTreeIterator::updateIValue(bool allow_invalid) ivalue.second = QPDFObjectHandle(); return; } - auto items = node.getKey(impl.details.itemsKey()); + Array items = node.getKey(impl.details.itemsKey()); if (!std::cmp_less(item_number + 1, items.size())) { impl.error(node, "update ivalue: items array is too short"); } @@ -85,7 +87,7 @@ NNTreeIterator::getNextKid(PathElement& pe, bool backward) { while (true) { pe.kid_number += backward ? -1 : 1; - auto kids = pe.node.getKey("/Kids"); + Array kids = pe.node.getKey("/Kids"); if (pe.kid_number >= 0 && std::cmp_less(pe.kid_number, kids.size())) { auto result = kids[pe.kid_number]; if (result.isDictionary() && @@ -117,7 +119,7 @@ NNTreeIterator::increment(bool backward) while (valid()) { item_number += backward ? -2 : 2; - auto items = node.getKey(impl.details.itemsKey()); + Array items = node.getKey(impl.details.itemsKey()); if (item_number < 0 || std::cmp_greater_equal(item_number, items.size())) { bool found = false; setItemNumber(QPDFObjectHandle(), -1); @@ -152,9 +154,9 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list::ite a_node.removeKey("/Limits"); break; } - auto kids = a_node.getKey("/Kids"); - size_t nkids = kids.isArray() ? kids.size() : 0; - auto items = a_node.getKey(impl.details.itemsKey()); + Array kids = a_node.getKey("/Kids"); + size_t nkids = kids.size(); + Array items = a_node.getKey(impl.details.itemsKey()); size_t nitems = items.size(); bool changed = true; @@ -167,8 +169,8 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list::ite auto first_kid = kids[0]; auto last_kid = kids[nkids - 1u]; if (first_kid.isDictionary() && last_kid.isDictionary()) { - auto first_limits = first_kid.getKey("/Limits"); - auto last_limits = last_kid.getKey("/Limits"); + Array first_limits = first_kid.getKey("/Limits"); + Array last_limits = last_kid.getKey("/Limits"); if (first_limits.size() >= 2 && last_limits.size() >= 2) { first = first_limits[0]; last = last_limits[1]; @@ -176,16 +178,14 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list::ite } } if (first && last) { - auto limits = QPDFObjectHandle::newArray(); - limits.appendItem(first); - limits.appendItem(last); - auto olimits = a_node.getKey("/Limits"); + Array limits({first, last}); + Array olimits = a_node.getKey("/Limits"); if (olimits.size() == 2) { auto ofirst = olimits[0]; auto olast = olimits[1]; if (impl.details.keyValid(ofirst) && impl.details.keyValid(olast) && - (impl.details.compareKeys(first, ofirst) == 0) && - (impl.details.compareKeys(last, olast) == 0)) { + impl.details.compareKeys(first, ofirst) == 0 && + impl.details.compareKeys(last, olast) == 0) { changed = false; } } @@ -240,24 +240,23 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list::iterato } // Find the array we actually need to split, which is either this node's kids or items. - auto kids = to_split.getKey("/Kids"); - int nkids = kids.isArray() ? static_cast(kids.size()) : 0; - auto items = to_split.getKey(impl.details.itemsKey()); - int nitems = items.isArray() ? static_cast(items.size()) : 0; + Array kids = to_split.getKey("/Kids"); + size_t nkids = kids.size(); + Array items = to_split.getKey(impl.details.itemsKey()); + size_t nitems = items.size(); - QPDFObjectHandle first_half; - int n = 0; + Array first_half; + size_t n = 0; std::string key; - int threshold = 0; + size_t threshold = static_cast(impl.split_threshold); if (nkids > 0) { first_half = kids; n = nkids; - threshold = impl.split_threshold; key = "/Kids"; } else if (nitems > 0) { first_half = items; n = nitems; - threshold = 2 * impl.split_threshold; + threshold *= 2; key = impl.details.itemsKey(); } else { throw std::logic_error("NNTreeIterator::split called on invalid node"); @@ -289,8 +288,8 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list::iterato auto first_node = impl.qpdf.makeIndirectObject(QPDFObjectHandle::newDictionary()); first_node.replaceKey(key, first_half); - QPDFObjectHandle new_kids = QPDFObjectHandle::newArray(); - new_kids.appendItem(first_node); + Array new_kids; + new_kids.push_back(first_node); to_split.removeKey("/Limits"); // already shouldn't be there for root to_split.removeKey(impl.details.itemsKey()); to_split.replaceKey("/Kids", new_kids); @@ -310,11 +309,11 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list::iterato // Create a second half array, and transfer the second half of the items into the second half // array. - QPDFObjectHandle second_half = QPDFObjectHandle::newArray(); - int start_idx = ((n / 2) & ~1); + Array second_half; + auto start_idx = static_cast((n / 2) & ~1u); while (std::cmp_greater(first_half.size(), start_idx)) { - second_half.appendItem(first_half[start_idx]); - first_half.eraseItem(start_idx); + second_half.push_back(first_half[start_idx]); + first_half.erase(start_idx); } resetLimits(to_split, parent); @@ -331,8 +330,8 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list::iterato // kid_number to traverse through it. We need to update to_split's path element, or the node if // this is a leaf, so that the kid/item number points to the right place. - auto parent_kids = parent->node.getKey("/Kids"); - parent_kids.insertItem(parent->kid_number + 1, second_node); + Array parent_kids = parent->node.getKey("/Kids"); + parent_kids.insert(parent->kid_number + 1, second_node); auto cur_elem = parent; ++cur_elem; // points to end() for leaf nodes int old_idx = (is_leaf ? item_number : cur_elem->kid_number); @@ -367,22 +366,21 @@ void NNTreeIterator::insertAfter(QPDFObjectHandle const& key, QPDFObjectHandle const& value) { if (!valid()) { - QTC::TC("qpdf", "NNTree insertAfter inserts first"); impl.insertFirst(key, value); deepen(impl.oh, true, false); return; } - auto items = node.getKey(impl.details.itemsKey()); + Array items = node.getKey(impl.details.itemsKey()); + if (!items) { + impl.error(node, "node contains no items array"); + } if (std::cmp_less(items.size(), item_number + 2)) { - if (!items.isArray()) { - impl.error(node, "node contains no items array"); - } impl.error(node, "insert: items array is too short"); } - items.insertItem(item_number + 2, key); - items.insertItem(item_number + 3, value); + items.insert(item_number + 2, key); + items.insert(item_number + 3, value); resetLimits(node, lastPathElement()); split(node, lastPathElement()); increment(false); @@ -396,14 +394,14 @@ NNTreeIterator::remove() if (!valid()) { throw std::logic_error("attempt made to remove an invalid iterator"); } - auto items = node.getKey(impl.details.itemsKey()); + Array items = node.getKey(impl.details.itemsKey()); int nitems = static_cast(items.size()); if (std::cmp_greater(item_number + 2, nitems)) { impl.error(node, "found short items array while removing an item"); } - items.eraseItem(item_number); - items.eraseItem(item_number); + items.erase(item_number); + items.erase(item_number); nitems -= 2; if (nitems > 0) { @@ -443,8 +441,8 @@ NNTreeIterator::remove() auto element = lastPathElement(); auto parent = element; --parent; - auto kids = element->node.getKey("/Kids"); - kids.eraseItem(element->kid_number); + Array kids = element->node.getKey("/Kids"); + kids.erase(element->kid_number); auto nkids = kids.size(); if (nkids > 0) { // The logic here is similar to the items case. @@ -462,7 +460,7 @@ NNTreeIterator::remove() } } else { // Next kid is in deleted kid's position - deepen(kids.getArrayItem(element->kid_number), true, true); + deepen(kids.get(element->kid_number), true, true); } return; } @@ -470,7 +468,7 @@ NNTreeIterator::remove() if (parent == path.end()) { // We erased the very last item. Convert the root to an empty items array. element->node.removeKey("/Kids"); - element->node.replaceKey(impl.details.itemsKey(), QPDFObjectHandle::newArray()); + element->node.replaceKey(impl.details.itemsKey(), Array()); path.clear(); setItemNumber(impl.oh, -1); return; @@ -527,10 +525,7 @@ NNTreeIterator::operator==(NNTreeIterator const& other) const ++tpi; ++opi; } - if (item_number != other.item_number) { - return false; - } - return true; + return item_number == other.item_number; } void @@ -574,27 +569,30 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) return fail(a_node, "non-dictionary node while traversing name/number tree"); } - auto items = a_node.getKey(impl.details.itemsKey()); + Array items = a_node.getKey(impl.details.itemsKey()); int nitems = static_cast(items.size()); if (nitems > 1) { setItemNumber(a_node, first ? 0 : nitems - 2); break; } - auto kids = a_node.getKey("/Kids"); - int nkids = kids.isArray() ? static_cast(kids.size()) : 0; + Array kids = a_node.getKey("/Kids"); + int nkids = static_cast(kids.size()); if (nkids > 0) { int kid_number = first ? 0 : nkids - 1; addPathElement(a_node, kid_number); auto next = kids[kid_number]; - if (!next.isIndirect()) { + if (!next) { + return fail(a_node, "kid number " + std::to_string(kid_number) + " is invalid"); + } + if (!next.indirect()) { if (impl.auto_repair) { impl.warn( a_node, "converting kid number " + std::to_string(kid_number) + " to an indirect object"); next = impl.qpdf.makeIndirectObject(next); - kids.setArrayItem(kid_number, next); + kids.set(kid_number, next); } else { impl.warn( a_node, @@ -602,7 +600,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) } } a_node = next; - } else if (allow_empty && items.isArray()) { + } else if (allow_empty && items) { setItemNumber(a_node, -1); break; } else { @@ -655,8 +653,8 @@ NNTreeImpl::last() int NNTreeImpl::withinLimits(QPDFObjectHandle const& key, QPDFObjectHandle const& node) { - auto limits = node.getKey("/Limits"); - if (!(limits.size() >= 2 && details.keyValid(limits[0]) && details.keyValid(limits[1]))) { + Array limits = node.getKey("/Limits"); + if (!(details.keyValid(limits[0]) && details.keyValid(limits[1]))) { error(node, "node is missing /Limits"); } if (details.compareKeys(key, limits[0]) < 0) { @@ -734,7 +732,7 @@ void NNTreeImpl::repair() { auto new_node = QPDFObjectHandle::newDictionary(); - new_node.replaceKey(details.itemsKey(), QPDFObjectHandle::newArray()); + new_node.replaceKey(details.itemsKey(), Array()); NNTreeImpl repl(details, qpdf, new_node, false); for (auto const& i: *this) { repl.insert(i.first, i.second); @@ -750,7 +748,6 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found) return findInternal(key, return_prev_if_not_found); } catch (QPDFExc& e) { if (auto_repair) { - QTC::TC("qpdf", "NNTree repair"); warn(oh, std::string("attempting to repair after error: ") + e.what()); repair(); return findInternal(key, return_prev_if_not_found); @@ -784,7 +781,7 @@ NNTreeImpl::findInternal(QPDFObjectHandle const& key, bool return_prev_if_not_fo error(node, "loop detected in find"); } - auto items = node.getKey(details.itemsKey()); + Array items = node.getKey(details.itemsKey()); size_t nitems = items.size(); if (nitems > 1) { int idx = binarySearch( @@ -795,8 +792,8 @@ NNTreeImpl::findInternal(QPDFObjectHandle const& key, bool return_prev_if_not_fo return result; } - auto kids = node.getKey("/Kids"); - size_t nkids = kids.isArray() ? kids.size() : 0; + Array kids = node.getKey("/Kids"); + size_t nkids = kids.size(); if (nkids > 0) { int idx = binarySearch(key, kids, nkids, true, &NNTreeImpl::compareKeyKid); if (idx == -1) { @@ -814,15 +811,15 @@ NNTreeImpl::iterator NNTreeImpl::insertFirst(QPDFObjectHandle const& key, QPDFObjectHandle const& value) { auto iter = begin(); - QPDFObjectHandle items; + Array items(nullptr); if (iter.node.isDictionary()) { items = iter.node.getKey(details.itemsKey()); } - if (!items.isArray()) { + if (!items) { error(oh, "unable to find a valid items node"); } - items.insertItem(0, key); - items.insertItem(1, value); + items.insert(0, key); + items.insert(1, value); iter.setItemNumber(iter.node, 0); iter.resetLimits(iter.node, iter.lastPathElement()); iter.split(iter.node, iter.lastPathElement()); @@ -836,8 +833,8 @@ NNTreeImpl::insert(QPDFObjectHandle const& key, QPDFObjectHandle const& value) if (!iter.valid()) { return insertFirst(key, value); } else if (details.compareKeys(key, iter->first) == 0) { - auto items = iter.node.getKey(details.itemsKey()); - items.setArrayItem(iter.item_number + 1, value); + Array items = iter.node.getKey(details.itemsKey()); + items.set(iter.item_number + 1, value); iter.updateIValue(); } else { iter.insertAfter(key, value); diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index 5e70ece..af62923 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -486,7 +486,7 @@ BaseHandle::write_json(int json_version, JSON::Writer& p) const } else { for (auto const& item: a.elements) { p.writeNext(); - auto item_og = item.getObj()->getObjGen(); + auto item_og = item.id_gen(); if (item_og.isIndirect()) { p << "\"" << item_og.unparse(' ') << " R\""; } else { @@ -1234,11 +1234,10 @@ QPDFObjectHandle::arrayOrStreamToStreamArray( if (auto array = as_array(strict)) { int n_items = static_cast(array.size()); for (int i = 0; i < n_items; ++i) { - QPDFObjectHandle item = array.at(i).second; + QPDFObjectHandle item = array[i]; if (item.isStream()) { result.emplace_back(item); } else { - QTC::TC("qpdf", "QPDFObjectHandle non-stream in stream array"); item.warn( {qpdf_e_damaged_pdf, "", diff --git a/libqpdf/QPDF_Array.cc b/libqpdf/QPDF_Array.cc index a88e0a9..2e9ba73 100644 --- a/libqpdf/QPDF_Array.cc +++ b/libqpdf/QPDF_Array.cc @@ -2,6 +2,7 @@ #include +#include #include using namespace std::literals; @@ -60,6 +61,21 @@ Array::array() const return nullptr; // unreachable } +Array::Array(bool empty) : + BaseHandle(empty ? QPDFObject::create() : nullptr) +{ +} + +Array::Array(std::vector const& items) : + BaseHandle(QPDFObject::create(items)) +{ +} + +Array::Array(std::vector&& items) : + BaseHandle(QPDFObject::create(std::move(items))) +{ +} + Array::iterator Array::begin() { @@ -159,22 +175,58 @@ Array::null() const size_t Array::size() const { - auto a = array(); - return a->sp ? a->sp->size : a->elements.size(); + if (auto a = as()) { + return a->sp ? a->sp->size : a->elements.size(); + } + return 0; } -std::pair -Array::at(int n) const +QPDFObjectHandle const& +Array::operator[](size_t n) const { - auto a = array(); - if (n < 0 || std::cmp_greater_equal(n, size())) { - return {false, {}}; + static const QPDFObjectHandle null_obj; + auto a = as(); + if (!a) { + return null_obj; + } + if (a->sp) { + auto const& iter = a->sp->elements.find(n); + return iter == a->sp->elements.end() ? null_obj : iter->second; } + return n >= a->elements.size() ? null_obj : a->elements[n]; +} + +QPDFObjectHandle const& +Array::operator[](int n) const +{ + static const QPDFObjectHandle null_obj; + if (n < 0) { + return null_obj; + } + return (*this)[static_cast(n)]; +} + +QPDFObjectHandle +Array::get(size_t n) const +{ + if (n >= size()) { + return {}; + } + auto a = array(); if (!a->sp) { - return {true, a->elements[to_s(n)]}; + return a->elements[n]; } - auto const& iter = a->sp->elements.find(to_s(n)); - return {true, iter == a->sp->elements.end() ? null() : iter->second}; + auto const& iter = a->sp->elements.find(n); + return iter == a->sp->elements.end() ? null() : iter->second; +} + +QPDFObjectHandle +Array::get(int n) const +{ + if (n < 0) { + return {}; + } + return get(to_s(n)); } std::vector @@ -196,21 +248,30 @@ Array::getAsVector() const } bool -Array::setAt(int at, QPDFObjectHandle const& oh) +Array::set(size_t at, QPDFObjectHandle const& oh) { - if (at < 0 || std::cmp_greater_equal(at, size())) { + if (at >= size()) { return false; } auto a = array(); checkOwnership(oh); if (a->sp) { - a->sp->elements[to_s(at)] = oh; + a->sp->elements[at] = oh; } else { - a->elements[to_s(at)] = oh; + a->elements[at] = oh; } return true; } +bool +Array::set(int at, QPDFObjectHandle const& oh) +{ + if (at < 0) { + return false; + } + return set(to_s(at), oh); +} + void Array::setFromVector(std::vector const& v) { @@ -224,43 +285,48 @@ Array::setFromVector(std::vector const& v) } bool -Array::insert(int at_i, QPDFObjectHandle const& item) +Array::insert(size_t at, QPDFObjectHandle const& item) { auto a = array(); size_t sz = size(); - if (at_i < 0) { - return false; - } - size_t at = to_s(at_i); if (at > sz) { return false; } + checkOwnership(item); if (at == sz) { // As special case, also allow insert beyond the end push_back(item); return true; } - checkOwnership(item); - if (a->sp) { - auto iter = a->sp->elements.crbegin(); - while (iter != a->sp->elements.crend()) { - auto key = (iter++)->first; - if (key >= at) { - auto nh = a->sp->elements.extract(key); - ++nh.key(); - a->sp->elements.insert(std::move(nh)); - } else { - break; - } + if (!a->sp) { + a->elements.insert(a->elements.cbegin() + to_i(at), item); + return true; + } + auto iter = a->sp->elements.crbegin(); + while (iter != a->sp->elements.crend()) { + auto key = (iter++)->first; + if (key >= at) { + auto nh = a->sp->elements.extract(key); + ++nh.key(); + a->sp->elements.insert(std::move(nh)); + } else { + break; } - a->sp->elements[at] = item.getObj(); - ++a->sp->size; - } else { - a->elements.insert(a->elements.cbegin() + at_i, item.getObj()); } + a->sp->elements[at] = item; + ++a->sp->size; return true; } +bool +Array::insert(int at_i, QPDFObjectHandle const& item) +{ + if (at_i < 0) { + return false; + } + return insert(to_s(at_i), item); +} + void Array::push_back(QPDFObjectHandle const& item) { @@ -274,61 +340,68 @@ Array::push_back(QPDFObjectHandle const& item) } bool -Array::erase(int at_i) +Array::erase(size_t at) { auto a = array(); - if (at_i < 0) { - return false; - } - size_t at = to_s(at_i); if (at >= size()) { return false; } - if (a->sp) { - auto end = a->sp->elements.end(); - if (auto iter = a->sp->elements.lower_bound(at); iter != end) { - if (iter->first == at) { - iter++; - a->sp->elements.erase(at); - } + if (!a->sp) { + a->elements.erase(a->elements.cbegin() + to_i(at)); + return true; + } + auto end = a->sp->elements.end(); + if (auto iter = a->sp->elements.lower_bound(at); iter != end) { + if (iter->first == at) { + iter++; + a->sp->elements.erase(at); + } - while (iter != end) { - auto nh = a->sp->elements.extract(iter++); - --nh.key(); - a->sp->elements.insert(std::move(nh)); - } + while (iter != end) { + auto nh = a->sp->elements.extract(iter++); + --nh.key(); + a->sp->elements.insert(std::move(nh)); } - --(a->sp->size); - } else { - a->elements.erase(a->elements.cbegin() + at_i); } + --(a->sp->size); return true; } +bool +Array::erase(int at_i) +{ + if (at_i < 0) { + return false; + } + return erase(to_s(at_i)); +} + int QPDFObjectHandle::getArrayNItems() const { - if (auto array = as_array(strict)) { - return to_i(array.size()); + auto s = size(); + if (s > 1 || isArray()) { + return to_i(s); } typeWarning("array", "treating as empty"); - QTC::TC("qpdf", "QPDFObjectHandle array treating as empty"); return 0; } QPDFObjectHandle QPDFObjectHandle::getArrayItem(int n) const { - if (auto array = as_array(strict)) { - if (auto const [success, oh] = array.at(n); success) { - return oh; - } else { - objectWarning("returning null for out of bounds array access"); - QTC::TC("qpdf", "QPDFObjectHandle array bounds"); + if (auto array = Array(*this)) { + if (auto result = array[n]) { + return result; } + if (n >= 0 && std::cmp_less(n, array.size())) { + // sparse array null + return newNull(); + } + objectWarning("returning null for out of bounds array access"); + } 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, ""); @@ -337,69 +410,61 @@ QPDFObjectHandle::getArrayItem(int n) const bool QPDFObjectHandle::isRectangle() const { - if (auto array = as_array(strict)) { - for (int i = 0; i < 4; ++i) { - if (auto item = array.at(i).second; !item.isNumber()) { - return false; - } + Array array(*this); + for (auto const& oh: array) { + if (!oh.isNumber()) { + return false; } - return array.size() == 4; } - return false; + return array.size() == 4; } bool QPDFObjectHandle::isMatrix() const { - if (auto array = as_array(strict)) { - for (int i = 0; i < 6; ++i) { - if (auto item = array.at(i).second; !item.isNumber()) { - return false; - } + Array array(*this); + for (auto const& oh: array) { + if (!oh.isNumber()) { + return false; } - return array.size() == 6; } - return false; + return array.size() == 6; } QPDFObjectHandle::Rectangle QPDFObjectHandle::getArrayAsRectangle() const { - if (auto array = as_array(strict)) { - if (array.size() != 4) { + Array array(*this); + if (array.size() != 4) { + return {}; + } + std::array items; + for (size_t i = 0; i < 4; ++i) { + if (!array[i].getValueAsNumber(items[i])) { return {}; } - double items[4]; - for (int i = 0; i < 4; ++i) { - if (auto item = array.at(i).second; !item.getValueAsNumber(items[i])) { - return {}; - } - } - return { - 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 {}; + return { + std::min(items[0], items[2]), + std::min(items[1], items[3]), + std::max(items[0], items[2]), + std::max(items[1], items[3])}; } QPDFObjectHandle::Matrix QPDFObjectHandle::getArrayAsMatrix() const { - if (auto array = as_array(strict)) { - if (array.size() != 6) { + Array array(*this); + if (array.size() != 6) { + return {}; + } + std::array items; + for (size_t i = 0; i < 6; ++i) { + if (!array[i].getValueAsNumber(items[i])) { return {}; } - double items[6]; - for (int i = 0; i < 6; ++i) { - if (auto item = array.at(i).second; !item.getValueAsNumber(items[i])) { - return {}; - } - } - return {items[0], items[1], items[2], items[3], items[4], items[5]}; } - return {}; + return {items[0], items[1], items[2], items[3], items[4], items[5]}; } std::vector @@ -417,13 +482,11 @@ void QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const& item) { if (auto array = as_array(strict)) { - if (!array.setAt(n, item)) { + if (!array.set(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 @@ -493,11 +556,9 @@ QPDFObjectHandle::eraseItem(int at) QPDFObjectHandle QPDFObjectHandle::eraseItemAndGetOld(int at) { - auto array = as_array(strict); - auto result = - (array && std::cmp_less(at, array.size()) && at >= 0) ? array.at(at).second : newNull(); + auto result = Array(*this)[at]; eraseItem(at); - return result; + return result ? result : newNull(); } size_t @@ -532,36 +593,13 @@ BaseHandle::size() const QPDFObjectHandle BaseHandle::operator[](size_t n) const { - switch (resolved_type_code()) { - case ::ot_array: - { - auto a = as(); - if (n >= a->size()) { - return {}; - } - return Array(obj).at(static_cast(n)).second; - } - case ::ot_uninitialized: - case ::ot_reserved: - case ::ot_null: - case ::ot_destroyed: - case ::ot_unresolved: - case ::ot_reference: - return {}; - case ::ot_boolean: - case ::ot_integer: - case ::ot_real: - case ::ot_string: - case ::ot_name: - case ::ot_dictionary: - case ::ot_stream: - case ::ot_inlineimage: - case ::ot_operator: - return {obj}; - default: - throw std::logic_error("Unexpected type code in size"); // unreachable - return {}; // unreachable + if (resolved_type_code() == ::ot_array) { + return Array(obj)[n]; } + if (n < size()) { + return *this; + } + return {}; } QPDFObjectHandle diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc index e6dd873..992cc84 100644 --- a/libqpdf/QPDF_Stream.cc +++ b/libqpdf/QPDF_Stream.cc @@ -442,7 +442,7 @@ Stream::filterable( int i = -1; for (auto& filter: filters) { - auto d_obj = decode_array.at(++i).second; + auto d_obj = decode_array.get(++i); if (!can_filter(decode_level, *filter, d_obj)) { return false; } diff --git a/libqpdf/qpdf/QPDFObjectHandle_private.hh b/libqpdf/qpdf/QPDFObjectHandle_private.hh index 9fa67cd..183b792 100644 --- a/libqpdf/qpdf/QPDFObjectHandle_private.hh +++ b/libqpdf/qpdf/QPDFObjectHandle_private.hh @@ -12,6 +12,30 @@ namespace qpdf class Array final: public BaseHandle { public: + // Create an empty Array. If 'empty' is false, create a null Array. + Array(bool empty = true); + + Array(std::vector const& items); + + Array(std::vector&& items); + + Array(Array const& other) : + BaseHandle(other.obj) + { + } + + Array& + operator=(Array const& other) + { + if (obj != other.obj) { + obj = other.obj; + } + return *this; + } + + Array(Array&&) = default; + Array& operator=(Array&&) = default; + explicit Array(std::shared_ptr const& obj) : BaseHandle(obj) { @@ -22,6 +46,34 @@ namespace qpdf { } + Array(QPDFObjectHandle const& oh) : + BaseHandle(oh.resolved_type_code() == ::ot_array ? oh : QPDFObjectHandle()) + { + } + + Array& + operator=(QPDFObjectHandle const& oh) + { + assign(::ot_array, oh); + return *this; + } + + Array(QPDFObjectHandle&& oh) : + BaseHandle(oh.resolved_type_code() == ::ot_array ? std::move(oh) : QPDFObjectHandle()) + { + } + + Array& + operator=(QPDFObjectHandle&& oh) + { + assign(::ot_array, std::move(oh)); + return *this; + } + + QPDFObjectHandle const& operator[](size_t n) const; + + QPDFObjectHandle const& operator[](int n) const; + using iterator = std::vector::iterator; using const_iterator = std::vector::const_iterator; using const_reverse_iterator = std::vector::const_reverse_iterator; @@ -38,11 +90,16 @@ namespace qpdf const_reverse_iterator crend(); + // Return the number of elements in the array. Return 0 if the object is not an array. size_t size() const; - std::pair at(int n) const; - bool setAt(int at, QPDFObjectHandle const& oh); + QPDFObjectHandle get(size_t n) const; + QPDFObjectHandle get(int n) const; + bool set(size_t at, QPDFObjectHandle const& oh); + bool set(int at, QPDFObjectHandle const& oh); + bool insert(size_t at, QPDFObjectHandle const& item); bool insert(int at, QPDFObjectHandle const& item); void push_back(QPDFObjectHandle const& item); + bool erase(size_t at); bool erase(int at); std::vector getAsVector() const; @@ -342,6 +399,32 @@ namespace qpdf return nullptr; } + inline BaseHandle::BaseHandle(QPDFObjectHandle const& oh) : + obj(oh.obj) + { + } + + inline BaseHandle::BaseHandle(QPDFObjectHandle&& oh) : + obj(std::move(oh.obj)) + { + } + + inline void + BaseHandle::assign(qpdf_object_type_e required, BaseHandle const& other) + { + if (obj != other.obj) { + obj = other.resolved_type_code() == required ? other.obj : nullptr; + } + } + + inline void + BaseHandle::assign(qpdf_object_type_e required, BaseHandle&& other) + { + if (obj != other.obj) { + obj = other.resolved_type_code() == required ? std::move(other.obj) : nullptr; + } + } + inline QPDFObjGen BaseHandle::id_gen() const { diff --git a/libtests/sparse_array.cc b/libtests/sparse_array.cc index d0fee40..261b982 100644 --- a/libtests/sparse_array.cc +++ b/libtests/sparse_array.cc @@ -26,69 +26,69 @@ main() a.push_back(QPDFObjectHandle::parse("null")); a.push_back(QPDFObjectHandle::parse("/Quack")); assert(a.size() == 5); - assert(a.at(0).second.isInteger() && (a.at(0).second.getIntValue() == 1)); - assert(a.at(1).second.isString() && (a.at(1).second.getStringValue() == "potato")); - assert(a.at(2).second.isNull()); - assert(a.at(3).second.isNull()); - assert(a.at(4).second.isName() && (a.at(4).second.getName() == "/Quack")); + assert(a[0].isInteger() && (a[0].getIntValue() == 1)); + assert(a[1].isString() && (a[1].getStringValue() == "potato")); + assert(a[2].isNull()); + assert(a[3].isNull()); + assert(a[4].isName() && (a[4].getName() == "/Quack")); a.insert(4, QPDFObjectHandle::parse("/BeforeQuack")); assert(a.size() == 6); - assert(a.at(0).second.isInteger() && (a.at(0).second.getIntValue() == 1)); - assert(a.at(4).second.isName() && (a.at(4).second.getName() == "/BeforeQuack")); - assert(a.at(5).second.isName() && (a.at(5).second.getName() == "/Quack")); + assert(a[0].isInteger() && (a[0].getIntValue() == 1)); + assert(a[4].isName() && (a[4].getName() == "/BeforeQuack")); + assert(a[5].isName() && (a[5].getName() == "/Quack")); a.insert(2, QPDFObjectHandle::parse("/Third")); assert(a.size() == 7); - assert(a.at(1).second.isString() && (a.at(1).second.getStringValue() == "potato")); - assert(a.at(2).second.isName() && (a.at(2).second.getName() == "/Third")); - assert(a.at(3).second.isNull()); - assert(a.at(6).second.isName() && (a.at(6).second.getName() == "/Quack")); + assert(a[1].isString() && (a[1].getStringValue() == "potato")); + assert(a[2].isName() && (a[2].getName() == "/Third")); + assert(a[3].isNull()); + assert(a[6].isName() && (a[6].getName() == "/Quack")); a.insert(0, QPDFObjectHandle::parse("/First")); assert(a.size() == 8); - assert(a.at(0).second.isName() && (a.at(0).second.getName() == "/First")); - assert(a.at(1).second.isInteger() && (a.at(1).second.getIntValue() == 1)); - assert(a.at(7).second.isName() && (a.at(7).second.getName() == "/Quack")); + assert(a[0].isName() && (a[0].getName() == "/First")); + assert(a[1].isInteger() && (a[1].getIntValue() == 1)); + assert(a[7].isName() && (a[7].getName() == "/Quack")); a.erase(6); assert(a.size() == 7); - assert(a.at(0).second.isName() && (a.at(0).second.getName() == "/First")); - assert(a.at(1).second.isInteger() && (a.at(1).second.getIntValue() == 1)); - assert(a.at(5).second.isNull()); - assert(a.at(6).second.isName() && (a.at(6).second.getName() == "/Quack")); + assert(a[0].isName() && (a[0].getName() == "/First")); + assert(a[1].isInteger() && (a[1].getIntValue() == 1)); + assert(a[5].isNull()); + assert(a[6].isName() && (a[6].getName() == "/Quack")); a.erase(6); assert(a.size() == 6); - assert(a.at(0).second.isName() && (a.at(0).second.getName() == "/First")); - assert(a.at(1).second.isInteger() && (a.at(1).second.getIntValue() == 1)); - assert(a.at(3).second.isName() && (a.at(3).second.getName() == "/Third")); - assert(a.at(4).second.isNull()); - assert(a.at(5).second.isNull()); + assert(a[0].isName() && (a[0].getName() == "/First")); + assert(a[1].isInteger() && (a[1].getIntValue() == 1)); + assert(a[3].isName() && (a[3].getName() == "/Third")); + assert(a[4].isNull()); + assert(a[5].isNull()); - a.setAt(4, QPDFObjectHandle::parse("12")); - assert(a.at(4).second.isInteger() && (a.at(4).second.getIntValue() == 12)); - a.setAt(4, QPDFObjectHandle::newNull()); - assert(a.at(4).second.isNull()); + a.set(4, QPDFObjectHandle::parse("12")); + assert(a[4].isInteger() && (a[4].getIntValue() == 12)); + a.set(4, QPDFObjectHandle::newNull()); + assert(a[4].isNull()); a.erase(to_i(a.size()) - 1); assert(a.size() == 5); - assert(a.at(0).second.isName() && (a.at(0).second.getName() == "/First")); - assert(a.at(1).second.isInteger() && (a.at(1).second.getIntValue() == 1)); - assert(a.at(3).second.isName() && (a.at(3).second.getName() == "/Third")); - assert(a.at(4).second.isNull()); + assert(a[0].isName() && (a[0].getName() == "/First")); + assert(a[1].isInteger() && (a[1].getIntValue() == 1)); + assert(a[3].isName() && (a[3].getName() == "/Third")); + assert(a[4].isNull()); a.erase(to_i(a.size()) - 1); assert(a.size() == 4); - assert(a.at(0).second.isName() && (a.at(0).second.getName() == "/First")); - assert(a.at(1).second.isInteger() && (a.at(1).second.getIntValue() == 1)); - assert(a.at(3).second.isName() && (a.at(3).second.getName() == "/Third")); + assert(a[0].isName() && (a[0].getName() == "/First")); + assert(a[1].isInteger() && (a[1].getIntValue() == 1)); + assert(a[3].isName() && (a[3].getName() == "/Third")); a.erase(to_i(a.size()) - 1); assert(a.size() == 3); - assert(a.at(0).second.isName() && (a.at(0).second.getName() == "/First")); - assert(a.at(1).second.isInteger() && (a.at(1).second.getIntValue() == 1)); - assert(a.at(2).second.isString() && (a.at(2).second.getStringValue() == "potato")); + assert(a[0].isName() && (a[0].getName() == "/First")); + assert(a[1].isInteger() && (a[1].getIntValue() == 1)); + assert(a[2].isString() && (a[2].getStringValue() == "potato")); QPDF pdf; pdf.emptyPDF(); @@ -96,22 +96,22 @@ main() obj = QPDFObject::create( std::vector{10, "null"_qpdf.getObj()}, true); auto b = qpdf::Array(obj); - b.setAt(5, pdf.newIndirectNull()); - b.setAt(7, "[0 1 2 3]"_qpdf); - assert(b.at(3).second.isNull()); - assert(b.at(8).second.isNull()); - assert(b.at(5).second.isIndirect()); + b.set(5, pdf.newIndirectNull()); + b.set(7, "[0 1 2 3]"_qpdf); + assert(b[3].null()); + assert(b[8].null()); + assert(b[5].indirect()); assert( QPDFObjectHandle(obj).unparse() == "[ null null null null null 3 0 R null [ 0 1 2 3 ] null null ]"); auto c = QPDFObjectHandle(obj).unsafeShallowCopy(); auto d = QPDFObjectHandle(obj).shallowCopy(); - b.at(7).second.setArrayItem(2, "42"_qpdf); + b.get(7).setArrayItem(2, "42"_qpdf); assert(c.unparse() == "[ null null null null null 3 0 R null [ 0 1 42 3 ] null null ]"); assert(d.unparse() == "[ null null null null null 3 0 R null [ 0 1 2 3 ] null null ]"); try { - b.setAt(3, {}); + b.set(3, {}); std::cout << "inserted uninitialized object\n"; } catch (std::logic_error&) { } @@ -119,7 +119,7 @@ main() pdf2.emptyPDF(); try { pdf.makeIndirectObject(obj); - b.setAt(3, pdf2.getObject(1, 0)); + b.set(3, pdf2.getObject(1, 0)); std::cout << "inserted uninitialized object\n"; } catch (std::logic_error&) { } diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 4208e13..5e9e65f 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -282,7 +282,6 @@ qpdf-c called qpdf_set_newline_before_endstream 0 SF_FlateLzwDecode TIFF predictor 0 QPDFTokenizer inline image at EOF 0 Pl_QPDFTokenizer found ID 0 -QPDFObjectHandle non-stream in stream array 0 QPDFObjectHandle coalesce called on stream 0 QPDFObjectHandle coalesce provide stream data 0 QPDF_Stream bad token at end during normalize 0 @@ -290,7 +289,6 @@ QPDFParser bad token in parse 0 QPDFParser bad token in parseRemainder 0 QPDFParser eof in parse 0 QPDFParser eof in parseRemainder 0 -QPDFObjectHandle array bounds 0 QPDFObjectHandle boolean returning false 0 QPDFObjectHandle integer returning 0 0 QPDFObjectHandle real returning 0.0 0 @@ -299,11 +297,7 @@ QPDFObjectHandle string returning empty string 0 QPDFObjectHandle string returning empty utf8 0 QPDFObjectHandle operator returning fake value 0 QPDFObjectHandle inlineimage returning empty data 0 -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 @@ -519,8 +513,6 @@ qpdf-c called qpdf_oh_unparse_resolved 0 qpdf-c called qpdf_oh_unparse_binary 0 QPDFWriter getFilterOnWrite false 0 QPDFPageObjectHelper::forEachXObject 3 -NNTree repair 0 -NNTree insertAfter inserts first 0 NNTree erased last kid/item in tree 1 QPDFPageObjectHelper unresolved names 0 QPDFPageObjectHelper resolving unresolved 0