newStream

git-svn-id: svn+q:///qpdf/trunk@991 71b93d88-0707-0410-a8cf-f5a4172ac649
This commit is contained in:
Jay Berkenbilt 2010-08-05 20:20:52 +00:00
parent 11df7809af
commit 6f2bd7eb3a
11 changed files with 290 additions and 30 deletions

View File

@ -28,6 +28,34 @@ class QPDF_Array;
class QPDFObjectHandle
{
public:
// This class is used by replaceStreamData. It provides an
// alternative way of associating stream data with a stream. See
// comments on replaceStreamData and newStream for additional
// details.
class StreamDataProvider
{
public:
QPDF_DLL
virtual ~StreamDataProvider()
{
}
// The implementation of this function must write the
// unencrypted, raw stream data to the given pipeline. Every
// call to provideStreamData for a given stream must write the
// same data. The number of bytes written must agree with the
// length provided at the time the StreamDataProvider object
// was associated with the stream. The object ID and
// generation passed to this method are those that belong to
// the stream on behalf of which the provider is called. They
// may be ignored or used by the implementation for indexing
// or other purposes. This information is made available just
// to make it more convenient to use a single
// StreamDataProvider object to provide data for multiple
// streams.
virtual void provideStreamData(int objid, int generation,
Pipeline* pipeline) = 0;
};
QPDF_DLL
QPDFObjectHandle();
QPDF_DLL
@ -83,6 +111,30 @@ class QPDFObjectHandle
static QPDFObjectHandle newDictionary(
std::map<std::string, QPDFObjectHandle> const& items);
// Create a new stream and associate it with the given qpdf
// object. A subsequent call must be made to replaceStreamData()
// to provide data for the stream. The stream's dictionary may be
// retrieved by calling getDict(), and the resulting dictionary
// may be modified.
QPDF_DLL
static QPDFObjectHandle newStream(QPDF* qpdf);
// Create a new stream and associate it with the given qpdf
// object. Use the given buffer as the stream data. The stream
// dictionary's /Length key will automatically be set to the size
// of the data buffer. If additional keys are required, the
// stream's dictionary may be retrieved by calling getDict(), and
// the resulting dictionary may be modified. This method is just
// a convient wrapper around the newStream() and
// replaceStreamData(). It is a convenience methods for streams
// that require no parameters beyond the stream length. Note that
// you don't have to deal with compression yourself if you use
// QPDFWriter. By default, QPDFWriter will automatically compress
// uncompressed stream data. Example programs are provided that
// illustrate this.
QPDF_DLL
static QPDFObjectHandle newStream(QPDF* qpdf, PointerHolder<Buffer> data);
// 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.
@ -198,34 +250,17 @@ class QPDFObjectHandle
QPDFObjectHandle const& filter,
QPDFObjectHandle const& decode_parms);
class StreamDataProvider
{
public:
QPDF_DLL
virtual ~StreamDataProvider()
{
}
// See replaceStreamData below for details on how to override
// this method.
virtual void provideStreamData(int objid, int generation,
Pipeline* pipeline) = 0;
};
// As above, replace this stream's stream data. Instead of
// directly providing a buffer with the stream data, call the
// given provider's provideStreamData method. The method is to
// write the unencrypted, raw stream data to the provided
// pipeline. The stream's /Length key will be set to the length
// as provided. This must match the number of bytes written to
// the pipeline. The provider must write exactly the same data to
// the pipeline every time it is called. The method is invoked
// with the object ID and generation number, which are just there
// to be available to the handler in case it is useful for
// indexing purposes. This makes it easier to reuse the same
// StreamDataProvider object for multiple streams. Although it is
// more complex to use this form of replaceStreamData, it makes it
// possible to avoid allocating memory for the stream data.
// Example programs are provided that use both forms of
// replaceStreamData.
// given provider's provideStreamData method. See comments on the
// StreamDataProvider class (defined above) for details on the
// method. The provider must write the number of bytes as
// indicated by the length parameter, and the data must be
// consistent with filter and decode_parms as provided. Although
// it is more complex to use this form of replaceStreamData than
// the one that takes a buffer, it makes it possible to avoid
// allocating memory for the stream data. Example programs are
// provided that use both forms of replaceStreamData.
QPDF_DLL
void replaceStreamData(PointerHolder<StreamDataProvider> provider,
QPDFObjectHandle const& filter,

View File

@ -1779,7 +1779,11 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
QPDFObjectHandle
QPDF::makeIndirectObject(QPDFObjectHandle oh)
{
ObjGen o1 = (*(this->obj_cache.rbegin())).first;
ObjGen o1(0, 0);
if (! this->obj_cache.empty())
{
o1 = (*(this->obj_cache.rbegin())).first;
}
ObjGen o2 = (*(this->xref_table.rbegin())).first;
QTC::TC("qpdf", "QPDF indirect last obj from xref",
(o2.obj > o1.obj) ? 1 : 0);

View File

@ -561,6 +561,30 @@ QPDFObjectHandle::newStream(QPDF* qpdf, int objid, int generation,
stream_dict, offset, length));
}
QPDFObjectHandle
QPDFObjectHandle::newStream(QPDF* qpdf)
{
QTC::TC("qpdf", "QPDFObjectHandle newStream");
std::map<std::string, QPDFObjectHandle> keys;
QPDFObjectHandle stream_dict = newDictionary(keys);
QPDFObjectHandle result = qpdf->makeIndirectObject(
QPDFObjectHandle(
new QPDF_Stream(qpdf, 0, 0, stream_dict, 0, 0)));
result.dereference();
QPDF_Stream* stream = dynamic_cast<QPDF_Stream*>(result.obj.getPointer());
stream->setObjGen(result.getObjectID(), result.getGeneration());
return result;
}
QPDFObjectHandle
QPDFObjectHandle::newStream(QPDF* qpdf, PointerHolder<Buffer> data)
{
QTC::TC("qpdf", "QPDFObjectHandle newStream with data");
QPDFObjectHandle result = newStream(qpdf);
result.replaceStreamData(data, newNull(), newNull());
return result;
}
void
QPDFObjectHandle::makeDirectInternal(std::set<int>& visited)
{
@ -649,7 +673,7 @@ QPDFObjectHandle::makeDirectInternal(std::set<int>& visited)
}
else
{
throw std::logic_error("QPDFObjectHandle::makeIndirect: "
throw std::logic_error("QPDFObjectHandle::makeDirectInternal: "
"unknown object type");
}

View File

@ -40,6 +40,19 @@ QPDF_Stream::~QPDF_Stream()
{
}
void
QPDF_Stream::setObjGen(int objid, int generation)
{
if (! ((this->objid == 0) && (this->generation == 0)))
{
throw std::logic_error(
"attempt to set object ID and generation of a stream"
" that already has them");
}
this->objid = objid;
this->generation = generation;
}
std::string
QPDF_Stream::unparse()
{
@ -353,6 +366,12 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool filter,
QUtil::int_to_string(desired_length) + " bytes");
}
}
else if (this->offset == 0)
{
QTC::TC("qpdf", "QPDF_Stream pipe no stream data");
throw std::logic_error(
"pipeStreamData called for stream with no data");
}
else
{
QTC::TC("qpdf", "QPDF_Stream pipe original stream data");

View File

@ -31,6 +31,11 @@ class QPDF_Stream: public QPDFObject
QPDFObjectHandle const& decode_parms,
size_t length);
// Replace object ID and generation. This may only be called if
// object ID and generation are 0. It is used by QPDFObjectHandle
// when adding streams to files.
void setObjGen(int objid, int generation);
private:
void replaceFilterData(QPDFObjectHandle const& filter,
QPDFObjectHandle const& decode_parms,

View File

@ -178,3 +178,6 @@ QPDF_Stream pipe original stream data 0
QPDF_Stream pipe replaced stream data 0
QPDF_Stream pipe use stream provider 0
QPDF_Stream provider length mismatch 0
QPDFObjectHandle newStream 0
QPDFObjectHandle newStream with data 0
QPDF_Stream pipe no stream data 0

View File

@ -77,7 +77,7 @@ flush_tiff_cache();
show_ntests();
# ----------
$td->notify("--- Miscellaneous Tests ---");
$n_tests += 26;
$n_tests += 28;
$td->runtest("qpdf version",
{$td->COMMAND => "qpdf --version"},
@ -104,7 +104,6 @@ $td->runtest("replace stream data",
$td->runtest("check output",
{$td->FILE => "a.pdf"},
{$td->FILE => "replaced-stream-data.out"});
$td->runtest("replace stream data compressed",
{$td->COMMAND => "test_driver 8 qstream.pdf"},
{$td->FILE => "test8.out", $td->EXIT_STATUS => 0},
@ -112,6 +111,13 @@ $td->runtest("replace stream data compressed",
$td->runtest("check output",
{$td->FILE => "a.pdf"},
{$td->FILE => "replaced-stream-data-flate.out"});
$td->runtest("new streams",
{$td->COMMAND => "test_driver 9 minimal.pdf"},
{$td->FILE => "test9.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("new stream",
{$td->FILE => "a.pdf"},
{$td->FILE => "new-streams.pdf"});
# Make sure we ignore decode parameters that we don't understand
$td->runtest("unknown decode parameters",

View File

@ -0,0 +1,79 @@
%PDF-1.3
1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/Kids [
3 0 R
]
/Count 1
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources <<
/ProcSet 5 0 R
/Font <<
/F1 6 0 R
>>
>>
>>
endobj
4 0 obj
<<
/Length 44
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato) Tj
ET
endstream
endobj
5 0 obj
[
/PDF
/Text
]
endobj
6 0 obj
<<
/Type /Font
/Subtype /Type1
/Name /F1
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
>>
endobj
xref
0 7
0000000000 65535 f
0000000009 00000 n
0000000063 00000 n
0000000135 00000 n
0000000307 00000 n
0000000403 00000 n
0000000438 00000 n
trailer <<
/Size 7
/Root 1 0 R
>>
startxref
556
%%EOF

View File

@ -0,0 +1,54 @@
%PDF-1.3
%¿÷¢þ
1 0 obj
<< /Pages 2 0 R /QStream 3 0 R /RStream 4 0 R /Type /Catalog >>
endobj
2 0 obj
<< /Count 1 /Kids [ 5 0 R ] /Type /Pages >>
endobj
3 0 obj
<< /Length 20 >>
stream
data for new stream
endstream
endobj
4 0 obj
<< /Length 22 >>
stream
data for other stream
endstream
endobj
5 0 obj
<< /Contents 6 0 R /MediaBox [ 0 0 612 792 ] /Parent 2 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
0000000094 00000 n
0000000153 00000 n
0000000222 00000 n
0000000293 00000 n
0000000436 00000 n
0000000529 00000 n
0000000636 00000 n
trailer << /Root 1 0 R /Size 9 /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] >>
startxref
666
%%EOF

View File

@ -0,0 +1,2 @@
exception: pipeStreamData called for stream with no data
test 9 done

View File

@ -398,6 +398,35 @@ void runtest(int n, char const* filename)
w.setStreamDataMode(qpdf_s_preserve);
w.write();
}
else if (n == 9)
{
QPDFObjectHandle root = pdf.getRoot();
PointerHolder<Buffer> b1 = new Buffer(20);
unsigned char* bp = b1.getPointer()->getBuffer();
memcpy(bp, (char*)"data for new stream\n", 20); // no null!
QPDFObjectHandle qstream = QPDFObjectHandle::newStream(&pdf, b1);
QPDFObjectHandle rstream = QPDFObjectHandle::newStream(&pdf);
try
{
rstream.getStreamData();
std::cout << "oops -- getStreamData didn't throw" << std::endl;
}
catch (std::logic_error const& e)
{
std::cout << "exception: " << e.what() << std::endl;
}
PointerHolder<Buffer> b2 = new Buffer(22);
bp = b2.getPointer()->getBuffer();
memcpy(bp, (char*)"data for other stream\n", 22); // no null!
rstream.replaceStreamData(
b2, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
root.replaceKey("/QStream", qstream);
root.replaceKey("/RStream", rstream);
QPDFWriter w(pdf, "a.pdf");
w.setStaticID(true);
w.setStreamDataMode(qpdf_s_preserve);
w.write();
}
else
{
throw std::runtime_error(std::string("invalid test ") +