Commit ae90619f9fe8ac6103faa105b7750acb41d199c8
1 parent
cc9facdb
Refactor `pushInheritedAttributesToPage`: move implementation to `QPDF_pages.cc`…
… to improve code organization.
Showing
2 changed files
with
131 additions
and
131 deletions
libqpdf/QPDF_optimization.cc
| ... | ... | @@ -135,137 +135,6 @@ Lin::optimize_internal( |
| 135 | 135 | } |
| 136 | 136 | |
| 137 | 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 | 138 | Lin::updateObjectMaps( |
| 270 | 139 | ObjUser const& first_ou, |
| 271 | 140 | QPDFObjectHandle first_oh, | ... | ... |
libqpdf/QPDF_pages.cc
| ... | ... | @@ -280,6 +280,137 @@ Pages::flattenPagesTree() |
| 280 | 280 | } |
| 281 | 281 | |
| 282 | 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 | 414 | Pages::insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate) |
| 284 | 415 | { |
| 285 | 416 | QPDFObjGen og(obj.getObjGen()); | ... | ... |