diff --git a/ChangeLog b/ChangeLog index 226423fd..77e31d89 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2020-04-02 Jay Berkenbilt + * Add method QPDFObjectHandle::unsafeShallowCopy for copying only + top-level dictionary keys or array items. See comments in + QPDFObjectHandle.hh for when this should be used. + * Remove Members class indirection for QPDFObjectHandle. Those are copied and assigned too often, and that change caused a very substantial performance hit. diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh index 38f29106..dcb7de35 100644 --- a/include/qpdf/QPDFObjectHandle.hh +++ b/include/qpdf/QPDFObjectHandle.hh @@ -670,10 +670,24 @@ class QPDFObjectHandle // traverse across indirect object boundaries. That means that, // for dictionaries and arrays, any keys or items that were // indirect objects will still be indirect objects that point to - // the same place. + // the same place. In the strictest sense, this is not a shallow + // copy because it recursively descends arrays and dictionaries; + // it just doesn't cross over indirect objects. See also + // unsafeShallowCopy(). QPDF_DLL QPDFObjectHandle shallowCopy(); + // Create a true shallow copy of an array or dictionary, just + // copying the immediate items (array) or keys (dictionary). This + // is "unsafe" because, if you *modify* any of the items in the + // copy, you are modifying the original, which is almost never + // what you want. However, if your intention is merely to + // *replace* top-level items or keys and not to modify lower-level + // items in the copy, this method is much faster than + // shallowCopy(). + QPDF_DLL + QPDFObjectHandle unsafeShallowCopy(); + // Mutator methods. Use with caution. // Recursively copy this object, making it direct. Throws an @@ -1053,7 +1067,9 @@ class QPDFObjectHandle void objectWarning(std::string const& warning); void assertType(char const* type_name, bool istype); void dereference(); - void copyObject(std::set& visited, bool cross_indirect); + void copyObject(std::set& visited, bool cross_indirect, + bool first_level_only); + void shallowCopyInternal(QPDFObjectHandle& oh, bool first_level_only); void releaseResolved(); static void setObjectDescriptionFromInput( QPDFObjectHandle, QPDF*, std::string const&, diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index e7a6223f..4c19b377 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -2460,6 +2460,23 @@ QPDFObjectHandle::hasObjectDescription() QPDFObjectHandle QPDFObjectHandle::shallowCopy() +{ + QPDFObjectHandle result; + shallowCopyInternal(result, false); + return result; +} + +QPDFObjectHandle +QPDFObjectHandle::unsafeShallowCopy() +{ + QPDFObjectHandle result; + shallowCopyInternal(result, true); + return result; +} + +void +QPDFObjectHandle::shallowCopyInternal(QPDFObjectHandle& new_obj, + bool first_level_only) { assertInitialized(); @@ -2470,7 +2487,6 @@ QPDFObjectHandle::shallowCopy() "attempt to make a shallow copy of a stream"); } - QPDFObjectHandle new_obj; if (isArray()) { QTC::TC("qpdf", "QPDFObjectHandle shallow copy array"); @@ -2491,13 +2507,12 @@ QPDFObjectHandle::shallowCopy() } std::set visited; - new_obj.copyObject(visited, false); - return new_obj; + new_obj.copyObject(visited, false, first_level_only); } void QPDFObjectHandle::copyObject(std::set& visited, - bool cross_indirect) + bool cross_indirect, bool first_level_only) { assertInitialized(); @@ -2573,9 +2588,11 @@ QPDFObjectHandle::copyObject(std::set& visited, for (int i = 0; i < n; ++i) { items.push_back(getArrayItem(i)); - if (cross_indirect || (! items.back().isIndirect())) + if ((! first_level_only) && + (cross_indirect || (! items.back().isIndirect()))) { - items.back().copyObject(visited, cross_indirect); + items.back().copyObject( + visited, cross_indirect, first_level_only); } } new_obj = new QPDF_Array(items); @@ -2589,9 +2606,11 @@ QPDFObjectHandle::copyObject(std::set& visited, iter != keys.end(); ++iter) { items[*iter] = getKey(*iter); - if (cross_indirect || (! items[*iter].isIndirect())) + if ((! first_level_only) && + (cross_indirect || (! items[*iter].isIndirect()))) { - items[*iter].copyObject(visited, cross_indirect); + items[*iter].copyObject( + visited, cross_indirect, first_level_only); } } new_obj = new QPDF_Dictionary(items); @@ -2614,7 +2633,7 @@ void QPDFObjectHandle::makeDirect() { std::set visited; - copyObject(visited, true); + copyObject(visited, true, false); } void