Add concept of reserved objects

QPDFObjectHandle::{new,is,assert}Reserved, QPDF::replaceReserved
provide a mechanism to add objects to a PDF file when there are
circular references.  This is a prerequisite to copying objects from
one PDF to another.
This commit is contained in:
Jay Berkenbilt 2012-07-08 14:19:19 -04:00
parent af64668ad1
commit 8a217eb3a2
13 changed files with 280 additions and 7 deletions

View File

@ -1,3 +1,12 @@
2012-07-08 Jay Berkenbilt <ejb@ql.org>
* Add QPDFObjectHandle::newReserved to create a reserved object
and QPDF::replaceReserved to replace it with a real object.
QPDFObjectHandle::newReserved reserves an object ID in a QPDF
object and ensures that any references to it remain unresolved.
When QPDF::replaceReserved is later called, previous references to
the reserved object will properly resolve to the replaced object.
2012-07-07 Jay Berkenbilt <ejb@ql.org>
* NOTE: BREAKING API CHANGE. Remove previously required length

View File

@ -161,7 +161,8 @@ class QPDF
// be associated with the PDF file. Note that replacing an object
// with QPDFObjectHandle::newNull() effectively removes the object
// from the file since a non-existent object is treated as a null
// object.
// object. To replace a reserved object, call replaceReserved
// instead.
QPDF_DLL
void replaceObject(int objid, int generation, QPDFObjectHandle);
@ -180,6 +181,15 @@ class QPDF
void swapObjects(int objid1, int generation1,
int objid2, int generation2);
// Replace a reserved object. This is a wrapper around
// replaceObject but it guarantees that the underlying object is a
// reserved object. After this call, reserved will be a reference
// to replacement.
QPDF_DLL
void
replaceReserved(QPDFObjectHandle reserved,
QPDFObjectHandle replacement);
// Encryption support
enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes };

View File

@ -81,6 +81,8 @@ class QPDFObjectHandle
bool isDictionary();
QPDF_DLL
bool isStream();
QPDF_DLL
bool isReserved();
// This returns true in addition to the query for the specific
// type for indirect objects.
@ -148,6 +150,24 @@ class QPDFObjectHandle
QPDF_DLL
static QPDFObjectHandle newStream(QPDF* qpdf, std::string const& data);
// A reserved object is a special sentinel used for qpdf to
// reserve a spot for an object that is going to be added to the
// QPDF object. Normally you don't have to use this type since
// you can just call QPDF::makeIndirectObject. However, in some
// cases, if you have to create objects with circular references,
// you may need to create a reserved object so that you can have a
// reference to it and then replace the object later. Reserved
// objects have the special property that they can't be resolved
// to direct objects. This makes it possible to replace a
// reserved object with a new object while preserving existing
// references to them. When you are ready to replace a reserved
// object with its replacement, use QPDF::replaceReserved for this
// purpose rather than the more general QPDF::replaceObject. It
// is an error to try to write a QPDF with QPDFWriter if it has
// any reserved objects in it.
QPDF_DLL
static QPDFObjectHandle newReserved(QPDF* qpdf);
// Accessor methods. If an accessor method that is valid for only
// a particular object type is called on an object of the wrong
// type, an exception is thrown.
@ -430,6 +450,8 @@ class QPDFObjectHandle
void assertDictionary();
QPDF_DLL
void assertStream();
QPDF_DLL
void assertReserved();
QPDF_DLL
void assertScalar();
@ -459,6 +481,7 @@ class QPDFObjectHandle
int objid; // 0 for direct object
int generation;
PointerHolder<QPDFObject> obj;
bool reserved;
};
#endif // __QPDFOBJECTHANDLE_HH__

View File

