Commit 5d3f93be29800dd0eb876c3062451d32e7798948
Committed by
Jay Berkenbilt
1 parent
405a549f
Added first version of pages API.
Showing
2 changed files
with
170 additions
and
0 deletions
include/qpdf/QPDF.hh
| ... | ... | @@ -344,9 +344,24 @@ class QPDF |
| 344 | 344 | QPDF_DLL |
| 345 | 345 | std::vector<QPDFObjectHandle> const& getAllPages(); |
| 346 | 346 | |
| 347 | + // QPDF internally caches the /Pages tree. This method will clear | |
| 348 | + // the cache when e.g. direct modifications have been made. | |
| 347 | 349 | QPDF_DLL |
| 348 | 350 | void clearPagesCache(); |
| 349 | 351 | |
| 352 | + // Add new page at the beginning or the end of the current pdf | |
| 353 | + QPDF_DLL | |
| 354 | + void addPage(QPDFObjectHandle newpage, bool first); | |
| 355 | + | |
| 356 | + // Add new page before or after refpage | |
| 357 | + QPDF_DLL | |
| 358 | + void addPageAt(QPDFObjectHandle newpage, bool before, | |
| 359 | + QPDFObjectHandle const& refpage); | |
| 360 | + | |
| 361 | + // Remove pageoh from the pdf. | |
| 362 | + QPDF_DLL | |
| 363 | + void removePage(QPDFObjectHandle const& pageoh); | |
| 364 | + | |
| 350 | 365 | // Resolver class is restricted to QPDFObjectHandle so that only |
| 351 | 366 | // it can resolve indirect references. |
| 352 | 367 | class Resolver |
| ... | ... | @@ -521,8 +536,17 @@ class QPDF |
| 521 | 536 | off_t offset, size_t length, |
| 522 | 537 | QPDFObjectHandle dict, |
| 523 | 538 | Pipeline* pipeline); |
| 539 | + | |
| 540 | + // methods to support page handling | |
| 541 | + | |
| 524 | 542 | void getAllPagesInternal(QPDFObjectHandle cur_pages, |
| 525 | 543 | std::vector<QPDFObjectHandle>& result); |
| 544 | + // creates pageobj_to_pages_pos if necessary | |
| 545 | + // returns position, or -1 if not found | |
| 546 | + int findPage(int objid, int generation); | |
| 547 | + int findPage(QPDFObjectHandle const& pageoh); // convenience | |
| 548 | + | |
| 549 | + void flattenPagesTree(); | |
| 526 | 550 | |
| 527 | 551 | // methods to support encryption -- implemented in QPDF_encryption.cc |
| 528 | 552 | encryption_method_e interpretCF(QPDFObjectHandle); |
| ... | ... | @@ -887,6 +911,7 @@ class QPDF |
| 887 | 911 | std::map<ObjGen, ObjCache> obj_cache; |
| 888 | 912 | QPDFObjectHandle trailer; |
| 889 | 913 | std::vector<QPDFObjectHandle> all_pages; |
| 914 | + std::map<ObjGen, int> pageobj_to_pages_pos; | |
| 890 | 915 | std::vector<QPDFExc> warnings; |
| 891 | 916 | |
| 892 | 917 | // Linearization data | ... | ... |
libqpdf/QPDF.cc
| ... | ... | @@ -4,6 +4,7 @@ |
| 4 | 4 | #include <map> |
| 5 | 5 | #include <string.h> |
| 6 | 6 | #include <memory.h> |
| 7 | +#include <assert.h> | |
| 7 | 8 | |
| 8 | 9 | #include <qpdf/QTC.hh> |
| 9 | 10 | #include <qpdf/QUtil.hh> |
| ... | ... | @@ -2203,8 +2204,152 @@ QPDF::getAllPagesInternal(QPDFObjectHandle cur_pages, |
| 2203 | 2204 | } |
| 2204 | 2205 | } |
| 2205 | 2206 | |
| 2207 | +// FIXXX here down | |
| 2208 | + | |
| 2206 | 2209 | void |
| 2207 | 2210 | QPDF::clearPagesCache() |
| 2208 | 2211 | { |
| 2209 | 2212 | this->all_pages.clear(); |
| 2213 | + this->pageobj_to_pages_pos.clear(); | |
| 2214 | +} | |
| 2215 | + | |
| 2216 | +void | |
| 2217 | +QPDF::flattenPagesTree() | |
| 2218 | +{ | |
| 2219 | + clearPagesCache(); | |
| 2220 | + | |
| 2221 | + // FIXME: more specific method, we don't want to generate the extra stuff. | |
| 2222 | + // We also need cheap fixup after addPage/removePage. | |
| 2223 | + | |
| 2224 | + // no compressed objects to be produced here... | |
| 2225 | + std::map<int, int> object_stream_data; | |
| 2226 | + optimize(object_stream_data); // push down inheritance | |
| 2227 | + | |
| 2228 | + std::vector<QPDFObjectHandle> kids = this->getAllPages(); | |
| 2229 | + QPDFObjectHandle pages = this->trailer.getKey("/Root").getKey("/Pages"); | |
| 2230 | + | |
| 2231 | + const int len = kids.size(); | |
| 2232 | + for (int pos = 0; pos < len; ++pos) | |
| 2233 | + { | |
| 2234 | + // populate pageobj_to_pages_pos | |
| 2235 | + ObjGen og(kids[pos].getObjectID(), kids[pos].getGeneration()); | |
| 2236 | + if (! this->pageobj_to_pages_pos.insert(std::make_pair(og, pos)).second) | |
| 2237 | + { | |
| 2238 | + // insert failed: duplicate entry found | |
| 2239 | + *out_stream << "WARNING: duplicate page reference found, " | |
| 2240 | + << "but currently not fully supported." << std::endl; | |
| 2241 | + } | |
| 2242 | + | |
| 2243 | + // fix parent links | |
| 2244 | + kids[pos].replaceKey("/Parent", pages); | |
| 2245 | + } | |
| 2246 | + | |
| 2247 | + pages.replaceKey("/Kids", QPDFObjectHandle::newArray(kids)); | |
| 2248 | + // /Count has not changed | |
| 2249 | + assert(pages.getKey("/Count").getIntValue() == len); | |
| 2250 | +} | |
| 2251 | + | |
| 2252 | +int | |
| 2253 | +QPDF::findPage(int objid, int generation) | |
| 2254 | +{ | |
| 2255 | + if (this->pageobj_to_pages_pos.empty()) | |
| 2256 | + { | |
| 2257 | + flattenPagesTree(); | |
| 2258 | + } | |
| 2259 | + std::map<ObjGen, int>::iterator it = | |
| 2260 | + this->pageobj_to_pages_pos.find(ObjGen(objid, generation)); | |
| 2261 | + if (it != this->pageobj_to_pages_pos.end()) | |
| 2262 | + { | |
| 2263 | + return (*it).second; | |
| 2264 | + } | |
| 2265 | + return -1; // throw? | |
| 2266 | +} | |
| 2267 | + | |
| 2268 | +int | |
| 2269 | +QPDF::findPage(QPDFObjectHandle const& pageoh) | |
| 2270 | +{ | |
| 2271 | + if (!pageoh.isInitialized()) | |
| 2272 | + { | |
| 2273 | + return -1; | |
| 2274 | + // TODO? throw | |
| 2275 | + } | |
| 2276 | + return findPage(pageoh.getObjectID(), pageoh.getGeneration()); | |
| 2277 | +} | |
| 2278 | + | |
| 2279 | +void | |
| 2280 | +QPDF::addPage(QPDFObjectHandle newpage, bool first) | |
| 2281 | +{ | |
| 2282 | + if (this->pageobj_to_pages_pos.empty()) | |
| 2283 | + { | |
| 2284 | + flattenPagesTree(); | |
| 2285 | + } | |
| 2286 | + | |
| 2287 | + newpage.assertPageObject(); // FIXME: currently private | |
| 2288 | + | |
| 2289 | + QPDFObjectHandle pages = this->trailer.getKey("/Root").getKey("/Pages"); | |
| 2290 | + QPDFObjectHandle kids = pages.getKey("/Kids"); | |
| 2291 | + | |
| 2292 | + newpage.replaceKey("/Parent", pages); | |
| 2293 | + if (first) | |
| 2294 | + { | |
| 2295 | + kids.insertItem(0, newpage); | |
| 2296 | + } | |
| 2297 | + else | |
| 2298 | + { | |
| 2299 | + kids.appendItem(newpage); | |
| 2300 | + } | |
| 2301 | + pages.replaceKey("/Count", | |
| 2302 | + QPDFObjectHandle::newInteger(kids.getArrayNItems())); | |
| 2303 | + | |
| 2304 | + // FIXME: this is overkill, but cache is now stale | |
| 2305 | + clearPagesCache(); | |
| 2306 | +} | |
| 2307 | + | |
| 2308 | +void | |
| 2309 | +QPDF::addPageAt(QPDFObjectHandle newpage, bool before, | |
| 2310 | + QPDFObjectHandle const &refpage) | |
| 2311 | +{ | |
| 2312 | + int refpos = findPage(refpage); // also ensures flat /Pages | |
| 2313 | + if (refpos == -1) | |
| 2314 | + { | |
| 2315 | + throw "Could not find refpage"; | |
| 2316 | + } | |
| 2317 | + | |
| 2318 | + newpage.assertPageObject(); | |
| 2319 | + | |
| 2320 | + QPDFObjectHandle pages = this->trailer.getKey("/Root").getKey("/Pages"); | |
| 2321 | + QPDFObjectHandle kids = pages.getKey("/Kids"); | |
| 2322 | + | |
| 2323 | + if (! before) | |
| 2324 | + { | |
| 2325 | + ++refpos; | |
| 2326 | + } | |
| 2327 | + | |
| 2328 | + newpage.replaceKey("/Parent", pages); | |
| 2329 | + kids.insertItem(refpos, newpage); | |
| 2330 | + pages.replaceKey("/Count", | |
| 2331 | + QPDFObjectHandle::newInteger(kids.getArrayNItems())); | |
| 2332 | + | |
| 2333 | + // FIXME: this is overkill, but cache is now stale | |
| 2334 | + clearPagesCache(); | |
| 2335 | +} | |
| 2336 | + | |
| 2337 | +void | |
| 2338 | +QPDF::removePage(QPDFObjectHandle const& pageoh) | |
| 2339 | +{ | |
| 2340 | + int pos = findPage(pageoh); // also ensures flat /Pages | |
| 2341 | + if (pos == -1) | |
| 2342 | + { | |
| 2343 | + throw "Can't remove non-existing page"; | |
| 2344 | + } | |
| 2345 | + | |
| 2346 | + QPDFObjectHandle pages = this->trailer.getKey("/Root").getKey("/Pages"); | |
| 2347 | + QPDFObjectHandle kids = pages.getKey("/Kids"); | |
| 2348 | + | |
| 2349 | + kids.eraseItem(pos); | |
| 2350 | + pages.replaceKey("/Count", | |
| 2351 | + QPDFObjectHandle::newInteger(kids.getArrayNItems())); | |
| 2352 | + | |
| 2353 | + // FIXME: this is overkill, but cache is now stale | |
| 2354 | + clearPagesCache(); | |
| 2210 | 2355 | } | ... | ... |