mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-22 10:58:58 +00:00
Support copying objects from another QPDF object
This includes QPDF::copyForeignObject and supporting foreign objects as arguments to addPage*.
This commit is contained in:
parent
8a217eb3a2
commit
e7b8f297ba
18
ChangeLog
18
ChangeLog
@ -1,3 +1,21 @@
|
||||
2012-07-11 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* Added new APIs to copy objects from one QPDF to another. This
|
||||
includes letting QPDF::addPage() (and QPDF::addPageAt()) accept a
|
||||
page object from another QPDF and adding
|
||||
QPDF::copyForeignObject(). See QPDF.hh for details.
|
||||
|
||||
* Add method QPDFObjectHandle::getOwningQPDF() to return the QPDF
|
||||
object associated with an indirect QPDFObjectHandle.
|
||||
|
||||
* Add convenience methods to QPDFObjectHandle: assertIndirect(),
|
||||
isPageObject(), isPagesObject()
|
||||
|
||||
* Cache when QPDF::pushInheritedAttributesToPage() has been called
|
||||
to avoid traversing the pages trees multiple times. This state is
|
||||
cleared by QPDF::updateAllPagesCache() and ignored by
|
||||
QPDF::flattenPagesTree().
|
||||
|
||||
2012-07-08 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* Add QPDFObjectHandle::newReserved to create a reserved object
|
||||
|
149
TODO
149
TODO
@ -28,76 +28,54 @@ Next
|
||||
can only be used by one thread at a time, but multiple threads can
|
||||
simultaneously use separate objects.
|
||||
|
||||
* Write some documentation about the design of copyForeignObject.
|
||||
|
||||
* copyForeignObject still to do:
|
||||
|
||||
- qpdf command
|
||||
|
||||
Command line could be something like
|
||||
|
||||
--pages [ --new ] { file [password] numeric-range ... } ... --
|
||||
|
||||
The first file referenced would be the one whose other data would
|
||||
be preserved (like trailer, info, encryption, outlines, etc.).
|
||||
--new as first file would just use an empty file as the starting
|
||||
point. Be explicit about whether outlines, etc., are handled.
|
||||
They are not handled initially.
|
||||
|
||||
Example: to grab pages 1-5 from file1 and 11-15 from file2
|
||||
|
||||
--pages file1.pdf 1-5 file2.pdf 11-15 --
|
||||
|
||||
To implement this, we would remove all pages from file1 except
|
||||
pages 1 through 5. Then we would take pages 11 through 15 from
|
||||
file2, copy them to the file, and add them as pages.
|
||||
|
||||
- document that makeIndirectObject doesn't handle foreign objects
|
||||
automatically because copying a foreign object is a big enough
|
||||
deal that it should be explicit. However addPages* does handle
|
||||
foreign page objects automatically.
|
||||
|
||||
- Test /Outlines and see whether there's any point in handling
|
||||
them in the API. Maybe just copying them over works. What
|
||||
about command line tool? Also think about page labels.
|
||||
|
||||
- Tests through qpdf command line: copy pages from multiple PDFs
|
||||
starting with one PDF and also starting with empty.
|
||||
|
||||
* (Hopefully) Provide an option to copy encryption parameters from
|
||||
another file. This would make it possible to decrypt a file,
|
||||
manually work with it, and then re-encrypt it using the original
|
||||
encryption parameters including a possibly unknown owner password.
|
||||
|
||||
|
||||
Soon
|
||||
====
|
||||
|
||||
* Provide an option to copy encryption parameters from another file.
|
||||
This would make it possible to decrypt a file, manually work with
|
||||
it, and then re-encrypt it using the original encryption parameters
|
||||
including a possibly unknown owner password.
|
||||
|
||||
* See if I can support the new encryption formats mentioned in the
|
||||
open bug on sourceforge. Check other sourceforge bugs.
|
||||
|
||||
* Splitting/merging concepts
|
||||
|
||||
newPDF() could create a PDF with just a trailer, no pages, and a
|
||||
minimal info. Then the page routines could be used to add pages to
|
||||
it.
|
||||
|
||||
Starting with any pdf, you should be able to copy objects from
|
||||
another pdf. The copy should be smart about never traversing into
|
||||
a /Page or /Pages.
|
||||
|
||||
We could provide a method of copying objects from one PDF into
|
||||
another. This would do whatever optimization is necessary (maybe
|
||||
just optimizePagesTree) and then traverse the set of objects
|
||||
specified to find all objects referenced by the set. Each of those
|
||||
would be copied over with a table mapping old ID to new ID. This
|
||||
would be done from bottom up most likely disallowing cycles or
|
||||
handling them sanely.
|
||||
|
||||
Command line could be something like
|
||||
|
||||
--pages [ --new ] { file [password] numeric-range ... } ... --
|
||||
|
||||
The first file referenced would be the one whose other data would
|
||||
be preserved (like trailer, info, encryption, outlines, etc.).
|
||||
--new as first file would just use an empty file as the starting
|
||||
point.
|
||||
|
||||
Example: to grab pages 1-5 from file1 and 11-15 from file2
|
||||
|
||||
--pages file1.pdf 1-5 file2.pdf 11-15 --
|
||||
|
||||
To implement this, we would remove all pages from file1 except
|
||||
pages 1 through 5. Then we would take pages 11 through 15 from
|
||||
file2 and add them to a set for transfer. This would end up
|
||||
generating a list of indirect objects. We would copy those objects
|
||||
shallowly to the new PDF keeping track of the mapping and replacing
|
||||
any indirect object keys as appropriate, much like QPDFWriter does.
|
||||
|
||||
When all the objects are registered, we would add those pages to
|
||||
the result.
|
||||
|
||||
This approach could work for both splitting and merging. It's
|
||||
possible it could be implemented now without any new APIs, but most
|
||||
of the work should be doable by the library with only a small set
|
||||
of additions.
|
||||
|
||||
newPDF()
|
||||
QPDFObjectCopier c(qpdf1, qpdf2)
|
||||
QPDFObjectHandle obj = c.copyObject(<object from qpdf1>)
|
||||
Without traversing pages, copies all indirect objects referenced
|
||||
by <object from qpdf1> preserving referential integrity and
|
||||
returns an object handle in qpdf2 of the same object. If called
|
||||
multiple times on the same object, retraverses in case there were
|
||||
changes.
|
||||
|
||||
QPDFObjectHandle obj = c.getMapping(<object from qpdf1>)
|
||||
find the object in qpdf2 corresponding to the object from qpdf1.
|
||||
Return the null object if none.
|
||||
|
||||
General
|
||||
=======
|
||||
@ -110,23 +88,11 @@ General
|
||||
* Update qpdf docs about non-ascii passwords. See thread from
|
||||
2010-12-07,08 for details.
|
||||
|
||||
* Look at page splitting. Subramanyam provided a test file; see
|
||||
../misc/article-threads.pdf. Email Q-Count: 431864 from
|
||||
2009-11-03. See also "Splitting by Pages" below.
|
||||
|
||||
* Consider writing a PDF merge utility. With 2.2, it would be
|
||||
possible to have a StreamDataProvider that would allow stream data
|
||||
to be directly copied from one PDF file to another. One possible
|
||||
strategy would be to have a program that adds all the pages of one
|
||||
file to the end of another file. The basic
|
||||
strategy would be to create a table that adds new streams to the
|
||||
original file, mapping the new streams' obj/gen to a stream in the
|
||||
file whose pages are being appended. The StreamDataProvider, when
|
||||
asked, could simply pipe the streams of the file being appended to
|
||||
the provided pipeline and could copy the filter and decode
|
||||
parameters from the original file. Being able to do this requires
|
||||
a lot of the same logic as being able to do splitting, so a general
|
||||
split/merge program would be a great addition.
|
||||
* Consider impact of article threads on page splitting/merging.
|
||||
Subramanyam provided a test file; see ../misc/article-threads.pdf.
|
||||
Email Q-Count: 431864 from 2009-11-03. Other things to consider:
|
||||
outlines, page labels, thumbnails, zones. There are probably
|
||||
others.
|
||||
|
||||
* See whether it's possible to remove the call to
|
||||
flattenScalarReferences. I can't easily figure out why I do it,
|
||||
@ -279,26 +245,3 @@ Index: QPDFWriter.cc
|
||||
|
||||
* From a suggestion in bug 3152169, consisder having an option to
|
||||
re-encode inline images with an ASCII encoding.
|
||||
|
||||
|
||||
Splitting by Pages
|
||||
==================
|
||||
|
||||
Although qpdf does not currently support splitting a file into pages,
|
||||
the work done for linearization covers almost all the work. To do
|
||||
page splitting. If this functionality is needed, study
|
||||
obj_user_to_objects and object_to_obj_users created in
|
||||
QPDF_optimization for ideas. It's quite possible that the information
|
||||
computed by calculateLinearizationData is actually sufficient to do
|
||||
page splitting in many circumstances. That code knows which objects
|
||||
are used by which pages, though it doesn't do anything page-specific
|
||||
with outlines, thumbnails, page labels, or anything else.
|
||||
|
||||
Another approach would be to traverse only pages that are being output
|
||||
taking care not to traverse into the pages tree, and then to fabricate
|
||||
a new pages tree.
|
||||
|
||||
Either way, care must be taken to handle other things such as
|
||||
outlines, page labels, thumbnails, threads, zones, etc. in a sensible
|
||||
way. This may include simply omitting information other than page
|
||||
content.
|
||||
|
@ -190,6 +190,28 @@ class QPDF
|
||||
replaceReserved(QPDFObjectHandle reserved,
|
||||
QPDFObjectHandle replacement);
|
||||
|
||||
// Copy an object from another QPDF to this one. The return value
|
||||
// is an indirect reference to the copied object in this file.
|
||||
// This method is intended to be used to copy non-page objects and
|
||||
// will not copy page objects. To copy page objects, pass the
|
||||
// foreign page object directly to addPage (or addPageAt). If you
|
||||
// copy objects that contain references to pages, you should copy
|
||||
// the pages first using addPage(At). Otherwise references to the
|
||||
// pages that have not been copied will be replaced with nulls.
|
||||
|
||||
// When copying objects with this method, object structure will be
|
||||
// preserved, so all indirectly referenced indirect objects will
|
||||
// be copied as well. This includes any circular references that
|
||||
// may exist. The QPDF object keeps a record of what has already
|
||||
// been copied, so shared objects will not be copied multiple
|
||||
// times. This also means that if you mutate an object that has
|
||||
// already been copied and try to copy it again, it won't work
|
||||
// since the modified object will not be recopied. Therefore, you
|
||||
// should do all mutation on the original file that you are going
|
||||
// to do before you start copying its objects to a new file.
|
||||
QPDF_DLL
|
||||
QPDFObjectHandle copyForeignObject(QPDFObjectHandle foreign);
|
||||
|
||||
// Encryption support
|
||||
|
||||
enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes };
|
||||
@ -380,7 +402,10 @@ class QPDF
|
||||
// modify /Pages structures directly, you must call this method
|
||||
// afterwards. This method updates the internal list of pages, so
|
||||
// after calling this method, any previous references returned by
|
||||
// getAllPages() will be valid again.
|
||||
// getAllPages() will be valid again. It also resets any state
|
||||
// about having pushed inherited attributes in /Pages objects down
|
||||
// to the pages, so if you add any inheritable attributes to a
|
||||
// /Pages object, you should also call this method.
|
||||
QPDF_DLL
|
||||
void updateAllPagesCache();
|
||||
|
||||
@ -389,11 +414,19 @@ class QPDF
|
||||
// resolved by explicitly setting the values in each /Page.
|
||||
void pushInheritedAttributesToPage();
|
||||
|
||||
// Add new page at the beginning or the end of the current pdf
|
||||
// Add new page at the beginning or the end of the current pdf.
|
||||
// The newpage parameter may be either a direct object, an
|
||||
// indirect object from this QPDF, or an indirect object from
|
||||
// another QPDF. If it is a direct object, it will be made
|
||||
// indirect. If it is an indirect object from another QPDF, this
|
||||
// method will call pushInheritedAttributesToPage on the other
|
||||
// file and then copy the page to this QPDF using the same
|
||||
// underlying code as copyForeignObject.
|
||||
QPDF_DLL
|
||||
void addPage(QPDFObjectHandle newpage, bool first);
|
||||
|
||||
// Add new page before or after refpage
|
||||
// Add new page before or after refpage. See comments for addPage
|
||||
// for details about what newpage should be.
|
||||
QPDF_DLL
|
||||
void addPageAt(QPDFObjectHandle newpage, bool before,
|
||||
QPDFObjectHandle refpage);
|
||||
@ -542,6 +575,29 @@ class QPDF
|
||||
qpdf_offset_t end_after_space;
|
||||
};
|
||||
|
||||
class ObjCopier
|
||||
{
|
||||
public:
|
||||
std::map<ObjGen, QPDFObjectHandle> object_map;
|
||||
std::vector<QPDFObjectHandle> to_copy;
|
||||
std::set<ObjGen> visiting;
|
||||
};
|
||||
|
||||
class CopiedStreamDataProvider: public QPDFObjectHandle::StreamDataProvider
|
||||
{
|
||||
public:
|
||||
virtual ~CopiedStreamDataProvider()
|
||||
{
|
||||
}
|
||||
virtual void provideStreamData(int objid, int generation,
|
||||
Pipeline* pipeline);
|
||||
void registerForeignStream(ObjGen const& local_og,
|
||||
QPDFObjectHandle foreign_stream);
|
||||
|
||||
private:
|
||||
std::map<ObjGen, QPDFObjectHandle> foreign_streams;
|
||||
};
|
||||
|
||||
void parse(char const* password);
|
||||
void warn(QPDFExc const& e);
|
||||
void setTrailer(QPDFObjectHandle obj);
|
||||
@ -602,6 +658,14 @@ class QPDF
|
||||
QPDFObjectHandle& stream_dict,
|
||||
std::vector<PointerHolder<Pipeline> >& heap);
|
||||
|
||||
// Methods to support object copying
|
||||
QPDFObjectHandle copyForeignObject(
|
||||
QPDFObjectHandle foreign, bool allow_page);
|
||||
void reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier,
|
||||
bool top);
|
||||
QPDFObjectHandle replaceForeignIndirectObjects(
|
||||
QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top);
|
||||
|
||||
// Linearization Hint table structures.
|
||||
// Naming conventions:
|
||||
|
||||
@ -960,7 +1024,12 @@ class QPDF
|
||||
QPDFObjectHandle trailer;
|
||||
std::vector<QPDFObjectHandle> all_pages;
|
||||
std::map<ObjGen, int> pageobj_to_pages_pos;
|
||||
bool pushed_inherited_attributes_to_pages;
|
||||
std::vector<QPDFExc> warnings;
|
||||
std::map<QPDF*, ObjCopier> object_copiers;
|
||||
PointerHolder<QPDFObjectHandle::StreamDataProvider> copied_streams;
|
||||
// copied_stream_data_provider is owned by copied_streams
|
||||
CopiedStreamDataProvider* copied_stream_data_provider;
|
||||
|
||||
// Linearization data
|
||||
qpdf_offset_t first_xref_item_offset; // actual value from file
|
||||
|
@ -222,6 +222,11 @@ class QPDFObjectHandle
|
||||
QPDF_DLL
|
||||
bool isOrHasName(std::string const&);
|
||||
|
||||
// Return the QPDF object that owns an indirect object. Returns
|
||||
// null for a direct object.
|
||||
QPDF_DLL
|
||||
QPDF* getOwningQPDF();
|
||||
|
||||
// Create a shallow copy of an object as a direct object. Since
|
||||
// this is a shallow copy, for dictionaries and arrays, any keys
|
||||
// or items that were indirect objects will still be indirect
|
||||
@ -453,10 +458,17 @@ class QPDFObjectHandle
|
||||
QPDF_DLL
|
||||
void assertReserved();
|
||||
|
||||
QPDF_DLL
|
||||
void assertIndirect();
|
||||
QPDF_DLL
|
||||
void assertScalar();
|
||||
QPDF_DLL
|
||||
void assertNumber();
|
||||
|
||||
QPDF_DLL
|
||||
bool isPageObject();
|
||||
QPDF_DLL
|
||||
bool isPagesObject();
|
||||
QPDF_DLL
|
||||
void assertPageObject();
|
||||
|
||||
|
257
libqpdf/QPDF.cc
257
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)
|
||||
|
@ -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;
|
||||
|
||||
@ -945,6 +954,16 @@ QPDFObjectHandle::assertReserved()
|
||||
assertType("Reserved", isReserved());
|
||||
}
|
||||
|
||||
void
|
||||
QPDFObjectHandle::assertIndirect()
|
||||
{
|
||||
if (! isIndirect())
|
||||
{
|
||||
throw std::logic_error(
|
||||
"operation for indirect object attempted on direct object");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
QPDFObjectHandle::assertScalar()
|
||||
{
|
||||
@ -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");
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -218,3 +218,18 @@ QPDF unknown key not inherited 0
|
||||
QPDF_Stream provider length not provided 0
|
||||
QPDF_Stream unknown stream length 0
|
||||
QPDF replaceReserved 0
|
||||
QPDF copyForeign direct 0
|
||||
QPDF copyForeign not foreign 0
|
||||
QPDF copy indirect 0
|
||||
QPDF loop reserving objects 0
|
||||
QPDF replace indirect 0
|
||||
QPDF replace array 0
|
||||
QPDF replace dictionary 0
|
||||
QPDF replace stream 0
|
||||
QPDF reserve array 0
|
||||
QPDF reserve dictionary 0
|
||||
QPDF reserve stream 0
|
||||
QPDF not crossing page boundary 0
|
||||
QPDF replace foreign indirect with null 0
|
||||
QPDF not copying pages object 0
|
||||
QPDF insert foreign page 0
|
||||
|
@ -379,6 +379,27 @@ $td->runtest("check output",
|
||||
{$td->FILE => "a.pdf"},
|
||||
{$td->FILE => "from-scratch-0.pdf"});
|
||||
# ----------
|
||||
$td->notify("--- Copy Foreign Objects ---");
|
||||
$n_tests += 7;
|
||||
|
||||
foreach my $d ([25, 1], [26, 2], [27, 3])
|
||||
{
|
||||
my ($testn, $outn) = @$d;
|
||||
$td->runtest("copy objects $outn",
|
||||
{$td->COMMAND => "test_driver $testn" .
|
||||
" copy-foreign-objects-in.pdf"},
|
||||
{$td->STRING => "test $testn done\n", $td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
$td->runtest("check output",
|
||||
{$td->FILE => "a.pdf"},
|
||||
{$td->FILE => "copy-foreign-objects-out$outn.pdf"});
|
||||
}
|
||||
$td->runtest("copy objects error",
|
||||
{$td->COMMAND => "test_driver 28 copy-foreign-objects-in.pdf"},
|
||||
{$td->FILE => "copy-foreign-objects-errors.out",
|
||||
$td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
# ----------
|
||||
$td->notify("--- Error Condition Tests ---");
|
||||
# $n_tests incremented after initialization of badfiles below.
|
||||
|
||||
|
3
qpdf/qtest/qpdf/copy-foreign-objects-errors.out
Normal file
3
qpdf/qtest/qpdf/copy-foreign-objects-errors.out
Normal file
@ -0,0 +1,3 @@
|
||||
logic error: QPDF::copyForeign called with object from this QPDF
|
||||
logic error: QPDF::copyForeign called with direct object handle
|
||||
test 28 done
|
335
qpdf/qtest/qpdf/copy-foreign-objects-in.pdf
Normal file
335
qpdf/qtest/qpdf/copy-foreign-objects-in.pdf
Normal file
@ -0,0 +1,335 @@
|
||||
%PDF-1.3
|
||||
%¿÷¢þ
|
||||
%QDF-1.0
|
||||
|
||||
% This test file is specifically crafted for testing copyForeignObject
|
||||
% and also for testing addPage when called with a page from another
|
||||
% file.
|
||||
|
||||
% The /QTest key in trailer has pointers to several indirect objects:
|
||||
% O1, O2, O3 where O1 is an array that contains a dictionary that has
|
||||
% a key that points to O2, O2 is a dictionary that contains an array
|
||||
% that points to O1, and O3 is a page object that inherits some
|
||||
% resource from its parent /Pages and also points to some other page.
|
||||
% O1 also points to a stream whose dictionary has a key that points to
|
||||
% another stream whose dictionary points back to the first stream.
|
||||
|
||||
1 0 obj
|
||||
<<
|
||||
/Pages 2 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<<
|
||||
/Count 5
|
||||
/Kids [
|
||||
3 0 R
|
||||
4 0 R
|
||||
5 0 R
|
||||
6 0 R
|
||||
7 0 R
|
||||
]
|
||||
/Rotate 180
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Page 1
|
||||
3 0 obj
|
||||
<<
|
||||
/Contents 8 0 R
|
||||
/MediaBox [
|
||||
0
|
||||
0
|
||||
612
|
||||
792
|
||||
]
|
||||
/Parent 2 0 R
|
||||
/Resources <<
|
||||
/Font <<
|
||||
/F1 10 0 R
|
||||
>>
|
||||
/ProcSet [
|
||||
/PDF
|
||||
/Text
|
||||
]
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Page 2
|
||||
4 0 obj
|
||||
<<
|
||||
/Contents 11 0 R
|
||||
/MediaBox [
|
||||
0
|
||||
0
|
||||
612
|
||||
792
|
||||
]
|
||||
/Parent 2 0 R
|
||||
/Resources <<
|
||||
/Font <<
|
||||
/F1 10 0 R
|
||||
>>
|
||||
/ProcSet [
|
||||
/PDF
|
||||
/Text
|
||||
]
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Page 3, object O3
|
||||
5 0 obj
|
||||
<<
|
||||
/This-is-O3 true
|
||||
/Contents 13 0 R
|
||||
/MediaBox [
|
||||
0
|
||||
0
|
||||
612
|
||||
792
|
||||
]
|
||||
/Parent 2 0 R
|
||||
/Resources <<
|
||||
/Font <<
|
||||
/F1 10 0 R
|
||||
>>
|
||||
/ProcSet [
|
||||
/PDF
|
||||
/Text
|
||||
]
|
||||
>>
|
||||
/OtherPage 6 0 R
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Page 4
|
||||
6 0 obj
|
||||
<<
|
||||
/This-is-O3-other-page true
|
||||
/Contents 15 0 R
|
||||
/MediaBox [
|
||||
0
|
||||
0
|
||||
612
|
||||
792
|
||||
]
|
||||
/Parent 2 0 R
|
||||
/Resources <<
|
||||
/Font <<
|
||||
/F1 10 0 R
|
||||
>>
|
||||
/ProcSet [
|
||||
/PDF
|
||||
/Text
|
||||
]
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Page 5
|
||||
7 0 obj
|
||||
<<
|
||||
/Contents 17 0 R
|
||||
/MediaBox [
|
||||
0
|
||||
0
|
||||
612
|
||||
792
|
||||
]
|
||||
/Parent 2 0 R
|
||||
/Resources <<
|
||||
/Font <<
|
||||
/F1 10 0 R
|
||||
>>
|
||||
/ProcSet [
|
||||
/PDF
|
||||
/Text
|
||||
]
|
||||
>>
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Contents for page 1
|
||||
8 0 obj
|
||||
<<
|
||||
/Length 9 0 R
|
||||
>>
|
||||
stream
|
||||
BT /F1 15 Tf 72 720 Td (Original page 0) Tj ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
9 0 obj
|
||||
47
|
||||
endobj
|
||||
|
||||
10 0 obj
|
||||
<<
|
||||
/BaseFont /Times-Roman
|
||||
/Encoding /WinAnsiEncoding
|
||||
/Subtype /Type1
|
||||
/Type /Font
|
||||
>>
|
||||
endobj
|
||||
|
||||
%% Contents for page 2
|
||||
11 0 obj
|
||||
<<
|
||||
/Length 12 0 R
|
||||
>>
|
||||
stream
|
||||
BT /F1 15 Tf 72 720 Td (Original page 1) Tj ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
12 0 obj
|
||||
47
|
||||
endobj
|
||||
|
||||
%% Contents for page 3
|
||||
13 0 obj
|
||||
<<
|
||||
/Length 14 0 R
|
||||
>>
|
||||
stream
|
||||
BT /F1 15 Tf 72 720 Td (Original page 2) Tj ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
14 0 obj
|
||||
47
|
||||
endobj
|
||||
|
||||
%% Contents for page 4
|
||||
15 0 obj
|
||||
<<
|
||||
/Length 16 0 R
|
||||
>>
|
||||
stream
|
||||
BT /F1 15 Tf 72 720 Td (Original page 3) Tj ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
16 0 obj
|
||||
47
|
||||
endobj
|
||||
|
||||
%% Contents for page 5
|
||||
17 0 obj
|
||||
<<
|
||||
/Length 18 0 R
|
||||
>>
|
||||
stream
|
||||
BT /F1 15 Tf 72 720 Td (Original page 4) Tj ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
18 0 obj
|
||||
47
|
||||
endobj
|
||||
|
||||
% O1
|
||||
19 0 obj
|
||||
[
|
||||
/This-is-O1
|
||||
/potato
|
||||
<< /O2 [3.14159 << /O2 20 0 R >> 2.17828 ] >>
|
||||
/salad
|
||||
/O2 20 0 R
|
||||
/Stream1 21 0 R
|
||||
]
|
||||
endobj
|
||||
|
||||
% O2
|
||||
20 0 obj
|
||||
<<
|
||||
/This-is-O2 true
|
||||
/K1 [2.236 /O1 19 0 R 1.732]
|
||||
/O1 19 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
% stream1
|
||||
21 0 obj
|
||||
<<
|
||||
/This-is-Stream1 true
|
||||
/Length 22 0 R
|
||||
/Stream2 23 0 R
|
||||
>>
|
||||
stream
|
||||
This is stream 1.
|
||||
endstream
|
||||
endobj
|
||||
|
||||
22 0 obj
|
||||
18
|
||||
endobj
|
||||
|
||||
% stream2
|
||||
23 0 obj
|
||||
<<
|
||||
/This-is-Stream2 true
|
||||
/Length 24 0 R
|
||||
/Stream1 21 0 R
|
||||
>>
|
||||
stream
|
||||
This is stream 2.
|
||||
endstream
|
||||
endobj
|
||||
|
||||
24 0 obj
|
||||
18
|
||||
endobj
|
||||
|
||||
% QTest
|
||||
25 0 obj
|
||||
<< /This-is-QTest true /O1 19 0 R /O2 20 0 R /O3 5 0 R >>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 26
|
||||
0000000000 65535 f
|
||||
0000000655 00000 n
|
||||
0000000709 00000 n
|
||||
0000000845 00000 n
|
||||
0000001073 00000 n
|
||||
0000001313 00000 n
|
||||
0000001580 00000 n
|
||||
0000001839 00000 n
|
||||
0000002081 00000 n
|
||||
0000002183 00000 n
|
||||
0000002202 00000 n
|
||||
0000002334 00000 n
|
||||
0000002438 00000 n
|
||||
0000002481 00000 n
|
||||
0000002585 00000 n
|
||||
0000002628 00000 n
|
||||
0000002732 00000 n
|
||||
0000002775 00000 n
|
||||
0000002879 00000 n
|
||||
0000002904 00000 n
|
||||
0000003042 00000 n
|
||||
0000003138 00000 n
|
||||
0000003255 00000 n
|
||||
0000003285 00000 n
|
||||
0000003402 00000 n
|
||||
0000003430 00000 n
|
||||
trailer <<
|
||||
/Root 1 0 R
|
||||
/Size 26
|
||||
/QTest 25 0 R
|
||||
/ID [<d15f7aca3be584a96c1c94adb0931e71><9adb6b2fdb22e857340f7103917b16e4>]
|
||||
>>
|
||||
startxref
|
||||
3505
|
||||
%%EOF
|
66
qpdf/qtest/qpdf/copy-foreign-objects-out1.pdf
Normal file
66
qpdf/qtest/qpdf/copy-foreign-objects-out1.pdf
Normal file
@ -0,0 +1,66 @@
|
||||
%PDF-1.3
|
||||
%¿÷¢þ
|
||||
1 0 obj
|
||||
<< /Pages 3 0 R /Type /Catalog >>
|
||||
endobj
|
||||
2 0 obj
|
||||
<< /O1 4 0 R /O2 5 0 R /This-is-QTest true >>
|
||||
endobj
|
||||
3 0 obj
|
||||
<< /Count 1 /Kids [ 6 0 R ] /Type /Pages >>
|
||||
endobj
|
||||
4 0 obj
|
||||
[ /This-is-O1 /potato << /O2 [ 3.14159 << /O2 5 0 R >> 2.17828 ] >> /salad /O2 5 0 R /Stream1 7 0 R ]
|
||||
endobj
|
||||
5 0 obj
|
||||
<< /K1 [ 2.236 /O1 4 0 R 1.732 ] /O1 4 0 R /This-is-O2 true >>
|
||||
endobj
|
||||
6 0 obj
|
||||
<< /Contents 8 0 R /MediaBox [ 0 0 612 792 ] /Parent 3 0 R /Resources << /Font << /F1 9 0 R >> /ProcSet 10 0 R >> /Type /Page >>
|
||||
endobj
|
||||
7 0 obj
|
||||
<< /Stream2 11 0 R /This-is-Stream1 true /Length 18 >>
|
||||
stream
|
||||
This is stream 1.
|
||||
endstream
|
||||
endobj
|
||||
8 0 obj
|
||||
<< /Length 44 >>
|
||||
stream
|
||||
BT
|
||||
/F1 24 Tf
|
||||
72 720 Td
|
||||
(Potato) Tj
|
||||
ET
|
||||
endstream
|
||||
endobj
|
||||
9 0 obj
|
||||
<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >>
|
||||
endobj
|
||||
10 0 obj
|
||||
[ /PDF /Text ]
|
||||
endobj
|
||||
11 0 obj
|
||||
<< /Stream1 7 0 R /This-is-Stream2 true /Length 18 >>
|
||||
stream
|
||||
This is stream 2.
|
||||
endstream
|
||||
endobj
|
||||
xref
|
||||
0 12
|
||||
0000000000 65535 f
|
||||
0000000015 00000 n
|
||||
0000000064 00000 n
|
||||
0000000125 00000 n
|
||||
0000000184 00000 n
|
||||
0000000301 00000 n
|
||||
0000000379 00000 n
|
||||
0000000523 00000 n
|
||||
0000000628 00000 n
|
||||
0000000721 00000 n
|
||||
0000000828 00000 n
|
||||
0000000859 00000 n
|
||||
trailer << /QTest 2 0 R /Root 1 0 R /Size 12 /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] >>
|
||||
startxref
|
||||
964
|
||||
%%EOF
|
81
qpdf/qtest/qpdf/copy-foreign-objects-out2.pdf
Normal file
81
qpdf/qtest/qpdf/copy-foreign-objects-out2.pdf
Normal file
@ -0,0 +1,81 @@
|
||||
%PDF-1.3
|
||||
%¿÷¢þ
|
||||
1 0 obj
|
||||
<< /Pages 3 0 R /Type /Catalog >>
|
||||
endobj
|
||||
2 0 obj
|
||||
<< /O1 4 0 R /O2 5 0 R /O3 6 0 R /This-is-QTest true >>
|
||||
endobj
|
||||
3 0 obj
|
||||
<< /Count 2 /Kids [ 7 0 R 6 0 R ] /Type /Pages >>
|
||||
endobj
|
||||
4 0 obj
|
||||
[ /This-is-O1 /potato << /O2 [ 3.14159 << /O2 5 0 R >> 2.17828 ] >> /salad /O2 5 0 R /Stream1 8 0 R ]
|
||||
endobj
|
||||
5 0 obj
|
||||
<< /K1 [ 2.236 /O1 4 0 R 1.732 ] /O1 4 0 R /This-is-O2 true >>
|
||||
endobj
|
||||
6 0 obj
|
||||
<< /Contents 9 0 R /MediaBox [ 0 0 612 792 ] /Parent 3 0 R /Resources << /Font << /F1 10 0 R >> /ProcSet [ /PDF /Text ] >> /Rotate 180 /This-is-O3 true /Type /Page >>
|
||||
endobj
|
||||
7 0 obj
|
||||
<< /Contents 11 0 R /MediaBox [ 0 0 612 792 ] /Parent 3 0 R /Resources << /Font << /F1 12 0 R >> /ProcSet 13 0 R >> /Type /Page >>
|
||||
endobj
|
||||
8 0 obj
|
||||
<< /Stream2 14 0 R /This-is-Stream1 true /Length 18 >>
|
||||
stream
|
||||
This is stream 1.
|
||||
endstream
|
||||
endobj
|
||||
9 0 obj
|
||||
<< /Length 47 >>
|
||||
stream
|
||||
BT /F1 15 Tf 72 720 Td (Original page 2) Tj ET
|
||||
endstream
|
||||
endobj
|
||||
10 0 obj
|
||||
<< /BaseFont /Times-Roman /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >>
|
||||
endobj
|
||||
11 0 obj
|
||||
<< /Length 44 >>
|
||||
stream
|
||||
BT
|
||||
/F1 24 Tf
|
||||
72 720 Td
|
||||
(Potato) Tj
|
||||
ET
|
||||
endstream
|
||||
endobj
|
||||
12 0 obj
|
||||
<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >>
|
||||
endobj
|
||||
13 0 obj
|
||||
[ /PDF /Text ]
|
||||
endobj
|
||||
14 0 obj
|
||||
<< /Stream1 8 0 R /This-is-Stream2 true /Length 18 >>
|
||||
stream
|
||||
This is stream 2.
|
||||
endstream
|
||||
endobj
|
||||
xref
|
||||
0 15
|
||||
0000000000 65535 f
|
||||
0000000015 00000 n
|
||||
0000000064 00000 n
|
||||
0000000135 00000 n
|
||||
0000000200 00000 n
|
||||
0000000317 00000 n
|
||||
0000000395 00000 n
|
||||
0000000577 00000 n
|
||||
0000000723 00000 n
|
||||
0000000828 00000 n
|
||||
0000000924 00000 n
|
||||
0000001024 00000 n
|
||||
0000001118 00000 n
|
||||
0000001226 00000 n
|
||||
0000001257 00000 n
|
||||
trailer << /QTest 2 0 R /Root 1 0 R /Size 15 /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] >>
|
||||
startxref
|
||||
1362
|
||||
%%EOF
|
92
qpdf/qtest/qpdf/copy-foreign-objects-out3.pdf
Normal file
92
qpdf/qtest/qpdf/copy-foreign-objects-out3.pdf
Normal file
@ -0,0 +1,92 @@
|
||||
%PDF-1.3
|
||||
%¿÷¢þ
|
||||
1 0 obj
|
||||
<< /Pages 3 0 R /Type /Catalog >>
|
||||
endobj
|
||||
2 0 obj
|
||||
<< /O1 4 0 R /O2 5 0 R /O3 6 0 R /This-is-QTest true >>
|
||||
endobj
|
||||
3 0 obj
|
||||
<< /Count 3 /Kids [ 7 0 R 8 0 R 6 0 R ] /Type /Pages >>
|
||||
endobj
|
||||
4 0 obj
|
||||
[ /This-is-O1 /potato << /O2 [ 3.14159 << /O2 5 0 R >> 2.17828 ] >> /salad /O2 5 0 R /Stream1 9 0 R ]
|
||||
endobj
|
||||
5 0 obj
|
||||
<< /K1 [ 2.236 /O1 4 0 R 1.732 ] /O1 4 0 R /This-is-O2 true >>
|
||||
endobj
|
||||
6 0 obj
|
||||
<< /Contents 10 0 R /MediaBox [ 0 0 612 792 ] /OtherPage 8 0 R /Parent 3 0 R /Resources << /Font << /F1 11 0 R >> /ProcSet [ /PDF /Text ] >> /Rotate 180 /This-is-O3 true /Type /Page >>
|
||||
endobj
|
||||
7 0 obj
|
||||
<< /Contents 12 0 R /MediaBox [ 0 0 612 792 ] /Parent 3 0 R /Resources << /Font << /F1 13 0 R >> /ProcSet 14 0 R >> /Type /Page >>
|
||||
endobj
|
||||
8 0 obj
|
||||
<< /Contents 15 0 R /MediaBox [ 0 0 612 792 ] /Parent 3 0 R /Resources << /Font << /F1 11 0 R >> /ProcSet [ /PDF /Text ] >> /Rotate 180 /This-is-O3-other-page true /Type /Page >>
|
||||
endobj
|
||||
9 0 obj
|
||||
<< /Stream2 16 0 R /This-is-Stream1 true /Length 18 >>
|
||||
stream
|
||||
This is stream 1.
|
||||
endstream
|
||||
endobj
|
||||
10 0 obj
|
||||
<< /Length 47 >>
|
||||
stream
|
||||
BT /F1 15 Tf 72 720 Td (Original page 2) Tj ET
|
||||
endstream
|
||||
endobj
|
||||
11 0 obj
|
||||
<< /BaseFont /Times-Roman /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >>
|
||||
endobj
|
||||
12 0 obj
|
||||
<< /Length 44 >>
|
||||
stream
|
||||
BT
|
||||
/F1 24 Tf
|
||||
72 720 Td
|
||||
(Potato) Tj
|
||||
ET
|
||||
endstream
|
||||
endobj
|
||||
13 0 obj
|
||||
<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >>
|
||||
endobj
|
||||
14 0 obj
|
||||
[ /PDF /Text ]
|
||||
endobj
|
||||
15 0 obj
|
||||
<< /Length 47 >>
|
||||
stream
|
||||
BT /F1 15 Tf 72 720 Td (Original page 3) Tj ET
|
||||
endstream
|
||||
endobj
|
||||
16 0 obj
|
||||
<< /Stream1 9 0 R /This-is-Stream2 true /Length 18 >>
|
||||
stream
|
||||
This is stream 2.
|
||||
endstream
|
||||
endobj
|
||||
xref
|
||||
0 17
|
||||
0000000000 65535 f
|
||||
0000000015 00000 n
|
||||
0000000064 00000 n
|
||||
0000000135 00000 n
|
||||
0000000206 00000 n
|
||||
0000000323 00000 n
|
||||
0000000401 00000 n
|
||||
0000000601 00000 n
|
||||
0000000747 00000 n
|
||||
0000000941 00000 n
|
||||
0000001046 00000 n
|
||||
0000001143 00000 n
|
||||
0000001243 00000 n
|
||||
0000001337 00000 n
|
||||
0000001445 00000 n
|
||||
0000001476 00000 n
|
||||
0000001573 00000 n
|
||||
trailer << /QTest 2 0 R /Root 1 0 R /Size 17 /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] >>
|
||||
startxref
|
||||
1678
|
||||
%%EOF
|
@ -916,6 +916,89 @@ void runtest(int n, char const* filename)
|
||||
w.setStreamDataMode(qpdf_s_preserve);
|
||||
w.write();
|
||||
}
|
||||
else if (n == 25)
|
||||
{
|
||||
// The copy object tests are designed to work with a specific
|
||||
// file. Look at the test suite for the file, and look at the
|
||||
// file for comments about the file's structure.
|
||||
|
||||
// Copy qtest without crossing page boundaries. Should get O1
|
||||
// and O2 and their streams but not O3 or any other pages.
|
||||
|
||||
QPDF newpdf;
|
||||
newpdf.processFile("minimal.pdf");
|
||||
QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest");
|
||||
newpdf.getTrailer().replaceKey(
|
||||
"/QTest", newpdf.copyForeignObject(qtest));
|
||||
|
||||
QPDFWriter w(newpdf, "a.pdf");
|
||||
w.setStaticID(true);
|
||||
w.setStreamDataMode(qpdf_s_preserve);
|
||||
w.write();
|
||||
}
|
||||
else if (n == 26)
|
||||
{
|
||||
// Copy the O3 page using addPage. Copy qtest without
|
||||
// crossing page boundaries. In addition to previous results,
|
||||
// should get page O3 but no other pages including the page
|
||||
// that O3 points to. Also, inherited object will have been
|
||||
// pushed down and will be preserved.
|
||||
|
||||
QPDF newpdf;
|
||||
newpdf.processFile("minimal.pdf");
|
||||
QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest");
|
||||
QPDFObjectHandle O3 = qtest.getKey("/O3");
|
||||
newpdf.addPage(O3, false);
|
||||
newpdf.getTrailer().replaceKey(
|
||||
"/QTest", newpdf.copyForeignObject(qtest));
|
||||
|
||||
QPDFWriter w(newpdf, "a.pdf");
|
||||
w.setStaticID(true);
|
||||
w.setStreamDataMode(qpdf_s_preserve);
|
||||
w.write();
|
||||
}
|
||||
else if (n == 27)
|
||||
{
|
||||
// Copy O3 and the page O3 refers to before copying qtest.
|
||||
// Should get qtest plus only the O3 page and the page that O3
|
||||
// points to. Inherited objects should be preserved.
|
||||
|
||||
QPDF newpdf;
|
||||
newpdf.processFile("minimal.pdf");
|
||||
QPDFObjectHandle qtest = pdf.getTrailer().getKey("/QTest");
|
||||
QPDFObjectHandle O3 = qtest.getKey("/O3");
|
||||
newpdf.addPage(O3.getKey("/OtherPage"), false);
|
||||
newpdf.addPage(O3, false);
|
||||
newpdf.getTrailer().replaceKey(
|
||||
"/QTest", newpdf.copyForeignObject(qtest));
|
||||
|
||||
QPDFWriter w(newpdf, "a.pdf");
|
||||
w.setStaticID(true);
|
||||
w.setStreamDataMode(qpdf_s_preserve);
|
||||
w.write();
|
||||
}
|
||||
else if (n == 28)
|
||||
{
|
||||
// Copy foreign object errors
|
||||
try
|
||||
{
|
||||
pdf.copyForeignObject(pdf.getTrailer().getKey("/QTest"));
|
||||
std::cout << "oops -- didn't throw" << std::endl;
|
||||
}
|
||||
catch (std::logic_error e)
|
||||
{
|
||||
std::cout << "logic error: " << e.what() << std::endl;
|
||||
}
|
||||
try
|
||||
{
|
||||
pdf.copyForeignObject(QPDFObjectHandle::newInteger(1));
|
||||
std::cout << "oops -- didn't throw" << std::endl;
|
||||
}
|
||||
catch (std::logic_error e)
|
||||
{
|
||||
std::cout << "logic error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(std::string("invalid test ") +
|
||||
|
Loading…
Reference in New Issue
Block a user