Commit 0f07ecdd6cdd921b22daf64137ecfc33b780d5ab

Authored by m-holger
1 parent 50f21ae7

Create `Pages` class in `QPDF::Doc` and update references

Relocate `Pages` methods to `QPDF::Doc` for improved encapsulation of page-related logic. Adjust all references and methods to use the new placement.
include/qpdf/QPDF.hh
@@ -827,6 +827,12 @@ class QPDF @@ -827,6 +827,12 @@ class QPDF
827 int& O, 827 int& O,
828 bool compressed); 828 bool compressed);
829 829
  830 + // Get a list of objects that would be permitted in an object stream.
  831 + template <typename T>
  832 + std::vector<T> getCompressibleObjGens();
  833 + std::vector<QPDFObjGen> getCompressibleObjVector();
  834 + std::vector<bool> getCompressibleObjSet();
  835 +
830 // methods to support page handling 836 // methods to support page handling
831 837
832 void getAllPagesInternal( 838 void getAllPagesInternal(
@@ -927,12 +933,6 @@ class QPDF @@ -927,12 +933,6 @@ class QPDF
927 933
928 // Methods to support optimization 934 // Methods to support optimization
929 935
930 - void pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys);  
931 - void pushInheritedAttributesToPageInternal(  
932 - QPDFObjectHandle,  
933 - std::map<std::string, std::vector<QPDFObjectHandle>>&,  
934 - bool allow_changes,  
935 - bool warn_skipped_keys);  
936 void updateObjectMaps( 936 void updateObjectMaps(
937 ObjUser const& ou, 937 ObjUser const& ou,
938 QPDFObjectHandle oh, 938 QPDFObjectHandle oh,
libqpdf/QPDF.cc
@@ -181,6 +181,7 @@ QPDF::QPDFVersion() @@ -181,6 +181,7 @@ QPDF::QPDFVersion()
181 QPDF::Members::Members(QPDF& qpdf) : 181 QPDF::Members::Members(QPDF& qpdf) :
182 doc(qpdf, *this), 182 doc(qpdf, *this),
183 objects(doc.objects()), 183 objects(doc.objects()),
  184 + pages(doc.pages()),
184 log(QPDFLogger::defaultLogger()), 185 log(QPDFLogger::defaultLogger()),
185 file(new InvalidInputSource()), 186 file(new InvalidInputSource()),
186 encp(new EncryptionParameters) 187 encp(new EncryptionParameters)
libqpdf/QPDF_optimization.cc
@@ -7,6 +7,8 @@ @@ -7,6 +7,8 @@
7 #include <qpdf/QPDFWriter_private.hh> 7 #include <qpdf/QPDFWriter_private.hh>
8 #include <qpdf/QTC.hh> 8 #include <qpdf/QTC.hh>
9 9
  10 +using Pages = QPDF::Doc::Pages;
  11 +
10 QPDF::ObjUser::ObjUser(user_e type) : 12 QPDF::ObjUser::ObjUser(user_e type) :
11 ou_type(type) 13 ou_type(type)
12 { 14 {
@@ -86,14 +88,13 @@ QPDF::optimize_internal( @@ -86,14 +88,13 @@ QPDF::optimize_internal(
86 if (root.getKey("/Outlines").isDictionary()) { 88 if (root.getKey("/Outlines").isDictionary()) {
87 QPDFObjectHandle outlines = root.getKey("/Outlines"); 89 QPDFObjectHandle outlines = root.getKey("/Outlines");
88 if (!outlines.isIndirect()) { 90 if (!outlines.isIndirect()) {
89 - QTC::TC("qpdf", "QPDF_optimization indirect outlines");  
90 root.replaceKey("/Outlines", makeIndirectObject(outlines)); 91 root.replaceKey("/Outlines", makeIndirectObject(outlines));
91 } 92 }
92 } 93 }
93 94
94 // Traverse pages tree pushing all inherited resources down to the page level. This also 95 // Traverse pages tree pushing all inherited resources down to the page level. This also
95 // initializes m->all_pages. 96 // initializes m->all_pages.
96 - pushInheritedAttributesToPage(allow_changes, false); 97 + m->pages.pushInheritedAttributesToPage(allow_changes, false);
97 98
98 // Traverse pages 99 // Traverse pages
99 size_t n = m->all_pages.size(); 100 size_t n = m->all_pages.size();
@@ -136,11 +137,11 @@ void @@ -136,11 +137,11 @@ void
136 QPDF::pushInheritedAttributesToPage() 137 QPDF::pushInheritedAttributesToPage()
137 { 138 {
138 // Public API should not have access to allow_changes. 139 // Public API should not have access to allow_changes.
139 - pushInheritedAttributesToPage(true, false); 140 + m->pages.pushInheritedAttributesToPage(true, false);
140 } 141 }
141 142
142 void 143 void
143 -QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys) 144 +Pages::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys)
144 { 145 {
145 // Traverse pages tree pushing all inherited resources down to the page level. 146 // Traverse pages tree pushing all inherited resources down to the page level.
146 147
@@ -152,7 +153,7 @@ QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys) @@ -152,7 +153,7 @@ QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys)
152 153
153 // Calling getAllPages() resolves any duplicated page objects, repairs broken nodes, and detects 154 // Calling getAllPages() resolves any duplicated page objects, repairs broken nodes, and detects
154 // loops, so we don't have to do those activities here. 155 // loops, so we don't have to do those activities here.
155 - getAllPages(); 156 + qpdf.getAllPages();
156 157
157 // key_ancestors is a mapping of page attribute keys to a stack of Pages nodes that contain 158 // key_ancestors is a mapping of page attribute keys to a stack of Pages nodes that contain
158 // values for them. 159 // values for them.
@@ -171,7 +172,7 @@ QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys) @@ -171,7 +172,7 @@ QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys)
171 } 172 }
172 173
173 void 174 void
174 -QPDF::pushInheritedAttributesToPageInternal( 175 +Pages ::pushInheritedAttributesToPageInternal(
175 QPDFObjectHandle cur_pages, 176 QPDFObjectHandle cur_pages,
176 std::map<std::string, std::vector<QPDFObjectHandle>>& key_ancestors, 177 std::map<std::string, std::vector<QPDFObjectHandle>>& key_ancestors,
177 bool allow_changes, 178 bool allow_changes,
@@ -183,8 +184,7 @@ QPDF::pushInheritedAttributesToPageInternal( @@ -183,8 +184,7 @@ QPDF::pushInheritedAttributesToPageInternal(
183 184
184 std::set<std::string> inheritable_keys; 185 std::set<std::string> inheritable_keys;
185 for (auto const& key: cur_pages.getKeys()) { 186 for (auto const& key: cur_pages.getKeys()) {
186 - if ((key == "/MediaBox") || (key == "/CropBox") || (key == "/Resources") ||  
187 - (key == "/Rotate")) { 187 + if (key == "/MediaBox" || key == "/CropBox" || key == "/Resources" || key == "/Rotate") {
188 if (!allow_changes) { 188 if (!allow_changes) {
189 throw QPDFExc( 189 throw QPDFExc(
190 qpdf_e_internal, 190 qpdf_e_internal,
@@ -197,21 +197,19 @@ QPDF::pushInheritedAttributesToPageInternal( @@ -197,21 +197,19 @@ QPDF::pushInheritedAttributesToPageInternal(
197 // This is an inheritable resource 197 // This is an inheritable resource
198 inheritable_keys.insert(key); 198 inheritable_keys.insert(key);
199 QPDFObjectHandle oh = cur_pages.getKey(key); 199 QPDFObjectHandle oh = cur_pages.getKey(key);
200 - QTC::TC("qpdf", "QPDF opt direct pages resource", oh.isIndirect() ? 0 : 1);  
201 - if (!oh.isIndirect()) { 200 + QTC::TC("qpdf", "QPDF opt direct pages resource", oh.indirect() ? 0 : 1);
  201 + if (!oh.indirect()) {
202 if (!oh.isScalar()) { 202 if (!oh.isScalar()) {
203 // Replace shared direct object non-scalar resources with indirect objects to 203 // Replace shared direct object non-scalar resources with indirect objects to
204 // avoid copying large structures around. 204 // avoid copying large structures around.
205 - cur_pages.replaceKey(key, makeIndirectObject(oh)); 205 + cur_pages.replaceKey(key, qpdf.makeIndirectObject(oh));
206 oh = cur_pages.getKey(key); 206 oh = cur_pages.getKey(key);
207 } else { 207 } else {
208 // It's okay to copy scalars. 208 // It's okay to copy scalars.
209 - QTC::TC("qpdf", "QPDF opt inherited scalar");  
210 } 209 }
211 } 210 }
212 key_ancestors[key].push_back(oh); 211 key_ancestors[key].push_back(oh);
213 if (key_ancestors[key].size() > 1) { 212 if (key_ancestors[key].size() > 1) {
214 - QTC::TC("qpdf", "QPDF opt key ancestors depth > 1");  
215 } 213 }
216 // Remove this resource from this node. It will be reattached at the page level. 214 // Remove this resource from this node. It will be reattached at the page level.
217 cur_pages.removeKey(key); 215 cur_pages.removeKey(key);
@@ -219,7 +217,7 @@ QPDF::pushInheritedAttributesToPageInternal( @@ -219,7 +217,7 @@ QPDF::pushInheritedAttributesToPageInternal(
219 // Warn when flattening, but not if the key is at the top level (i.e. "/Parent" not 217 // Warn when flattening, but not if the key is at the top level (i.e. "/Parent" not
220 // set), as we don't change these; but flattening removes intermediate /Pages nodes. 218 // set), as we don't change these; but flattening removes intermediate /Pages nodes.
221 if (warn_skipped_keys && cur_pages.hasKey("/Parent")) { 219 if (warn_skipped_keys && cur_pages.hasKey("/Parent")) {
222 - warn( 220 + qpdf.warn(
223 qpdf_e_pages, 221 qpdf_e_pages,
224 "Pages object: object " + cur_pages.id_gen().unparse(' '), 222 "Pages object: object " + cur_pages.id_gen().unparse(' '),
225 0, 223 0,
@@ -242,7 +240,6 @@ QPDF::pushInheritedAttributesToPageInternal( @@ -242,7 +240,6 @@ QPDF::pushInheritedAttributesToPageInternal(
242 for (auto const& iter: key_ancestors) { 240 for (auto const& iter: key_ancestors) {
243 std::string const& key = iter.first; 241 std::string const& key = iter.first;
244 if (!kid.hasKey(key)) { 242 if (!kid.hasKey(key)) {
245 - QTC::TC("qpdf", "QPDF opt resource inherited");  
246 kid.replaceKey(key, iter.second.back()); 243 kid.replaceKey(key, iter.second.back());
247 } else { 244 } else {
248 QTC::TC("qpdf", "QPDF opt page resource hides ancestor"); 245 QTC::TC("qpdf", "QPDF opt page resource hides ancestor");
@@ -256,11 +253,9 @@ QPDF::pushInheritedAttributesToPageInternal( @@ -256,11 +253,9 @@ QPDF::pushInheritedAttributesToPageInternal(
256 // which inheritable attributes are available. 253 // which inheritable attributes are available.
257 254
258 if (!inheritable_keys.empty()) { 255 if (!inheritable_keys.empty()) {
259 - QTC::TC("qpdf", "QPDF opt inheritable keys");  
260 for (auto const& key: inheritable_keys) { 256 for (auto const& key: inheritable_keys) {
261 key_ancestors[key].pop_back(); 257 key_ancestors[key].pop_back();
262 if (key_ancestors[key].empty()) { 258 if (key_ancestors[key].empty()) {
263 - QTC::TC("qpdf", "QPDF opt erase empty key ancestor");  
264 key_ancestors.erase(key); 259 key_ancestors.erase(key);
265 } 260 }
266 } 261 }
libqpdf/QPDF_pages.cc
@@ -37,6 +37,8 @@ @@ -37,6 +37,8 @@
37 // insertPage, and removePage, along with methods they call, are concerned with it. Everything else 37 // insertPage, and removePage, along with methods they call, are concerned with it. Everything else
38 // goes through one of those methods. 38 // goes through one of those methods.
39 39
  40 +using Pages = QPDF::Doc::Pages;
  41 +
40 std::vector<QPDFObjectHandle> const& 42 std::vector<QPDFObjectHandle> const&
41 QPDF::getAllPages() 43 QPDF::getAllPages()
42 { 44 {
@@ -75,14 +77,14 @@ QPDF::getAllPages() @@ -75,14 +77,14 @@ QPDF::getAllPages()
75 qpdf_e_pages, m->file->getName(), "", 0, "root of pages tree has no /Kids array"); 77 qpdf_e_pages, m->file->getName(), "", 0, "root of pages tree has no /Kids array");
76 } 78 }
77 try { 79 try {
78 - getAllPagesInternal(pages, visited, seen, false, false); 80 + m->pages.getAllPagesInternal(pages, visited, seen, false, false);
79 } catch (...) { 81 } catch (...) {
80 m->all_pages.clear(); 82 m->all_pages.clear();
81 m->invalid_page_found = false; 83 m->invalid_page_found = false;
82 throw; 84 throw;
83 } 85 }
84 if (m->invalid_page_found) { 86 if (m->invalid_page_found) {
85 - flattenPagesTree(); 87 + m->pages.flattenPagesTree();
86 m->invalid_page_found = false; 88 m->invalid_page_found = false;
87 } 89 }
88 } 90 }
@@ -90,7 +92,7 @@ QPDF::getAllPages() @@ -90,7 +92,7 @@ QPDF::getAllPages()
90 } 92 }
91 93
92 void 94 void
93 -QPDF::getAllPagesInternal( 95 +Pages::getAllPagesInternal(
94 QPDFObjectHandle cur_node, 96 QPDFObjectHandle cur_node,
95 QPDFObjGen::set& visited, 97 QPDFObjGen::set& visited,
96 QPDFObjGen::set& seen, 98 QPDFObjGen::set& seen,
@@ -139,17 +141,15 @@ QPDF::getAllPagesInternal( @@ -139,17 +141,15 @@ QPDF::getAllPagesInternal(
139 continue; 141 continue;
140 } 142 }
141 if (!kid.isIndirect()) { 143 if (!kid.isIndirect()) {
142 - QTC::TC("qpdf", "QPDF handle direct page object");  
143 cur_node.warn( 144 cur_node.warn(
144 "kid " + std::to_string(i) + " (from 0) is direct; converting to indirect"); 145 "kid " + std::to_string(i) + " (from 0) is direct; converting to indirect");
145 - kid = makeIndirectObject(kid); 146 + kid = qpdf.makeIndirectObject(kid);
146 ++errors; 147 ++errors;
147 } 148 }
148 if (kid.hasKey("/Kids")) { 149 if (kid.hasKey("/Kids")) {
149 getAllPagesInternal(kid, visited, seen, media_box, resources); 150 getAllPagesInternal(kid, visited, seen, media_box, resources);
150 } else { 151 } else {
151 if (!media_box && !kid.getKey("/MediaBox").isRectangle()) { 152 if (!media_box && !kid.getKey("/MediaBox").isRectangle()) {
152 - QTC::TC("qpdf", "QPDF missing mediabox");  
153 kid.warn( 153 kid.warn(
154 "kid " + std::to_string(i) + 154 "kid " + std::to_string(i) +
155 " (from 0) MediaBox is undefined; setting to letter / ANSI A"); 155 " (from 0) MediaBox is undefined; setting to letter / ANSI A");
@@ -193,7 +193,6 @@ QPDF::getAllPagesInternal( @@ -193,7 +193,6 @@ QPDF::getAllPagesInternal(
193 if (!seen.add(kid)) { 193 if (!seen.add(kid)) {
194 // Make a copy of the page. This does the same as shallowCopyPage in 194 // Make a copy of the page. This does the same as shallowCopyPage in
195 // QPDFPageObjectHelper. 195 // QPDFPageObjectHelper.
196 - QTC::TC("qpdf", "QPDF resolve duplicated page object");  
197 if (!m->reconstructed_xref) { 196 if (!m->reconstructed_xref) {
198 cur_node.warn( 197 cur_node.warn(
199 "kid " + std::to_string(i) + 198 "kid " + std::to_string(i) +
@@ -201,7 +200,7 @@ QPDF::getAllPagesInternal( @@ -201,7 +200,7 @@ QPDF::getAllPagesInternal(
201 " creating a new page object as a copy"); 200 " creating a new page object as a copy");
202 // This needs to be fixed. shallowCopy does not necessarily produce a valid 201 // This needs to be fixed. shallowCopy does not necessarily produce a valid
203 // page. 202 // page.
204 - kid = makeIndirectObject(QPDFObjectHandle(kid).shallowCopy()); 203 + kid = qpdf.makeIndirectObject(QPDFObjectHandle(kid).shallowCopy());
205 seen.add(kid); 204 seen.add(kid);
206 } else { 205 } else {
207 cur_node.warn( 206 cur_node.warn(
@@ -239,7 +238,6 @@ QPDF::updateAllPagesCache() @@ -239,7 +238,6 @@ QPDF::updateAllPagesCache()
239 // Force regeneration of the pages cache. We force immediate recalculation of all_pages since 238 // Force regeneration of the pages cache. We force immediate recalculation of all_pages since
240 // users may have references to it that they got from calls to getAllPages(). We can defer 239 // users may have references to it that they got from calls to getAllPages(). We can defer
241 // recalculation of pageobj_to_pages_pos until needed. 240 // recalculation of pageobj_to_pages_pos until needed.
242 - QTC::TC("qpdf", "QPDF updateAllPagesCache");  
243 m->all_pages.clear(); 241 m->all_pages.clear();
244 m->pageobj_to_pages_pos.clear(); 242 m->pageobj_to_pages_pos.clear();
245 m->pushed_inherited_attributes_to_pages = false; 243 m->pushed_inherited_attributes_to_pages = false;
@@ -247,7 +245,7 @@ QPDF::updateAllPagesCache() @@ -247,7 +245,7 @@ QPDF::updateAllPagesCache()
247 } 245 }
248 246
249 void 247 void
250 -QPDF::flattenPagesTree() 248 +Pages::flattenPagesTree()
251 { 249 {
252 // If not already done, flatten the /Pages structure and initialize pageobj_to_pages_pos. 250 // If not already done, flatten the /Pages structure and initialize pageobj_to_pages_pos.
253 251
@@ -259,7 +257,7 @@ QPDF::flattenPagesTree() @@ -259,7 +257,7 @@ QPDF::flattenPagesTree()
259 // generated. 257 // generated.
260 pushInheritedAttributesToPage(true, true); 258 pushInheritedAttributesToPage(true, true);
261 259
262 - QPDFObjectHandle pages = getRoot().getKey("/Pages"); 260 + QPDFObjectHandle pages = qpdf.getRoot().getKey("/Pages");
263 261
264 size_t const len = m->all_pages.size(); 262 size_t const len = m->all_pages.size();
265 for (size_t pos = 0; pos < len; ++pos) { 263 for (size_t pos = 0; pos < len; ++pos) {
@@ -282,7 +280,7 @@ QPDF::flattenPagesTree() @@ -282,7 +280,7 @@ QPDF::flattenPagesTree()
282 } 280 }
283 281
284 void 282 void
285 -QPDF::insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate) 283 +Pages::insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate)
286 { 284 {
287 QPDFObjGen og(obj.getObjGen()); 285 QPDFObjGen og(obj.getObjGen());
288 if (check_duplicate) { 286 if (check_duplicate) {
@@ -301,24 +299,22 @@ QPDF::insertPageobjToPage(QPDFObjectHandle const&amp; obj, int pos, bool check_dupli @@ -301,24 +299,22 @@ QPDF::insertPageobjToPage(QPDFObjectHandle const&amp; obj, int pos, bool check_dupli
301 } 299 }
302 300
303 void 301 void
304 -QPDF::insertPage(QPDFObjectHandle newpage, int pos) 302 +Pages::insertPage(QPDFObjectHandle newpage, int pos)
305 { 303 {
306 // pos is numbered from 0, so pos = 0 inserts at the beginning and pos = npages adds to the end. 304 // pos is numbered from 0, so pos = 0 inserts at the beginning and pos = npages adds to the end.
307 305
308 flattenPagesTree(); 306 flattenPagesTree();
309 307
310 if (!newpage.isIndirect()) { 308 if (!newpage.isIndirect()) {
311 - QTC::TC("qpdf", "QPDF insert non-indirect page");  
312 - newpage = makeIndirectObject(newpage);  
313 - } else if (newpage.getOwningQPDF() != this) {  
314 - QTC::TC("qpdf", "QPDF insert foreign page"); 309 + newpage = qpdf.makeIndirectObject(newpage);
  310 + } else if (newpage.getOwningQPDF() != &qpdf) {
315 newpage.getQPDF().pushInheritedAttributesToPage(); 311 newpage.getQPDF().pushInheritedAttributesToPage();
316 - newpage = copyForeignObject(newpage); 312 + newpage = qpdf.copyForeignObject(newpage);
317 } else { 313 } else {
318 QTC::TC("qpdf", "QPDF insert indirect page"); 314 QTC::TC("qpdf", "QPDF insert indirect page");
319 } 315 }
320 316
321 - if ((pos < 0) || (toS(pos) > m->all_pages.size())) { 317 + if (pos < 0 || toS(pos) > m->all_pages.size()) {
322 throw std::runtime_error("QPDF::insertPage called with pos out of range"); 318 throw std::runtime_error("QPDF::insertPage called with pos out of range");
323 } 319 }
324 320
@@ -331,11 +327,10 @@ QPDF::insertPage(QPDFObjectHandle newpage, int pos) @@ -331,11 +327,10 @@ QPDF::insertPage(QPDFObjectHandle newpage, int pos)
331 327
332 auto og = newpage.getObjGen(); 328 auto og = newpage.getObjGen();
333 if (m->pageobj_to_pages_pos.contains(og)) { 329 if (m->pageobj_to_pages_pos.contains(og)) {
334 - QTC::TC("qpdf", "QPDF resolve duplicated page in insert");  
335 - newpage = makeIndirectObject(QPDFObjectHandle(newpage).shallowCopy()); 330 + newpage = qpdf.makeIndirectObject(QPDFObjectHandle(newpage).shallowCopy());
336 } 331 }
337 332
338 - QPDFObjectHandle pages = getRoot().getKey("/Pages"); 333 + QPDFObjectHandle pages = qpdf.getRoot().getKey("/Pages");
339 QPDFObjectHandle kids = pages.getKey("/Kids"); 334 QPDFObjectHandle kids = pages.getKey("/Kids");
340 335
341 newpage.replaceKey("/Parent", pages); 336 newpage.replaceKey("/Parent", pages);
@@ -369,7 +364,7 @@ QPDF::removePage(QPDFObjectHandle page) @@ -369,7 +364,7 @@ QPDF::removePage(QPDFObjectHandle page)
369 m->all_pages.erase(m->all_pages.begin() + pos); 364 m->all_pages.erase(m->all_pages.begin() + pos);
370 m->pageobj_to_pages_pos.erase(page.getObjGen()); 365 m->pageobj_to_pages_pos.erase(page.getObjGen());
371 for (int i = pos; i < npages; ++i) { 366 for (int i = pos; i < npages; ++i) {
372 - insertPageobjToPage(m->all_pages.at(toS(i)), i, false); 367 + m->pages.insertPageobjToPage(m->all_pages.at(toS(i)), i, false);
373 } 368 }
374 } 369 }
375 370
@@ -380,16 +375,17 @@ QPDF::addPageAt(QPDFObjectHandle newpage, bool before, QPDFObjectHandle refpage) @@ -380,16 +375,17 @@ QPDF::addPageAt(QPDFObjectHandle newpage, bool before, QPDFObjectHandle refpage)
380 if (!before) { 375 if (!before) {
381 ++refpos; 376 ++refpos;
382 } 377 }
383 - insertPage(newpage, refpos); 378 + m->pages.insertPage(newpage, refpos);
384 } 379 }
385 380
386 void 381 void
387 QPDF::addPage(QPDFObjectHandle newpage, bool first) 382 QPDF::addPage(QPDFObjectHandle newpage, bool first)
388 { 383 {
389 if (first) { 384 if (first) {
390 - insertPage(newpage, 0); 385 + m->pages.insertPage(newpage, 0);
391 } else { 386 } else {
392 - insertPage(newpage, getRoot().getKey("/Pages").getKey("/Count").getIntValueAsInt()); 387 + m->pages.insertPage(
  388 + newpage, getRoot().getKey("/Pages").getKey("/Count").getIntValueAsInt());
393 } 389 }
394 } 390 }
395 391
@@ -402,7 +398,7 @@ QPDF::findPage(QPDFObjectHandle&amp; page) @@ -402,7 +398,7 @@ QPDF::findPage(QPDFObjectHandle&amp; page)
402 int 398 int
403 QPDF::findPage(QPDFObjGen og) 399 QPDF::findPage(QPDFObjGen og)
404 { 400 {
405 - flattenPagesTree(); 401 + m->pages.flattenPagesTree();
406 auto it = m->pageobj_to_pages_pos.find(og); 402 auto it = m->pageobj_to_pages_pos.find(og);
407 if (it == m->pageobj_to_pages_pos.end()) { 403 if (it == m->pageobj_to_pages_pos.end()) {
408 throw QPDFExc( 404 throw QPDFExc(
libqpdf/qpdf/QPDF_private.hh
@@ -550,7 +550,45 @@ class QPDF::Doc @@ -550,7 +550,45 @@ class QPDF::Doc
550 private: 550 private:
551 QPDF& qpdf; 551 QPDF& qpdf;
552 QPDF::Members* m; 552 QPDF::Members* m;
553 - }; // class Objects 553 + }; // class QPDF::Doc::Objects
  554 +
  555 + // This class is used to represent a PDF Pages tree.
  556 + class Pages
  557 + {
  558 + public:
  559 + Pages() = delete;
  560 + Pages(Pages const&) = delete;
  561 + Pages(Pages&&) = delete;
  562 + Pages& operator=(Pages const&) = delete;
  563 + Pages& operator=(Pages&&) = delete;
  564 + ~Pages() = default;
  565 +
  566 + Pages(QPDF& qpdf, QPDF::Members* m) :
  567 + qpdf(qpdf),
  568 + m(m)
  569 + {
  570 + }
  571 +
  572 + void getAllPagesInternal(
  573 + QPDFObjectHandle cur_pages,
  574 + QPDFObjGen::set& visited,
  575 + QPDFObjGen::set& seen,
  576 + bool media_box,
  577 + bool resources);
  578 + void insertPage(QPDFObjectHandle newpage, int pos);
  579 + void flattenPagesTree();
  580 + void insertPageobjToPage(QPDFObjectHandle const& obj, int pos, bool check_duplicate);
  581 + void pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys);
  582 + void pushInheritedAttributesToPageInternal(
  583 + QPDFObjectHandle,
  584 + std::map<std::string, std::vector<QPDFObjectHandle>>&,
  585 + bool allow_changes,
  586 + bool warn_skipped_keys);
  587 +
  588 + private:
  589 + QPDF& qpdf;
  590 + QPDF::Members* m;
  591 + }; // class QPDF::Doc::Pages
554 592
555 // StreamCopier class is restricted to QPDFObjectHandle so it can copy stream data. 593 // StreamCopier class is restricted to QPDFObjectHandle so it can copy stream data.
556 class StreamCopier 594 class StreamCopier
@@ -575,7 +613,8 @@ class QPDF::Doc @@ -575,7 +613,8 @@ class QPDF::Doc
575 Doc(QPDF& qpdf, QPDF::Members& m) : 613 Doc(QPDF& qpdf, QPDF::Members& m) :
576 qpdf(qpdf), 614 qpdf(qpdf),
577 m(m), 615 m(m),
578 - objects_(qpdf, &m) 616 + objects_(qpdf, &m),
  617 + pages_(qpdf, &m)
579 { 618 {
580 } 619 }
581 620
@@ -585,6 +624,12 @@ class QPDF::Doc @@ -585,6 +624,12 @@ class QPDF::Doc
585 return objects_; 624 return objects_;
586 }; 625 };
587 626
  627 + Pages&
  628 + pages()
  629 + {
  630 + return pages_;
  631 + }
  632 +
588 bool reconstructed_xref() const; 633 bool reconstructed_xref() const;
589 634
590 QPDFAcroFormDocumentHelper& 635 QPDFAcroFormDocumentHelper&
@@ -637,6 +682,7 @@ class QPDF::Doc @@ -637,6 +682,7 @@ class QPDF::Doc
637 QPDF::Members& m; 682 QPDF::Members& m;
638 683
639 Objects objects_; 684 Objects objects_;
  685 + Pages pages_;
640 686
641 // Document Helpers; 687 // Document Helpers;
642 std::unique_ptr<QPDFAcroFormDocumentHelper> acroform_; 688 std::unique_ptr<QPDFAcroFormDocumentHelper> acroform_;
@@ -659,6 +705,7 @@ class QPDF::Members @@ -659,6 +705,7 @@ class QPDF::Members
659 private: 705 private:
660 Doc doc; 706 Doc doc;
661 Doc::Objects& objects; 707 Doc::Objects& objects;
  708 + Doc::Pages& pages;
662 std::shared_ptr<QPDFLogger> log; 709 std::shared_ptr<QPDFLogger> log;
663 unsigned long long unique_id{0}; 710 unsigned long long unique_id{0};
664 qpdf::Tokenizer tokenizer; 711 qpdf::Tokenizer tokenizer;
qpdf/qpdf.testcov
@@ -2,12 +2,8 @@ ignored-scope: libtests @@ -2,12 +2,8 @@ ignored-scope: libtests
2 QPDF hint table length direct 0 2 QPDF hint table length direct 0
3 QPDF P absent in lindict 1 3 QPDF P absent in lindict 1
4 QPDF opt direct pages resource 1 4 QPDF opt direct pages resource 1
5 -QPDF opt inheritable keys 0  
6 QPDF opt no inheritable keys 0 5 QPDF opt no inheritable keys 0
7 -QPDF opt erase empty key ancestor 0  
8 -QPDF opt resource inherited 0  
9 QPDF opt page resource hides ancestor 0 6 QPDF opt page resource hides ancestor 0
10 -QPDF opt key ancestors depth > 1 0  
11 QPDF opt loop detected 0 7 QPDF opt loop detected 0
12 QPDF categorize pagemode present 1 8 QPDF categorize pagemode present 1
13 QPDF categorize pagemode outlines 1 9 QPDF categorize pagemode outlines 1
@@ -30,7 +26,6 @@ main QTest dictionary indirect 1 @@ -30,7 +26,6 @@ main QTest dictionary indirect 1
30 main QTest stream 0 26 main QTest stream 0
31 QPDF lin write nshared_total > nshared_first_page 1 27 QPDF lin write nshared_total > nshared_first_page 1
32 QPDFWriter encrypted hint stream 0 28 QPDFWriter encrypted hint stream 0
33 -QPDF opt inherited scalar 0  
34 QPDF xref gen > 0 1 29 QPDF xref gen > 0 1
35 QPDF startxref more than 1024 before end 0 30 QPDF startxref more than 1024 before end 0
36 QPDFParser bad brace 0 31 QPDFParser bad brace 0
@@ -127,8 +122,6 @@ exercise processFile(FILE*) 0 @@ -127,8 +122,6 @@ exercise processFile(FILE*) 0
127 exercise processMemoryFile 0 122 exercise processMemoryFile 0
128 QPDF remove page 2 123 QPDF remove page 2
129 QPDF insert page 2 124 QPDF insert page 2
130 -QPDF updateAllPagesCache 0  
131 -QPDF insert non-indirect page 0  
132 QPDF insert indirect page 0 125 QPDF insert indirect page 0
133 QPDF_Stream ERR shallow copy stream 0 126 QPDF_Stream ERR shallow copy stream 0
134 QPDFObjectHandle newStream with string 0 127 QPDFObjectHandle newStream with string 0
@@ -142,7 +135,6 @@ QPDF replace array 0 @@ -142,7 +135,6 @@ QPDF replace array 0
142 QPDF replace dictionary 0 135 QPDF replace dictionary 0
143 QPDF replace stream 0 136 QPDF replace stream 0
144 QPDF replace foreign indirect with null 0 137 QPDF replace foreign indirect with null 0
145 -QPDF insert foreign page 0  
146 QPDFWriter copy use_aes 1 138 QPDFWriter copy use_aes 1
147 QPDFParser indirect without context 0 139 QPDFParser indirect without context 0
148 QPDFObjectHandle trailing data in parse 0 140 QPDFObjectHandle trailing data in parse 0
@@ -162,7 +154,6 @@ qpdf-c called qpdf_set_r6_encryption_parameters 0 @@ -162,7 +154,6 @@ qpdf-c called qpdf_set_r6_encryption_parameters 0
162 QPDFObjectHandle EOF in inline image 0 154 QPDFObjectHandle EOF in inline image 0
163 QPDFObjectHandle inline image token 0 155 QPDFObjectHandle inline image token 0
164 QPDF not caching overridden objstm object 0 156 QPDF not caching overridden objstm object 0
165 -QPDF_optimization indirect outlines 0  
166 QPDF xref space 2 157 QPDF xref space 2
167 QPDFJob pages range omitted in middle 0 158 QPDFJob pages range omitted in middle 0
168 QPDFWriter standard deterministic ID 1 159 QPDFWriter standard deterministic ID 1
@@ -282,9 +273,6 @@ QPDFPageDocumentHelper ignore annotation with no appearance 0 @@ -282,9 +273,6 @@ QPDFPageDocumentHelper ignore annotation with no appearance 0
282 QPDFFormFieldObjectHelper replaced BMC at EOF 0 273 QPDFFormFieldObjectHelper replaced BMC at EOF 0
283 QPDFFormFieldObjectHelper fallback Tf 0 274 QPDFFormFieldObjectHelper fallback Tf 0
284 QPDFPageObjectHelper copy shared attribute 1 275 QPDFPageObjectHelper copy shared attribute 1
285 -QPDF resolve duplicated page object 0  
286 -QPDF handle direct page object 0  
287 -QPDF missing mediabox 0  
288 QPDF inherit mediabox 1 276 QPDF inherit mediabox 1
289 QPDFTokenizer finder found wrong word 0 277 QPDFTokenizer finder found wrong word 0
290 QPDFTokenizer found EI by byte count 0 278 QPDFTokenizer found EI by byte count 0
@@ -402,7 +390,6 @@ QPDFAcroFormDocumentHelper /DA parse error 0 @@ -402,7 +390,6 @@ QPDFAcroFormDocumentHelper /DA parse error 0
402 QPDFAcroFormDocumentHelper AP parse error 1 390 QPDFAcroFormDocumentHelper AP parse error 1
403 QPDFJob copy fields not this file 0 391 QPDFJob copy fields not this file 0
404 QPDFJob copy fields non-first from orig 0 392 QPDFJob copy fields non-first from orig 0
405 -QPDF resolve duplicated page in insert 0  
406 QPDFWriter exclude from object stream 0 393 QPDFWriter exclude from object stream 0
407 QPDFJob weak crypto error 0 394 QPDFJob weak crypto error 0
408 qpdf-c called qpdf_oh_is_initialized 0 395 qpdf-c called qpdf_oh_is_initialized 0