aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2010-08-05 22:20:52 +0200
committerJay Berkenbilt <ejb@ql.org>2010-08-05 22:20:52 +0200
commit6f2bd7eb3a64ae6ffbdf9ae256d822056ddcb7b0 (patch)
treeb2853762f4a564e1110ddf37f4821ea02d40a367
parent11df7809af7131af139be2e76f2db87128700939 (diff)
downloadqpdf-6f2bd7eb3a64ae6ffbdf9ae256d822056ddcb7b0.tar.zst
newStream
git-svn-id: svn+q:///qpdf/trunk@991 71b93d88-0707-0410-a8cf-f5a4172ac649
-rw-r--r--include/qpdf/QPDFObjectHandle.hh87
-rw-r--r--libqpdf/QPDF.cc6
-rw-r--r--libqpdf/QPDFObjectHandle.cc26
-rw-r--r--libqpdf/QPDF_Stream.cc19
-rw-r--r--libqpdf/qpdf/QPDF_Stream.hh5
-rw-r--r--qpdf/qpdf.testcov3
-rw-r--r--qpdf/qtest/qpdf.test10
-rw-r--r--qpdf/qtest/qpdf/minimal.pdf79
-rw-r--r--qpdf/qtest/qpdf/new-streams.pdf54
-rw-r--r--qpdf/qtest/qpdf/test9.out2
-rw-r--r--qpdf/test_driver.cc29
11 files changed, 290 insertions, 30 deletions
diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh
index e8d24d40..298e480b 100644
--- a/include/qpdf/QPDFObjectHandle.hh
+++ b/include/qpdf/QPDFObjectHandle.hh
@@ -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,
diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc
index cbc2450c..3869e00c 100644
--- a/libqpdf/QPDF.cc
+++ b/libqpdf/QPDF.cc
@@ -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);
diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc
index 649ce3f0..19b4f94e 100644
--- a/libqpdf/QPDFObjectHandle.cc
+++ b/libqpdf/QPDFObjectHandle.cc
@@ -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");
}
diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc
index 1f48b2c1..87c2daa9 100644
--- a/libqpdf/QPDF_Stream.cc
+++ b/libqpdf/QPDF_Stream.cc
@@ -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");
diff --git a/libqpdf/qpdf/QPDF_Stream.hh b/libqpdf/qpdf/QPDF_Stream.hh
index 1790121e..73261f39 100644
--- a/libqpdf/qpdf/QPDF_Stream.hh
+++ b/libqpdf/qpdf/QPDF_Stream.hh
@@ -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,
diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov
index c880188e..49b03c42 100644
--- a/qpdf/qpdf.testcov
+++ b/qpdf/qpdf.testcov
@@ -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
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index 6768ebd4..dc47af18 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -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",
diff --git a/qpdf/qtest/qpdf/minimal.pdf b/qpdf/qtest/qpdf/minimal.pdf
new file mode 100644
index 00000000..a7e01f91
--- /dev/null
+++ b/qpdf/qtest/qpdf/minimal.pdf
@@ -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
diff --git a/qpdf/qtest/qpdf/new-streams.pdf b/qpdf/qtest/qpdf/new-streams.pdf
new file mode 100644
index 00000000..d3966a33
--- /dev/null
+++ b/qpdf/qtest/qpdf/new-streams.pdf
@@ -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
diff --git a/qpdf/qtest/qpdf/test9.out b/qpdf/qtest/qpdf/test9.out
new file mode 100644
index 00000000..3489ba0c
--- /dev/null
+++ b/qpdf/qtest/qpdf/test9.out
@@ -0,0 +1,2 @@
+exception: pipeStreamData called for stream with no data
+test 9 done
diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc
index e9739e92..09458227 100644
--- a/qpdf/test_driver.cc
+++ b/qpdf/test_driver.cc
@@ -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 ") +