summaryrefslogtreecommitdiffstats
path: root/libqpdf
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2012-07-11 21:29:41 +0200
committerJay Berkenbilt <ejb@ql.org>2012-07-11 21:54:33 +0200
commite7b8f297ba92f4cadf88efcb394830dc24d54738 (patch)
tree4bc25b3928545d81c4b8029fab389af8bccfdbc5 /libqpdf
parent8a217eb3a26931453b4f003c6c18ad8569230cf1 (diff)
downloadqpdf-e7b8f297ba92f4cadf88efcb394830dc24d54738.tar.zst
Support copying objects from another QPDF object
This includes QPDF::copyForeignObject and supporting foreign objects as arguments to addPage*.
Diffstat (limited to 'libqpdf')
-rw-r--r--libqpdf/QPDF.cc257
-rw-r--r--libqpdf/QPDFObjectHandle.cc36
-rw-r--r--libqpdf/QPDF_optimization.cc9
-rw-r--r--libqpdf/QPDF_pages.cc7
4 files changed, 307 insertions, 2 deletions
diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc
index 1c4e5d8d..4a764964 100644
--- a/libqpdf/QPDF.cc
+++ b/libqpdf/QPDF.cc
@@ -348,6 +348,23 @@ QPDF::ObjGen::operator<(ObjGen const& rhs) const
((this->obj == rhs.obj) && (this->gen < rhs.gen)));
}
+void
+QPDF::CopiedStreamDataProvider::provideStreamData(
+ int objid, int generation, Pipeline* pipeline)
+{
+ QPDFObjectHandle foreign_stream =
+ this->foreign_streams[ObjGen(objid, generation)];
+ foreign_stream.pipeStreamData(pipeline, false, false, false);
+}
+
+void
+QPDF::CopiedStreamDataProvider::registerForeignStream(
+ ObjGen const& local_og, QPDFObjectHandle foreign_stream)
+{
+ this->foreign_streams[local_og] = foreign_stream;
+}
+
+
std::string const&
QPDF::QPDFVersion()
{
@@ -369,6 +386,8 @@ QPDF::QPDF() :
cf_file(e_none),
cached_key_objid(0),
cached_key_generation(0),
+ pushed_inherited_attributes_to_pages(false),
+ copied_stream_data_provider(0),
first_xref_item_offset(0),
uncompressed_after_compressed(false)
{
@@ -2067,6 +2086,244 @@ QPDF::replaceReserved(QPDFObjectHandle reserved,
replacement);
}
+QPDFObjectHandle
+QPDF::copyForeignObject(QPDFObjectHandle foreign)
+{
+ return copyForeignObject(foreign, false);
+}
+
+QPDFObjectHandle
+QPDF::copyForeignObject(QPDFObjectHandle foreign, bool allow_page)
+{
+ if (! foreign.isIndirect())
+ {
+ QTC::TC("qpdf", "QPDF copyForeign direct");
+ throw std::logic_error(
+ "QPDF::copyForeign called with direct object handle");
+ }
+ QPDF* other = foreign.getOwningQPDF();
+ if (other == this)
+ {
+ QTC::TC("qpdf", "QPDF copyForeign not foreign");
+ throw std::logic_error(
+ "QPDF::copyForeign called with object from this QPDF");
+ }
+
+ ObjCopier& obj_copier = this->object_copiers[other];
+ if (! obj_copier.visiting.empty())
+ {
+ throw std::logic_error("obj_copier.visiting is not empty"
+ " at the beginning of copyForeignObject");
+ }
+
+ // Make sure we have an object in this file for every referenced
+ // object in the old file. obj_copier.object_map maps foreign
+ // ObjGen to local objects. For everything new that we have to
+ // copy, the local object will be a reservation, unless it is a
+ // stream, in which case the local object will already be a
+ // stream.
+ reserveObjects(foreign, obj_copier, true);
+
+ if (! obj_copier.visiting.empty())
+ {
+ throw std::logic_error("obj_copier.visiting is not empty"
+ " after reserving objects");
+ }
+
+ // Copy any new objects and replace the reservations.
+ for (std::vector<QPDFObjectHandle>::iterator iter =
+ obj_copier.to_copy.begin();
+ iter != obj_copier.to_copy.end(); ++iter)
+ {
+ QPDFObjectHandle& to_copy = *iter;
+ QPDFObjectHandle copy =
+ replaceForeignIndirectObjects(to_copy, obj_copier, true);
+ if (! to_copy.isStream())
+ {
+ ObjGen og(to_copy.getObjectID(), to_copy.getGeneration());
+ replaceReserved(obj_copier.object_map[og], copy);
+ }
+ }
+ obj_copier.to_copy.clear();
+
+ return obj_copier.object_map[ObjGen(foreign.getObjectID(),
+ foreign.getGeneration())];
+}
+
+void
+QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier,
+ bool top)
+{
+ if (foreign.isReserved())
+ {
+ throw std::logic_error(
+ "QPDF: attempting to copy a foreign reserved object");
+ }
+
+ if (foreign.isPagesObject())
+ {
+ QTC::TC("qpdf", "QPDF not copying pages object");
+ return;
+ }
+
+ if ((! top) && foreign.isPageObject())
+ {
+ QTC::TC("qpdf", "QPDF not crossing page boundary");
+ return;
+ }
+
+ if (foreign.isIndirect())
+ {
+ ObjGen foreign_og(foreign.getObjectID(), foreign.getGeneration());
+ if (obj_copier.visiting.find(foreign_og) != obj_copier.visiting.end())
+ {
+ QTC::TC("qpdf", "QPDF loop reserving objects");
+ return;
+ }
+ QTC::TC("qpdf", "QPDF copy indirect");
+ obj_copier.visiting.insert(foreign_og);
+ std::map<ObjGen, QPDFObjectHandle>::iterator mapping =
+ obj_copier.object_map.find(foreign_og);
+ if (mapping == obj_copier.object_map.end())
+ {
+ obj_copier.to_copy.push_back(foreign);
+ QPDFObjectHandle reservation;
+ if (foreign.isStream())
+ {
+ reservation = QPDFObjectHandle::newStream(this);
+ }
+ else
+ {
+ reservation = QPDFObjectHandle::newReserved(this);
+ }
+ obj_copier.object_map[foreign_og] = reservation;
+ }
+ }
+
+ if (foreign.isArray())
+ {
+ QTC::TC("qpdf", "QPDF reserve array");
+ int n = foreign.getArrayNItems();
+ for (int i = 0; i < n; ++i)
+ {
+ reserveObjects(foreign.getArrayItem(i), obj_copier, false);
+ }
+ }
+ else if (foreign.isDictionary())
+ {
+ QTC::TC("qpdf", "QPDF reserve dictionary");
+ std::set<std::string> keys = foreign.getKeys();
+ for (std::set<std::string>::iterator iter = keys.begin();
+ iter != keys.end(); ++iter)
+ {
+ reserveObjects(foreign.getKey(*iter), obj_copier, false);
+ }
+ }
+ else if (foreign.isStream())
+ {
+ QTC::TC("qpdf", "QPDF reserve stream");
+ reserveObjects(foreign.getDict(), obj_copier, false);
+ }
+
+ if (foreign.isIndirect())
+ {
+ ObjGen foreign_og(foreign.getObjectID(), foreign.getGeneration());
+ obj_copier.visiting.erase(foreign_og);
+ }
+}
+
+QPDFObjectHandle
+QPDF::replaceForeignIndirectObjects(
+ QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top)
+{
+ QPDFObjectHandle result;
+ if ((! top) && foreign.isIndirect())
+ {
+ QTC::TC("qpdf", "QPDF replace indirect");
+ ObjGen foreign_og(foreign.getObjectID(), foreign.getGeneration());
+ std::map<ObjGen, QPDFObjectHandle>::iterator mapping =
+ obj_copier.object_map.find(foreign_og);
+ if (mapping == obj_copier.object_map.end())
+ {
+ // This case would occur if this is a reference to a Page
+ // or Pages object that we didn't traverse into.
+ QTC::TC("qpdf", "QPDF replace foreign indirect with null");
+ result = QPDFObjectHandle::newNull();
+ }
+ else
+ {
+ result = obj_copier.object_map[foreign_og];
+ }
+ }
+ else if (foreign.isArray())
+ {
+ QTC::TC("qpdf", "QPDF replace array");
+ result = QPDFObjectHandle::newArray();
+ int n = foreign.getArrayNItems();
+ for (int i = 0; i < n; ++i)
+ {
+ result.appendItem(
+ replaceForeignIndirectObjects(
+ foreign.getArrayItem(i), obj_copier, false));
+ }
+ }
+ else if (foreign.isDictionary())
+ {
+ QTC::TC("qpdf", "QPDF replace dictionary");
+ result = QPDFObjectHandle::newDictionary();
+ std::set<std::string> keys = foreign.getKeys();
+ for (std::set<std::string>::iterator iter = keys.begin();
+ iter != keys.end(); ++iter)
+ {
+ result.replaceKey(
+ *iter,
+ replaceForeignIndirectObjects(
+ foreign.getKey(*iter), obj_copier, false));
+ }
+ }
+ else if (foreign.isStream())
+ {
+ QTC::TC("qpdf", "QPDF replace stream");
+ ObjGen foreign_og(foreign.getObjectID(), foreign.getGeneration());
+ result = obj_copier.object_map[foreign_og];
+ result.assertStream();
+ QPDFObjectHandle dict = result.getDict();
+ QPDFObjectHandle old_dict = foreign.getDict();
+ std::set<std::string> keys = old_dict.getKeys();
+ for (std::set<std::string>::iterator iter = keys.begin();
+ iter != keys.end(); ++iter)
+ {
+ dict.replaceKey(
+ *iter,
+ replaceForeignIndirectObjects(
+ old_dict.getKey(*iter), obj_copier, false));
+ }
+ if (this->copied_stream_data_provider == 0)
+ {
+ this->copied_stream_data_provider = new CopiedStreamDataProvider();
+ this->copied_streams = this->copied_stream_data_provider;
+ }
+ ObjGen local_og(result.getObjectID(), result.getGeneration());
+ this->copied_stream_data_provider->registerForeignStream(
+ local_og, foreign);
+ result.replaceStreamData(this->copied_streams,
+ dict.getKey("/Filter"),
+ dict.getKey("/DecodeParms"));
+ }
+ else
+ {
+ foreign.assertScalar();
+ result = foreign;
+ result.makeDirect();
+ }
+
+ if (top && (! result.isStream()) && result.isIndirect())
+ {
+ throw std::logic_error("replacement for foreign object is indirect");
+ }
+
+ return result;
+}
void
QPDF::swapObjects(int objid1, int generation1, int objid2, int generation2)
diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc
index 25298bee..4f43aa89 100644
--- a/libqpdf/QPDFObjectHandle.cc
+++ b/libqpdf/QPDFObjectHandle.cc
@@ -355,6 +355,14 @@ QPDFObjectHandle::isOrHasName(std::string const& value)
return false;
}
+// Indirect object accessors
+QPDF*
+QPDFObjectHandle::getOwningQPDF()
+{
+ // Will be null for direct objects
+ return this->qpdf;
+}
+
// Dictionary mutators
void
@@ -784,6 +792,7 @@ QPDFObjectHandle::makeDirectInternal(std::set<int>& visited)
}
dereference();
+ this->qpdf = 0;
this->objid = 0;
this->generation = 0;
@@ -946,6 +955,16 @@ QPDFObjectHandle::assertReserved()
}
void
+QPDFObjectHandle::assertIndirect()
+{
+ if (! isIndirect())
+ {
+ throw std::logic_error(
+ "operation for indirect object attempted on direct object");
+ }
+}
+
+void
QPDFObjectHandle::assertScalar()
{
assertType("Scalar", isScalar());
@@ -957,11 +976,24 @@ QPDFObjectHandle::assertNumber()
assertType("Number", isNumber());
}
+bool
+QPDFObjectHandle::isPageObject()
+{
+ return (this->isDictionary() && this->hasKey("/Type") &&
+ (this->getKey("/Type").getName() == "/Page"));
+}
+
+bool
+QPDFObjectHandle::isPagesObject()
+{
+ return (this->isDictionary() && this->hasKey("/Type") &&
+ (this->getKey("/Type").getName() == "/Pages"));
+}
+
void
QPDFObjectHandle::assertPageObject()
{
- if (! (this->isDictionary() && this->hasKey("/Type") &&
- (this->getKey("/Type").getName() == "/Page")))
+ if (! isPageObject())
{
throw std::logic_error("page operation called on non-Page object");
}
diff --git a/libqpdf/QPDF_optimization.cc b/libqpdf/QPDF_optimization.cc
index e6ad2750..e1fa8e76 100644
--- a/libqpdf/QPDF_optimization.cc
+++ b/libqpdf/QPDF_optimization.cc
@@ -232,6 +232,14 @@ QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys)
// Traverse pages tree pushing all inherited resources down to the
// page level.
+ // The record of whether we've done this is cleared by
+ // updateAllPagesCache(). If we're warning for skipped keys,
+ // re-traverse unconditionally.
+ if (this->pushed_inherited_attributes_to_pages && (! warn_skipped_keys))
+ {
+ return;
+ }
+
// key_ancestors is a mapping of page attribute keys to a stack of
// Pages nodes that contain values for them.
std::map<std::string, std::vector<QPDFObjectHandle> > key_ancestors;
@@ -240,6 +248,7 @@ QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys)
this->trailer.getKey("/Root").getKey("/Pages"),
key_ancestors, this->all_pages, allow_changes, warn_skipped_keys);
assert(key_ancestors.empty());
+ this->pushed_inherited_attributes_to_pages = true;
}
void
diff --git a/libqpdf/QPDF_pages.cc b/libqpdf/QPDF_pages.cc
index 818215c4..ddb672a1 100644
--- a/libqpdf/QPDF_pages.cc
+++ b/libqpdf/QPDF_pages.cc
@@ -89,6 +89,7 @@ QPDF::updateAllPagesCache()
QTC::TC("qpdf", "QPDF updateAllPagesCache");
this->all_pages.clear();
this->pageobj_to_pages_pos.clear();
+ this->pushed_inherited_attributes_to_pages = false;
getAllPages();
}
@@ -161,6 +162,12 @@ QPDF::insertPage(QPDFObjectHandle newpage, int pos)
QTC::TC("qpdf", "QPDF insert non-indirect page");
newpage = this->makeIndirectObject(newpage);
}
+ else if (newpage.getOwningQPDF() != this)
+ {
+ QTC::TC("qpdf", "QPDF insert foreign page");
+ newpage.getOwningQPDF()->pushInheritedAttributesToPage();
+ newpage = this->copyForeignObject(newpage, true);
+ }
else
{
QTC::TC("qpdf", "QPDF insert indirect page");