Commit d17f11e721be15e2cf3b394fd6e7539c1b49dbef
1 parent
72e3a968
Make QPDF::updateObjectMaps iterative
Showing
2 changed files
with
72 additions
and
66 deletions
include/qpdf/QPDF.hh
| @@ -1343,6 +1343,18 @@ class QPDF | @@ -1343,6 +1343,18 @@ class QPDF | ||
| 1343 | std::string key; // if ou_trailer_key or ou_root_key | 1343 | std::string key; // if ou_trailer_key or ou_root_key |
| 1344 | }; | 1344 | }; |
| 1345 | 1345 | ||
| 1346 | + struct UpdateObjectMapsFrame | ||
| 1347 | + { | ||
| 1348 | + UpdateObjectMapsFrame( | ||
| 1349 | + ObjUser const& ou, | ||
| 1350 | + QPDFObjectHandle oh, | ||
| 1351 | + bool top); | ||
| 1352 | + | ||
| 1353 | + ObjUser const& ou; | ||
| 1354 | + QPDFObjectHandle oh; | ||
| 1355 | + bool top; | ||
| 1356 | + }; | ||
| 1357 | + | ||
| 1346 | class PatternFinder: public InputSource::Finder | 1358 | class PatternFinder: public InputSource::Finder |
| 1347 | { | 1359 | { |
| 1348 | public: | 1360 | public: |
| @@ -1426,12 +1438,6 @@ class QPDF | @@ -1426,12 +1438,6 @@ class QPDF | ||
| 1426 | ObjUser const& ou, | 1438 | ObjUser const& ou, |
| 1427 | QPDFObjectHandle oh, | 1439 | QPDFObjectHandle oh, |
| 1428 | std::function<int(QPDFObjectHandle&)> skip_stream_parameters); | 1440 | std::function<int(QPDFObjectHandle&)> skip_stream_parameters); |
| 1429 | - void updateObjectMapsInternal( | ||
| 1430 | - ObjUser const& ou, | ||
| 1431 | - QPDFObjectHandle oh, | ||
| 1432 | - std::function<int(QPDFObjectHandle&)> skip_stream_parameters, | ||
| 1433 | - QPDFObjGen::set& visited, | ||
| 1434 | - bool top); | ||
| 1435 | void filterCompressedObjects(std::map<int, int> const& object_stream_data); | 1441 | void filterCompressedObjects(std::map<int, int> const& object_stream_data); |
| 1436 | void filterCompressedObjects(QPDFWriter::ObjTable const& object_stream_data); | 1442 | void filterCompressedObjects(QPDFWriter::ObjTable const& object_stream_data); |
| 1437 | 1443 |
libqpdf/QPDF_optimization.cc
| @@ -54,6 +54,14 @@ QPDF::ObjUser::operator<(ObjUser const& rhs) const | @@ -54,6 +54,14 @@ QPDF::ObjUser::operator<(ObjUser const& rhs) const | ||
| 54 | return false; | 54 | return false; |
| 55 | } | 55 | } |
| 56 | 56 | ||
| 57 | +QPDF::UpdateObjectMapsFrame::UpdateObjectMapsFrame( | ||
| 58 | + QPDF::ObjUser const& ou, QPDFObjectHandle oh, bool top) : | ||
| 59 | + ou(ou), | ||
| 60 | + oh(oh), | ||
| 61 | + top(top) | ||
| 62 | +{ | ||
| 63 | +} | ||
| 64 | + | ||
| 57 | void | 65 | void |
| 58 | QPDF::optimize( | 66 | QPDF::optimize( |
| 59 | std::map<int, int> const& object_stream_data, | 67 | std::map<int, int> const& object_stream_data, |
| @@ -277,78 +285,70 @@ QPDF::pushInheritedAttributesToPageInternal( | @@ -277,78 +285,70 @@ QPDF::pushInheritedAttributesToPageInternal( | ||
| 277 | 285 | ||
| 278 | void | 286 | void |
| 279 | QPDF::updateObjectMaps( | 287 | QPDF::updateObjectMaps( |
| 280 | - ObjUser const& ou, | ||
| 281 | - QPDFObjectHandle oh, | 288 | + ObjUser const& first_ou, |
| 289 | + QPDFObjectHandle first_oh, | ||
| 282 | std::function<int(QPDFObjectHandle&)> skip_stream_parameters) | 290 | std::function<int(QPDFObjectHandle&)> skip_stream_parameters) |
| 283 | { | 291 | { |
| 284 | QPDFObjGen::set visited; | 292 | QPDFObjGen::set visited; |
| 285 | - updateObjectMapsInternal(ou, oh, skip_stream_parameters, visited, true); | ||
| 286 | -} | ||
| 287 | - | ||
| 288 | -void | ||
| 289 | -QPDF::updateObjectMapsInternal( | ||
| 290 | - ObjUser const& ou, | ||
| 291 | - QPDFObjectHandle oh, | ||
| 292 | - std::function<int(QPDFObjectHandle&)> skip_stream_parameters, | ||
| 293 | - QPDFObjGen::set& visited, | ||
| 294 | - bool top) | ||
| 295 | -{ | 293 | + std::vector<UpdateObjectMapsFrame> pending; |
| 294 | + pending.emplace_back(first_ou, first_oh, true); | ||
| 296 | // Traverse the object tree from this point taking care to avoid crossing page boundaries. | 295 | // Traverse the object tree from this point taking care to avoid crossing page boundaries. |
| 296 | + std::unique_ptr<ObjUser> thumb_ou; | ||
| 297 | + while (!pending.empty()) { | ||
| 298 | + auto cur = pending.back(); | ||
| 299 | + pending.pop_back(); | ||
| 297 | 300 | ||
| 298 | - bool is_page_node = false; | 301 | + bool is_page_node = false; |
| 299 | 302 | ||
| 300 | - if (oh.isDictionaryOfType("/Page")) { | ||
| 301 | - is_page_node = true; | ||
| 302 | - if (!top) { | ||
| 303 | - return; | 303 | + if (cur.oh.isDictionaryOfType("/Page")) { |
| 304 | + is_page_node = true; | ||
| 305 | + if (!cur.top) { | ||
| 306 | + continue; | ||
| 307 | + } | ||
| 304 | } | 308 | } |
| 305 | - } | ||
| 306 | 309 | ||
| 307 | - if (oh.isIndirect()) { | ||
| 308 | - QPDFObjGen og(oh.getObjGen()); | ||
| 309 | - if (!visited.add(og)) { | ||
| 310 | - QTC::TC("qpdf", "QPDF opt loop detected"); | ||
| 311 | - return; | 310 | + if (cur.oh.isIndirect()) { |
| 311 | + QPDFObjGen og(cur.oh.getObjGen()); | ||
| 312 | + if (!visited.add(og)) { | ||
| 313 | + QTC::TC("qpdf", "QPDF opt loop detected"); | ||
| 314 | + continue; | ||
| 315 | + } | ||
| 316 | + m->obj_user_to_objects[cur.ou].insert(og); | ||
| 317 | + m->object_to_obj_users[og].insert(cur.ou); | ||
| 312 | } | 318 | } |
| 313 | - m->obj_user_to_objects[ou].insert(og); | ||
| 314 | - m->object_to_obj_users[og].insert(ou); | ||
| 315 | - } | ||
| 316 | 319 | ||
| 317 | - if (oh.isArray()) { | ||
| 318 | - int n = oh.getArrayNItems(); | ||
| 319 | - for (int i = 0; i < n; ++i) { | ||
| 320 | - updateObjectMapsInternal( | ||
| 321 | - ou, oh.getArrayItem(i), skip_stream_parameters, visited, false); | ||
| 322 | - } | ||
| 323 | - } else if (oh.isDictionary() || oh.isStream()) { | ||
| 324 | - QPDFObjectHandle dict = oh; | ||
| 325 | - bool is_stream = oh.isStream(); | ||
| 326 | - int ssp = 0; | ||
| 327 | - if (is_stream) { | ||
| 328 | - dict = oh.getDict(); | ||
| 329 | - if (skip_stream_parameters) { | ||
| 330 | - ssp = skip_stream_parameters(oh); | 320 | + if (cur.oh.isArray()) { |
| 321 | + int n = cur.oh.getArrayNItems(); | ||
| 322 | + for (int i = 0; i < n; ++i) { | ||
| 323 | + pending.emplace_back(cur.ou, cur.oh.getArrayItem(i), false); | ||
| 324 | + } | ||
| 325 | + } else if (cur.oh.isDictionary() || cur.oh.isStream()) { | ||
| 326 | + QPDFObjectHandle dict = cur.oh; | ||
| 327 | + bool is_stream = cur.oh.isStream(); | ||
| 328 | + int ssp = 0; | ||
| 329 | + if (is_stream) { | ||
| 330 | + dict = cur.oh.getDict(); | ||
| 331 | + if (skip_stream_parameters) { | ||
| 332 | + ssp = skip_stream_parameters(cur.oh); | ||
| 333 | + } | ||
| 331 | } | 334 | } |
| 332 | - } | ||
| 333 | 335 | ||
| 334 | - for (auto const& key: dict.getKeys()) { | ||
| 335 | - if (is_page_node && (key == "/Thumb")) { | ||
| 336 | - // Traverse page thumbnail dictionaries as a special case. | ||
| 337 | - updateObjectMapsInternal( | ||
| 338 | - ObjUser(ObjUser::ou_thumb, ou.pageno), | ||
| 339 | - dict.getKey(key), | ||
| 340 | - skip_stream_parameters, | ||
| 341 | - visited, | ||
| 342 | - false); | ||
| 343 | - } else if (is_page_node && (key == "/Parent")) { | ||
| 344 | - // Don't traverse back up the page tree | ||
| 345 | - } else if ( | ||
| 346 | - ((ssp >= 1) && (key == "/Length")) || | ||
| 347 | - ((ssp >= 2) && ((key == "/Filter") || (key == "/DecodeParms")))) { | ||
| 348 | - // Don't traverse into stream parameters that we are not going to write. | ||
| 349 | - } else { | ||
| 350 | - updateObjectMapsInternal( | ||
| 351 | - ou, dict.getKey(key), skip_stream_parameters, visited, false); | 336 | + for (auto const& key: dict.getKeys()) { |
| 337 | + if (is_page_node && (key == "/Thumb")) { | ||
| 338 | + // Traverse page thumbnail dictionaries as a special case. There can only ever | ||
| 339 | + // be one /Thumb key on a page, and we see at most one page node per call. | ||
| 340 | + thumb_ou = std::make_unique<ObjUser>(ObjUser::ou_thumb, cur.ou.pageno); | ||
| 341 | + pending.emplace_back(*thumb_ou, dict.getKey(key), false); | ||
| 342 | + } else if (is_page_node && (key == "/Parent")) { | ||
| 343 | + // Don't traverse back up the page tree | ||
| 344 | + } else if ( | ||
| 345 | + ((ssp >= 1) && (key == "/Length")) || | ||
| 346 | + ((ssp >= 2) && ((key == "/Filter") || (key == "/DecodeParms")))) { | ||
| 347 | + // Don't traverse into stream parameters that we are not going to write. | ||
| 348 | + } else { | ||
| 349 | + pending.emplace_back( | ||
| 350 | + cur.ou, dict.getKey(key), false); | ||
| 351 | + } | ||
| 352 | } | 352 | } |
| 353 | } | 353 | } |
| 354 | } | 354 | } |