@ -2056,6 +2056,18 @@ QPDF::replaceObject(int objid, int generation, QPDFObjectHandle oh)
ObjCache(QPDFObjectHandle::ObjAccessor::getObject(oh), -1, -1);
}
void
QPDF::replaceReserved(QPDFObjectHandle reserved,
QPDFObjectHandle replacement)
{
QTC::TC("qpdf", "QPDF replaceReserved");
reserved.assertReserved();
replaceObject(reserved.getObjectID(),
reserved.getGeneration(),
replacement);
}
void
QPDF::swapObjects(int objid1, int generation1, int objid2, int generation2)
{

View File

@ -10,6 +10,7 @@
#include <qpdf/QPDF_Array.hh>
#include <qpdf/QPDF_Dictionary.hh>
#include <qpdf/QPDF_Stream.hh>
#include <qpdf/QPDF_Reserved.hh>
#include <qpdf/QTC.hh>
#include <qpdf/QUtil.hh>
@ -20,7 +21,8 @@
QPDFObjectHandle::QPDFObjectHandle() :
initialized(false),
objid(0),
generation(0)
generation(0),
reserved(false)
{
}
@ -28,7 +30,8 @@ QPDFObjectHandle::QPDFObjectHandle(QPDF* qpdf, int objid, int generation) :
initialized(true),
qpdf(qpdf),
objid(objid),
generation(generation)
generation(generation),
reserved(false)
{
}
@ -37,7 +40,8 @@ QPDFObjectHandle::QPDFObjectHandle(QPDFObject* data) :
qpdf(0),
objid(0),
generation(0),
obj(data)
obj(data),
reserved(false)
{
}
@ -165,6 +169,14 @@ QPDFObjectHandle::isStream()
return QPDFObjectTypeAccessor<QPDF_Stream>::check(obj.getPointer());
}
bool
QPDFObjectHandle::isReserved()
{
// dereference will clear reserved if this has been replaced
dereference();
return this->reserved;
}
bool
QPDFObjectHandle::isIndirect()
{
@ -568,6 +580,11 @@ QPDFObjectHandle::unparse()
std::string
QPDFObjectHandle::unparseResolved()
{
if (this->reserved)
{
throw std::logic_error(
"QPDFObjectHandle: attempting to unparse a reserved object");
}
dereference();
return this->obj->unparse();
}
@ -689,6 +706,19 @@ QPDFObjectHandle::newStream(QPDF* qpdf, std::string const& data)
return QPDFObjectHandle::newStream(qpdf, b);
}
QPDFObjectHandle
QPDFObjectHandle::newReserved(QPDF* qpdf)
{
// Reserve a spot for this object by assigning it an object
// number, but then return an unresolved handle to the object.
QPDFObjectHandle reserved = qpdf->makeIndirectObject(
QPDFObjectHandle(new QPDF_Reserved()));
QPDFObjectHandle result =
newIndirect(qpdf, reserved.objid, reserved.generation);
result.reserved = true;
return result;
}
QPDFObjectHandle
QPDFObjectHandle::shallowCopy()
{
@ -746,6 +776,13 @@ QPDFObjectHandle::makeDirectInternal(std::set<int>& visited)
visited.insert(cur_objid);
}
if (isReserved())
{
throw std::logic_error(
"QPDFObjectHandle: attempting to make a"
" reserved object handle direct");
}
dereference();
this->objid = 0;
this->generation = 0;
@ -902,6 +939,12 @@ QPDFObjectHandle::assertStream()
assertType("Stream", isStream());
}
void
QPDFObjectHandle::assertReserved()
{
assertType("Reserved", isReserved());
}
void
QPDFObjectHandle::assertScalar()
{
@ -929,12 +972,21 @@ QPDFObjectHandle::dereference()
{
if (this->obj.getPointer() == 0)
{
this->obj = QPDF::Resolver::resolve(
PointerHolder<QPDFObject> obj = QPDF::Resolver::resolve(
this->qpdf, this->objid, this->generation);
if (this->obj.getPointer() == 0)
if (obj.getPointer() == 0)
{
QTC::TC("qpdf", "QPDFObjectHandle indirect to unknown");
this->obj = new QPDF_Null();
}
else if (dynamic_cast<QPDF_Reserved*>(obj.getPointer()))
{
// Do not resolve
}
else
{
this->reserved = false;
this->obj = obj;
}
}
}

13
libqpdf/QPDF_Reserved.cc Normal file
View File

@ -0,0 +1,13 @@
#include <qpdf/QPDF_Reserved.hh>
#include <stdexcept>
QPDF_Reserved::~QPDF_Reserved()
{
}
std::string
QPDF_Reserved::unparse()
{
throw std::logic_error("attempt to unparse QPDF_Reserved");
return "";
}

View File

@ -39,6 +39,7 @@ SRCS_libqpdf = \
libqpdf/QPDF_Name.cc \
libqpdf/QPDF_Null.cc \
libqpdf/QPDF_Real.cc \
libqpdf/QPDF_Reserved.cc \
libqpdf/QPDF_Stream.cc \
libqpdf/QPDF_String.cc \
libqpdf/QPDF_encryption.cc \

View File

@ -0,0 +1,13 @@
#ifndef __QPDF_RESERVED_HH__
#define __QPDF_RESERVED_HH__
#include <qpdf/QPDFObject.hh>
class QPDF_Reserved: public QPDFObject
{
public:
virtual ~QPDF_Reserved();
virtual std::string unparse();
};
#endif // __QPDF_RESERVED_HH__

View File

@ -217,3 +217,4 @@ QPDFObjectHandle newStream with string 0
QPDF unknown key not inherited 0
QPDF_Stream provider length not provided 0
QPDF_Stream unknown stream length 0
QPDF replaceReserved 0

View File

@ -149,7 +149,7 @@ $td->runtest("remove page we don't have",
$td->NORMALIZE_NEWLINES);
# ----------
$td->notify("--- Miscellaneous Tests ---");
$n_tests += 41;
$n_tests += 43;
$td->runtest("qpdf version",
{$td->COMMAND => "qpdf --version"},
@ -358,6 +358,13 @@ $td->runtest("warn for unknown key in Pages",
{$td->COMMAND => "test_driver 23 lin-special.pdf"},
{$td->FILE => "pages-warning.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("reserved objects",
{$td->COMMAND => "test_driver 24 minimal.pdf"},
{$td->FILE => "reserved-objects.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("check output",
{$td->FILE => "a.pdf"},
{$td->FILE => "reserved-objects.pdf"});
show_ntests();
# ----------

View File

@ -0,0 +1,8 @@
res1 is still reserved after checking if array
res1 is no longer reserved
res1 is an array
logic error: QPDFObjectHandle: attempting to unparse a reserved object
logic error: QPDFObjectHandle: attempting to make a reserved object handle direct
res2 is an array
circular access and lazy resolution worked
test 24 done

View File

@ -0,0 +1,48 @@
%PDF-1.3
%¿÷¢þ
1 0 obj
<< /Pages 4 0 R /Type /Catalog >>
endobj
2 0 obj
[ 3 0 R 1 ]
endobj
3 0 obj
[ 2 0 R 2 ]
endobj
4 0 obj
<< /Count 1 /Kids [ 5 0 R ] /Type /Pages >>
endobj
5 0 obj
<< /Contents 6 0 R /MediaBox [ 0 0 612 792 ] /Parent 4 0 R /Resources << /Font << /F1 7 0 R >> /ProcSet 8 0 R >> /Type /Page >>
endobj
6 0 obj
<< /Length 44 >>
stream
BT
/F1 24 Tf
72 720 Td
(Potato) Tj
ET
endstream
endobj
7 0 obj
<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >>
endobj
8 0 obj
[ /PDF /Text ]
endobj
xref
0 9
0000000000 65535 f
0000000015 00000 n
0000000064 00000 n
0000000091 00000 n
0000000118 00000 n
0000000177 00000 n
0000000320 00000 n
0000000413 00000 n
0000000520 00000 n
trailer << /Root 1 0 R /Size 9 Array1 2 0 R Array2 3 0 R /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] >>
startxref
550
%%EOF

View File

@ -840,6 +840,82 @@ void runtest(int n, char const* filename)
std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
pdf.removePage(pages.back());
}
else if (n == 24)
{
// Test behavior of reserved objects
QPDFObjectHandle res1 = QPDFObjectHandle::newReserved(&pdf);
QPDFObjectHandle res2 = QPDFObjectHandle::newReserved(&pdf);
QPDFObjectHandle trailer = pdf.getTrailer();
trailer.replaceKey("Array1", res1);
trailer.replaceKey("Array2", res2);
QPDFObjectHandle array1 = QPDFObjectHandle::newArray();
QPDFObjectHandle array2 = QPDFObjectHandle::newArray();
array1.appendItem(res2);
array1.appendItem(QPDFObjectHandle::newInteger(1));
array2.appendItem(res1);
array2.appendItem(QPDFObjectHandle::newInteger(2));
// Make sure trying to ask questions about a reserved object
// doesn't break it.
if (res1.isArray())
{
std::cout << "oops -- res1 is an array" << std::endl;
}
if (res1.isReserved())
{
std::cout << "res1 is still reserved after checking if array"
<< std::endl;
}
pdf.replaceReserved(res1, array1);
if (res1.isReserved())
{
std::cout << "oops -- res1 is still reserved" << std::endl;
}
else
{
std::cout << "res1 is no longer reserved" << std::endl;
}
res1.assertArray();
std::cout << "res1 is an array" << std::endl;
try
{
res2.unparseResolved();
std::cout << "oops -- didn't throw" << std::endl;
}
catch (std::logic_error e)
{
std::cout << "logic error: " << e.what() << std::endl;
}
try
{
res2.makeDirect();
std::cout << "oops -- didn't throw" << std::endl;
}
catch (std::logic_error e)
{
std::cout << "logic error: " << e.what() << std::endl;
}
pdf.replaceReserved(res2, array2);
res2.assertArray();
std::cout << "res2 is an array" << std::endl;
// Verify that the previously added reserved keys can be
// dereferenced properly now
int i1 = res1.getArrayItem(0).getArrayItem(1).getIntValue();
int i2 = res2.getArrayItem(0).getArrayItem(1).getIntValue();
if ((i1 == 2) && (i2 == 1))
{
std::cout << "circular access and lazy resolution worked" << std::endl;
}
QPDFWriter w(pdf, "a.pdf");
w.setStaticID(true);
w.setStreamDataMode(qpdf_s_preserve);
w.write();
}
else
{
throw std::runtime_error(std::string("invalid test ") +