Commit a85635b839755765713eb27d767f42b357056b2e
Committed by
GitHub
Merge pull request #929 from m-holger/ogguard
Add new convenience class QPDFObjGen::Guard
Showing
17 changed files
with
357 additions
and
342 deletions
include/qpdf/QPDF.hh
| @@ -1017,7 +1017,7 @@ class QPDF | @@ -1017,7 +1017,7 @@ class QPDF | ||
| 1017 | public: | 1017 | public: |
| 1018 | std::map<QPDFObjGen, QPDFObjectHandle> object_map; | 1018 | std::map<QPDFObjGen, QPDFObjectHandle> object_map; |
| 1019 | std::vector<QPDFObjectHandle> to_copy; | 1019 | std::vector<QPDFObjectHandle> to_copy; |
| 1020 | - std::set<QPDFObjGen> visiting; | 1020 | + QPDFObjGen::set visiting; |
| 1021 | }; | 1021 | }; |
| 1022 | 1022 | ||
| 1023 | class EncryptionParameters | 1023 | class EncryptionParameters |
| @@ -1252,8 +1252,8 @@ class QPDF | @@ -1252,8 +1252,8 @@ class QPDF | ||
| 1252 | 1252 | ||
| 1253 | void getAllPagesInternal( | 1253 | void getAllPagesInternal( |
| 1254 | QPDFObjectHandle cur_pages, | 1254 | QPDFObjectHandle cur_pages, |
| 1255 | - std::set<QPDFObjGen>& visited, | ||
| 1256 | - std::set<QPDFObjGen>& seen); | 1255 | + QPDFObjGen::set& visited, |
| 1256 | + QPDFObjGen::set& seen); | ||
| 1257 | void insertPage(QPDFObjectHandle newpage, int pos); | 1257 | void insertPage(QPDFObjectHandle newpage, int pos); |
| 1258 | void flattenPagesTree(); | 1258 | void flattenPagesTree(); |
| 1259 | void insertPageobjToPage( | 1259 | void insertPageobjToPage( |
| @@ -1645,7 +1645,7 @@ class QPDF | @@ -1645,7 +1645,7 @@ class QPDF | ||
| 1645 | ObjUser const& ou, | 1645 | ObjUser const& ou, |
| 1646 | QPDFObjectHandle oh, | 1646 | QPDFObjectHandle oh, |
| 1647 | std::function<int(QPDFObjectHandle&)> skip_stream_parameters, | 1647 | std::function<int(QPDFObjectHandle&)> skip_stream_parameters, |
| 1648 | - std::set<QPDFObjGen>& visited, | 1648 | + QPDFObjGen::set& visited, |
| 1649 | bool top); | 1649 | bool top); |
| 1650 | void filterCompressedObjects(std::map<int, int> const& object_stream_data); | 1650 | void filterCompressedObjects(std::map<int, int> const& object_stream_data); |
| 1651 | 1651 |
include/qpdf/QPDFAcroFormDocumentHelper.hh
| @@ -254,7 +254,7 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper | @@ -254,7 +254,7 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper | ||
| 254 | QPDFObjectHandle field, | 254 | QPDFObjectHandle field, |
| 255 | QPDFObjectHandle parent, | 255 | QPDFObjectHandle parent, |
| 256 | int depth, | 256 | int depth, |
| 257 | - std::set<QPDFObjGen>& visited); | 257 | + QPDFObjGen::set& visited); |
| 258 | QPDFObjectHandle getOrCreateAcroForm(); | 258 | QPDFObjectHandle getOrCreateAcroForm(); |
| 259 | void adjustInheritedFields( | 259 | void adjustInheritedFields( |
| 260 | QPDFObjectHandle obj, | 260 | QPDFObjectHandle obj, |
include/qpdf/QPDFJob.hh
| @@ -571,7 +571,7 @@ class QPDFJob | @@ -571,7 +571,7 @@ class QPDFJob | ||
| 571 | 571 | ||
| 572 | // JSON | 572 | // JSON |
| 573 | void doJSON(QPDF& pdf, Pipeline*); | 573 | void doJSON(QPDF& pdf, Pipeline*); |
| 574 | - std::set<QPDFObjGen> getWantedJSONObjects(); | 574 | + QPDFObjGen::set getWantedJSONObjects(); |
| 575 | void doJSONObject( | 575 | void doJSONObject( |
| 576 | Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle&); | 576 | Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle&); |
| 577 | void doJSONObjects(Pipeline* p, bool& first, QPDF& pdf); | 577 | void doJSONObjects(Pipeline* p, bool& first, QPDF& pdf); |
include/qpdf/QPDFObjGen.hh
| @@ -24,6 +24,10 @@ | @@ -24,6 +24,10 @@ | ||
| 24 | 24 | ||
| 25 | #include <qpdf/DLL.h> | 25 | #include <qpdf/DLL.h> |
| 26 | #include <iostream> | 26 | #include <iostream> |
| 27 | +#include <set> | ||
| 28 | + | ||
| 29 | +class QPDFObjectHandle; | ||
| 30 | +class QPDFObjectHelper; | ||
| 27 | 31 | ||
| 28 | // This class represents an object ID and generation pair. It is | 32 | // This class represents an object ID and generation pair. It is |
| 29 | // suitable to use as a key in a map or set. | 33 | // suitable to use as a key in a map or set. |
| @@ -31,6 +35,7 @@ | @@ -31,6 +35,7 @@ | ||
| 31 | class QPDFObjGen | 35 | class QPDFObjGen |
| 32 | { | 36 | { |
| 33 | public: | 37 | public: |
| 38 | + // ABI: change to default. | ||
| 34 | QPDF_DLL | 39 | QPDF_DLL |
| 35 | QPDFObjGen() : | 40 | QPDFObjGen() : |
| 36 | obj(0), | 41 | obj(0), |
| @@ -84,12 +89,72 @@ class QPDFObjGen | @@ -84,12 +89,72 @@ class QPDFObjGen | ||
| 84 | QPDF_DLL | 89 | QPDF_DLL |
| 85 | friend std::ostream& operator<<(std::ostream& os, const QPDFObjGen& og); | 90 | friend std::ostream& operator<<(std::ostream& os, const QPDFObjGen& og); |
| 86 | 91 | ||
| 92 | + // Convenience class for loop detection when processing objects. | ||
| 93 | + // | ||
| 94 | + // The class adds 'add' methods to a std::set<QPDFObjGen> which allows | ||
| 95 | + // to test whether an QPDFObjGen is present in the set and to insert it in | ||
| 96 | + // a single operation. The 'add' method is overloaded to take a QPDFObjGen, | ||
| 97 | + // QPDFObjectHandle or an QPDFObjectHelper as parameter. | ||
| 98 | + // | ||
| 99 | + // The erase method is modified to ignore requests to erase | ||
| 100 | + // QPDFObjGen(0, 0). | ||
| 101 | + // | ||
| 102 | + // Usage example: | ||
| 103 | + // | ||
| 104 | + // void process_object(QPDFObjectHandle oh, QPDFObjGen::Tracker& seen) | ||
| 105 | + // { | ||
| 106 | + // if (seen.add(oh)) { | ||
| 107 | + // // handle first encounter of oh | ||
| 108 | + // } else { | ||
| 109 | + // // handle loop / subsequent encounter of oh | ||
| 110 | + // } | ||
| 111 | + // } | ||
| 112 | + class QPDF_DLL_CLASS set: public std::set<QPDFObjGen> | ||
| 113 | + { | ||
| 114 | + public: | ||
| 115 | + // Add 'og' to the set. Return false if 'og' is already present in | ||
| 116 | + // the set. Attempts to insert QPDFObjGen(0, 0) are ignored. | ||
| 117 | + QPDF_DLL | ||
| 118 | + bool | ||
| 119 | + add(QPDFObjGen og) | ||
| 120 | + { | ||
| 121 | + if (og.isIndirect()) { | ||
| 122 | + if (count(og) > 0) { | ||
| 123 | + return false; | ||
| 124 | + } | ||
| 125 | + emplace(og); | ||
| 126 | + } | ||
| 127 | + return true; | ||
| 128 | + } | ||
| 129 | + | ||
| 130 | + QPDF_DLL | ||
| 131 | + bool add(QPDFObjectHandle const& oh); | ||
| 132 | + | ||
| 133 | + QPDF_DLL | ||
| 134 | + bool add(QPDFObjectHelper const& oh); | ||
| 135 | + | ||
| 136 | + QPDF_DLL | ||
| 137 | + void | ||
| 138 | + erase(QPDFObjGen og) | ||
| 139 | + { | ||
| 140 | + if (og.isIndirect()) { | ||
| 141 | + std::set<QPDFObjGen>::erase(og); | ||
| 142 | + } | ||
| 143 | + } | ||
| 144 | + | ||
| 145 | + QPDF_DLL | ||
| 146 | + void erase(QPDFObjectHandle const& oh); | ||
| 147 | + | ||
| 148 | + QPDF_DLL | ||
| 149 | + void erase(QPDFObjectHelper const& oh); | ||
| 150 | + }; | ||
| 151 | + | ||
| 87 | private: | 152 | private: |
| 88 | // This class does not use the Members pattern to avoid a memory | 153 | // This class does not use the Members pattern to avoid a memory |
| 89 | // allocation for every one of these. A lot of these get created | 154 | // allocation for every one of these. A lot of these get created |
| 90 | // and destroyed. | 155 | // and destroyed. |
| 91 | - int obj; | ||
| 92 | - int gen; | 156 | + int obj{0}; |
| 157 | + int gen{0}; | ||
| 93 | }; | 158 | }; |
| 94 | 159 | ||
| 95 | #endif // QPDFOBJGEN_HH | 160 | #endif // QPDFOBJGEN_HH |
include/qpdf/QPDFObjectHandle.hh
| @@ -1611,7 +1611,7 @@ class QPDFObjectHandle | @@ -1611,7 +1611,7 @@ class QPDFObjectHandle | ||
| 1611 | void objectWarning(std::string const& warning); | 1611 | void objectWarning(std::string const& warning); |
| 1612 | void assertType(char const* type_name, bool istype); | 1612 | void assertType(char const* type_name, bool istype); |
| 1613 | inline bool dereference(); | 1613 | inline bool dereference(); |
| 1614 | - void makeDirect(std::set<QPDFObjGen>& visited, bool stop_at_streams); | 1614 | + void makeDirect(QPDFObjGen::set& visited, bool stop_at_streams); |
| 1615 | void disconnect(); | 1615 | void disconnect(); |
| 1616 | void setParsedOffset(qpdf_offset_t offset); | 1616 | void setParsedOffset(qpdf_offset_t offset); |
| 1617 | void parseContentStream_internal( | 1617 | void parseContentStream_internal( |
include/qpdf/QPDFOutlineDocumentHelper.hh
| @@ -22,13 +22,13 @@ | @@ -22,13 +22,13 @@ | ||
| 22 | #ifndef QPDFOUTLINEDOCUMENTHELPER_HH | 22 | #ifndef QPDFOUTLINEDOCUMENTHELPER_HH |
| 23 | #define QPDFOUTLINEDOCUMENTHELPER_HH | 23 | #define QPDFOUTLINEDOCUMENTHELPER_HH |
| 24 | 24 | ||
| 25 | +#include <qpdf/QPDF.hh> | ||
| 25 | #include <qpdf/QPDFDocumentHelper.hh> | 26 | #include <qpdf/QPDFDocumentHelper.hh> |
| 26 | #include <qpdf/QPDFNameTreeObjectHelper.hh> | 27 | #include <qpdf/QPDFNameTreeObjectHelper.hh> |
| 28 | +#include <qpdf/QPDFObjGen.hh> | ||
| 27 | #include <qpdf/QPDFOutlineObjectHelper.hh> | 29 | #include <qpdf/QPDFOutlineObjectHelper.hh> |
| 28 | 30 | ||
| 29 | -#include <qpdf/QPDF.hh> | ||
| 30 | #include <map> | 31 | #include <map> |
| 31 | -#include <set> | ||
| 32 | #include <vector> | 32 | #include <vector> |
| 33 | 33 | ||
| 34 | #include <qpdf/DLL.h> | 34 | #include <qpdf/DLL.h> |
| @@ -69,16 +69,16 @@ class QPDFOutlineDocumentHelper: public QPDFDocumentHelper | @@ -69,16 +69,16 @@ class QPDFOutlineDocumentHelper: public QPDFDocumentHelper | ||
| 69 | { | 69 | { |
| 70 | friend class QPDFOutlineObjectHelper; | 70 | friend class QPDFOutlineObjectHelper; |
| 71 | 71 | ||
| 72 | + // ABI: remove QPDF_DLL and pass og by value. | ||
| 72 | QPDF_DLL | 73 | QPDF_DLL |
| 73 | static bool | 74 | static bool |
| 74 | checkSeen(QPDFOutlineDocumentHelper& dh, QPDFObjGen const& og) | 75 | checkSeen(QPDFOutlineDocumentHelper& dh, QPDFObjGen const& og) |
| 75 | { | 76 | { |
| 76 | - return dh.checkSeen(og); | 77 | + return !dh.m->seen.add(og); |
| 77 | } | 78 | } |
| 78 | }; | 79 | }; |
| 79 | 80 | ||
| 80 | private: | 81 | private: |
| 81 | - bool checkSeen(QPDFObjGen const& og); | ||
| 82 | void initializeByPage(); | 82 | void initializeByPage(); |
| 83 | 83 | ||
| 84 | class Members | 84 | class Members |
| @@ -94,7 +94,7 @@ class QPDFOutlineDocumentHelper: public QPDFDocumentHelper | @@ -94,7 +94,7 @@ class QPDFOutlineDocumentHelper: public QPDFDocumentHelper | ||
| 94 | Members(Members const&) = delete; | 94 | Members(Members const&) = delete; |
| 95 | 95 | ||
| 96 | std::vector<QPDFOutlineObjectHelper> outlines; | 96 | std::vector<QPDFOutlineObjectHelper> outlines; |
| 97 | - std::set<QPDFObjGen> seen; | 97 | + QPDFObjGen::set seen; |
| 98 | QPDFObjectHandle dest_dict; | 98 | QPDFObjectHandle dest_dict; |
| 99 | std::shared_ptr<QPDFNameTreeObjectHelper> names_dest; | 99 | std::shared_ptr<QPDFNameTreeObjectHelper> names_dest; |
| 100 | std::map<QPDFObjGen, std::vector<QPDFOutlineObjectHelper>> by_page; | 100 | std::map<QPDFObjGen, std::vector<QPDFOutlineObjectHelper>> by_page; |
libqpdf/NNTree.cc
| @@ -638,26 +638,21 @@ NNTreeIterator::deepen(QPDFObjectHandle node, bool first, bool allow_empty) | @@ -638,26 +638,21 @@ NNTreeIterator::deepen(QPDFObjectHandle node, bool first, bool allow_empty) | ||
| 638 | auto opath = this->path; | 638 | auto opath = this->path; |
| 639 | bool failed = false; | 639 | bool failed = false; |
| 640 | 640 | ||
| 641 | - std::set<QPDFObjGen> seen; | 641 | + QPDFObjGen::set seen; |
| 642 | for (auto i: this->path) { | 642 | for (auto i: this->path) { |
| 643 | - if (i.node.isIndirect()) { | ||
| 644 | - seen.insert(i.node.getObjGen()); | ||
| 645 | - } | 643 | + seen.add(i.node); |
| 646 | } | 644 | } |
| 647 | while (!failed) { | 645 | while (!failed) { |
| 648 | - if (node.isIndirect()) { | ||
| 649 | - auto og = node.getObjGen(); | ||
| 650 | - if (seen.count(og)) { | ||
| 651 | - QTC::TC("qpdf", "NNTree deepen: loop"); | ||
| 652 | - warn( | ||
| 653 | - impl.qpdf, | ||
| 654 | - node, | ||
| 655 | - "loop detected while traversing name/number tree"); | ||
| 656 | - failed = true; | ||
| 657 | - break; | ||
| 658 | - } | ||
| 659 | - seen.insert(og); | 646 | + if (!seen.add(node)) { |
| 647 | + QTC::TC("qpdf", "NNTree deepen: loop"); | ||
| 648 | + warn( | ||
| 649 | + impl.qpdf, | ||
| 650 | + node, | ||
| 651 | + "loop detected while traversing name/number tree"); | ||
| 652 | + failed = true; | ||
| 653 | + break; | ||
| 660 | } | 654 | } |
| 655 | + | ||
| 661 | if (!node.isDictionary()) { | 656 | if (!node.isDictionary()) { |
| 662 | QTC::TC("qpdf", "NNTree node is not a dictionary"); | 657 | QTC::TC("qpdf", "NNTree node is not a dictionary"); |
| 663 | warn( | 658 | warn( |
| @@ -928,17 +923,15 @@ NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found) | @@ -928,17 +923,15 @@ NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found) | ||
| 928 | } | 923 | } |
| 929 | } | 924 | } |
| 930 | 925 | ||
| 931 | - std::set<QPDFObjGen> seen; | 926 | + QPDFObjGen::set seen; |
| 932 | auto node = this->oh; | 927 | auto node = this->oh; |
| 933 | iterator result(*this); | 928 | iterator result(*this); |
| 934 | 929 | ||
| 935 | while (true) { | 930 | while (true) { |
| 936 | - auto og = node.getObjGen(); | ||
| 937 | - if (seen.count(og)) { | 931 | + if (!seen.add(node)) { |
| 938 | QTC::TC("qpdf", "NNTree loop in find"); | 932 | QTC::TC("qpdf", "NNTree loop in find"); |
| 939 | error(qpdf, node, "loop detected in find"); | 933 | error(qpdf, node, "loop detected in find"); |
| 940 | } | 934 | } |
| 941 | - seen.insert(og); | ||
| 942 | 935 | ||
| 943 | auto kids = node.getKey("/Kids"); | 936 | auto kids = node.getKey("/Kids"); |
| 944 | int nkids = kids.isArray() ? kids.getArrayNItems() : 0; | 937 | int nkids = kids.isArray() ? kids.getArrayNItems() : 0; |
libqpdf/QPDF.cc
| @@ -2176,7 +2176,8 @@ QPDF::copyForeignObject(QPDFObjectHandle foreign) | @@ -2176,7 +2176,8 @@ QPDF::copyForeignObject(QPDFObjectHandle foreign) | ||
| 2176 | void | 2176 | void |
| 2177 | QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top) | 2177 | QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top) |
| 2178 | { | 2178 | { |
| 2179 | - if (foreign.isReserved()) { | 2179 | + auto foreign_tc = foreign.getTypeCode(); |
| 2180 | + if (foreign_tc == ::ot_reserved) { | ||
| 2180 | throw std::logic_error( | 2181 | throw std::logic_error( |
| 2181 | "QPDF: attempting to copy a foreign reserved object"); | 2182 | "QPDF: attempting to copy a foreign reserved object"); |
| 2182 | } | 2183 | } |
| @@ -2193,70 +2194,62 @@ QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top) | @@ -2193,70 +2194,62 @@ QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top) | ||
| 2193 | 2194 | ||
| 2194 | if (foreign.isIndirect()) { | 2195 | if (foreign.isIndirect()) { |
| 2195 | QPDFObjGen foreign_og(foreign.getObjGen()); | 2196 | QPDFObjGen foreign_og(foreign.getObjGen()); |
| 2196 | - if (obj_copier.visiting.find(foreign_og) != obj_copier.visiting.end()) { | ||
| 2197 | - QTC::TC("qpdf", "QPDF loop reserving objects"); | 2197 | + if (obj_copier.object_map.count(foreign_og) > 0) { |
| 2198 | + QTC::TC("qpdf", "QPDF already reserved object"); | ||
| 2199 | + if (obj_copier.visiting.count(foreign_og)) { | ||
| 2200 | + QTC::TC("qpdf", "QPDF loop reserving objects"); | ||
| 2201 | + } | ||
| 2198 | return; | 2202 | return; |
| 2199 | } | 2203 | } |
| 2200 | - if (obj_copier.object_map.find(foreign_og) != | ||
| 2201 | - obj_copier.object_map.end()) { | ||
| 2202 | - QTC::TC("qpdf", "QPDF already reserved object"); | 2204 | + if (!obj_copier.visiting.add(foreign_og)) { |
| 2203 | return; | 2205 | return; |
| 2204 | } | 2206 | } |
| 2205 | QTC::TC("qpdf", "QPDF copy indirect"); | 2207 | QTC::TC("qpdf", "QPDF copy indirect"); |
| 2206 | - obj_copier.visiting.insert(foreign_og); | ||
| 2207 | - auto mapping = obj_copier.object_map.find(foreign_og); | ||
| 2208 | - if (mapping == obj_copier.object_map.end()) { | 2208 | + if (obj_copier.object_map.count(foreign_og) == 0) { |
| 2209 | obj_copier.to_copy.push_back(foreign); | 2209 | obj_copier.to_copy.push_back(foreign); |
| 2210 | - QPDFObjectHandle reservation; | ||
| 2211 | - if (foreign.isStream()) { | ||
| 2212 | - reservation = newStream(); | ||
| 2213 | - } else { | ||
| 2214 | - reservation = QPDFObjectHandle::newReserved(this); | ||
| 2215 | - } | ||
| 2216 | - obj_copier.object_map[foreign_og] = reservation; | 2210 | + obj_copier.object_map[foreign_og] = foreign.isStream() |
| 2211 | + ? newStream() | ||
| 2212 | + : QPDFObjectHandle::newReserved(this); | ||
| 2217 | } | 2213 | } |
| 2218 | } | 2214 | } |
| 2219 | 2215 | ||
| 2220 | - if (foreign.isArray()) { | 2216 | + if (foreign_tc == ::ot_array) { |
| 2221 | QTC::TC("qpdf", "QPDF reserve array"); | 2217 | QTC::TC("qpdf", "QPDF reserve array"); |
| 2222 | int n = foreign.getArrayNItems(); | 2218 | int n = foreign.getArrayNItems(); |
| 2223 | for (int i = 0; i < n; ++i) { | 2219 | for (int i = 0; i < n; ++i) { |
| 2224 | reserveObjects(foreign.getArrayItem(i), obj_copier, false); | 2220 | reserveObjects(foreign.getArrayItem(i), obj_copier, false); |
| 2225 | } | 2221 | } |
| 2226 | - } else if (foreign.isDictionary()) { | 2222 | + } else if (foreign_tc == ::ot_dictionary) { |
| 2227 | QTC::TC("qpdf", "QPDF reserve dictionary"); | 2223 | QTC::TC("qpdf", "QPDF reserve dictionary"); |
| 2228 | for (auto const& key: foreign.getKeys()) { | 2224 | for (auto const& key: foreign.getKeys()) { |
| 2229 | reserveObjects(foreign.getKey(key), obj_copier, false); | 2225 | reserveObjects(foreign.getKey(key), obj_copier, false); |
| 2230 | } | 2226 | } |
| 2231 | - } else if (foreign.isStream()) { | 2227 | + } else if (foreign_tc == ::ot_stream) { |
| 2232 | QTC::TC("qpdf", "QPDF reserve stream"); | 2228 | QTC::TC("qpdf", "QPDF reserve stream"); |
| 2233 | reserveObjects(foreign.getDict(), obj_copier, false); | 2229 | reserveObjects(foreign.getDict(), obj_copier, false); |
| 2234 | } | 2230 | } |
| 2235 | 2231 | ||
| 2236 | - if (foreign.isIndirect()) { | ||
| 2237 | - QPDFObjGen foreign_og(foreign.getObjGen()); | ||
| 2238 | - obj_copier.visiting.erase(foreign_og); | ||
| 2239 | - } | 2232 | + obj_copier.visiting.erase(foreign); |
| 2240 | } | 2233 | } |
| 2241 | 2234 | ||
| 2242 | QPDFObjectHandle | 2235 | QPDFObjectHandle |
| 2243 | QPDF::replaceForeignIndirectObjects( | 2236 | QPDF::replaceForeignIndirectObjects( |
| 2244 | QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top) | 2237 | QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top) |
| 2245 | { | 2238 | { |
| 2239 | + auto foreign_tc = foreign.getTypeCode(); | ||
| 2246 | QPDFObjectHandle result; | 2240 | QPDFObjectHandle result; |
| 2247 | if ((!top) && foreign.isIndirect()) { | 2241 | if ((!top) && foreign.isIndirect()) { |
| 2248 | QTC::TC("qpdf", "QPDF replace indirect"); | 2242 | QTC::TC("qpdf", "QPDF replace indirect"); |
| 2249 | - QPDFObjGen foreign_og(foreign.getObjGen()); | ||
| 2250 | - auto mapping = obj_copier.object_map.find(foreign_og); | 2243 | + auto mapping = obj_copier.object_map.find(foreign.getObjGen()); |
| 2251 | if (mapping == obj_copier.object_map.end()) { | 2244 | if (mapping == obj_copier.object_map.end()) { |
| 2252 | // This case would occur if this is a reference to a Page | 2245 | // This case would occur if this is a reference to a Page |
| 2253 | // or Pages object that we didn't traverse into. | 2246 | // or Pages object that we didn't traverse into. |
| 2254 | QTC::TC("qpdf", "QPDF replace foreign indirect with null"); | 2247 | QTC::TC("qpdf", "QPDF replace foreign indirect with null"); |
| 2255 | result = QPDFObjectHandle::newNull(); | 2248 | result = QPDFObjectHandle::newNull(); |
| 2256 | } else { | 2249 | } else { |
| 2257 | - result = obj_copier.object_map[foreign_og]; | 2250 | + result = mapping->second; |
| 2258 | } | 2251 | } |
| 2259 | - } else if (foreign.isArray()) { | 2252 | + } else if (foreign_tc == ::ot_array) { |
| 2260 | QTC::TC("qpdf", "QPDF replace array"); | 2253 | QTC::TC("qpdf", "QPDF replace array"); |
| 2261 | result = QPDFObjectHandle::newArray(); | 2254 | result = QPDFObjectHandle::newArray(); |
| 2262 | int n = foreign.getArrayNItems(); | 2255 | int n = foreign.getArrayNItems(); |
| @@ -2266,7 +2259,7 @@ QPDF::replaceForeignIndirectObjects( | @@ -2266,7 +2259,7 @@ QPDF::replaceForeignIndirectObjects( | ||
| 2266 | replaceForeignIndirectObjects( | 2259 | replaceForeignIndirectObjects( |
| 2267 | foreign.getArrayItem(i), obj_copier, false)); | 2260 | foreign.getArrayItem(i), obj_copier, false)); |
| 2268 | } | 2261 | } |
| 2269 | - } else if (foreign.isDictionary()) { | 2262 | + } else if (foreign_tc == ::ot_dictionary) { |
| 2270 | QTC::TC("qpdf", "QPDF replace dictionary"); | 2263 | QTC::TC("qpdf", "QPDF replace dictionary"); |
| 2271 | result = QPDFObjectHandle::newDictionary(); | 2264 | result = QPDFObjectHandle::newDictionary(); |
| 2272 | std::set<std::string> keys = foreign.getKeys(); | 2265 | std::set<std::string> keys = foreign.getKeys(); |
| @@ -2276,10 +2269,9 @@ QPDF::replaceForeignIndirectObjects( | @@ -2276,10 +2269,9 @@ QPDF::replaceForeignIndirectObjects( | ||
| 2276 | replaceForeignIndirectObjects( | 2269 | replaceForeignIndirectObjects( |
| 2277 | foreign.getKey(iter), obj_copier, false)); | 2270 | foreign.getKey(iter), obj_copier, false)); |
| 2278 | } | 2271 | } |
| 2279 | - } else if (foreign.isStream()) { | 2272 | + } else if (foreign_tc == ::ot_stream) { |
| 2280 | QTC::TC("qpdf", "QPDF replace stream"); | 2273 | QTC::TC("qpdf", "QPDF replace stream"); |
| 2281 | - QPDFObjGen foreign_og(foreign.getObjGen()); | ||
| 2282 | - result = obj_copier.object_map[foreign_og]; | 2274 | + result = obj_copier.object_map[foreign.getObjGen()]; |
| 2283 | result.assertStream(); | 2275 | result.assertStream(); |
| 2284 | QPDFObjectHandle dict = result.getDict(); | 2276 | QPDFObjectHandle dict = result.getDict(); |
| 2285 | QPDFObjectHandle old_dict = foreign.getDict(); | 2277 | QPDFObjectHandle old_dict = foreign.getDict(); |
| @@ -2511,7 +2503,7 @@ QPDF::getCompressibleObjGens() | @@ -2511,7 +2503,7 @@ QPDF::getCompressibleObjGens() | ||
| 2511 | QPDFObjectHandle encryption_dict = this->m->trailer.getKey("/Encrypt"); | 2503 | QPDFObjectHandle encryption_dict = this->m->trailer.getKey("/Encrypt"); |
| 2512 | QPDFObjGen encryption_dict_og = encryption_dict.getObjGen(); | 2504 | QPDFObjGen encryption_dict_og = encryption_dict.getObjGen(); |
| 2513 | 2505 | ||
| 2514 | - std::set<QPDFObjGen> visited; | 2506 | + QPDFObjGen::set visited; |
| 2515 | std::list<QPDFObjectHandle> queue; | 2507 | std::list<QPDFObjectHandle> queue; |
| 2516 | queue.push_front(this->m->trailer); | 2508 | queue.push_front(this->m->trailer); |
| 2517 | std::vector<QPDFObjGen> result; | 2509 | std::vector<QPDFObjGen> result; |
| @@ -2520,7 +2512,7 @@ QPDF::getCompressibleObjGens() | @@ -2520,7 +2512,7 @@ QPDF::getCompressibleObjGens() | ||
| 2520 | queue.pop_front(); | 2512 | queue.pop_front(); |
| 2521 | if (obj.isIndirect()) { | 2513 | if (obj.isIndirect()) { |
| 2522 | QPDFObjGen og = obj.getObjGen(); | 2514 | QPDFObjGen og = obj.getObjGen(); |
| 2523 | - if (visited.count(og)) { | 2515 | + if (!visited.add(og)) { |
| 2524 | QTC::TC("qpdf", "QPDF loop detected traversing objects"); | 2516 | QTC::TC("qpdf", "QPDF loop detected traversing objects"); |
| 2525 | continue; | 2517 | continue; |
| 2526 | } | 2518 | } |
| @@ -2532,7 +2524,6 @@ QPDF::getCompressibleObjGens() | @@ -2532,7 +2524,6 @@ QPDF::getCompressibleObjGens() | ||
| 2532 | obj.hasKey("/Contents")))) { | 2524 | obj.hasKey("/Contents")))) { |
| 2533 | result.push_back(og); | 2525 | result.push_back(og); |
| 2534 | } | 2526 | } |
| 2535 | - visited.insert(og); | ||
| 2536 | } | 2527 | } |
| 2537 | if (obj.isStream()) { | 2528 | if (obj.isStream()) { |
| 2538 | QPDFObjectHandle dict = obj.getDict(); | 2529 | QPDFObjectHandle dict = obj.getDict(); |
libqpdf/QPDFAcroFormDocumentHelper.cc
| @@ -57,7 +57,7 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) | @@ -57,7 +57,7 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) | ||
| 57 | "/Fields", QPDFObjectHandle::newArray()); | 57 | "/Fields", QPDFObjectHandle::newArray()); |
| 58 | } | 58 | } |
| 59 | fields.appendItem(ff.getObjectHandle()); | 59 | fields.appendItem(ff.getObjectHandle()); |
| 60 | - std::set<QPDFObjGen> visited; | 60 | + QPDFObjGen::set visited; |
| 61 | traverseField( | 61 | traverseField( |
| 62 | ff.getObjectHandle(), QPDFObjectHandle::newNull(), 0, visited); | 62 | ff.getObjectHandle(), QPDFObjectHandle::newNull(), 0, visited); |
| 63 | } | 63 | } |
| @@ -68,53 +68,48 @@ QPDFAcroFormDocumentHelper::addAndRenameFormFields( | @@ -68,53 +68,48 @@ QPDFAcroFormDocumentHelper::addAndRenameFormFields( | ||
| 68 | { | 68 | { |
| 69 | analyze(); | 69 | analyze(); |
| 70 | std::map<std::string, std::string> renames; | 70 | std::map<std::string, std::string> renames; |
| 71 | - std::list<QPDFObjectHandle> queue; | ||
| 72 | - queue.insert(queue.begin(), fields.begin(), fields.end()); | ||
| 73 | - std::set<QPDFObjGen> seen; | ||
| 74 | - while (!queue.empty()) { | ||
| 75 | - QPDFObjectHandle obj = queue.front(); | ||
| 76 | - queue.pop_front(); | ||
| 77 | - auto og = obj.getObjGen(); | ||
| 78 | - if (seen.count(og)) { | ||
| 79 | - // loop | ||
| 80 | - continue; | ||
| 81 | - } | ||
| 82 | - seen.insert(og); | ||
| 83 | - auto kids = obj.getKey("/Kids"); | ||
| 84 | - if (kids.isArray()) { | ||
| 85 | - for (auto kid: kids.aitems()) { | ||
| 86 | - queue.push_back(kid); | 71 | + QPDFObjGen::set seen; |
| 72 | + for (std::list<QPDFObjectHandle> queue{fields.begin(), fields.end()}; | ||
| 73 | + !queue.empty(); | ||
| 74 | + queue.pop_front()) { | ||
| 75 | + auto& obj = queue.front(); | ||
| 76 | + if (seen.add(obj)) { | ||
| 77 | + auto kids = obj.getKey("/Kids"); | ||
| 78 | + if (kids.isArray()) { | ||
| 79 | + for (auto kid: kids.aitems()) { | ||
| 80 | + queue.push_back(kid); | ||
| 81 | + } | ||
| 87 | } | 82 | } |
| 88 | - } | ||
| 89 | 83 | ||
| 90 | - if (obj.hasKey("/T")) { | ||
| 91 | - // Find something we can append to the partial name that | ||
| 92 | - // makes the fully qualified name unique. When we find | ||
| 93 | - // something, reuse the same suffix for all fields in this | ||
| 94 | - // group with the same name. We can only change the name | ||
| 95 | - // of fields that have /T, and this field's /T is always | ||
| 96 | - // at the end of the fully qualified name, appending to /T | ||
| 97 | - // has the effect of appending the same thing to the fully | ||
| 98 | - // qualified name. | ||
| 99 | - std::string old_name = | ||
| 100 | - QPDFFormFieldObjectHelper(obj).getFullyQualifiedName(); | ||
| 101 | - if (renames.count(old_name) == 0) { | ||
| 102 | - std::string new_name = old_name; | ||
| 103 | - int suffix = 0; | ||
| 104 | - std::string append; | ||
| 105 | - while (!getFieldsWithQualifiedName(new_name).empty()) { | ||
| 106 | - ++suffix; | ||
| 107 | - append = "+" + std::to_string(suffix); | ||
| 108 | - new_name = old_name + append; | 84 | + if (obj.hasKey("/T")) { |
| 85 | + // Find something we can append to the partial name that | ||
| 86 | + // makes the fully qualified name unique. When we find | ||
| 87 | + // something, reuse the same suffix for all fields in this | ||
| 88 | + // group with the same name. We can only change the name | ||
| 89 | + // of fields that have /T, and this field's /T is always | ||
| 90 | + // at the end of the fully qualified name, appending to /T | ||
| 91 | + // has the effect of appending the same thing to the fully | ||
| 92 | + // qualified name. | ||
| 93 | + std::string old_name = | ||
| 94 | + QPDFFormFieldObjectHelper(obj).getFullyQualifiedName(); | ||
| 95 | + if (renames.count(old_name) == 0) { | ||
| 96 | + std::string new_name = old_name; | ||
| 97 | + int suffix = 0; | ||
| 98 | + std::string append; | ||
| 99 | + while (!getFieldsWithQualifiedName(new_name).empty()) { | ||
| 100 | + ++suffix; | ||
| 101 | + append = "+" + std::to_string(suffix); | ||
| 102 | + new_name = old_name + append; | ||
| 103 | + } | ||
| 104 | + renames[old_name] = append; | ||
| 105 | + } | ||
| 106 | + std::string append = renames[old_name]; | ||
| 107 | + if (!append.empty()) { | ||
| 108 | + obj.replaceKey( | ||
| 109 | + "/T", | ||
| 110 | + QPDFObjectHandle::newUnicodeString( | ||
| 111 | + obj.getKey("/T").getUTF8Value() + append)); | ||
| 109 | } | 112 | } |
| 110 | - renames[old_name] = append; | ||
| 111 | - } | ||
| 112 | - std::string append = renames[old_name]; | ||
| 113 | - if (!append.empty()) { | ||
| 114 | - obj.replaceKey( | ||
| 115 | - "/T", | ||
| 116 | - QPDFObjectHandle::newUnicodeString( | ||
| 117 | - obj.getKey("/T").getUTF8Value() + append)); | ||
| 118 | } | 113 | } |
| 119 | } | 114 | } |
| 120 | } | 115 | } |
| @@ -172,7 +167,7 @@ QPDFAcroFormDocumentHelper::setFormFieldName( | @@ -172,7 +167,7 @@ QPDFAcroFormDocumentHelper::setFormFieldName( | ||
| 172 | QPDFFormFieldObjectHelper ff, std::string const& name) | 167 | QPDFFormFieldObjectHelper ff, std::string const& name) |
| 173 | { | 168 | { |
| 174 | ff.setFieldAttribute("/T", name); | 169 | ff.setFieldAttribute("/T", name); |
| 175 | - std::set<QPDFObjGen> visited; | 170 | + QPDFObjGen::set visited; |
| 176 | auto ff_oh = ff.getObjectHandle(); | 171 | auto ff_oh = ff.getObjectHandle(); |
| 177 | traverseField(ff_oh, ff_oh.getKey("/Parent"), 0, visited); | 172 | traverseField(ff_oh, ff_oh.getKey("/Parent"), 0, visited); |
| 178 | } | 173 | } |
| @@ -193,12 +188,11 @@ QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const& name) | @@ -193,12 +188,11 @@ QPDFAcroFormDocumentHelper::getFieldsWithQualifiedName(std::string const& name) | ||
| 193 | { | 188 | { |
| 194 | analyze(); | 189 | analyze(); |
| 195 | // Keep from creating an empty entry | 190 | // Keep from creating an empty entry |
| 196 | - std::set<QPDFObjGen> result; | ||
| 197 | auto iter = this->m->name_to_fields.find(name); | 191 | auto iter = this->m->name_to_fields.find(name); |
| 198 | if (iter != this->m->name_to_fields.end()) { | 192 | if (iter != this->m->name_to_fields.end()) { |
| 199 | - result = iter->second; | 193 | + return iter->second; |
| 200 | } | 194 | } |
| 201 | - return result; | 195 | + return {}; |
| 202 | } | 196 | } |
| 203 | 197 | ||
| 204 | std::vector<QPDFAnnotationObjectHelper> | 198 | std::vector<QPDFAnnotationObjectHelper> |
| @@ -223,18 +217,12 @@ std::vector<QPDFFormFieldObjectHelper> | @@ -223,18 +217,12 @@ std::vector<QPDFFormFieldObjectHelper> | ||
| 223 | QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph) | 217 | QPDFAcroFormDocumentHelper::getFormFieldsForPage(QPDFPageObjectHelper ph) |
| 224 | { | 218 | { |
| 225 | analyze(); | 219 | analyze(); |
| 226 | - std::set<QPDFObjGen> added; | 220 | + QPDFObjGen::set todo; |
| 227 | std::vector<QPDFFormFieldObjectHelper> result; | 221 | std::vector<QPDFFormFieldObjectHelper> result; |
| 228 | - auto widget_annotations = getWidgetAnnotationsForPage(ph); | ||
| 229 | - for (auto annot: widget_annotations) { | ||
| 230 | - auto field = getFieldForAnnotation(annot); | ||
| 231 | - field = field.getTopLevelField(); | ||
| 232 | - auto og = field.getObjectHandle().getObjGen(); | ||
| 233 | - if (!added.count(og)) { | ||
| 234 | - added.insert(og); | ||
| 235 | - if (field.getObjectHandle().isDictionary()) { | ||
| 236 | - result.push_back(field); | ||
| 237 | - } | 222 | + for (auto& annot: getWidgetAnnotationsForPage(ph)) { |
| 223 | + auto field = getFieldForAnnotation(annot).getTopLevelField(); | ||
| 224 | + if (todo.add(field) && field.getObjectHandle().isDictionary()) { | ||
| 225 | + result.push_back(field); | ||
| 238 | } | 226 | } |
| 239 | } | 227 | } |
| 240 | return result; | 228 | return result; |
| @@ -278,7 +266,7 @@ QPDFAcroFormDocumentHelper::analyze() | @@ -278,7 +266,7 @@ QPDFAcroFormDocumentHelper::analyze() | ||
| 278 | // Traverse /AcroForm to find annotations and map them | 266 | // Traverse /AcroForm to find annotations and map them |
| 279 | // bidirectionally to fields. | 267 | // bidirectionally to fields. |
| 280 | 268 | ||
| 281 | - std::set<QPDFObjGen> visited; | 269 | + QPDFObjGen::set visited; |
| 282 | int nfields = fields.getArrayNItems(); | 270 | int nfields = fields.getArrayNItems(); |
| 283 | QPDFObjectHandle null(QPDFObjectHandle::newNull()); | 271 | QPDFObjectHandle null(QPDFObjectHandle::newNull()); |
| 284 | for (int i = 0; i < nfields; ++i) { | 272 | for (int i = 0; i < nfields; ++i) { |
| @@ -324,7 +312,7 @@ QPDFAcroFormDocumentHelper::traverseField( | @@ -324,7 +312,7 @@ QPDFAcroFormDocumentHelper::traverseField( | ||
| 324 | QPDFObjectHandle field, | 312 | QPDFObjectHandle field, |
| 325 | QPDFObjectHandle parent, | 313 | QPDFObjectHandle parent, |
| 326 | int depth, | 314 | int depth, |
| 327 | - std::set<QPDFObjGen>& visited) | 315 | + QPDFObjGen::set& visited) |
| 328 | { | 316 | { |
| 329 | if (depth > 100) { | 317 | if (depth > 100) { |
| 330 | // Arbitrarily cut off recursion at a fixed depth to avoid | 318 | // Arbitrarily cut off recursion at a fixed depth to avoid |
| @@ -346,12 +334,11 @@ QPDFAcroFormDocumentHelper::traverseField( | @@ -346,12 +334,11 @@ QPDFAcroFormDocumentHelper::traverseField( | ||
| 346 | return; | 334 | return; |
| 347 | } | 335 | } |
| 348 | QPDFObjGen og(field.getObjGen()); | 336 | QPDFObjGen og(field.getObjGen()); |
| 349 | - if (visited.count(og) != 0) { | 337 | + if (!visited.add(og)) { |
| 350 | QTC::TC("qpdf", "QPDFAcroFormDocumentHelper loop"); | 338 | QTC::TC("qpdf", "QPDFAcroFormDocumentHelper loop"); |
| 351 | field.warnIfPossible("loop detected while traversing /AcroForm"); | 339 | field.warnIfPossible("loop detected while traversing /AcroForm"); |
| 352 | return; | 340 | return; |
| 353 | } | 341 | } |
| 354 | - visited.insert(og); | ||
| 355 | 342 | ||
| 356 | // A dictionary encountered while traversing the /AcroForm field | 343 | // A dictionary encountered while traversing the /AcroForm field |
| 357 | // may be a form field, an annotation, or the merger of the two. A | 344 | // may be a form field, an annotation, or the merger of the two. A |
| @@ -888,7 +875,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | @@ -888,7 +875,7 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | ||
| 888 | 875 | ||
| 889 | // Now do the actual copies. | 876 | // Now do the actual copies. |
| 890 | 877 | ||
| 891 | - std::set<QPDFObjGen> added_new_fields; | 878 | + QPDFObjGen::set added_new_fields; |
| 892 | for (auto annot: old_annots.aitems()) { | 879 | for (auto annot: old_annots.aitems()) { |
| 893 | if (annot.isStream()) { | 880 | if (annot.isStream()) { |
| 894 | annot.warnIfPossible("ignoring annotation that's a stream"); | 881 | annot.warnIfPossible("ignoring annotation that's a stream"); |
| @@ -970,73 +957,68 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | @@ -970,73 +957,68 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | ||
| 970 | // Traverse the field, copying kids, and preserving | 957 | // Traverse the field, copying kids, and preserving |
| 971 | // integrity. | 958 | // integrity. |
| 972 | std::list<QPDFObjectHandle> queue; | 959 | std::list<QPDFObjectHandle> queue; |
| 960 | + QPDFObjGen::set seen; | ||
| 973 | if (maybe_copy_object(top_field)) { | 961 | if (maybe_copy_object(top_field)) { |
| 974 | queue.push_back(top_field); | 962 | queue.push_back(top_field); |
| 975 | } | 963 | } |
| 976 | - std::set<QPDFObjGen> seen; | ||
| 977 | - while (!queue.empty()) { | ||
| 978 | - QPDFObjectHandle obj = queue.front(); | ||
| 979 | - queue.pop_front(); | ||
| 980 | - auto orig_og = obj.getObjGen(); | ||
| 981 | - if (seen.count(orig_og)) { | ||
| 982 | - // loop | ||
| 983 | - break; | ||
| 984 | - } | ||
| 985 | - seen.insert(orig_og); | ||
| 986 | - auto parent = obj.getKey("/Parent"); | ||
| 987 | - if (parent.isIndirect()) { | ||
| 988 | - auto parent_og = parent.getObjGen(); | ||
| 989 | - if (orig_to_copy.count(parent_og)) { | ||
| 990 | - obj.replaceKey("/Parent", orig_to_copy[parent_og]); | ||
| 991 | - } else { | ||
| 992 | - parent.warnIfPossible( | ||
| 993 | - "while traversing field " + | ||
| 994 | - obj.getObjGen().unparse(',') + ", found parent (" + | ||
| 995 | - parent_og.unparse(',') + | ||
| 996 | - ") that had not been seen, indicating likely" | ||
| 997 | - " invalid field structure"); | 964 | + for (; !queue.empty(); queue.pop_front()) { |
| 965 | + auto& obj = queue.front(); | ||
| 966 | + if (seen.add(obj)) { | ||
| 967 | + auto parent = obj.getKey("/Parent"); | ||
| 968 | + if (parent.isIndirect()) { | ||
| 969 | + auto parent_og = parent.getObjGen(); | ||
| 970 | + if (orig_to_copy.count(parent_og)) { | ||
| 971 | + obj.replaceKey("/Parent", orig_to_copy[parent_og]); | ||
| 972 | + } else { | ||
| 973 | + parent.warnIfPossible( | ||
| 974 | + "while traversing field " + | ||
| 975 | + obj.getObjGen().unparse(',') + | ||
| 976 | + ", found parent (" + parent_og.unparse(',') + | ||
| 977 | + ") that had not been seen, indicating likely" | ||
| 978 | + " invalid field structure"); | ||
| 979 | + } | ||
| 998 | } | 980 | } |
| 999 | - } | ||
| 1000 | - auto kids = obj.getKey("/Kids"); | ||
| 1001 | - if (kids.isArray()) { | ||
| 1002 | - for (int i = 0; i < kids.getArrayNItems(); ++i) { | ||
| 1003 | - auto kid = kids.getArrayItem(i); | ||
| 1004 | - if (maybe_copy_object(kid)) { | ||
| 1005 | - kids.setArrayItem(i, kid); | ||
| 1006 | - queue.push_back(kid); | 981 | + auto kids = obj.getKey("/Kids"); |
| 982 | + if (kids.isArray()) { | ||
| 983 | + for (int i = 0; i < kids.getArrayNItems(); ++i) { | ||
| 984 | + auto kid = kids.getArrayItem(i); | ||
| 985 | + if (maybe_copy_object(kid)) { | ||
| 986 | + kids.setArrayItem(i, kid); | ||
| 987 | + queue.push_back(kid); | ||
| 988 | + } | ||
| 1007 | } | 989 | } |
| 1008 | } | 990 | } |
| 1009 | - } | ||
| 1010 | 991 | ||
| 1011 | - if (override_da || override_q) { | ||
| 1012 | - adjustInheritedFields( | ||
| 1013 | - obj, | ||
| 1014 | - override_da, | ||
| 1015 | - from_default_da, | ||
| 1016 | - override_q, | ||
| 1017 | - from_default_q); | ||
| 1018 | - } | ||
| 1019 | - if (foreign) { | ||
| 1020 | - // Lazily initialize our /DR and the conflict map. | ||
| 1021 | - init_dr_map(); | ||
| 1022 | - // The spec doesn't say anything about /DR on the | ||
| 1023 | - // field, but lots of writers put one there, and | ||
| 1024 | - // it is frequently the same as the document-level | ||
| 1025 | - // /DR. To avoid having the field's /DR point to | ||
| 1026 | - // information that we are not maintaining, just | ||
| 1027 | - // reset it to that if it exists. Empirical | ||
| 1028 | - // evidence suggests that many readers, including | ||
| 1029 | - // Acrobat, Adobe Acrobat Reader, chrome, firefox, | ||
| 1030 | - // the mac Preview application, and several of the | ||
| 1031 | - // free readers on Linux all ignore /DR at the | ||
| 1032 | - // field level. | ||
| 1033 | - if (obj.hasKey("/DR")) { | ||
| 1034 | - obj.replaceKey("/DR", dr); | 992 | + if (override_da || override_q) { |
| 993 | + adjustInheritedFields( | ||
| 994 | + obj, | ||
| 995 | + override_da, | ||
| 996 | + from_default_da, | ||
| 997 | + override_q, | ||
| 998 | + from_default_q); | ||
| 999 | + } | ||
| 1000 | + if (foreign) { | ||
| 1001 | + // Lazily initialize our /DR and the conflict map. | ||
| 1002 | + init_dr_map(); | ||
| 1003 | + // The spec doesn't say anything about /DR on the | ||
| 1004 | + // field, but lots of writers put one there, and | ||
| 1005 | + // it is frequently the same as the document-level | ||
| 1006 | + // /DR. To avoid having the field's /DR point to | ||
| 1007 | + // information that we are not maintaining, just | ||
| 1008 | + // reset it to that if it exists. Empirical | ||
| 1009 | + // evidence suggests that many readers, including | ||
| 1010 | + // Acrobat, Adobe Acrobat Reader, chrome, firefox, | ||
| 1011 | + // the mac Preview application, and several of the | ||
| 1012 | + // free readers on Linux all ignore /DR at the | ||
| 1013 | + // field level. | ||
| 1014 | + if (obj.hasKey("/DR")) { | ||
| 1015 | + obj.replaceKey("/DR", dr); | ||
| 1016 | + } | ||
| 1017 | + } | ||
| 1018 | + if (foreign && obj.getKey("/DA").isString() && | ||
| 1019 | + (!dr_map.empty())) { | ||
| 1020 | + adjustDefaultAppearances(obj, dr_map); | ||
| 1035 | } | 1021 | } |
| 1036 | - } | ||
| 1037 | - if (foreign && obj.getKey("/DA").isString() && | ||
| 1038 | - (!dr_map.empty())) { | ||
| 1039 | - adjustDefaultAppearances(obj, dr_map); | ||
| 1040 | } | 1022 | } |
| 1041 | } | 1023 | } |
| 1042 | 1024 | ||
| @@ -1064,9 +1046,8 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | @@ -1064,9 +1046,8 @@ QPDFAcroFormDocumentHelper::transformAnnotations( | ||
| 1064 | maybe_copy_object(annot); | 1046 | maybe_copy_object(annot); |
| 1065 | 1047 | ||
| 1066 | // Now we have copies, so we can safely mutate. | 1048 | // Now we have copies, so we can safely mutate. |
| 1067 | - if (have_field && !added_new_fields.count(top_field.getObjGen())) { | 1049 | + if (have_field && added_new_fields.add(top_field)) { |
| 1068 | new_fields.push_back(top_field); | 1050 | new_fields.push_back(top_field); |
| 1069 | - added_new_fields.insert(top_field.getObjGen()); | ||
| 1070 | } | 1051 | } |
| 1071 | new_annots.push_back(annot); | 1052 | new_annots.push_back(annot); |
| 1072 | 1053 |
libqpdf/QPDFFormFieldObjectHelper.cc
| @@ -36,20 +36,14 @@ QPDFFormFieldObjectHelper | @@ -36,20 +36,14 @@ QPDFFormFieldObjectHelper | ||
| 36 | QPDFFormFieldObjectHelper::getTopLevelField(bool* is_different) | 36 | QPDFFormFieldObjectHelper::getTopLevelField(bool* is_different) |
| 37 | { | 37 | { |
| 38 | auto top_field = this->oh; | 38 | auto top_field = this->oh; |
| 39 | - std::set<QPDFObjGen> seen; | ||
| 40 | - while (top_field.isDictionary() && | ||
| 41 | - (!top_field.getKey("/Parent").isNull())) { | 39 | + QPDFObjGen::set seen; |
| 40 | + while (seen.add(top_field) && !top_field.getKeyIfDict("/Parent").isNull()) { | ||
| 42 | top_field = top_field.getKey("/Parent"); | 41 | top_field = top_field.getKey("/Parent"); |
| 43 | if (is_different) { | 42 | if (is_different) { |
| 44 | *is_different = true; | 43 | *is_different = true; |
| 45 | } | 44 | } |
| 46 | - auto og = top_field.getObjGen(); | ||
| 47 | - if (seen.count(og)) { | ||
| 48 | - break; | ||
| 49 | - } | ||
| 50 | - seen.insert(og); | ||
| 51 | } | 45 | } |
| 52 | - return QPDFFormFieldObjectHelper(top_field); | 46 | + return {top_field}; |
| 53 | } | 47 | } |
| 54 | 48 | ||
| 55 | QPDFObjectHandle | 49 | QPDFObjectHandle |
| @@ -76,17 +70,17 @@ QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& name) | @@ -76,17 +70,17 @@ QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& name) | ||
| 76 | return QPDFObjectHandle::newNull(); | 70 | return QPDFObjectHandle::newNull(); |
| 77 | } | 71 | } |
| 78 | QPDFObjectHandle result(node.getKey(name)); | 72 | QPDFObjectHandle result(node.getKey(name)); |
| 79 | - std::set<QPDFObjGen> seen; | ||
| 80 | - while (result.isNull() && node.hasKey("/Parent")) { | ||
| 81 | - seen.insert(node.getObjGen()); | ||
| 82 | - node = node.getKey("/Parent"); | ||
| 83 | - if (seen.count(node.getObjGen())) { | ||
| 84 | - break; | ||
| 85 | - } | ||
| 86 | - result = node.getKey(name); | ||
| 87 | - if (!result.isNull()) { | ||
| 88 | - QTC::TC( | ||
| 89 | - "qpdf", "QPDFFormFieldObjectHelper non-trivial inheritance"); | 73 | + if (result.isNull()) { |
| 74 | + QPDFObjGen::set seen; | ||
| 75 | + while (seen.add(node) && node.hasKey("/Parent")) { | ||
| 76 | + node = node.getKey("/Parent"); | ||
| 77 | + result = node.getKey(name); | ||
| 78 | + if (!result.isNull()) { | ||
| 79 | + QTC::TC( | ||
| 80 | + "qpdf", | ||
| 81 | + "QPDFFormFieldObjectHelper non-trivial inheritance"); | ||
| 82 | + return result; | ||
| 83 | + } | ||
| 90 | } | 84 | } |
| 91 | } | 85 | } |
| 92 | return result; | 86 | return result; |
| @@ -127,8 +121,8 @@ QPDFFormFieldObjectHelper::getFullyQualifiedName() | @@ -127,8 +121,8 @@ QPDFFormFieldObjectHelper::getFullyQualifiedName() | ||
| 127 | { | 121 | { |
| 128 | std::string result; | 122 | std::string result; |
| 129 | QPDFObjectHandle node = this->oh; | 123 | QPDFObjectHandle node = this->oh; |
| 130 | - std::set<QPDFObjGen> seen; | ||
| 131 | - while ((!node.isNull()) && (seen.count(node.getObjGen()) == 0)) { | 124 | + QPDFObjGen::set seen; |
| 125 | + while (!node.isNull() && seen.add(node)) { | ||
| 132 | if (node.getKey("/T").isString()) { | 126 | if (node.getKey("/T").isString()) { |
| 133 | if (!result.empty()) { | 127 | if (!result.empty()) { |
| 134 | QTC::TC( | 128 | QTC::TC( |
| @@ -138,7 +132,6 @@ QPDFFormFieldObjectHelper::getFullyQualifiedName() | @@ -138,7 +132,6 @@ QPDFFormFieldObjectHelper::getFullyQualifiedName() | ||
| 138 | } | 132 | } |
| 139 | result = node.getKey("/T").getUTF8Value() + result; | 133 | result = node.getKey("/T").getUTF8Value() + result; |
| 140 | } | 134 | } |
| 141 | - seen.insert(node.getObjGen()); | ||
| 142 | node = node.getKey("/Parent"); | 135 | node = node.getKey("/Parent"); |
| 143 | } | 136 | } |
| 144 | return result; | 137 | return result; |
libqpdf/QPDFJob.cc
| @@ -1001,18 +1001,16 @@ QPDFJob::parse_object_id( | @@ -1001,18 +1001,16 @@ QPDFJob::parse_object_id( | ||
| 1001 | } | 1001 | } |
| 1002 | } | 1002 | } |
| 1003 | 1003 | ||
| 1004 | -std::set<QPDFObjGen> | 1004 | +QPDFObjGen::set |
| 1005 | QPDFJob::getWantedJSONObjects() | 1005 | QPDFJob::getWantedJSONObjects() |
| 1006 | { | 1006 | { |
| 1007 | - std::set<QPDFObjGen> wanted_og; | 1007 | + QPDFObjGen::set wanted_og; |
| 1008 | for (auto const& iter: m->json_objects) { | 1008 | for (auto const& iter: m->json_objects) { |
| 1009 | bool trailer; | 1009 | bool trailer; |
| 1010 | int obj = 0; | 1010 | int obj = 0; |
| 1011 | int gen = 0; | 1011 | int gen = 0; |
| 1012 | parse_object_id(iter, trailer, obj, gen); | 1012 | parse_object_id(iter, trailer, obj, gen); |
| 1013 | - if (obj) { | ||
| 1014 | - wanted_og.insert(QPDFObjGen(obj, gen)); | ||
| 1015 | - } | 1013 | + wanted_og.add(QPDFObjGen(obj, gen)); |
| 1016 | } | 1014 | } |
| 1017 | return wanted_og; | 1015 | return wanted_og; |
| 1018 | } | 1016 | } |
| @@ -1045,7 +1043,7 @@ QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf) | @@ -1045,7 +1043,7 @@ QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf) | ||
| 1045 | bool first_object = true; | 1043 | bool first_object = true; |
| 1046 | JSON::writeDictionaryOpen(p, first_object, 1); | 1044 | JSON::writeDictionaryOpen(p, first_object, 1); |
| 1047 | bool all_objects = m->json_objects.empty(); | 1045 | bool all_objects = m->json_objects.empty(); |
| 1048 | - std::set<QPDFObjGen> wanted_og = getWantedJSONObjects(); | 1046 | + auto wanted_og = getWantedJSONObjects(); |
| 1049 | for (auto& obj: pdf.getAllObjects()) { | 1047 | for (auto& obj: pdf.getAllObjects()) { |
| 1050 | std::string key = obj.unparse(); | 1048 | std::string key = obj.unparse(); |
| 1051 | if (this->m->json_version > 1) { | 1049 | if (this->m->json_version > 1) { |
| @@ -1065,11 +1063,8 @@ QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf) | @@ -1065,11 +1063,8 @@ QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf) | ||
| 1065 | if (this->m->json_objects.count("trailer")) { | 1063 | if (this->m->json_objects.count("trailer")) { |
| 1066 | json_objects.insert("trailer"); | 1064 | json_objects.insert("trailer"); |
| 1067 | } | 1065 | } |
| 1068 | - auto wanted = getWantedJSONObjects(); | ||
| 1069 | - for (auto const& og: wanted) { | ||
| 1070 | - std::ostringstream s; | ||
| 1071 | - s << "obj:" << og.unparse(' ') << " R"; | ||
| 1072 | - json_objects.insert(s.str()); | 1066 | + for (auto og: getWantedJSONObjects()) { |
| 1067 | + json_objects.emplace("obj:" + og.unparse(' ') + " R"); | ||
| 1073 | } | 1068 | } |
| 1074 | pdf.writeJSON( | 1069 | pdf.writeJSON( |
| 1075 | this->m->json_version, | 1070 | this->m->json_version, |
| @@ -1090,7 +1085,7 @@ QPDFJob::doJSONObjectinfo(Pipeline* p, bool& first, QPDF& pdf) | @@ -1090,7 +1085,7 @@ QPDFJob::doJSONObjectinfo(Pipeline* p, bool& first, QPDF& pdf) | ||
| 1090 | bool first_object = true; | 1085 | bool first_object = true; |
| 1091 | JSON::writeDictionaryOpen(p, first_object, 1); | 1086 | JSON::writeDictionaryOpen(p, first_object, 1); |
| 1092 | bool all_objects = m->json_objects.empty(); | 1087 | bool all_objects = m->json_objects.empty(); |
| 1093 | - std::set<QPDFObjGen> wanted_og = getWantedJSONObjects(); | 1088 | + auto wanted_og = getWantedJSONObjects(); |
| 1094 | for (auto& obj: pdf.getAllObjects()) { | 1089 | for (auto& obj: pdf.getAllObjects()) { |
| 1095 | if (all_objects || wanted_og.count(obj.getObjGen())) { | 1090 | if (all_objects || wanted_og.count(obj.getObjGen())) { |
| 1096 | auto j_details = JSON::makeDictionary(); | 1091 | auto j_details = JSON::makeDictionary(); |
| @@ -2451,8 +2446,8 @@ QPDFJob::shouldRemoveUnreferencedResources(QPDF& pdf) | @@ -2451,8 +2446,8 @@ QPDFJob::shouldRemoveUnreferencedResources(QPDF& pdf) | ||
| 2451 | 2446 | ||
| 2452 | // Return true as soon as we find any shared resources. | 2447 | // Return true as soon as we find any shared resources. |
| 2453 | 2448 | ||
| 2454 | - std::set<QPDFObjGen> resources_seen; // shared resources detection | ||
| 2455 | - std::set<QPDFObjGen> nodes_seen; // loop detection | 2449 | + QPDFObjGen::set resources_seen; // shared resources detection |
| 2450 | + QPDFObjGen::set nodes_seen; // loop detection | ||
| 2456 | 2451 | ||
| 2457 | doIfVerbose([&](Pipeline& v, std::string const& prefix) { | 2452 | doIfVerbose([&](Pipeline& v, std::string const& prefix) { |
| 2458 | v << prefix << ": " << pdf.getFilename() | 2453 | v << prefix << ": " << pdf.getFilename() |
| @@ -2465,10 +2460,9 @@ QPDFJob::shouldRemoveUnreferencedResources(QPDF& pdf) | @@ -2465,10 +2460,9 @@ QPDFJob::shouldRemoveUnreferencedResources(QPDF& pdf) | ||
| 2465 | QPDFObjectHandle node = *queue.begin(); | 2460 | QPDFObjectHandle node = *queue.begin(); |
| 2466 | queue.pop_front(); | 2461 | queue.pop_front(); |
| 2467 | QPDFObjGen og = node.getObjGen(); | 2462 | QPDFObjGen og = node.getObjGen(); |
| 2468 | - if (nodes_seen.count(og)) { | 2463 | + if (!nodes_seen.add(og)) { |
| 2469 | continue; | 2464 | continue; |
| 2470 | } | 2465 | } |
| 2471 | - nodes_seen.insert(og); | ||
| 2472 | QPDFObjectHandle dict = node.isStream() ? node.getDict() : node; | 2466 | QPDFObjectHandle dict = node.isStream() ? node.getDict() : node; |
| 2473 | QPDFObjectHandle kids = dict.getKey("/Kids"); | 2467 | QPDFObjectHandle kids = dict.getKey("/Kids"); |
| 2474 | if (kids.isArray()) { | 2468 | if (kids.isArray()) { |
| @@ -2489,33 +2483,29 @@ QPDFJob::shouldRemoveUnreferencedResources(QPDF& pdf) | @@ -2489,33 +2483,29 @@ QPDFJob::shouldRemoveUnreferencedResources(QPDF& pdf) | ||
| 2489 | // This is a leaf node or a form XObject. | 2483 | // This is a leaf node or a form XObject. |
| 2490 | QPDFObjectHandle resources = dict.getKey("/Resources"); | 2484 | QPDFObjectHandle resources = dict.getKey("/Resources"); |
| 2491 | if (resources.isIndirect()) { | 2485 | if (resources.isIndirect()) { |
| 2492 | - QPDFObjGen resources_og = resources.getObjGen(); | ||
| 2493 | - if (resources_seen.count(resources_og)) { | 2486 | + if (!resources_seen.add(resources)) { |
| 2494 | QTC::TC("qpdf", "QPDFJob found shared resources in leaf"); | 2487 | QTC::TC("qpdf", "QPDFJob found shared resources in leaf"); |
| 2495 | doIfVerbose([&](Pipeline& v, std::string const& prefix) { | 2488 | doIfVerbose([&](Pipeline& v, std::string const& prefix) { |
| 2496 | v << " found shared resources in leaf node " | 2489 | v << " found shared resources in leaf node " |
| 2497 | << og.unparse(' ') << ": " | 2490 | << og.unparse(' ') << ": " |
| 2498 | - << resources_og.unparse(' ') << "\n"; | 2491 | + << resources.getObjGen().unparse(' ') << "\n"; |
| 2499 | }); | 2492 | }); |
| 2500 | return true; | 2493 | return true; |
| 2501 | } | 2494 | } |
| 2502 | - resources_seen.insert(resources_og); | ||
| 2503 | } | 2495 | } |
| 2504 | QPDFObjectHandle xobject = | 2496 | QPDFObjectHandle xobject = |
| 2505 | (resources.isDictionary() ? resources.getKey("/XObject") | 2497 | (resources.isDictionary() ? resources.getKey("/XObject") |
| 2506 | : QPDFObjectHandle::newNull()); | 2498 | : QPDFObjectHandle::newNull()); |
| 2507 | if (xobject.isIndirect()) { | 2499 | if (xobject.isIndirect()) { |
| 2508 | - QPDFObjGen xobject_og = xobject.getObjGen(); | ||
| 2509 | - if (resources_seen.count(xobject_og)) { | 2500 | + if (!resources_seen.add(xobject)) { |
| 2510 | QTC::TC("qpdf", "QPDFJob found shared xobject in leaf"); | 2501 | QTC::TC("qpdf", "QPDFJob found shared xobject in leaf"); |
| 2511 | doIfVerbose([&](Pipeline& v, std::string const& prefix) { | 2502 | doIfVerbose([&](Pipeline& v, std::string const& prefix) { |
| 2512 | v << " found shared xobject in leaf node " | 2503 | v << " found shared xobject in leaf node " |
| 2513 | - << og.unparse(' ') << ": " << xobject_og.unparse(' ') | ||
| 2514 | - << "\n"; | 2504 | + << og.unparse(' ') << ": " |
| 2505 | + << xobject.getObjGen().unparse(' ') << "\n"; | ||
| 2515 | }); | 2506 | }); |
| 2516 | return true; | 2507 | return true; |
| 2517 | } | 2508 | } |
| 2518 | - resources_seen.insert(xobject_og); | ||
| 2519 | } | 2509 | } |
| 2520 | if (xobject.isDictionary()) { | 2510 | if (xobject.isDictionary()) { |
| 2521 | for (auto const& k: xobject.getKeys()) { | 2511 | for (auto const& k: xobject.getKeys()) { |
libqpdf/QPDFObjGen.cc
| 1 | #include <qpdf/QPDFObjGen.hh> | 1 | #include <qpdf/QPDFObjGen.hh> |
| 2 | 2 | ||
| 3 | -#include <qpdf/QUtil.hh> | 3 | +#include <qpdf/QPDFObjectHandle.hh> |
| 4 | +#include <qpdf/QPDFObjectHelper.hh> | ||
| 5 | +#include <qpdf/QPDFObject_private.hh> | ||
| 4 | 6 | ||
| 7 | +#include <stdexcept> | ||
| 8 | + | ||
| 9 | +// ABI: inline and pass og by value | ||
| 5 | std::ostream& | 10 | std::ostream& |
| 6 | operator<<(std::ostream& os, const QPDFObjGen& og) | 11 | operator<<(std::ostream& os, const QPDFObjGen& og) |
| 7 | { | 12 | { |
| @@ -9,8 +14,55 @@ operator<<(std::ostream& os, const QPDFObjGen& og) | @@ -9,8 +14,55 @@ operator<<(std::ostream& os, const QPDFObjGen& og) | ||
| 9 | return os; | 14 | return os; |
| 10 | } | 15 | } |
| 11 | 16 | ||
| 17 | +// ABI: inline | ||
| 12 | std::string | 18 | std::string |
| 13 | QPDFObjGen::unparse(char separator) const | 19 | QPDFObjGen::unparse(char separator) const |
| 14 | { | 20 | { |
| 15 | - return std::to_string(this->obj) + separator + std::to_string(this->gen); | 21 | + return std::to_string(obj) + separator + std::to_string(gen); |
| 22 | +} | ||
| 23 | + | ||
| 24 | +bool | ||
| 25 | +QPDFObjGen::set::add(QPDFObjectHandle const& oh) | ||
| 26 | +{ | ||
| 27 | + if (auto* ptr = oh.getObjectPtr()) { | ||
| 28 | + return add(ptr->getObjGen()); | ||
| 29 | + } else { | ||
| 30 | + throw std::logic_error("attempt to retrieve QPDFObjGen from " | ||
| 31 | + "uninitialized QPDFObjectHandle"); | ||
| 32 | + return false; | ||
| 33 | + } | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +bool | ||
| 37 | +QPDFObjGen::set::add(QPDFObjectHelper const& helper) | ||
| 38 | +{ | ||
| 39 | + if (auto* ptr = helper.getObjectHandle().getObjectPtr()) { | ||
| 40 | + return add(ptr->getObjGen()); | ||
| 41 | + } else { | ||
| 42 | + throw std::logic_error("attempt to retrieve QPDFObjGen from " | ||
| 43 | + "uninitialized QPDFObjectHandle"); | ||
| 44 | + return false; | ||
| 45 | + } | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +void | ||
| 49 | +QPDFObjGen::set::erase(QPDFObjectHandle const& oh) | ||
| 50 | +{ | ||
| 51 | + if (auto* ptr = oh.getObjectPtr()) { | ||
| 52 | + erase(ptr->getObjGen()); | ||
| 53 | + } else { | ||
| 54 | + throw std::logic_error("attempt to retrieve QPDFObjGen from " | ||
| 55 | + "uninitialized QPDFObjectHandle"); | ||
| 56 | + } | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +void | ||
| 60 | +QPDFObjGen::set::erase(QPDFObjectHelper const& helper) | ||
| 61 | +{ | ||
| 62 | + if (auto* ptr = helper.getObjectHandle().getObjectPtr()) { | ||
| 63 | + erase(ptr->getObjGen()); | ||
| 64 | + } else { | ||
| 65 | + throw std::logic_error("attempt to retrieve QPDFObjGen from " | ||
| 66 | + "uninitialized QPDFObjectHandle"); | ||
| 67 | + } | ||
| 16 | } | 68 | } |
libqpdf/QPDFObjectHandle.cc
| @@ -1588,22 +1588,12 @@ QPDFObjectHandle::rotatePage(int angle, bool relative) | @@ -1588,22 +1588,12 @@ QPDFObjectHandle::rotatePage(int angle, bool relative) | ||
| 1588 | int new_angle = angle; | 1588 | int new_angle = angle; |
| 1589 | if (relative) { | 1589 | if (relative) { |
| 1590 | int old_angle = 0; | 1590 | int old_angle = 0; |
| 1591 | - bool found_rotate = false; | ||
| 1592 | QPDFObjectHandle cur_obj = *this; | 1591 | QPDFObjectHandle cur_obj = *this; |
| 1593 | - bool searched_parent = false; | ||
| 1594 | - std::set<QPDFObjGen> visited; | ||
| 1595 | - while (!found_rotate) { | ||
| 1596 | - if (visited.count(cur_obj.getObjGen())) { | ||
| 1597 | - // Don't get stuck in an infinite loop | 1592 | + QPDFObjGen::set visited; |
| 1593 | + while (visited.add(cur_obj)) { | ||
| 1594 | + // Don't get stuck in an infinite loop | ||
| 1595 | + if (cur_obj.getKey("/Rotate").getValueAsInt(old_angle)) { | ||
| 1598 | break; | 1596 | break; |
| 1599 | - } | ||
| 1600 | - if (!visited.empty()) { | ||
| 1601 | - searched_parent = true; | ||
| 1602 | - } | ||
| 1603 | - visited.insert(cur_obj.getObjGen()); | ||
| 1604 | - if (cur_obj.getKey("/Rotate").isInteger()) { | ||
| 1605 | - found_rotate = true; | ||
| 1606 | - old_angle = cur_obj.getKey("/Rotate").getIntValueAsInt(); | ||
| 1607 | } else if (cur_obj.getKey("/Parent").isDictionary()) { | 1597 | } else if (cur_obj.getKey("/Parent").isDictionary()) { |
| 1608 | cur_obj = cur_obj.getKey("/Parent"); | 1598 | cur_obj = cur_obj.getKey("/Parent"); |
| 1609 | } else { | 1599 | } else { |
| @@ -1613,7 +1603,7 @@ QPDFObjectHandle::rotatePage(int angle, bool relative) | @@ -1613,7 +1603,7 @@ QPDFObjectHandle::rotatePage(int angle, bool relative) | ||
| 1613 | QTC::TC( | 1603 | QTC::TC( |
| 1614 | "qpdf", | 1604 | "qpdf", |
| 1615 | "QPDFObjectHandle found old angle", | 1605 | "QPDFObjectHandle found old angle", |
| 1616 | - searched_parent ? 0 : 1); | 1606 | + visited.size() > 1 ? 0 : 1); |
| 1617 | if ((old_angle % 90) != 0) { | 1607 | if ((old_angle % 90) != 0) { |
| 1618 | old_angle = 0; | 1608 | old_angle = 0; |
| 1619 | } | 1609 | } |
| @@ -2181,20 +2171,15 @@ QPDFObjectHandle::unsafeShallowCopy() | @@ -2181,20 +2171,15 @@ QPDFObjectHandle::unsafeShallowCopy() | ||
| 2181 | } | 2171 | } |
| 2182 | 2172 | ||
| 2183 | void | 2173 | void |
| 2184 | -QPDFObjectHandle::makeDirect( | ||
| 2185 | - std::set<QPDFObjGen>& visited, bool stop_at_streams) | 2174 | +QPDFObjectHandle::makeDirect(QPDFObjGen::set& visited, bool stop_at_streams) |
| 2186 | { | 2175 | { |
| 2187 | assertInitialized(); | 2176 | assertInitialized(); |
| 2188 | 2177 | ||
| 2189 | auto cur_og = getObjGen(); | 2178 | auto cur_og = getObjGen(); |
| 2190 | - if (cur_og.getObj() != 0) { | ||
| 2191 | - if (visited.count(cur_og)) { | ||
| 2192 | - QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop"); | ||
| 2193 | - throw std::runtime_error( | ||
| 2194 | - "loop detected while converting object from " | ||
| 2195 | - "indirect to direct"); | ||
| 2196 | - } | ||
| 2197 | - visited.insert(cur_og); | 2179 | + if (!visited.add(cur_og)) { |
| 2180 | + QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop"); | ||
| 2181 | + throw std::runtime_error("loop detected while converting object from " | ||
| 2182 | + "indirect to direct"); | ||
| 2198 | } | 2183 | } |
| 2199 | 2184 | ||
| 2200 | if (isBool() || isInteger() || isName() || isNull() || isReal() || | 2185 | if (isBool() || isInteger() || isName() || isNull() || isReal() || |
| @@ -2232,9 +2217,7 @@ QPDFObjectHandle::makeDirect( | @@ -2232,9 +2217,7 @@ QPDFObjectHandle::makeDirect( | ||
| 2232 | "unknown object type"); | 2217 | "unknown object type"); |
| 2233 | } | 2218 | } |
| 2234 | 2219 | ||
| 2235 | - if (cur_og.getObj()) { | ||
| 2236 | - visited.erase(cur_og); | ||
| 2237 | - } | 2220 | + visited.erase(cur_og); |
| 2238 | } | 2221 | } |
| 2239 | 2222 | ||
| 2240 | QPDFObjectHandle | 2223 | QPDFObjectHandle |
| @@ -2258,7 +2241,7 @@ QPDFObjectHandle::copyStream() | @@ -2258,7 +2241,7 @@ QPDFObjectHandle::copyStream() | ||
| 2258 | void | 2241 | void |
| 2259 | QPDFObjectHandle::makeDirect(bool allow_streams) | 2242 | QPDFObjectHandle::makeDirect(bool allow_streams) |
| 2260 | { | 2243 | { |
| 2261 | - std::set<QPDFObjGen> visited; | 2244 | + QPDFObjGen::set visited; |
| 2262 | makeDirect(visited, allow_streams); | 2245 | makeDirect(visited, allow_streams); |
| 2263 | } | 2246 | } |
| 2264 | 2247 |
libqpdf/QPDFOutlineDocumentHelper.cc
| @@ -15,13 +15,8 @@ QPDFOutlineDocumentHelper::QPDFOutlineDocumentHelper(QPDF& qpdf) : | @@ -15,13 +15,8 @@ QPDFOutlineDocumentHelper::QPDFOutlineDocumentHelper(QPDF& qpdf) : | ||
| 15 | return; | 15 | return; |
| 16 | } | 16 | } |
| 17 | QPDFObjectHandle cur = outlines.getKey("/First"); | 17 | QPDFObjectHandle cur = outlines.getKey("/First"); |
| 18 | - std::set<QPDFObjGen> seen; | ||
| 19 | - while (!cur.isNull()) { | ||
| 20 | - auto og = cur.getObjGen(); | ||
| 21 | - if (seen.count(og)) { | ||
| 22 | - break; | ||
| 23 | - } | ||
| 24 | - seen.insert(og); | 18 | + QPDFObjGen::set seen; |
| 19 | + while (!cur.isNull() && seen.add(cur)) { | ||
| 25 | this->m->outlines.push_back( | 20 | this->m->outlines.push_back( |
| 26 | QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1)); | 21 | QPDFOutlineObjectHelper::Accessor::create(cur, *this, 1)); |
| 27 | cur = cur.getKey("/Next"); | 22 | cur = cur.getKey("/Next"); |
| @@ -104,13 +99,3 @@ QPDFOutlineDocumentHelper::resolveNamedDest(QPDFObjectHandle name) | @@ -104,13 +99,3 @@ QPDFOutlineDocumentHelper::resolveNamedDest(QPDFObjectHandle name) | ||
| 104 | } | 99 | } |
| 105 | return result; | 100 | return result; |
| 106 | } | 101 | } |
| 107 | - | ||
| 108 | -bool | ||
| 109 | -QPDFOutlineDocumentHelper::checkSeen(QPDFObjGen const& og) | ||
| 110 | -{ | ||
| 111 | - if (this->m->seen.count(og) > 0) { | ||
| 112 | - return true; | ||
| 113 | - } | ||
| 114 | - this->m->seen.insert(og); | ||
| 115 | - return false; | ||
| 116 | -} |
libqpdf/QPDFPageObjectHelper.cc
| @@ -246,32 +246,23 @@ QPDFPageObjectHelper::getAttribute( | @@ -246,32 +246,23 @@ QPDFPageObjectHelper::getAttribute( | ||
| 246 | std::function<QPDFObjectHandle()> get_fallback, | 246 | std::function<QPDFObjectHandle()> get_fallback, |
| 247 | bool copy_if_fallback) | 247 | bool copy_if_fallback) |
| 248 | { | 248 | { |
| 249 | - QPDFObjectHandle result; | ||
| 250 | - QPDFObjectHandle dict; | ||
| 251 | - bool is_form_xobject = this->oh.isFormXObject(); | 249 | + const bool is_form_xobject = this->oh.isFormXObject(); |
| 252 | bool inherited = false; | 250 | bool inherited = false; |
| 253 | - if (is_form_xobject) { | ||
| 254 | - dict = this->oh.getDict(); | ||
| 255 | - result = dict.getKey(name); | ||
| 256 | - } else { | ||
| 257 | - dict = this->oh; | ||
| 258 | - bool inheritable = | ||
| 259 | - ((name == "/MediaBox") || (name == "/CropBox") || | ||
| 260 | - (name == "/Resources") || (name == "/Rotate")); | 251 | + auto dict = is_form_xobject ? oh.getDict() : oh; |
| 252 | + auto result = dict.getKey(name); | ||
| 261 | 253 | ||
| 254 | + if (!is_form_xobject && result.isNull() && | ||
| 255 | + (name == "/MediaBox" || name == "/CropBox" || name == "/Resources" || | ||
| 256 | + name == "/Rotate")) { | ||
| 262 | QPDFObjectHandle node = dict; | 257 | QPDFObjectHandle node = dict; |
| 263 | - result = node.getKey(name); | ||
| 264 | - std::set<QPDFObjGen> seen; | ||
| 265 | - while (inheritable && result.isNull() && node.hasKey("/Parent")) { | ||
| 266 | - seen.insert(node.getObjGen()); | 258 | + QPDFObjGen::set seen{}; |
| 259 | + while (seen.add(node) && node.hasKey("/Parent")) { | ||
| 267 | node = node.getKey("/Parent"); | 260 | node = node.getKey("/Parent"); |
| 268 | - if (seen.count(node.getObjGen())) { | ||
| 269 | - break; | ||
| 270 | - } | ||
| 271 | result = node.getKey(name); | 261 | result = node.getKey(name); |
| 272 | if (!result.isNull()) { | 262 | if (!result.isNull()) { |
| 273 | QTC::TC("qpdf", "QPDFPageObjectHelper non-trivial inheritance"); | 263 | QTC::TC("qpdf", "QPDFPageObjectHelper non-trivial inheritance"); |
| 274 | inherited = true; | 264 | inherited = true; |
| 265 | + break; | ||
| 275 | } | 266 | } |
| 276 | } | 267 | } |
| 277 | } | 268 | } |
| @@ -361,30 +352,27 @@ QPDFPageObjectHelper::forEachXObject( | @@ -361,30 +352,27 @@ QPDFPageObjectHelper::forEachXObject( | ||
| 361 | "QPDFPageObjectHelper::forEachXObject", | 352 | "QPDFPageObjectHelper::forEachXObject", |
| 362 | recursive ? (this->oh.isFormXObject() ? 0 : 1) | 353 | recursive ? (this->oh.isFormXObject() ? 0 : 1) |
| 363 | : (this->oh.isFormXObject() ? 2 : 3)); | 354 | : (this->oh.isFormXObject() ? 2 : 3)); |
| 364 | - std::set<QPDFObjGen> seen; | 355 | + QPDFObjGen::set seen; |
| 365 | std::list<QPDFPageObjectHelper> queue; | 356 | std::list<QPDFPageObjectHelper> queue; |
| 366 | queue.push_back(*this); | 357 | queue.push_back(*this); |
| 367 | while (!queue.empty()) { | 358 | while (!queue.empty()) { |
| 368 | - QPDFPageObjectHelper ph = queue.front(); | ||
| 369 | - queue.pop_front(); | ||
| 370 | - QPDFObjGen og = ph.oh.getObjGen(); | ||
| 371 | - if (seen.count(og)) { | ||
| 372 | - continue; | ||
| 373 | - } | ||
| 374 | - seen.insert(og); | ||
| 375 | - QPDFObjectHandle resources = ph.getAttribute("/Resources", false); | ||
| 376 | - if (resources.isDictionary() && resources.hasKey("/XObject")) { | ||
| 377 | - QPDFObjectHandle xobj_dict = resources.getKey("/XObject"); | ||
| 378 | - for (auto const& key: xobj_dict.getKeys()) { | ||
| 379 | - QPDFObjectHandle obj = xobj_dict.getKey(key); | ||
| 380 | - if ((!selector) || selector(obj)) { | ||
| 381 | - action(obj, xobj_dict, key); | ||
| 382 | - } | ||
| 383 | - if (recursive && obj.isFormXObject()) { | ||
| 384 | - queue.push_back(QPDFPageObjectHelper(obj)); | 359 | + auto& ph = queue.front(); |
| 360 | + if (seen.add(ph)) { | ||
| 361 | + auto xobj_dict = | ||
| 362 | + ph.getAttribute("/Resources", false).getKeyIfDict("/XObject"); | ||
| 363 | + if (xobj_dict.isDictionary()) { | ||
| 364 | + for (auto const& key: xobj_dict.getKeys()) { | ||
| 365 | + QPDFObjectHandle obj = xobj_dict.getKey(key); | ||
| 366 | + if ((!selector) || selector(obj)) { | ||
| 367 | + action(obj, xobj_dict, key); | ||
| 368 | + } | ||
| 369 | + if (recursive && obj.isFormXObject()) { | ||
| 370 | + queue.emplace_back(obj); | ||
| 371 | + } | ||
| 385 | } | 372 | } |
| 386 | } | 373 | } |
| 387 | } | 374 | } |
| 375 | + queue.pop_front(); | ||
| 388 | } | 376 | } |
| 389 | } | 377 | } |
| 390 | 378 |
libqpdf/QPDF_optimization.cc
| @@ -284,7 +284,7 @@ QPDF::updateObjectMaps( | @@ -284,7 +284,7 @@ QPDF::updateObjectMaps( | ||
| 284 | QPDFObjectHandle oh, | 284 | QPDFObjectHandle oh, |
| 285 | std::function<int(QPDFObjectHandle&)> skip_stream_parameters) | 285 | std::function<int(QPDFObjectHandle&)> skip_stream_parameters) |
| 286 | { | 286 | { |
| 287 | - std::set<QPDFObjGen> visited; | 287 | + QPDFObjGen::set visited; |
| 288 | updateObjectMapsInternal(ou, oh, skip_stream_parameters, visited, true); | 288 | updateObjectMapsInternal(ou, oh, skip_stream_parameters, visited, true); |
| 289 | } | 289 | } |
| 290 | 290 | ||
| @@ -293,7 +293,7 @@ QPDF::updateObjectMapsInternal( | @@ -293,7 +293,7 @@ QPDF::updateObjectMapsInternal( | ||
| 293 | ObjUser const& ou, | 293 | ObjUser const& ou, |
| 294 | QPDFObjectHandle oh, | 294 | QPDFObjectHandle oh, |
| 295 | std::function<int(QPDFObjectHandle&)> skip_stream_parameters, | 295 | std::function<int(QPDFObjectHandle&)> skip_stream_parameters, |
| 296 | - std::set<QPDFObjGen>& visited, | 296 | + QPDFObjGen::set& visited, |
| 297 | bool top) | 297 | bool top) |
| 298 | { | 298 | { |
| 299 | // Traverse the object tree from this point taking care to avoid | 299 | // Traverse the object tree from this point taking care to avoid |
| @@ -310,13 +310,12 @@ QPDF::updateObjectMapsInternal( | @@ -310,13 +310,12 @@ QPDF::updateObjectMapsInternal( | ||
| 310 | 310 | ||
| 311 | if (oh.isIndirect()) { | 311 | if (oh.isIndirect()) { |
| 312 | QPDFObjGen og(oh.getObjGen()); | 312 | QPDFObjGen og(oh.getObjGen()); |
| 313 | - if (visited.count(og)) { | 313 | + if (!visited.add(og)) { |
| 314 | QTC::TC("qpdf", "QPDF opt loop detected"); | 314 | QTC::TC("qpdf", "QPDF opt loop detected"); |
| 315 | return; | 315 | return; |
| 316 | } | 316 | } |
| 317 | this->m->obj_user_to_objects[ou].insert(og); | 317 | this->m->obj_user_to_objects[ou].insert(og); |
| 318 | this->m->object_to_obj_users[og].insert(ou); | 318 | this->m->object_to_obj_users[og].insert(ou); |
| 319 | - visited.insert(og); | ||
| 320 | } | 319 | } |
| 321 | 320 | ||
| 322 | if (oh.isArray()) { | 321 | if (oh.isArray()) { |
libqpdf/QPDF_pages.cc
| @@ -55,13 +55,13 @@ QPDF::getAllPages() | @@ -55,13 +55,13 @@ QPDF::getAllPages() | ||
| 55 | // initialize this->m->all_pages. | 55 | // initialize this->m->all_pages. |
| 56 | if (this->m->all_pages.empty()) { | 56 | if (this->m->all_pages.empty()) { |
| 57 | this->m->ever_called_get_all_pages = true; | 57 | this->m->ever_called_get_all_pages = true; |
| 58 | - std::set<QPDFObjGen> visited; | ||
| 59 | - std::set<QPDFObjGen> seen; | 58 | + QPDFObjGen::set visited; |
| 59 | + QPDFObjGen::set seen; | ||
| 60 | QPDFObjectHandle pages = getRoot().getKey("/Pages"); | 60 | QPDFObjectHandle pages = getRoot().getKey("/Pages"); |
| 61 | bool warned = false; | 61 | bool warned = false; |
| 62 | bool changed_pages = false; | 62 | bool changed_pages = false; |
| 63 | while (pages.isDictionary() && pages.hasKey("/Parent")) { | 63 | while (pages.isDictionary() && pages.hasKey("/Parent")) { |
| 64 | - if (seen.count(pages.getObjGen())) { | 64 | + if (!seen.add(pages)) { |
| 65 | // loop -- will be detected again and reported later | 65 | // loop -- will be detected again and reported later |
| 66 | break; | 66 | break; |
| 67 | } | 67 | } |
| @@ -74,7 +74,6 @@ QPDF::getAllPages() | @@ -74,7 +74,6 @@ QPDF::getAllPages() | ||
| 74 | " to the root of the page tree; attempting to correct"); | 74 | " to the root of the page tree; attempting to correct"); |
| 75 | warned = true; | 75 | warned = true; |
| 76 | } | 76 | } |
| 77 | - seen.insert(pages.getObjGen()); | ||
| 78 | changed_pages = true; | 77 | changed_pages = true; |
| 79 | pages = pages.getKey("/Parent"); | 78 | pages = pages.getKey("/Parent"); |
| 80 | } | 79 | } |
| @@ -92,12 +91,9 @@ QPDF::getAllPages() | @@ -92,12 +91,9 @@ QPDF::getAllPages() | ||
| 92 | 91 | ||
| 93 | void | 92 | void |
| 94 | QPDF::getAllPagesInternal( | 93 | QPDF::getAllPagesInternal( |
| 95 | - QPDFObjectHandle cur_node, | ||
| 96 | - std::set<QPDFObjGen>& visited, | ||
| 97 | - std::set<QPDFObjGen>& seen) | 94 | + QPDFObjectHandle cur_node, QPDFObjGen::set& visited, QPDFObjGen::set& seen) |
| 98 | { | 95 | { |
| 99 | - QPDFObjGen cur_node_og = cur_node.getObjGen(); | ||
| 100 | - if (visited.count(cur_node_og) > 0) { | 96 | + if (!visited.add(cur_node)) { |
| 101 | throw QPDFExc( | 97 | throw QPDFExc( |
| 102 | qpdf_e_pages, | 98 | qpdf_e_pages, |
| 103 | this->m->file->getName(), | 99 | this->m->file->getName(), |
| @@ -105,7 +101,6 @@ QPDF::getAllPagesInternal( | @@ -105,7 +101,6 @@ QPDF::getAllPagesInternal( | ||
| 105 | 0, | 101 | 0, |
| 106 | "Loop detected in /Pages structure (getAllPages)"); | 102 | "Loop detected in /Pages structure (getAllPages)"); |
| 107 | } | 103 | } |
| 108 | - visited.insert(cur_node_og); | ||
| 109 | if (!cur_node.isDictionaryOfType("/Pages")) { | 104 | if (!cur_node.isDictionaryOfType("/Pages")) { |
| 110 | cur_node.warnIfPossible( | 105 | cur_node.warnIfPossible( |
| 111 | "/Type key should be /Pages but is not; overriding"); | 106 | "/Type key should be /Pages but is not; overriding"); |
| @@ -125,7 +120,7 @@ QPDF::getAllPagesInternal( | @@ -125,7 +120,7 @@ QPDF::getAllPagesInternal( | ||
| 125 | " (from 0) is direct; converting to indirect"); | 120 | " (from 0) is direct; converting to indirect"); |
| 126 | kid = makeIndirectObject(kid); | 121 | kid = makeIndirectObject(kid); |
| 127 | kids.setArrayItem(i, kid); | 122 | kids.setArrayItem(i, kid); |
| 128 | - } else if (seen.count(kid.getObjGen())) { | 123 | + } else if (!seen.add(kid)) { |
| 129 | // Make a copy of the page. This does the same as | 124 | // Make a copy of the page. This does the same as |
| 130 | // shallowCopyPage in QPDFPageObjectHelper. | 125 | // shallowCopyPage in QPDFPageObjectHelper. |
| 131 | QTC::TC("qpdf", "QPDF resolve duplicated page object"); | 126 | QTC::TC("qpdf", "QPDF resolve duplicated page object"); |
| @@ -134,6 +129,7 @@ QPDF::getAllPagesInternal( | @@ -134,6 +129,7 @@ QPDF::getAllPagesInternal( | ||
| 134 | " (from 0) appears more than once in the pages tree;" | 129 | " (from 0) appears more than once in the pages tree;" |
| 135 | " creating a new page object as a copy"); | 130 | " creating a new page object as a copy"); |
| 136 | kid = makeIndirectObject(QPDFObjectHandle(kid).shallowCopy()); | 131 | kid = makeIndirectObject(QPDFObjectHandle(kid).shallowCopy()); |
| 132 | + seen.add(kid); | ||
| 137 | kids.setArrayItem(i, kid); | 133 | kids.setArrayItem(i, kid); |
| 138 | } | 134 | } |
| 139 | if (!kid.isDictionaryOfType("/Page")) { | 135 | if (!kid.isDictionaryOfType("/Page")) { |
| @@ -141,7 +137,6 @@ QPDF::getAllPagesInternal( | @@ -141,7 +137,6 @@ QPDF::getAllPagesInternal( | ||
| 141 | "/Type key should be /Page but is not; overriding"); | 137 | "/Type key should be /Page but is not; overriding"); |
| 142 | kid.replaceKey("/Type", "/Page"_qpdf); | 138 | kid.replaceKey("/Type", "/Page"_qpdf); |
| 143 | } | 139 | } |
| 144 | - seen.insert(kid.getObjGen()); | ||
| 145 | m->all_pages.push_back(kid); | 140 | m->all_pages.push_back(kid); |
| 146 | } | 141 | } |
| 147 | } | 142 | } |