aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2021-12-17 19:28:11 +0100
committerJay Berkenbilt <ejb@ql.org>2021-12-17 19:28:11 +0100
commitfeafcc4e88798626c80840797822efbf55722716 (patch)
tree1eabc4f818084d56a25a9571d87823f8004c7b9f
parenta0dbb71a64e6adb26f8f02881e8d0ea8476ee875 (diff)
downloadqpdf-feafcc4e88798626c80840797822efbf55722716.tar.zst
C API: add several stream functions (fixes #596)
-rw-r--r--ChangeLog3
-rw-r--r--include/qpdf/qpdf-c.h74
-rw-r--r--libqpdf/qpdf-c.cc90
-rw-r--r--manual/index.rst3
-rw-r--r--qpdf/qpdf-ctest.c115
-rw-r--r--qpdf/qpdf.testcov6
-rw-r--r--qpdf/qtest/qpdf.test28
-rw-r--r--qpdf/qtest/qpdf/c-foreign.pdf573
-rw-r--r--qpdf/qtest/qpdf/c-get-stream.out13
-rw-r--r--qpdf/qtest/qpdf/c-new-stream.pdfbin0 -> 1116 bytes
10 files changed, 904 insertions, 1 deletions
diff --git a/ChangeLog b/ChangeLog
index 7def42f5..d49a0e40 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,8 @@
2021-12-17 Jay Berkenbilt <ejb@ql.org>
+ * C API: add functions for working with stream data. Search for
+ "STREAM FUNCTIONS" in qpdf-c.h. Fixes #596.
+
* QPDFObjectHandle object types have been moved from
QPDFObject::object_type_e to qpdf_object_type_e (defined in
Constants.h). Old values are available for backward compatibility.
diff --git a/include/qpdf/qpdf-c.h b/include/qpdf/qpdf-c.h
index 4a6eb3a5..92cad46e 100644
--- a/include/qpdf/qpdf-c.h
+++ b/include/qpdf/qpdf-c.h
@@ -129,7 +129,9 @@ extern "C" {
qpdf_data qpdf_init();
/* Pass a pointer to the qpdf_data pointer created by qpdf_init to
- * clean up resources.
+ * clean up resources. This does not include buffers initialized
+ * by functions that return stream data but it otherwise includes
+ * all data associated with the QPDF object or any object handles.
*/
QPDF_DLL
void qpdf_cleanup(qpdf_data* qpdf);
@@ -752,6 +754,15 @@ extern "C" {
QPDF_DLL
qpdf_oh qpdf_oh_new_dictionary(qpdf_data qpdf);
+ /* Create a new stream. Use qpdf_oh_get_dict to get (and
+ * subsequently modify) the stream dictionary if needed. See
+ * comments in QPDFObjectHandle.hh for newStream() for additional
+ * notes. You must call qpdf_oh_replace_stream_data to provide
+ * data for the stream. See STREAM FUNCTIONS below.
+ */
+ QPDF_DLL
+ qpdf_oh qpdf_oh_new_stream(qpdf_data qpdf);
+
QPDF_DLL
void qpdf_oh_make_direct(qpdf_data qpdf, qpdf_oh oh);
@@ -789,6 +800,67 @@ extern "C" {
QPDF_DLL
char const* qpdf_oh_unparse_binary(qpdf_data qpdf, qpdf_oh oh);
+ /* Note about foreign objects: the C API does not have enough
+ * information in the value of a qpdf_oh to know what QPDF object
+ * it belongs to. To uniquely specify a qpdf object handle from a
+ * specific qpdf_data instance, you always pair the qpdf_oh with
+ * the correct qpdf_data. Otherwise, you are likely to get
+ * completely the wrong object if you are not lucky enough to get
+ * an error about the object being invalid.
+ */
+
+ /* Copy foreign object: the qpdf_oh returned belongs to `qpdf`,
+ * while `foreign_oh` belongs to `other_qpdf`.
+ */
+ QPDF_DLL
+ qpdf_oh qpdf_oh_copy_foreign_object(
+ qpdf_data qpdf, qpdf_data other_qpdf, qpdf_oh foreign_oh);
+
+ /* STREAM FUNCTIONS */
+
+ /* These functions provide basic access to streams and stream
+ * data. They are not as comprehensive as what is in
+ * QPDFObjectHandle, but they do allow for working with streams
+ * and stream data as caller-managed memory.
+ */
+
+ /* Get stream data as a buffer. The buffer is allocated with
+ * malloc and must be freed by the caller. The size of the buffer
+ * is stored in *len. The arguments are similar to those in
+ * QPDFObjectHandle::pipeStreamData. To get raw stream data, pass
+ * qpdf_dl_none as decode_level. Otherwise, filtering is attempted
+ * and *filtered is set to indicate whether it was successful. If
+ * *filtered is QPDF_FALSE, then raw, unfiltered stream data was
+ * returned. You may pass a null pointer as filtered if you don't
+ * care about the result. If you pass a null pointer as bufp (and
+ * len), the value of filtered will be set to whether the stream
+ * can be filterable.
+ */
+ QPDF_DLL
+ QPDF_ERROR_CODE qpdf_oh_get_stream_data(
+ qpdf_data qpdf, qpdf_oh stream_oh,
+ enum qpdf_stream_decode_level_e decode_level, QPDF_BOOL* filtered,
+ unsigned char** bufp, size_t* len);
+
+ /* This function returns the concatenation of all of a page's
+ * content streams as a single, dynamically allocated buffer. As
+ * with qpdf_oh_get_stream_data, the buffer is allocated with
+ * malloc and must be freed by the caller.
+ */
+ QPDF_DLL
+ QPDF_ERROR_CODE qpdf_oh_get_page_content_data(
+ qpdf_data qpdf, qpdf_oh page_oh,
+ unsigned char** bufp, size_t* len);
+
+ /* The data pointed to by bufp will be copied by the library. It
+ * does not need to remain valid after the call returns.
+ */
+ QPDF_DLL
+ void qpdf_oh_replace_stream_data(
+ qpdf_data qpdf, qpdf_oh stream_oh,
+ unsigned char const* buf, size_t len,
+ qpdf_oh filter, qpdf_oh decode_parms);
+
/* PAGE FUNCTIONS */
/* The first time a page function is called, qpdf will traverse
diff --git a/libqpdf/qpdf-c.cc b/libqpdf/qpdf-c.cc
index 0ce9c39e..299377c7 100644
--- a/libqpdf/qpdf-c.cc
+++ b/libqpdf/qpdf-c.cc
@@ -5,6 +5,7 @@
#include <qpdf/QTC.hh>
#include <qpdf/QPDFExc.hh>
#include <qpdf/Pl_Discard.hh>
+#include <qpdf/Pl_Buffer.hh>
#include <qpdf/QIntC.hh>
#include <qpdf/QUtil.hh>
@@ -1427,6 +1428,13 @@ qpdf_oh qpdf_oh_new_dictionary(qpdf_data qpdf)
return new_object(qpdf, QPDFObjectHandle::newDictionary());
}
+qpdf_oh qpdf_oh_new_stream(qpdf_data qpdf)
+{
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_new_stream");
+ return new_object(
+ qpdf, QPDFObjectHandle::newStream(qpdf->qpdf.getPointer()));
+}
+
void qpdf_oh_make_direct(qpdf_data qpdf, qpdf_oh oh)
{
do_with_oh_void(
@@ -1580,6 +1588,88 @@ char const* qpdf_oh_unparse_binary(qpdf_data qpdf, qpdf_oh oh)
});
}
+qpdf_oh qpdf_oh_copy_foreign_object(
+ qpdf_data qpdf, qpdf_data other_qpdf, qpdf_oh foreign_oh)
+{
+ return do_with_oh<qpdf_oh>(
+ other_qpdf, foreign_oh,
+ return_uninitialized(qpdf),
+ [qpdf](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_copy_foreign_object");
+ return new_object(qpdf, qpdf->qpdf->copyForeignObject(o));
+ });
+}
+
+QPDF_ERROR_CODE qpdf_oh_get_stream_data(
+ qpdf_data qpdf, qpdf_oh stream_oh,
+ qpdf_stream_decode_level_e decode_level, QPDF_BOOL* filtered,
+ unsigned char** bufp, size_t* len)
+{
+ return trap_errors(qpdf, [stream_oh, decode_level,
+ filtered, bufp, len] (qpdf_data q) {
+ auto stream = qpdf_oh_item_internal(q, stream_oh);
+ Pipeline* p = nullptr;
+ Pl_Buffer buf("stream data");
+ if (bufp)
+ {
+ p = &buf;
+ }
+ bool was_filtered = false;
+ if (stream.pipeStreamData(
+ p, &was_filtered, 0, decode_level, false, false))
+ {
+ QTC::TC("qpdf", "qpdf-c stream data buf set",
+ bufp ? 0 : 1);
+ if (p && bufp && len)
+ {
+ buf.getMallocBuffer(bufp, len);
+ }
+ QTC::TC("qpdf", "qpdf-c stream data filtered set",
+ filtered ? 0 : 1);
+ if (filtered)
+ {
+ *filtered = was_filtered ? QPDF_TRUE : QPDF_FALSE;
+ }
+ }
+ else
+ {
+ throw std::runtime_error(
+ "unable to access stream data for stream " + stream.unparse());
+ }
+ });
+}
+
+QPDF_ERROR_CODE qpdf_oh_get_page_content_data(
+ qpdf_data qpdf, qpdf_oh page_oh,
+ unsigned char** bufp, size_t* len)
+{
+ return trap_errors(qpdf, [page_oh, bufp, len] (qpdf_data q) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_page_content_data");
+ auto o = qpdf_oh_item_internal(q, page_oh);
+ Pl_Buffer buf("page contents");
+ o.pipePageContents(&buf);
+ buf.getMallocBuffer(bufp, len);
+ });
+}
+
+void qpdf_oh_replace_stream_data(
+ qpdf_data qpdf, qpdf_oh stream_oh,
+ unsigned char const* buf, size_t len,
+ qpdf_oh filter_oh, qpdf_oh decode_parms_oh)
+{
+ do_with_oh_void(qpdf, stream_oh, [
+ qpdf, buf, len, filter_oh,
+ decode_parms_oh](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_replace_stream_data");
+ auto filter = qpdf_oh_item_internal(qpdf, filter_oh);
+ auto decode_parms = qpdf_oh_item_internal(qpdf, decode_parms_oh);
+ // XXX test with binary data with null
+ o.replaceStreamData(
+ std::string(reinterpret_cast<char const*>(buf), len),
+ filter, decode_parms);
+ });
+}
+
int qpdf_get_num_pages(qpdf_data qpdf)
{
QTC::TC("qpdf", "qpdf-c called qpdf_num_pages");
diff --git a/manual/index.rst b/manual/index.rst
index a77fae29..49bfe751 100644
--- a/manual/index.rst
+++ b/manual/index.rst
@@ -3659,6 +3659,9 @@ For a detailed list of changes, please see the file
- Add several functions for working with pages. See ``PAGE
FUNCTIONS`` in ``include/qpdf/qpdf-c.h`` for details.
+ - Add several functions for working with streams. See ``STREAM
+ FUNCTIONS`` in ``include/qpdf/qpdf-c.h`` for details.
+
- Documentation change
- The documentation sources have been switched from docbook to
diff --git a/qpdf/qpdf-ctest.c b/qpdf/qpdf-ctest.c
index 90ebdcd4..cf03f0e0 100644
--- a/qpdf/qpdf-ctest.c
+++ b/qpdf/qpdf-ctest.c
@@ -1063,6 +1063,118 @@ static void test37(char const* infile,
assert(qpdf_get_num_pages(qpdf) == 10);
}
+static void test38(char const* infile,
+ char const* password,
+ char const* outfile,
+ char const* xarg)
+{
+ /* This test expects 11-pages.pdf. */
+
+ /* Read stream data */
+
+ assert(qpdf_read(qpdf, infile, password) == 0);
+ qpdf_oh stream = qpdf_get_object_by_id(qpdf, 17, 0);
+ qpdf_oh dict = qpdf_oh_get_dict(qpdf, stream);
+ assert(qpdf_oh_get_int_value_as_int(
+ qpdf, qpdf_oh_get_key(qpdf, dict, "/Length")) == 53);
+ /* Get raw data */
+ unsigned char *buf = 0;
+ size_t len = 0;
+ assert(qpdf_oh_get_stream_data(
+ qpdf, stream, qpdf_dl_none, 0, &buf, &len) == 0);
+ assert(len == 53);
+ assert(((int)buf[0] == 'x') && ((int)buf[1] == 0234));
+ free(buf);
+
+ /* Test whether filterable */
+ QPDF_BOOL filtered = QPDF_FALSE;
+ assert(qpdf_oh_get_stream_data(
+ qpdf, stream, qpdf_dl_all, &filtered, 0, 0) == 0);
+ assert(filtered == QPDF_TRUE);
+
+ /* Get filtered data */
+ assert(qpdf_oh_get_stream_data(
+ qpdf, stream, qpdf_dl_all, 0, &buf, &len) == 0);
+ assert(len == 47);
+ assert(memcmp((char const*)buf,
+ "BT /F1 15 Tf 72 720 Td (Original page 2) Tj ET\n",
+ len) == 0);
+
+ /* Get page data */
+ qpdf_oh page2 = qpdf_get_page_n(qpdf, 1); /* 0-based index */
+ unsigned char* buf2 = 0;
+ assert(qpdf_oh_get_page_content_data(qpdf, page2, &buf2, &len) == 0);
+ assert(len == 47);
+ assert(memcmp(buf, buf2, len) == 0);
+ free(buf);
+ free(buf2);
+
+ /* errors */
+ printf("page content on broken page\n");
+ qpdf_oh_replace_key(qpdf, page2, "/Contents", qpdf_oh_new_integer(qpdf, 3));
+ buf = 0;
+ qpdf_oh_get_page_content_data(qpdf, page2, &buf, &len);
+ assert(buf == 0);
+ report_errors();
+ printf("stream data for non stream\n");
+ qpdf_oh root = qpdf_get_root(qpdf);
+ assert(qpdf_oh_get_stream_data(qpdf, root, qpdf_dl_all, 0, 0, 0) != 0);
+ report_errors();
+}
+
+static void test39(char const* infile,
+ char const* password,
+ char const* outfile,
+ char const* xarg)
+{
+ /* This test expects 11-pages.pdf as file1 and minimal.pdf as xarg. */
+
+ /* Foreign object */
+
+ qpdf_data qpdf2 = qpdf_init();
+ assert(qpdf_read(qpdf, infile, password) == 0);
+ assert(qpdf_read(qpdf2, xarg, "") == 0);
+
+ qpdf_oh resources = qpdf_get_object_by_id(qpdf2, 3, 0);
+ qpdf_oh copy = qpdf_oh_copy_foreign_object(qpdf, qpdf2, resources);
+ qpdf_oh root = qpdf_get_root(qpdf);
+ qpdf_oh_replace_key(qpdf, root, "/Copy", copy);
+
+ qpdf_init_write(qpdf, outfile);
+ qpdf_set_static_ID(qpdf, QPDF_TRUE);
+ qpdf_set_qdf_mode(qpdf, QPDF_TRUE);
+ qpdf_set_suppress_original_object_IDs(qpdf, QPDF_TRUE);
+ qpdf_write(qpdf);
+ report_errors();
+ qpdf_cleanup(&qpdf2);
+}
+
+static void test40(char const* infile,
+ char const* password,
+ char const* outfile,
+ char const* xarg)
+{
+ /* This test expects minimal.pdf. */
+
+ /* New stream */
+
+ assert(qpdf_read(qpdf, infile, password) == 0);
+ qpdf_oh stream = qpdf_oh_new_stream(qpdf);
+ qpdf_oh_replace_stream_data(
+ qpdf, stream,
+ (unsigned char*)"12345\000abcde", 11, /* embedded null */
+ qpdf_oh_new_null(qpdf), qpdf_oh_new_null(qpdf));
+ qpdf_oh root = qpdf_get_root(qpdf);
+ qpdf_oh_replace_key(qpdf, root, "/Potato", stream);
+
+ qpdf_init_write(qpdf, outfile);
+ qpdf_set_static_ID(qpdf, QPDF_TRUE);
+ qpdf_set_qdf_mode(qpdf, QPDF_TRUE);
+ qpdf_set_suppress_original_object_IDs(qpdf, QPDF_TRUE);
+ qpdf_write(qpdf);
+ report_errors();
+}
+
int main(int argc, char* argv[])
{
char* p = 0;
@@ -1140,6 +1252,9 @@ int main(int argc, char* argv[])
(n == 35) ? test35 :
(n == 36) ? test36 :
(n == 37) ? test37 :
+ (n == 38) ? test38 :
+ (n == 39) ? test39 :
+ (n == 40) ? test40 :
0);
if (fn == 0)
diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov
index 315c1200..ebe0d2b3 100644
--- a/qpdf/qpdf.testcov
+++ b/qpdf/qpdf.testcov
@@ -617,3 +617,9 @@ qpdf-c called qpdf_push_inherited_attributes_to_page 0
qpdf-c called qpdf_add_page 0
qpdf-c called qpdf_add_page_at 0
qpdf-c called qpdf_remove_page 0
+qpdf-c called qpdf_oh_new_stream 0
+qpdf-c called qpdf_oh_copy_foreign_object 0
+qpdf-c stream data filtered set 1
+qpdf-c stream data buf set 1
+qpdf-c called qpdf_oh_get_page_content_data 0
+qpdf-c called qpdf_oh_replace_stream_data 0
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index 3825cfaa..a5d69a4b 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -4904,6 +4904,34 @@ $td->runtest("C pages cache",
show_ntests();
# ----------
+$td->notify("--- C API Stream Functions ---");
+$n_tests += 5;
+
+$td->runtest("C read streams",
+ {$td->COMMAND =>
+ "qpdf-ctest 38 11-pages.pdf '' ''"},
+ {$td->FILE => "c-get-stream.out", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+
+$td->runtest("C foreign object",
+ {$td->COMMAND =>
+ "qpdf-ctest 39 11-pages.pdf '' a.pdf minimal.pdf"},
+ {$td->STRING => "C test 39 done\n", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("check output",
+ {$td->FILE => 'a.pdf'},
+ {$td->FILE => 'c-foreign.pdf'});
+
+$td->runtest("C new stream",
+ {$td->COMMAND =>
+ "qpdf-ctest 40 minimal.pdf '' a.pdf"},
+ {$td->STRING => "C test 40 done\n", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("check output",
+ {$td->FILE => 'a.pdf'},
+ {$td->FILE => 'c-new-stream.pdf'});
+show_ntests();
+# ----------
$td->notify("--- Content Preservation Tests ---");
# $n_tests incremented below
diff --git a/qpdf/qtest/qpdf/c-foreign.pdf b/qpdf/qtest/qpdf/c-foreign.pdf
new file mode 100644
index 00000000..662d9238
--- /dev/null
+++ b/qpdf/qtest/qpdf/c-foreign.pdf
@@ -0,0 +1,573 @@
+%PDF-1.4
+%¿÷¢þ
+%QDF-1.0
+
+1 0 obj
+<<
+ /Copy 3 0 R
+ /Pages 4 0 R
+ /Type /Catalog
+>>
+endobj
+
+2 0 obj
+<<
+ /CreationDate (D:20120721200217)
+ /Producer (Apex PDFWriter)
+>>
+endobj
+
+3 0 obj
+<<
+ /Contents 5 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Resources <<
+ /Font <<
+ /F1 7 0 R
+ >>
+ /ProcSet 8 0 R
+ >>
+ /Type /Page
+>>
+endobj
+
+4 0 obj
+<<
+ /Count 11
+ /Kids [
+ 9 0 R
+ 10 0 R
+ 11 0 R
+ 12 0 R
+ 13 0 R
+ 14 0 R
+ 15 0 R
+ 16 0 R
+ 17 0 R
+ 18 0 R
+ 19 0 R
+ ]
+ /Type /Pages
+>>
+endobj
+
+5 0 obj
+<<
+ /Length 6 0 R
+>>
+stream
+BT
+ /F1 24 Tf
+ 72 720 Td
+ (Potato) Tj
+ET
+endstream
+endobj
+
+6 0 obj
+44
+endobj
+
+7 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+8 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+%% Page 1
+9 0 obj
+<<
+ /Contents 20 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 4 0 R
+ /Resources <<
+ /Font <<
+ /F1 22 0 R
+ >>
+ /ProcSet [
+ /PDF
+ /Text
+ ]
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Page 2
+10 0 obj
+<<
+ /Contents 23 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 4 0 R
+ /Resources <<
+ /Font <<
+ /F1 22 0 R
+ >>
+ /ProcSet [
+ /PDF
+ /Text
+ ]
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Page 3
+11 0 obj
+<<
+ /Contents 25 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 4 0 R
+ /Resources <<
+ /Font <<
+ /F1 22 0 R
+ >>
+ /ProcSet [
+ /PDF
+ /Text
+ ]
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Page 4
+12 0 obj
+<<
+ /Contents 27 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 4 0 R
+ /Resources <<
+ /Font <<
+ /F1 22 0 R
+ >>
+ /ProcSet [
+ /PDF
+ /Text
+ ]
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Page 5
+13 0 obj
+<<
+ /Contents 29 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 4 0 R
+ /Resources <<
+ /Font <<
+ /F1 22 0 R
+ >>
+ /ProcSet [
+ /PDF
+ /Text
+ ]
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Page 6
+14 0 obj
+<<
+ /Contents 31 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 4 0 R
+ /Resources <<
+ /Font <<
+ /F1 22 0 R
+ >>
+ /ProcSet [
+ /PDF
+ /Text
+ ]
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Page 7
+15 0 obj
+<<
+ /Contents 33 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 4 0 R
+ /Resources <<
+ /Font <<
+ /F1 22 0 R
+ >>
+ /ProcSet [
+ /PDF
+ /Text
+ ]
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Page 8
+16 0 obj
+<<
+ /Contents 35 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 4 0 R
+ /Resources <<
+ /Font <<
+ /F1 22 0 R
+ >>
+ /ProcSet [
+ /PDF
+ /Text
+ ]
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Page 9
+17 0 obj
+<<
+ /Contents 37 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 4 0 R
+ /Resources <<
+ /Font <<
+ /F1 22 0 R
+ >>
+ /ProcSet [
+ /PDF
+ /Text
+ ]
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Page 10
+18 0 obj
+<<
+ /Contents 39 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 4 0 R
+ /Resources <<
+ /Font <<
+ /F1 22 0 R
+ >>
+ /ProcSet [
+ /PDF
+ /Text
+ ]
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Page 11
+19 0 obj
+<<
+ /Contents 41 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 4 0 R
+ /Resources <<
+ /Font <<
+ /F1 22 0 R
+ >>
+ /ProcSet [
+ /PDF
+ /Text
+ ]
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Contents for page 1
+20 0 obj
+<<
+ /Length 21 0 R
+>>
+stream
+BT /F1 15 Tf 72 720 Td (Original page 1) Tj ET
+endstream
+endobj
+
+21 0 obj
+47
+endobj
+
+22 0 obj
+<<
+ /BaseFont /Times-Roman
+ /Encoding /WinAnsiEncoding
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+%% Contents for page 2
+23 0 obj
+<<
+ /Length 24 0 R
+>>
+stream
+BT /F1 15 Tf 72 720 Td (Original page 2) Tj ET
+endstream
+endobj
+
+24 0 obj
+47
+endobj
+
+%% Contents for page 3
+25 0 obj
+<<
+ /Length 26 0 R
+>>
+stream
+BT /F1 15 Tf 72 720 Td (Original page 3) Tj ET
+endstream
+endobj
+
+26 0 obj
+47
+endobj
+
+%% Contents for page 4
+27 0 obj
+<<
+ /Length 28 0 R
+>>
+stream
+BT /F1 15 Tf 72 720 Td (Original page 4) Tj ET
+endstream
+endobj
+
+28 0 obj
+47
+endobj
+
+%% Contents for page 5
+29 0 obj
+<<
+ /Length 30 0 R
+>>
+stream
+BT /F1 15 Tf 72 720 Td (Original page 5) Tj ET
+endstream
+endobj
+
+30 0 obj
+47
+endobj
+
+%% Contents for page 6
+31 0 obj
+<<
+ /Length 32 0 R
+>>
+stream
+BT /F1 15 Tf 72 720 Td (Original page 6) Tj ET
+endstream
+endobj
+
+32 0 obj
+47
+endobj
+
+%% Contents for page 7
+33 0 obj
+<<
+ /Length 34 0 R
+>>
+stream
+BT /F1 15 Tf 72 720 Td (Original page 7) Tj ET
+endstream
+endobj
+
+34 0 obj
+47
+endobj
+
+%% Contents for page 8
+35 0 obj
+<<
+ /Length 36 0 R
+>>
+stream
+BT /F1 15 Tf 72 720 Td (Original page 8) Tj ET
+endstream
+endobj
+
+36 0 obj
+47
+endobj
+
+%% Contents for page 9
+37 0 obj
+<<
+ /Length 38 0 R
+>>
+stream
+BT /F1 15 Tf 72 720 Td (Original page 9) Tj ET
+endstream
+endobj
+
+38 0 obj
+47
+endobj
+
+%% Contents for page 10
+39 0 obj
+<<
+ /Length 40 0 R
+>>
+stream
+BT /F1 15 Tf 72 720 Td (Original page 10) Tj ET
+endstream
+endobj
+
+40 0 obj
+48
+endobj
+
+%% Contents for page 11
+41 0 obj
+<<
+ /Length 42 0 R
+>>
+stream
+BT /F1 15 Tf 72 720 Td (Original page 11) Tj ET
+endstream
+endobj
+
+42 0 obj
+48
+endobj
+
+xref
+0 43
+0000000000 65535 f
+0000000025 00000 n
+0000000093 00000 n
+0000000179 00000 n
+0000000355 00000 n
+0000000538 00000 n
+0000000637 00000 n
+0000000656 00000 n
+0000000774 00000 n
+0000000819 00000 n
+0000001048 00000 n
+0000001278 00000 n
+0000001508 00000 n
+0000001738 00000 n
+0000001968 00000 n
+0000002198 00000 n
+0000002428 00000 n
+0000002658 00000 n
+0000002889 00000 n
+0000003120 00000 n
+0000003363 00000 n
+0000003467 00000 n
+0000003487 00000 n
+0000003619 00000 n
+0000003723 00000 n
+0000003766 00000 n
+0000003870 00000 n
+0000003913 00000 n
+0000004017 00000 n
+0000004060 00000 n
+0000004164 00000 n
+0000004207 00000 n
+0000004311 00000 n
+0000004354 00000 n
+0000004458 00000 n
+0000004501 00000 n
+0000004605 00000 n
+0000004648 00000 n
+0000004752 00000 n
+0000004796 00000 n
+0000004901 00000 n
+0000004945 00000 n
+0000005050 00000 n
+trailer <<
+ /Info 2 0 R
+ /Root 1 0 R
+ /Size 43
+ /ID [<e032a88c7a987db6ca3abee555506ccc><31415926535897932384626433832795>]
+>>
+startxref
+5070
+%%EOF
diff --git a/qpdf/qtest/qpdf/c-get-stream.out b/qpdf/qtest/qpdf/c-get-stream.out
new file mode 100644
index 00000000..e78bd4eb
--- /dev/null
+++ b/qpdf/qtest/qpdf/c-get-stream.out
@@ -0,0 +1,13 @@
+page content on broken page
+error: page object 5 0: object is supposed to be a stream or an array of streams but is neither
+ code: 5
+ file:
+ pos : 0
+ text: object is supposed to be a stream or an array of streams but is neither
+stream data for non stream
+error: operation for stream attempted on object of type dictionary
+ code: 2
+ file:
+ pos : 0
+ text: operation for stream attempted on object of type dictionary
+C test 38 done
diff --git a/qpdf/qtest/qpdf/c-new-stream.pdf b/qpdf/qtest/qpdf/c-new-stream.pdf
new file mode 100644
index 00000000..95ff378f
--- /dev/null
+++ b/qpdf/qtest/qpdf/c-new-stream.pdf
Binary files differ