summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/qpdf/QPDF.hh25
-rw-r--r--libqpdf/QPDF.cc145
2 files changed, 170 insertions, 0 deletions
diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh
index 048bad47..8d8f4a3e 100644
--- a/include/qpdf/QPDF.hh
+++ b/include/qpdf/QPDF.hh
@@ -344,9 +344,24 @@ class QPDF
QPDF_DLL
std::vector<QPDFObjectHandle> const& getAllPages();
+ // QPDF internally caches the /Pages tree. This method will clear
+ // the cache when e.g. direct modifications have been made.
QPDF_DLL
void clearPagesCache();
+ // Add new page at the beginning or the end of the current pdf
+ QPDF_DLL
+ void addPage(QPDFObjectHandle newpage, bool first);
+
+ // Add new page before or after refpage
+ QPDF_DLL
+ void addPageAt(QPDFObjectHandle newpage, bool before,
+ QPDFObjectHandle const& refpage);
+
+ // Remove pageoh from the pdf.
+ QPDF_DLL
+ void removePage(QPDFObjectHandle const& pageoh);
+
// Resolver class is restricted to QPDFObjectHandle so that only
// it can resolve indirect references.
class Resolver
@@ -521,8 +536,17 @@ class QPDF
off_t offset, size_t length,
QPDFObjectHandle dict,
Pipeline* pipeline);
+
+ // methods to support page handling
+
void getAllPagesInternal(QPDFObjectHandle cur_pages,
std::vector<QPDFObjectHandle>& result);
+ // creates pageobj_to_pages_pos if necessary
+ // returns position, or -1 if not found
+ int findPage(int objid, int generation);
+ int findPage(QPDFObjectHandle const& pageoh); // convenience
+
+ void flattenPagesTree();
// methods to support encryption -- implemented in QPDF_encryption.cc
encryption_method_e interpretCF(QPDFObjectHandle);
@@ -887,6 +911,7 @@ class QPDF
std::map<ObjGen, ObjCache> obj_cache;
QPDFObjectHandle trailer;
std::vector<QPDFObjectHandle> all_pages;
+ std::map<ObjGen, int> pageobj_to_pages_pos;
std::vector<QPDFExc> warnings;
// Linearization data
diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc
index 64c454a2..d36aebe2 100644
--- a/libqpdf/QPDF.cc
+++ b/libqpdf/QPDF.cc
@@ -4,6 +4,7 @@
#include <map>
#include <string.h>
#include <memory.h>
+#include <assert.h>
#include <qpdf/QTC.hh>
#include <qpdf/QUtil.hh>
@@ -2203,8 +2204,152 @@ QPDF::getAllPagesInternal(QPDFObjectHandle cur_pages,
}
}
+// FIXXX here down
+
void
QPDF::clearPagesCache()
{
this->all_pages.clear();
+ this->pageobj_to_pages_pos.clear();
+}
+
+void
+QPDF::flattenPagesTree()
+{
+ clearPagesCache();
+
+ // FIXME: more specific method, we don't want to generate the extra stuff.
+ // We also need cheap fixup after addPage/removePage.
+
+ // no compressed objects to be produced here...
+ std::map<int, int> object_stream_data;
+ optimize(object_stream_data); // push down inheritance
+
+ std::vector<QPDFObjectHandle> kids = this->getAllPages();
+ QPDFObjectHandle pages = this->trailer.getKey("/Root").getKey("/Pages");
+
+ const int len = kids.size();
+ for (int pos = 0; pos < len; ++pos)
+ {
+ // populate pageobj_to_pages_pos
+ ObjGen og(kids[pos].getObjectID(), kids[pos].getGeneration());
+ if (! this->pageobj_to_pages_pos.insert(std::make_pair(og, pos)).second)
+ {
+ // insert failed: duplicate entry found
+ *out_stream << "WARNING: duplicate page reference found, "
+ << "but currently not fully supported." << std::endl;
+ }
+
+ // fix parent links
+ kids[pos].replaceKey("/Parent", pages);
+ }
+
+ pages.replaceKey("/Kids", QPDFObjectHandle::newArray(kids));
+ // /Count has not changed
+ assert(pages.getKey("/Count").getIntValue() == len);
+}
+
+int
+QPDF::findPage(int objid, int generation)
+{
+ if (this->pageobj_to_pages_pos.empty())
+ {
+ flattenPagesTree();
+ }
+ std::map<ObjGen, int>::iterator it =
+ this->pageobj_to_pages_pos.find(ObjGen(objid, generation));
+ if (it != this->pageobj_to_pages_pos.end())
+ {
+ return (*it).second;
+ }
+ return -1; // throw?
+}
+
+int
+QPDF::findPage(QPDFObjectHandle const& pageoh)
+{
+ if (!pageoh.isInitialized())
+ {
+ return -1;
+ // TODO? throw
+ }
+ return findPage(pageoh.getObjectID(), pageoh.getGeneration());
+}
+
+void
+QPDF::addPage(QPDFObjectHandle newpage, bool first)
+{
+ if (this->pageobj_to_pages_pos.empty())
+ {
+ flattenPagesTree();
+ }
+
+ newpage.assertPageObject(); // FIXME: currently private
+
+ QPDFObjectHandle pages = this->trailer.getKey("/Root").getKey("/Pages");
+ QPDFObjectHandle kids = pages.getKey("/Kids");
+
+ newpage.replaceKey("/Parent", pages);
+ if (first)
+ {
+ kids.insertItem(0, newpage);
+ }
+ else
+ {
+ kids.appendItem(newpage);
+ }
+ pages.replaceKey("/Count",
+ QPDFObjectHandle::newInteger(kids.getArrayNItems()));
+
+ // FIXME: this is overkill, but cache is now stale
+ clearPagesCache();
+}
+
+void
+QPDF::addPageAt(QPDFObjectHandle newpage, bool before,
+ QPDFObjectHandle const &refpage)
+{
+ int refpos = findPage(refpage); // also ensures flat /Pages
+ if (refpos == -1)
+ {
+ throw "Could not find refpage";
+ }
+
+ newpage.assertPageObject();
+
+ QPDFObjectHandle pages = this->trailer.getKey("/Root").getKey("/Pages");
+ QPDFObjectHandle kids = pages.getKey("/Kids");
+
+ if (! before)
+ {
+ ++refpos;
+ }
+
+ newpage.replaceKey("/Parent", pages);
+ kids.insertItem(refpos, newpage);
+ pages.replaceKey("/Count",
+ QPDFObjectHandle::newInteger(kids.getArrayNItems()));
+
+ // FIXME: this is overkill, but cache is now stale
+ clearPagesCache();
+}
+
+void
+QPDF::removePage(QPDFObjectHandle const& pageoh)
+{
+ int pos = findPage(pageoh); // also ensures flat /Pages
+ if (pos == -1)
+ {
+ throw "Can't remove non-existing page";
+ }
+
+ QPDFObjectHandle pages = this->trailer.getKey("/Root").getKey("/Pages");
+ QPDFObjectHandle kids = pages.getKey("/Kids");
+
+ kids.eraseItem(pos);
+ pages.replaceKey("/Count",
+ QPDFObjectHandle::newInteger(kids.getArrayNItems()));
+
+ // FIXME: this is overkill, but cache is now stale
+ clearPagesCache();
}