Commit ae90619f9fe8ac6103faa105b7750acb41d199c8

Authored by m-holger
1 parent cc9facdb

Refactor `pushInheritedAttributesToPage`: move implementation to `QPDF_pages.cc`…

… to improve code organization.
libqpdf/QPDF_optimization.cc
@@ -135,137 +135,6 @@ Lin::optimize_internal( @@ -135,137 +135,6 @@ Lin::optimize_internal(
135 } 135 }
136 136
137 void 137 void
138 -QPDF::pushInheritedAttributesToPage()  
139 -{  
140 - // Public API should not have access to allow_changes.  
141 - m->pages.pushInheritedAttributesToPage(true, false);  
142 -}  
143 -  
144 -void  
145 -Pages::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys)  
146 -{  
147 - // Traverse pages tree pushing all inherited resources down to the page level.  
148 -  
149 - // The record of whether we've done this is cleared by updateAllPagesCache(). If we're warning  
150 - // for skipped keys, re-traverse unconditionally.  
151 - if (m->pushed_inherited_attributes_to_pages && (!warn_skipped_keys)) {  
152 - return;  
153 - }  
154 -  
155 - // Calling getAllPages() resolves any duplicated page objects, repairs broken nodes, and detects  
156 - // loops, so we don't have to do those activities here.  
157 - qpdf.getAllPages();  
158 -  
159 - // key_ancestors is a mapping of page attribute keys to a stack of Pages nodes that contain  
160 - // values for them.  
161 - std::map<std::string, std::vector<QPDFObjectHandle>> key_ancestors;  
162 - pushInheritedAttributesToPageInternal(  
163 - m->trailer.getKey("/Root").getKey("/Pages"),  
164 - key_ancestors,  
165 - allow_changes,  
166 - warn_skipped_keys);  
167 - if (!key_ancestors.empty()) {  
168 - throw std::logic_error(  
169 - "key_ancestors not empty after pushing inherited attributes to pages");  
170 - }  
171 - m->pushed_inherited_attributes_to_pages = true;  
172 - m->ever_pushed_inherited_attributes_to_pages = true;  
173 -}  
174 -  
175 -void  
176 -Pages ::pushInheritedAttributesToPageInternal(  
177 - QPDFObjectHandle cur_pages,  
178 - std::map<std::string, std::vector<QPDFObjectHandle>>& key_ancestors,  
179 - bool allow_changes,  
180 - bool warn_skipped_keys)  
181 -{  
182 - // Make a list of inheritable keys. Only the keys /MediaBox, /CropBox, /Resources, and /Rotate  
183 - // are inheritable attributes. Push this object onto the stack of pages nodes that have values  
184 - // for this attribute.  
185 -  
186 - std::set<std::string> inheritable_keys;  
187 - for (auto const& key: cur_pages.getKeys()) {  
188 - if (key == "/MediaBox" || key == "/CropBox" || key == "/Resources" || key == "/Rotate") {  
189 - if (!allow_changes) {  
190 - throw QPDFExc(  
191 - qpdf_e_internal,  
192 - m->file->getName(),  
193 - m->last_object_description,  
194 - m->file->getLastOffset(),  
195 - "optimize detected an inheritable attribute when called in no-change mode");  
196 - }  
197 -  
198 - // This is an inheritable resource  
199 - inheritable_keys.insert(key);  
200 - QPDFObjectHandle oh = cur_pages.getKey(key);  
201 - QTC::TC("qpdf", "QPDF opt direct pages resource", oh.indirect() ? 0 : 1);  
202 - if (!oh.indirect()) {  
203 - if (!oh.isScalar()) {  
204 - // Replace shared direct object non-scalar resources with indirect objects to  
205 - // avoid copying large structures around.  
206 - cur_pages.replaceKey(key, qpdf.makeIndirectObject(oh));  
207 - oh = cur_pages.getKey(key);  
208 - } else {  
209 - // It's okay to copy scalars.  
210 - }  
211 - }  
212 - key_ancestors[key].push_back(oh);  
213 - if (key_ancestors[key].size() > 1) {  
214 - }  
215 - // Remove this resource from this node. It will be reattached at the page level.  
216 - cur_pages.removeKey(key);  
217 - } else if (!(key == "/Type" || key == "/Parent" || key == "/Kids" || key == "/Count")) {  
218 - // Warn when flattening, but not if the key is at the top level (i.e. "/Parent" not  
219 - // set), as we don't change these; but flattening removes intermediate /Pages nodes.  
220 - if (warn_skipped_keys && cur_pages.hasKey("/Parent")) {  
221 - warn(  
222 - qpdf_e_pages,  
223 - "Pages object: object " + cur_pages.id_gen().unparse(' '),  
224 - 0,  
225 - ("Unknown key " + key +  
226 - " in /Pages object is being discarded as a result of flattening the /Pages "  
227 - "tree"));  
228 - }  
229 - }  
230 - }  
231 -  
232 - // Process descendant nodes. This method does not perform loop detection because all code paths  
233 - // that lead here follow a call to getAllPages, which already throws an exception in the event  
234 - // of a loop in the pages tree.  
235 - for (auto& kid: cur_pages.getKey("/Kids").aitems()) {  
236 - if (kid.isDictionaryOfType("/Pages")) {  
237 - pushInheritedAttributesToPageInternal(  
238 - kid, key_ancestors, allow_changes, warn_skipped_keys);  
239 - } else {  
240 - // Add all available inheritable attributes not present in this object to this object.  
241 - for (auto const& iter: key_ancestors) {  
242 - std::string const& key = iter.first;  
243 - if (!kid.hasKey(key)) {  
244 - kid.replaceKey(key, iter.second.back());  
245 - } else {  
246 - QTC::TC("qpdf", "QPDF opt page resource hides ancestor");  
247 - }  
248 - }  
249 - }  
250 - }  
251 -  
252 - // For each inheritable key, pop the stack. If the stack becomes empty, remove it from the map.  
253 - // That way, the invariant that the list of keys in key_ancestors is exactly those keys for  
254 - // which inheritable attributes are available.  
255 -  
256 - if (!inheritable_keys.empty()) {  
257 - for (auto const& key: inheritable_keys) {  
258 - key_ancestors[key].pop_back();  
259 - if (key_ancestors[key].empty()) {  
260 - key_ancestors.erase(key);  
261 - }  
262 - }  
263 - } else {  
264 - QTC::TC("qpdf", "QPDF opt no inheritable keys");  
265 - }  
266 -}  
267 -  
268 -void  
269 Lin::updateObjectMaps( 138 Lin::updateObjectMaps(
270 ObjUser const& first_ou, 139 ObjUser const& first_ou,
271 QPDFObjectHandle first_oh, 140 QPDFObjectHandle first_oh,
libqpdf/QPDF_pages.cc
@@ -280,6 +280,137 @@ Pages::flattenPagesTree() @@ -280,6 +280,137 @@ Pages::flattenPagesTree()
280 } 280 }
281 281
282 void 282 void
  283 +QPDF::pushInheritedAttributesToPage()
  284 +{
  285 + // Public API should not have access to allow_changes.
  286 + m->pages.pushInheritedAttributesToPage(true, false);
  287 +}
  288 +
  289 +void
  290 +Pages::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys)
  291 +{
  292 + // Traverse pages tree pushing all inherited resources down to the page level.
  293 +
  294 + // The record of whether we've done this is cleared by updateAllPagesCache(). If we're warning
  295 + // for skipped keys, re-traverse unconditionally.
  296 + if (m->pushed_inherited_attributes_to_pages && (!warn_skipped_keys)) {
  297 + return;
  298 + }
  299 +
  300 + // Calling getAllPages() resolves any duplicated page objects, repairs broken nodes, and detects
  301 + // loops, so we don't have to do those activities here.
  302 + qpdf.getAllPages();
  303 +
  304 + // key_ancestors is a mapping of page attribute keys to a stack of Pages nodes that contain
  305 + // values for them.
  306 + std::map<std::string, std::vector<QPDFObjectHandle>> key_ancestors;
  307 + pushInheritedAttributesToPageInternal(
  308 + m->trailer.getKey("/Root").getKey("/Pages"),
  309 + key_ancestors,
  310 + allow_changes,
  311 + warn_skipped_keys);
  312 + if (!key_ancestors.empty()) {
  313 + throw std::logic_error(
  314 + "key_ancestors not empty after pushing inherited attributes to pages");
  315 + }
  316 + m->pushed_inherited_attributes_to_pages = true;
  317 + m->ever_pushed_inherited_attributes_to_pages = true;
  318 +}
  319 +
  320 +void
  321 +Pages::pushInheritedAttributesToPageInternal(
  322 + QPDFObjectHandle cur_pages,
  323 + std::map<std::string, std::vector<QPDFObjectHandle>>& key_ancestors,
  324 + bool allow_changes,
  325 + bool warn_skipped_keys)
  326 +{
  327 + // Make a list of inheritable keys. Only the keys /MediaBox, /CropBox, /Resources, and /Rotate
  328 + // are inheritable attributes. Push this object onto the stack of pages nodes that have values
  329 + // for this attribute.
  330 +
  331 + std::set<std::string> inheritable_keys;
  332 + for (auto const& key: cur_pages.getKeys()) {
  333 + if (key == "/MediaBox" || key == "/CropBox" || key == "/Resources" || key == "/Rotate") {
  334 + if (!allow_changes) {
  335 + throw QPDFExc(
  336 + qpdf_e_internal,
  337 + m->file->getName(),
  338 + m->last_object_description,
  339 + m->file->getLastOffset(),
  340 + "optimize detected an inheritable attribute when called in no-change mode");
  341 + }
  342 +
  343 + // This is an inheritable resource
  344 + inheritable_keys.insert(key);
  345 + QPDFObjectHandle oh = cur_pages.getKey(key);
  346 + QTC::TC("qpdf", "QPDF opt direct pages resource", oh.indirect() ? 0 : 1);
  347 + if (!oh.indirect()) {
  348 + if (!oh.isScalar()) {
  349 + // Replace shared direct object non-scalar resources with indirect objects to
  350 + // avoid copying large structures around.
  351 + cur_pages.replaceKey(key, qpdf.makeIndirectObject(oh));
  352 + oh = cur_pages.getKey(key);
  353 + } else {
  354 + // It's okay to copy scalars.
  355 + }
  356 + }
  357 + key_ancestors[key].push_back(oh);
  358 + if (key_ancestors[key].size() > 1) {
  359 + }
  360 + // Remove this resource from this node. It will be reattached at the page level.
  361 + cur_pages.removeKey(key);
  362 + } else if (!(key == "/Type" || key == "/Parent" || key == "/Kids" || key == "/Count")) {
  363 + // Warn when flattening, but not if the key is at the top level (i.e. "/Parent" not
  364 + // set), as we don't change these; but flattening removes intermediate /Pages nodes.
  365 + if (warn_skipped_keys && cur_pages.hasKey("/Parent")) {
  366 + warn(
  367 + qpdf_e_pages,
  368 + "Pages object: object " + cur_pages.id_gen().unparse(' '),
  369 + 0,
  370 + ("Unknown key " + key +
  371 + " in /Pages object is being discarded as a result of flattening the /Pages "
  372 + "tree"));
  373 + }
  374 + }
  375 + }
  376 +
  377 + // Process descendant nodes. This method does not perform loop detection because all code paths
  378 + // that lead here follow a call to getAllPages, which already throws an exception in the event
  379 + // of a loop in the pages tree.
  380 + for (auto& kid: cur_pages.getKey("/Kids").aitems()) {
  381 + if (kid.isDictionaryOfType("/Pages")) {
  382 + pushInheritedAttributesToPageInternal(
  383 + kid, key_ancestors, allow_changes, warn_skipped_keys);
  384 + } else {
  385 + // Add all available inheritable attributes not present in this object to this object.
  386 + for (auto const& iter: key_ancestors) {
  387 + std::string const& key = iter.first;
  388 + if (!kid.hasKey(key)) {
  389 + kid.replaceKey(key, iter.second.back());
  390 + } else {
  391 + QTC::TC("qpdf", "QPDF opt page resource hides ancestor");
  392 + }
  393 + }
  394 + }
  395 + }
  396 +
  397 + // For each inheritable key, pop the stack. If the stack becomes empty, remove it from the map.
  398 + // That way, the invariant that the list of keys in key_ancestors is exactly those keys for
  399 + // which inheritable attributes are available.
  400 +
  401 + if (!inheritable_keys.empty()) {
  402 + for (auto const& key: inheritable_keys) {
  403 + key_ancestors[key].pop_back();
  404 + if (key_ancestors[key].empty()) {
  405 + key_ancestors.erase(key);
  406 + }
  407 + }
  408 + } else {
  409 + QTC::TC("qpdf", "QPDF opt no inheritable keys");
  410 + }
  411 +}
  412 +
  413 +void
283 Pages::insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate) 414 Pages::insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate)
284 { 415 {
285 QPDFObjGen og(obj.getObjGen()); 416 QPDFObjGen og(obj.getObjGen());