Commit 5d3f93be29800dd0eb876c3062451d32e7798948

Authored by Tobias Hoffmann
Committed by Jay Berkenbilt
1 parent 405a549f

Added first version of pages API.

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 }
... ...