diff --git a/libqpdf/QPDF_optimization.cc b/libqpdf/QPDF_optimization.cc index 4cd246f..9a4ede4 100644 --- a/libqpdf/QPDF_optimization.cc +++ b/libqpdf/QPDF_optimization.cc @@ -135,137 +135,6 @@ Lin::optimize_internal( } void -QPDF::pushInheritedAttributesToPage() -{ - // Public API should not have access to allow_changes. - m->pages.pushInheritedAttributesToPage(true, false); -} - -void -Pages::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys) -{ - // Traverse pages tree pushing all inherited resources down to the page level. - - // The record of whether we've done this is cleared by updateAllPagesCache(). If we're warning - // for skipped keys, re-traverse unconditionally. - if (m->pushed_inherited_attributes_to_pages && (!warn_skipped_keys)) { - return; - } - - // Calling getAllPages() resolves any duplicated page objects, repairs broken nodes, and detects - // loops, so we don't have to do those activities here. - qpdf.getAllPages(); - - // key_ancestors is a mapping of page attribute keys to a stack of Pages nodes that contain - // values for them. - std::map> key_ancestors; - pushInheritedAttributesToPageInternal( - m->trailer.getKey("/Root").getKey("/Pages"), - key_ancestors, - allow_changes, - warn_skipped_keys); - if (!key_ancestors.empty()) { - throw std::logic_error( - "key_ancestors not empty after pushing inherited attributes to pages"); - } - m->pushed_inherited_attributes_to_pages = true; - m->ever_pushed_inherited_attributes_to_pages = true; -} - -void -Pages ::pushInheritedAttributesToPageInternal( - QPDFObjectHandle cur_pages, - std::map>& key_ancestors, - bool allow_changes, - bool warn_skipped_keys) -{ - // Make a list of inheritable keys. Only the keys /MediaBox, /CropBox, /Resources, and /Rotate - // are inheritable attributes. Push this object onto the stack of pages nodes that have values - // for this attribute. - - std::set inheritable_keys; - for (auto const& key: cur_pages.getKeys()) { - if (key == "/MediaBox" || key == "/CropBox" || key == "/Resources" || key == "/Rotate") { - if (!allow_changes) { - throw QPDFExc( - qpdf_e_internal, - m->file->getName(), - m->last_object_description, - m->file->getLastOffset(), - "optimize detected an inheritable attribute when called in no-change mode"); - } - - // This is an inheritable resource - inheritable_keys.insert(key); - QPDFObjectHandle oh = cur_pages.getKey(key); - QTC::TC("qpdf", "QPDF opt direct pages resource", oh.indirect() ? 0 : 1); - if (!oh.indirect()) { - if (!oh.isScalar()) { - // Replace shared direct object non-scalar resources with indirect objects to - // avoid copying large structures around. - cur_pages.replaceKey(key, qpdf.makeIndirectObject(oh)); - oh = cur_pages.getKey(key); - } else { - // It's okay to copy scalars. - } - } - key_ancestors[key].push_back(oh); - if (key_ancestors[key].size() > 1) { - } - // Remove this resource from this node. It will be reattached at the page level. - cur_pages.removeKey(key); - } else if (!(key == "/Type" || key == "/Parent" || key == "/Kids" || key == "/Count")) { - // Warn when flattening, but not if the key is at the top level (i.e. "/Parent" not - // set), as we don't change these; but flattening removes intermediate /Pages nodes. - if (warn_skipped_keys && cur_pages.hasKey("/Parent")) { - warn( - qpdf_e_pages, - "Pages object: object " + cur_pages.id_gen().unparse(' '), - 0, - ("Unknown key " + key + - " in /Pages object is being discarded as a result of flattening the /Pages " - "tree")); - } - } - } - - // Process descendant nodes. This method does not perform loop detection because all code paths - // that lead here follow a call to getAllPages, which already throws an exception in the event - // of a loop in the pages tree. - for (auto& kid: cur_pages.getKey("/Kids").aitems()) { - if (kid.isDictionaryOfType("/Pages")) { - pushInheritedAttributesToPageInternal( - kid, key_ancestors, allow_changes, warn_skipped_keys); - } else { - // Add all available inheritable attributes not present in this object to this object. - for (auto const& iter: key_ancestors) { - std::string const& key = iter.first; - if (!kid.hasKey(key)) { - kid.replaceKey(key, iter.second.back()); - } else { - QTC::TC("qpdf", "QPDF opt page resource hides ancestor"); - } - } - } - } - - // For each inheritable key, pop the stack. If the stack becomes empty, remove it from the map. - // That way, the invariant that the list of keys in key_ancestors is exactly those keys for - // which inheritable attributes are available. - - if (!inheritable_keys.empty()) { - for (auto const& key: inheritable_keys) { - key_ancestors[key].pop_back(); - if (key_ancestors[key].empty()) { - key_ancestors.erase(key); - } - } - } else { - QTC::TC("qpdf", "QPDF opt no inheritable keys"); - } -} - -void Lin::updateObjectMaps( ObjUser const& first_ou, QPDFObjectHandle first_oh, diff --git a/libqpdf/QPDF_pages.cc b/libqpdf/QPDF_pages.cc index 0239314..e5a4e97 100644 --- a/libqpdf/QPDF_pages.cc +++ b/libqpdf/QPDF_pages.cc @@ -280,6 +280,137 @@ Pages::flattenPagesTree() } void +QPDF::pushInheritedAttributesToPage() +{ + // Public API should not have access to allow_changes. + m->pages.pushInheritedAttributesToPage(true, false); +} + +void +Pages::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys) +{ + // Traverse pages tree pushing all inherited resources down to the page level. + + // The record of whether we've done this is cleared by updateAllPagesCache(). If we're warning + // for skipped keys, re-traverse unconditionally. + if (m->pushed_inherited_attributes_to_pages && (!warn_skipped_keys)) { + return; + } + + // Calling getAllPages() resolves any duplicated page objects, repairs broken nodes, and detects + // loops, so we don't have to do those activities here. + qpdf.getAllPages(); + + // key_ancestors is a mapping of page attribute keys to a stack of Pages nodes that contain + // values for them. + std::map> key_ancestors; + pushInheritedAttributesToPageInternal( + m->trailer.getKey("/Root").getKey("/Pages"), + key_ancestors, + allow_changes, + warn_skipped_keys); + if (!key_ancestors.empty()) { + throw std::logic_error( + "key_ancestors not empty after pushing inherited attributes to pages"); + } + m->pushed_inherited_attributes_to_pages = true; + m->ever_pushed_inherited_attributes_to_pages = true; +} + +void +Pages::pushInheritedAttributesToPageInternal( + QPDFObjectHandle cur_pages, + std::map>& key_ancestors, + bool allow_changes, + bool warn_skipped_keys) +{ + // Make a list of inheritable keys. Only the keys /MediaBox, /CropBox, /Resources, and /Rotate + // are inheritable attributes. Push this object onto the stack of pages nodes that have values + // for this attribute. + + std::set inheritable_keys; + for (auto const& key: cur_pages.getKeys()) { + if (key == "/MediaBox" || key == "/CropBox" || key == "/Resources" || key == "/Rotate") { + if (!allow_changes) { + throw QPDFExc( + qpdf_e_internal, + m->file->getName(), + m->last_object_description, + m->file->getLastOffset(), + "optimize detected an inheritable attribute when called in no-change mode"); + } + + // This is an inheritable resource + inheritable_keys.insert(key); + QPDFObjectHandle oh = cur_pages.getKey(key); + QTC::TC("qpdf", "QPDF opt direct pages resource", oh.indirect() ? 0 : 1); + if (!oh.indirect()) { + if (!oh.isScalar()) { + // Replace shared direct object non-scalar resources with indirect objects to + // avoid copying large structures around. + cur_pages.replaceKey(key, qpdf.makeIndirectObject(oh)); + oh = cur_pages.getKey(key); + } else { + // It's okay to copy scalars. + } + } + key_ancestors[key].push_back(oh); + if (key_ancestors[key].size() > 1) { + } + // Remove this resource from this node. It will be reattached at the page level. + cur_pages.removeKey(key); + } else if (!(key == "/Type" || key == "/Parent" || key == "/Kids" || key == "/Count")) { + // Warn when flattening, but not if the key is at the top level (i.e. "/Parent" not + // set), as we don't change these; but flattening removes intermediate /Pages nodes. + if (warn_skipped_keys && cur_pages.hasKey("/Parent")) { + warn( + qpdf_e_pages, + "Pages object: object " + cur_pages.id_gen().unparse(' '), + 0, + ("Unknown key " + key + + " in /Pages object is being discarded as a result of flattening the /Pages " + "tree")); + } + } + } + + // Process descendant nodes. This method does not perform loop detection because all code paths + // that lead here follow a call to getAllPages, which already throws an exception in the event + // of a loop in the pages tree. + for (auto& kid: cur_pages.getKey("/Kids").aitems()) { + if (kid.isDictionaryOfType("/Pages")) { + pushInheritedAttributesToPageInternal( + kid, key_ancestors, allow_changes, warn_skipped_keys); + } else { + // Add all available inheritable attributes not present in this object to this object. + for (auto const& iter: key_ancestors) { + std::string const& key = iter.first; + if (!kid.hasKey(key)) { + kid.replaceKey(key, iter.second.back()); + } else { + QTC::TC("qpdf", "QPDF opt page resource hides ancestor"); + } + } + } + } + + // For each inheritable key, pop the stack. If the stack becomes empty, remove it from the map. + // That way, the invariant that the list of keys in key_ancestors is exactly those keys for + // which inheritable attributes are available. + + if (!inheritable_keys.empty()) { + for (auto const& key: inheritable_keys) { + key_ancestors[key].pop_back(); + if (key_ancestors[key].empty()) { + key_ancestors.erase(key); + } + } + } else { + QTC::TC("qpdf", "QPDF opt no inheritable keys"); + } +} + +void Pages::insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate) { QPDFObjGen og(obj.getObjGen());