mirror of
https://github.com/qpdf/qpdf.git
synced 2024-06-09 05:32:43 +00:00
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
|
class QPDFObjectHandle
|
||||||
{
|
{
|
||||||
public:
|
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
|
QPDF_DLL
|
||||||
QPDFObjectHandle();
|
QPDFObjectHandle();
|
||||||
QPDF_DLL
|
QPDF_DLL
|
||||||
|
@ -83,6 +111,30 @@ class QPDFObjectHandle
|
||||||
static QPDFObjectHandle newDictionary(
|
static QPDFObjectHandle newDictionary(
|
||||||
std::map<std::string, QPDFObjectHandle> const& items);
|
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
|
// Accessor methods. If an accessor method that is valid for only
|
||||||
// a particular object type is called on an object of the wrong
|
// a particular object type is called on an object of the wrong
|
||||||
// type, an exception is thrown.
|
// type, an exception is thrown.
|
||||||
|
@ -198,34 +250,17 @@ class QPDFObjectHandle
|
||||||
QPDFObjectHandle const& filter,
|
QPDFObjectHandle const& filter,
|
||||||
QPDFObjectHandle const& decode_parms);
|
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
|
// As above, replace this stream's stream data. Instead of
|
||||||
// directly providing a buffer with the stream data, call the
|
// directly providing a buffer with the stream data, call the
|
||||||
// given provider's provideStreamData method. The method is to
|
// given provider's provideStreamData method. See comments on the
|
||||||
// write the unencrypted, raw stream data to the provided
|
// StreamDataProvider class (defined above) for details on the
|
||||||
// pipeline. The stream's /Length key will be set to the length
|
// method. The provider must write the number of bytes as
|
||||||
// as provided. This must match the number of bytes written to
|
// indicated by the length parameter, and the data must be
|
||||||
// the pipeline. The provider must write exactly the same data to
|
// consistent with filter and decode_parms as provided. Although
|
||||||
// the pipeline every time it is called. The method is invoked
|
// it is more complex to use this form of replaceStreamData than
|
||||||
// with the object ID and generation number, which are just there
|
// the one that takes a buffer, it makes it possible to avoid
|
||||||
// to be available to the handler in case it is useful for
|
// allocating memory for the stream data. Example programs are
|
||||||
// indexing purposes. This makes it easier to reuse the same
|
// provided that use both forms of replaceStreamData.
|
||||||
// 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.
|
|
||||||
QPDF_DLL
|
QPDF_DLL
|
||||||
void replaceStreamData(PointerHolder<StreamDataProvider> provider,
|
void replaceStreamData(PointerHolder<StreamDataProvider> provider,
|
||||||
QPDFObjectHandle const& filter,
|
QPDFObjectHandle const& filter,
|
||||||
|
|
|
@ -1779,7 +1779,11 @@ QPDF::resolveObjectsInStream(int obj_stream_number)
|
||||||
QPDFObjectHandle
|
QPDFObjectHandle
|
||||||
QPDF::makeIndirectObject(QPDFObjectHandle oh)
|
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;
|
ObjGen o2 = (*(this->xref_table.rbegin())).first;
|
||||||
QTC::TC("qpdf", "QPDF indirect last obj from xref",
|
QTC::TC("qpdf", "QPDF indirect last obj from xref",
|
||||||
(o2.obj > o1.obj) ? 1 : 0);
|
(o2.obj > o1.obj) ? 1 : 0);
|
||||||
|
|
|
@ -561,6 +561,30 @@ QPDFObjectHandle::newStream(QPDF* qpdf, int objid, int generation,
|
||||||
stream_dict, offset, length));
|
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
|
void
|
||||||
QPDFObjectHandle::makeDirectInternal(std::set<int>& visited)
|
QPDFObjectHandle::makeDirectInternal(std::set<int>& visited)
|
||||||
{
|
{
|
||||||
|
@ -649,7 +673,7 @@ QPDFObjectHandle::makeDirectInternal(std::set<int>& visited)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw std::logic_error("QPDFObjectHandle::makeIndirect: "
|
throw std::logic_error("QPDFObjectHandle::makeDirectInternal: "
|
||||||
"unknown object type");
|
"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
|
std::string
|
||||||
QPDF_Stream::unparse()
|
QPDF_Stream::unparse()
|
||||||
{
|
{
|
||||||
|
@ -353,6 +366,12 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool filter,
|
||||||
QUtil::int_to_string(desired_length) + " bytes");
|
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
|
else
|
||||||
{
|
{
|
||||||
QTC::TC("qpdf", "QPDF_Stream pipe original stream data");
|
QTC::TC("qpdf", "QPDF_Stream pipe original stream data");
|
||||||
|
|
|
@ -31,6 +31,11 @@ class QPDF_Stream: public QPDFObject
|
||||||
QPDFObjectHandle const& decode_parms,
|
QPDFObjectHandle const& decode_parms,
|
||||||
size_t length);
|
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:
|
private:
|
||||||
void replaceFilterData(QPDFObjectHandle const& filter,
|
void replaceFilterData(QPDFObjectHandle const& filter,
|
||||||
QPDFObjectHandle const& decode_parms,
|
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 replaced stream data 0
|
||||||
QPDF_Stream pipe use stream provider 0
|
QPDF_Stream pipe use stream provider 0
|
||||||
QPDF_Stream provider length mismatch 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();
|
show_ntests();
|
||||||
# ----------
|
# ----------
|
||||||
$td->notify("--- Miscellaneous Tests ---");
|
$td->notify("--- Miscellaneous Tests ---");
|
||||||
$n_tests += 26;
|
$n_tests += 28;
|
||||||
|
|
||||||
$td->runtest("qpdf version",
|
$td->runtest("qpdf version",
|
||||||
{$td->COMMAND => "qpdf --version"},
|
{$td->COMMAND => "qpdf --version"},
|
||||||
|
@ -104,7 +104,6 @@ $td->runtest("replace stream data",
|
||||||
$td->runtest("check output",
|
$td->runtest("check output",
|
||||||
{$td->FILE => "a.pdf"},
|
{$td->FILE => "a.pdf"},
|
||||||
{$td->FILE => "replaced-stream-data.out"});
|
{$td->FILE => "replaced-stream-data.out"});
|
||||||
|
|
||||||
$td->runtest("replace stream data compressed",
|
$td->runtest("replace stream data compressed",
|
||||||
{$td->COMMAND => "test_driver 8 qstream.pdf"},
|
{$td->COMMAND => "test_driver 8 qstream.pdf"},
|
||||||
{$td->FILE => "test8.out", $td->EXIT_STATUS => 0},
|
{$td->FILE => "test8.out", $td->EXIT_STATUS => 0},
|
||||||
|
@ -112,6 +111,13 @@ $td->runtest("replace stream data compressed",
|
||||||
$td->runtest("check output",
|
$td->runtest("check output",
|
||||||
{$td->FILE => "a.pdf"},
|
{$td->FILE => "a.pdf"},
|
||||||
{$td->FILE => "replaced-stream-data-flate.out"});
|
{$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
|
# Make sure we ignore decode parameters that we don't understand
|
||||||
$td->runtest("unknown decode parameters",
|
$td->runtest("unknown decode parameters",
|
||||||
|
|
79
qpdf/qtest/qpdf/minimal.pdf
Normal file
79
qpdf/qtest/qpdf/minimal.pdf
Normal 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
|
54
qpdf/qtest/qpdf/new-streams.pdf
Normal file
54
qpdf/qtest/qpdf/new-streams.pdf
Normal 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
|
2
qpdf/qtest/qpdf/test9.out
Normal file
2
qpdf/qtest/qpdf/test9.out
Normal file
|
@ -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.setStreamDataMode(qpdf_s_preserve);
|
||||||
w.write();
|
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
|
else
|
||||||
{
|
{
|
||||||
throw std::runtime_error(std::string("invalid test ") +
|
throw std::runtime_error(std::string("invalid test ") +
|
||||||
|
|
Loading…
Reference in New Issue
Block a user