diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index d7ac713..f51b12b 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -2077,8 +2077,8 @@ void QPDFWriter::initializeTables(size_t extra) { auto size = QIntC::to_size(QPDF::Writer::tableSize(m->pdf) + 100) + extra; - m->obj.initialize(size); - m->new_obj.initialize(size); + m->obj.resize(size); + m->new_obj.resize(size); } void diff --git a/libqpdf/qpdf/ObjTable.hh b/libqpdf/qpdf/ObjTable.hh index 84b51b7..3a36208 100644 --- a/libqpdf/qpdf/ObjTable.hh +++ b/libqpdf/qpdf/ObjTable.hh @@ -28,6 +28,7 @@ template class ObjTable: public std::vector { public: + using reference = T&; ObjTable() = default; ObjTable(const ObjTable&) = delete; ObjTable(ObjTable&&) = delete; @@ -99,17 +100,30 @@ class ObjTable: public std::vector return element(id); } + // emplace_back to the end of the vector. If there are any conflicting sparse elements, emplace + // them to the back of the vector before adding the current element. + template + inline T& + emplace_back(Args&&... args) + { + if (min_sparse == std::vector::size()) { + return emplace_back_large(std::forward(args...)); + } + return std::vector::emplace_back(std::forward(args...)); + } + void - initialize(size_t idx) - { - if (std::vector::size() > 0 || sparse_elements.size() > 0) { - throw ::std::logic_error("ObjTable accessed before initialization"); - } else if ( - idx >= static_cast(std::numeric_limits::max()) || - idx >= std::vector::max_size()) { - throw std::runtime_error("Invalid maximum object id initializing ObjTable."); - } else { - std::vector::resize(++idx); + resize(size_t a_size) + { + std::vector::resize(a_size); + if (a_size > min_sparse) { + auto it = sparse_elements.begin(); + auto end = sparse_elements.end(); + while (it != end && it->first < a_size) { + std::vector::operator[](it->first) = std::move(it->second); + it = sparse_elements.erase(it); + } + min_sparse = (it == end) ? std::numeric_limits::max() : it->first; } } @@ -127,30 +141,62 @@ class ObjTable: public std::vector private: std::map sparse_elements; + // Smallest id present in sparse_elements. + size_t min_sparse{std::numeric_limits::max()}; inline T& element(size_t idx) { if (idx < std::vector::size()) { return std::vector::operator[](idx); - } else if (idx < static_cast(std::numeric_limits::max())) { + } + return large_element(idx); + } + + // Must only be called by element. Separated out from element to keep inlined code tight. + T& + large_element(size_t idx) + { + static const size_t max_size = std::vector::max_size(); + if (idx < min_sparse) { + min_sparse = idx; + } + if (idx < max_size) { return sparse_elements[idx]; } - throw std::runtime_error("Invalid object id accessing ObjTable."); + throw std::runtime_error("Impossibly large object id encountered accessing ObjTable"); return element(0); // doesn't return } inline T const& element(size_t idx) const { + static const size_t max_size = std::vector::max_size(); if (idx < std::vector::size()) { return std::vector::operator[](idx); - } else if (idx < static_cast(std::numeric_limits::max())) { + } + if (idx < max_size) { return sparse_elements.at(idx); } - throw std::runtime_error("Invalid object id accessing ObjTable."); + throw std::runtime_error("Impossibly large object id encountered accessing ObjTable"); return element(0); // doesn't return } + + // Must only be called by emplace_back. Separated out from emplace_back to keep inlined code + // tight. + template + T& + emplace_back_large(Args&&... args) + { + auto it = sparse_elements.begin(); + auto end = sparse_elements.end(); + while (it != end && it->first == std::vector::size()) { + std::vector::emplace_back(std::move(it->second)); + it = sparse_elements.erase(it); + } + min_sparse = (it == end) ? std::numeric_limits::max() : it->first; + return std::vector::emplace_back(std::forward(args...)); + } }; #endif // OBJTABLE_HH diff --git a/libtests/obj_table.cc b/libtests/obj_table.cc index 5e83beb..2b58eca 100644 --- a/libtests/obj_table.cc +++ b/libtests/obj_table.cc @@ -2,6 +2,11 @@ struct Test { + Test() = default; + Test(int value) : + value(value) + { + } int value{0}; }; @@ -10,7 +15,7 @@ class Table: public ObjTable public: Table() { - initialize(5); + resize(5); } void @@ -20,9 +25,23 @@ class Table: public ObjTable (*this)[i].value = 2 * i; (*this)[1000 + i].value = 2 * (1000 + i); } + for (int i: {50, 60, 70, 98, 99, 100, 101, 150, 198, 199, 200, 201}) { + (*this)[i].value = 2 * i; + } + resize(100); + for (int i: {1, 99, 100, 105, 110, 120, 205, 206, 207, 210}) { + (*this)[i].value = 3 * i; + } + resize(200); + + for (int i = 1; i < 10; ++i) { + emplace_back(i); + } forEach([](auto i, auto const& item) -> void { - std::cout << std::to_string(i) << " : " << std::to_string(item.value) << "\n"; + if (item.value) { + std::cout << std::to_string(i) << " : " << std::to_string(item.value) << "\n"; + } }); std::cout << "2000 : " << std::to_string((*this)[2000].value) << "\n"; diff --git a/libtests/qtest/obj_table/obj_table.out b/libtests/qtest/obj_table/obj_table.out index 617e341..c564285 100644 --- a/libtests/qtest/obj_table/obj_table.out +++ b/libtests/qtest/obj_table/obj_table.out @@ -1,5 +1,4 @@ -0 : 0 -1 : 2 +1 : 3 2 : 4 3 : 6 4 : 8 @@ -8,6 +7,34 @@ 7 : 14 8 : 16 9 : 18 +50 : 100 +60 : 120 +70 : 140 +98 : 196 +99 : 297 +100 : 300 +101 : 202 +105 : 315 +110 : 330 +120 : 360 +150 : 300 +198 : 396 +199 : 398 +200 : 400 +201 : 402 +202 : 1 +203 : 2 +204 : 3 +205 : 615 +206 : 618 +207 : 621 +208 : 4 +209 : 5 +210 : 630 +211 : 6 +212 : 7 +213 : 8 +214 : 9 1000 : 2000 1001 : 2002 1002 : 2004