mirror of https://github.com/qpdf/qpdf.git
newStream
git-svn-id: svn+q:///qpdf/trunk@991 71b93d88-0707-0410-a8cf-f5a4172ac649
This commit is contained in:
parent
11df7809af
commit
6f2bd7eb3a
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
exception: pipeStreamData called for stream with no data
|
||||
test 9 done
|
|
@ -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 ") +
|
||||
|
|
Loading…
Reference in New Issue