mirror of
https://github.com/qpdf/qpdf.git
synced 2025-01-02 22:50:20 +00:00
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:
parent
af64668ad1
commit
8a217eb3a2
@ -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
|
||||
|
@ -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 };
|
||||
|
@ -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__
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
13
libqpdf/QPDF_Reserved.cc
Normal 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 "";
|
||||
}
|
@ -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 \
|
||||
|
13
libqpdf/qpdf/QPDF_Reserved.hh
Normal file
13
libqpdf/qpdf/QPDF_Reserved.hh
Normal 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__
|
@ -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
|
||||
|
@ -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();
|
||||
# ----------
|
||||
|
8
qpdf/qtest/qpdf/reserved-objects.out
Normal file
8
qpdf/qtest/qpdf/reserved-objects.out
Normal 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
|
48
qpdf/qtest/qpdf/reserved-objects.pdf
Normal file
48
qpdf/qtest/qpdf/reserved-objects.pdf
Normal 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
|
@ -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 ") +
|
||||
|
Loading…
Reference in New Issue
Block a user