aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2022-09-08 21:57:33 +0200
committerJay Berkenbilt <ejb@ql.org>2022-09-09 12:19:09 +0200
commitf1a2d3160a1dbaf735cce597d0f6f40e76f7f223 (patch)
tree41721144a2d94d962f3de9b48ad58d18bfd838e0
parent66f1fd2ad9f2a4e3172bd07f6d71bb1321b0dce0 (diff)
downloadqpdf-f1a2d3160a1dbaf735cce597d0f6f40e76f7f223.tar.zst
Add JSON v2 support to C API
-rw-r--r--ChangeLog6
-rw-r--r--TODO6
-rw-r--r--include/qpdf/qpdf-c.h75
-rw-r--r--libqpdf/qpdf-c.cc88
-rw-r--r--manual/release-notes.rst4
-rw-r--r--qpdf/qpdf-ctest.c119
-rw-r--r--qpdf/qtest/qpdf-json.test53
-rw-r--r--qpdf/qtest/qpdf/minimal-update.json17
-rw-r--r--qpdf/qtest/qpdf/minimal.json74
-rw-r--r--qpdf/qtest/qpdf/qpdf-ctest-42-43.pdfbin0 -> 799 bytes
-rw-r--r--qpdf/qtest/qpdf/qpdf-ctest-44-45.pdfbin0 -> 798 bytes
-rw-r--r--qpdf/qtest/qpdf/qpdf-ctest-46.json74
-rw-r--r--qpdf/qtest/qpdf/qpdf-ctest-47-45
-rw-r--r--qpdf/qtest/qpdf/qpdf-ctest-47.json25
14 files changed, 535 insertions, 11 deletions
diff --git a/ChangeLog b/ChangeLog
index 751b1aed..b7e793a8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
2022-09-08 Jay Berkenbilt <ejb@ql.org>
+ * Added new functions to the C API to support qpdf JSON:
+ qpdf_create_from_json_file, qpdf_create_from_json_data,
+ qpdf_update_from_json_file, qpdf_update_from_json_data, and
+ qpdf_write_json. Examples can be found in qpdf-ctest.c (in the
+ source tree), tests 42 through 47.
+
* Add QPDFObjectHandle::isDestroyed() to test whether an indirect
object was from a QPDF that has been destroyed.
diff --git a/TODO b/TODO
index 0ed0a24c..5f3cd99a 100644
--- a/TODO
+++ b/TODO
@@ -8,12 +8,6 @@ Always
Next
====
-Before Release:
-
-* Support json v2 in the C API. At a minimum, write_json,
- create_from_json, and update_from_json need to be there and should
- take the same kinds of functions as the C API for logger.
-
Pending changes:
* Consider also exposing a way to set a new logger and to get the
diff --git a/include/qpdf/qpdf-c.h b/include/qpdf/qpdf-c.h
index 4b40f068..434c4ede 100644
--- a/include/qpdf/qpdf-c.h
+++ b/include/qpdf/qpdf-c.h
@@ -257,8 +257,6 @@ extern "C" {
QPDF_DLL
QPDF_ERROR_CODE qpdf_check_pdf(qpdf_data qpdf);
- /* READ FUNCTIONS */
-
/* READ PARAMETER FUNCTIONS -- must be called before qpdf_read */
QPDF_DLL
@@ -267,6 +265,10 @@ extern "C" {
QPDF_DLL
void qpdf_set_attempt_recovery(qpdf_data qpdf, QPDF_BOOL value);
+ /* PROCESS FUNCTIONS */
+
+ /* This functions process a PDF or JSON input source. */
+
/* Calling qpdf_read causes processFile to be called in the C++
* API. Basic parsing is performed, but data from the file is
* only read as needed. For files without passwords, pass a null
@@ -297,8 +299,40 @@ extern "C" {
QPDF_DLL
QPDF_ERROR_CODE qpdf_empty_pdf(qpdf_data qpdf);
- /* Read functions below must be called after qpdf_read or
- * qpdf_read_memory. */
+ /* Create a PDF from a JSON file. This calls createFromJSON in the
+ * C++ API.
+ */
+ QPDF_DLL
+ QPDF_ERROR_CODE
+ qpdf_create_from_json_file(qpdf_data qpdf, char const* filename);
+
+ /* Create a PDF from JSON data in a null-terminated string. This
+ * calls createFromJSON in the C++ API.
+ */
+ QPDF_DLL
+ QPDF_ERROR_CODE
+ qpdf_create_from_json_data(
+ qpdf_data qpdf, char const* buffer, unsigned long long size);
+
+ /* JSON UPDATE FUNCTIONS */
+
+ /* Update a QPDF object from a JSON file or buffer. These
+ * functions call updateFromJSON. One of the other processing
+ * functions has to be called first so that the QPDF object is
+ * initialized with PDF data.
+ */
+ QPDF_DLL
+ QPDF_ERROR_CODE
+ qpdf_update_from_json_file(qpdf_data qpdf, char const* filename);
+ QPDF_DLL
+ QPDF_ERROR_CODE
+ qpdf_update_from_json_data(
+ qpdf_data qpdf, char const* buffer, unsigned long long size);
+
+ /* READ FUNCTIONS */
+
+ /* Read functions below must be called after qpdf_read or any of
+ * the other functions that process a PDF. */
/*
* NOTE: Functions that return char* are returning a pointer to an
@@ -371,6 +405,39 @@ extern "C" {
QPDF_DLL
QPDF_BOOL qpdf_allow_modify_all(qpdf_data qpdf);
+ /* JSON WRITE FUNCTIONS */
+
+ /* This function serializes the PDF to JSON. This calls writeJSON
+ * from the C++ API.
+ *
+ * - version: the JSON version, currently must be 2
+ * - fn: a function that will be called with blocks of JSON data;
+ * will be called with data, a length, and the value of the
+ * udata parameter to this function
+ * - udata: will be passed as the third argument to fn with each
+ * call; use this for your own tracking or pass a null pointer
+ * if you don't need it
+ * - For decode_level, json_stream_data, file_prefix, and
+ * wanted_objects, see comments in QPDF.hh. For this API,
+ * wanted_objects should be a null-terminated array of
+ * null-terminated strings. Pass a null pointer if you want all
+ * objects.
+ */
+
+ /* Function should return 0 on success. */
+ typedef int (*qpdf_write_fn_t)(char const* data, size_t len, void* udata);
+
+ QPDF_DLL
+ QPDF_ERROR_CODE qpdf_write_json(
+ qpdf_data qpdf,
+ int version,
+ qpdf_write_fn_t fn,
+ void* udata,
+ enum qpdf_stream_decode_level_e decode_level,
+ enum qpdf_json_stream_data_e json_stream_data,
+ char const* file_prefix,
+ char const* const* wanted_objects);
+
/* WRITE FUNCTIONS */
/* Set up for writing. No writing is actually performed until the
diff --git a/libqpdf/qpdf-c.cc b/libqpdf/qpdf-c.cc
index b5bae727..a8ae8102 100644
--- a/libqpdf/qpdf-c.cc
+++ b/libqpdf/qpdf-c.cc
@@ -2,8 +2,10 @@
#include <qpdf/QPDF.hh>
+#include <qpdf/BufferInputSource.hh>
#include <qpdf/Pl_Buffer.hh>
#include <qpdf/Pl_Discard.hh>
+#include <qpdf/Pl_Function.hh>
#include <qpdf/QIntC.hh>
#include <qpdf/QPDFExc.hh>
#include <qpdf/QPDFLogger.hh>
@@ -2029,3 +2031,89 @@ qpdf_remove_page(qpdf_data qpdf, qpdf_oh page)
auto p = qpdf_oh_item_internal(qpdf, page);
return trap_errors(qpdf, [&p](qpdf_data q) { q->qpdf->removePage(p); });
}
+
+QPDF_ERROR_CODE
+qpdf_create_from_json_file(qpdf_data qpdf, char const* filename)
+{
+ QPDF_ERROR_CODE status = QPDF_SUCCESS;
+ qpdf->filename = filename;
+ status = trap_errors(
+ qpdf, [](qpdf_data q) { q->qpdf->createFromJSON(q->filename); });
+ return status;
+}
+
+QPDF_ERROR_CODE
+qpdf_create_from_json_data(
+ qpdf_data qpdf, char const* buffer, unsigned long long size)
+{
+ QPDF_ERROR_CODE status = QPDF_SUCCESS;
+ qpdf->filename = "json buffer";
+ qpdf->buffer = buffer;
+ qpdf->size = size;
+ auto b =
+ new Buffer(QUtil::unsigned_char_pointer(buffer), QIntC::to_size(size));
+ auto is = std::make_shared<BufferInputSource>(qpdf->filename, b, true);
+ status =
+ trap_errors(qpdf, [&is](qpdf_data q) { q->qpdf->createFromJSON(is); });
+ return status;
+}
+
+QPDF_ERROR_CODE
+qpdf_update_from_json_file(qpdf_data qpdf, char const* filename)
+{
+ QPDF_ERROR_CODE status = QPDF_SUCCESS;
+ status = trap_errors(
+ qpdf, [filename](qpdf_data q) { q->qpdf->updateFromJSON(filename); });
+ return status;
+}
+
+QPDF_ERROR_CODE
+qpdf_update_from_json_data(
+ qpdf_data qpdf, char const* buffer, unsigned long long size)
+{
+ QPDF_ERROR_CODE status = QPDF_SUCCESS;
+ auto b =
+ new Buffer(QUtil::unsigned_char_pointer(buffer), QIntC::to_size(size));
+ auto is = std::make_shared<BufferInputSource>(qpdf->filename, b, true);
+ status =
+ trap_errors(qpdf, [&is](qpdf_data q) { q->qpdf->updateFromJSON(is); });
+ return status;
+}
+
+QPDF_ERROR_CODE
+qpdf_write_json(
+ qpdf_data qpdf,
+ int version,
+ qpdf_write_fn_t fn,
+ void* udata,
+ enum qpdf_stream_decode_level_e decode_level,
+ enum qpdf_json_stream_data_e json_stream_data,
+ char const* file_prefix,
+ char const* const* wanted_objects)
+{
+ QPDF_ERROR_CODE status = QPDF_SUCCESS;
+ auto p = std::make_shared<Pl_Function>("write_json", nullptr, fn, udata);
+ std::set<std::string> wanted_objects_set;
+ if (wanted_objects) {
+ for (auto i = wanted_objects; *i; ++i) {
+ wanted_objects_set.insert(*i);
+ }
+ }
+ status = trap_errors(
+ qpdf,
+ [version,
+ p,
+ decode_level,
+ json_stream_data,
+ file_prefix,
+ &wanted_objects_set](qpdf_data q) {
+ q->qpdf->writeJSON(
+ version,
+ p.get(),
+ decode_level,
+ json_stream_data,
+ file_prefix,
+ wanted_objects_set);
+ });
+ return status;
+}
diff --git a/manual/release-notes.rst b/manual/release-notes.rst
index bf6a8301..e53d3bfb 100644
--- a/manual/release-notes.rst
+++ b/manual/release-notes.rst
@@ -40,6 +40,10 @@ For a detailed list of changes, please see the file
- New C++ API calls: ``QPDF::writeJSON``,
``QPDF::createFromJSON``, ``QPDF::updateFromJSON``
+ - New C API calls: ``qpdf_create_from_json_file``,
+ ``qpdf_create_from_json_data``, ``qpdf_update_from_json_file``,
+ ``qpdf_update_from_json_data``, and ``qpdf_write_json``.
+
- Complete documentation can be found at :ref:`json`. A
comprehensive list of changes from version 1 to version 2 can be
found at :ref:`json-v2-changes`.
diff --git a/qpdf/qpdf-ctest.c b/qpdf/qpdf-ctest.c
index 07d6ca9e..b562ed8b 100644
--- a/qpdf/qpdf-ctest.c
+++ b/qpdf/qpdf-ctest.c
@@ -133,6 +133,13 @@ count_progress(int percent, void* data)
++(*(int*)data);
}
+static int
+write_to_file(char const* data, size_t size, void* udata)
+{
+ FILE* f = (FILE*)udata;
+ return fwrite(data, 1, size, f) != size;
+}
+
static void
test01(
char const* infile,
@@ -1458,7 +1465,7 @@ test41(
char const* outfile,
char const* xarg)
{
- /* Empty PDF -- infile is ignored*/
+ /* Empty PDF -- infile is ignored */
assert(qpdf_empty_pdf(qpdf) == 0);
qpdf_init_write(qpdf, outfile);
qpdf_set_static_ID(qpdf, QPDF_TRUE);
@@ -1466,6 +1473,110 @@ test41(
report_errors();
}
+static void
+test42(
+ char const* infile,
+ char const* password,
+ char const* outfile,
+ char const* xarg)
+{
+ assert(qpdf_create_from_json_file(qpdf, infile) == QPDF_SUCCESS);
+ qpdf_init_write(qpdf, outfile);
+ qpdf_set_static_ID(qpdf, QPDF_TRUE);
+ qpdf_write(qpdf);
+ report_errors();
+}
+
+static void
+test43(
+ char const* infile,
+ char const* password,
+ char const* outfile,
+ char const* xarg)
+{
+ char* buf = NULL;
+ unsigned long size = 0;
+ read_file_into_memory(infile, &buf, &size);
+ assert(qpdf_create_from_json_data(qpdf, buf, size) == QPDF_SUCCESS);
+ qpdf_init_write(qpdf, outfile);
+ qpdf_set_static_ID(qpdf, QPDF_TRUE);
+ qpdf_write(qpdf);
+ report_errors();
+ free(buf);
+}
+
+static void
+test44(
+ char const* infile,
+ char const* password,
+ char const* outfile,
+ char const* xarg)
+{
+ assert(qpdf_read(qpdf, infile, password) == 0);
+ assert(qpdf_update_from_json_file(qpdf, xarg) == QPDF_SUCCESS);
+ qpdf_init_write(qpdf, outfile);
+ qpdf_set_static_ID(qpdf, QPDF_TRUE);
+ qpdf_write(qpdf);
+ report_errors();
+}
+
+static void
+test45(
+ char const* infile,
+ char const* password,
+ char const* outfile,
+ char const* xarg)
+{
+ char* buf = NULL;
+ unsigned long size = 0;
+ read_file_into_memory(xarg, &buf, &size);
+ assert(qpdf_read(qpdf, infile, password) == 0);
+ assert(qpdf_update_from_json_data(qpdf, buf, size) == QPDF_SUCCESS);
+ qpdf_init_write(qpdf, outfile);
+ qpdf_set_static_ID(qpdf, QPDF_TRUE);
+ qpdf_write(qpdf);
+ report_errors();
+ free(buf);
+}
+
+static void
+test46(
+ char const* infile,
+ char const* password,
+ char const* outfile,
+ char const* xarg)
+{
+ FILE* f = safe_fopen(outfile, "wb");
+ assert(qpdf_read(qpdf, infile, password) == 0);
+ qpdf_write_json(
+ qpdf, 2, write_to_file, f, qpdf_dl_none, qpdf_sj_inline, "", NULL);
+ fclose(f);
+ report_errors();
+}
+
+static void
+test47(
+ char const* infile,
+ char const* password,
+ char const* outfile,
+ char const* xarg)
+{
+ FILE* f = safe_fopen(outfile, "wb");
+ assert(qpdf_read(qpdf, infile, password) == 0);
+ char const* wanted_objects[] = {"obj:4 0 R", "trailer", NULL};
+ qpdf_write_json(
+ qpdf,
+ 2,
+ write_to_file,
+ f,
+ qpdf_dl_specialized,
+ qpdf_sj_file,
+ xarg,
+ wanted_objects);
+ fclose(f);
+ report_errors();
+}
+
int
main(int argc, char* argv[])
{
@@ -1542,6 +1653,12 @@ main(int argc, char* argv[])
: (n == 39) ? test39
: (n == 40) ? test40
: (n == 41) ? test41
+ : (n == 42) ? test42
+ : (n == 43) ? test43
+ : (n == 44) ? test44
+ : (n == 45) ? test45
+ : (n == 46) ? test46
+ : (n == 47) ? test47
: 0);
if (fn == 0) {
diff --git a/qpdf/qtest/qpdf-json.test b/qpdf/qtest/qpdf-json.test
index 553a84e2..2867f8a7 100644
--- a/qpdf/qtest/qpdf-json.test
+++ b/qpdf/qtest/qpdf-json.test
@@ -288,5 +288,58 @@ $td->runtest("simple version of writeJSON",
{$td->FILE => "minimal-write-json.json", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
+$n_tests += 13;
+$td->runtest("C API create from json file",
+ {$td->COMMAND => "qpdf-ctest 42 minimal.json '' a.pdf"},
+ {$td->STRING => "C test 42 done\n", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("check C API create from file",
+ {$td->FILE => "a.pdf"},
+ {$td->FILE => "qpdf-ctest-42-43.pdf"});
+$td->runtest("C API create from json buffer",
+ {$td->COMMAND => "qpdf-ctest 43 minimal.json '' a.pdf"},
+ {$td->STRING => "C test 43 done\n", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("check C API create from buffer",
+ {$td->FILE => "a.pdf"},
+ {$td->FILE => "qpdf-ctest-42-43.pdf"});
+$td->runtest("C API update from json file",
+ {$td->COMMAND =>
+ "qpdf-ctest 44 minimal.pdf '' a.pdf minimal-update.json"},
+ {$td->STRING => "C test 44 done\n", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("check C API update from file",
+ {$td->FILE => "a.pdf"},
+ {$td->FILE => "qpdf-ctest-44-45.pdf"});
+$td->runtest("C API update from json buffer",
+ {$td->COMMAND =>
+ "qpdf-ctest 45 minimal.pdf '' a.pdf minimal-update.json"},
+ {$td->STRING => "C test 45 done\n", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("check C API update from buffer",
+ {$td->FILE => "a.pdf"},
+ {$td->FILE => "qpdf-ctest-44-45.pdf"});
+$td->runtest("C API write to JSON 1",
+ {$td->COMMAND =>
+ "qpdf-ctest 46 minimal.pdf '' a.json"},
+ {$td->STRING => "C test 46 done\n", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("check C API write to JSON 1",
+ {$td->FILE => "a.json"},
+ {$td->FILE => "qpdf-ctest-46.json"},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("C API write to JSON 2",
+ {$td->COMMAND =>
+ "qpdf-ctest 47 minimal.pdf '' a.json auto"},
+ {$td->STRING => "C test 47 done\n", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("check C API write to JSON 2",
+ {$td->FILE => "a.json"},
+ {$td->FILE => "qpdf-ctest-47.json"},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("check C API write to JSON stream",
+ {$td->FILE => "auto-4"},
+ {$td->FILE => "qpdf-ctest-47-4"});
+
cleanup();
$td->report($n_tests);
diff --git a/qpdf/qtest/qpdf/minimal-update.json b/qpdf/qtest/qpdf/minimal-update.json
new file mode 100644
index 00000000..1bd9abc0
--- /dev/null
+++ b/qpdf/qtest/qpdf/minimal-update.json
@@ -0,0 +1,17 @@
+{
+ "qpdf": [
+ {
+ "jsonversion": 2,
+ "pushedinheritedpageresources": false,
+ "calledgetallpages": false
+ },
+ {
+ "obj:4 0 R": {
+ "stream": {
+ "data": "QlQKICAvRjEgMjQgVGYKICA3MiA3MjAgVGQKICAoU2FsYWQpIFRqCkVUCg==",
+ "dict": {}
+ }
+ }
+ }
+ ]
+}
diff --git a/qpdf/qtest/qpdf/minimal.json b/qpdf/qtest/qpdf/minimal.json
new file mode 100644
index 00000000..ae4a9faa
--- /dev/null
+++ b/qpdf/qtest/qpdf/minimal.json
@@ -0,0 +1,74 @@
+{
+ "qpdf": [
+ {
+ "jsonversion": 2,
+ "pdfversion": "1.3",
+ "pushedinheritedpageresources": false,
+ "calledgetallpages": false,
+ "maxobjectid": 6
+ },
+ {
+ "obj:1 0 R": {
+ "value": {
+ "/Pages": "2 0 R",
+ "/Type": "/Catalog"
+ }
+ },
+ "obj:2 0 R": {
+ "value": {
+ "/Count": 1,
+ "/Kids": [
+ "3 0 R"
+ ],
+ "/Type": "/Pages"
+ }
+ },
+ "obj:3 0 R": {
+ "value": {
+ "/Contents": "4 0 R",
+ "/MediaBox": [
+ 0,
+ 0,
+ 612,
+ 792
+ ],
+ "/Parent": "2 0 R",
+ "/Resources": {
+ "/Font": {
+ "/F1": "6 0 R"
+ },
+ "/ProcSet": "5 0 R"
+ },
+ "/Type": "/Page"
+ }
+ },
+ "obj:4 0 R": {
+ "stream": {
+ "data": "QlQKICAvRjEgMjQgVGYKICA3MiA3MjAgVGQKICAoUG90YXRvKSBUagpFVAo=",
+ "dict": {}
+ }
+ },
+ "obj:5 0 R": {
+ "value": [
+ "/PDF",
+ "/Text"
+ ]
+ },
+ "obj:6 0 R": {
+ "value": {
+ "/BaseFont": "/Helvetica",
+ "/Encoding": "/WinAnsiEncoding",
+ "/Name": "/F1",
+ "/Subtype": "/Type1",
+ "/Type": "/Font"
+ }
+ },
+ "trailer": {
+ "value": {
+ "/Root": "1 0 R",
+ "/Size": 7
+ }
+ }
+ }
+ ]
+}
diff --git a/qpdf/qtest/qpdf/qpdf-ctest-42-43.pdf b/qpdf/qtest/qpdf/qpdf-ctest-42-43.pdf
new file mode 100644
index 00000000..b8c692ed
--- /dev/null
+++ b/qpdf/qtest/qpdf/qpdf-ctest-42-43.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/qpdf-ctest-44-45.pdf b/qpdf/qtest/qpdf/qpdf-ctest-44-45.pdf
new file mode 100644
index 00000000..f7f56fd9
--- /dev/null
+++ b/qpdf/qtest/qpdf/qpdf-ctest-44-45.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/qpdf-ctest-46.json b/qpdf/qtest/qpdf/qpdf-ctest-46.json
new file mode 100644
index 00000000..ae4a9faa
--- /dev/null
+++ b/qpdf/qtest/qpdf/qpdf-ctest-46.json
@@ -0,0 +1,74 @@
+{
+ "qpdf": [
+ {
+ "jsonversion": 2,
+ "pdfversion": "1.3",
+ "pushedinheritedpageresources": false,
+ "calledgetallpages": false,
+ "maxobjectid": 6
+ },
+ {
+ "obj:1 0 R": {
+ "value": {
+ "/Pages": "2 0 R",
+ "/Type": "/Catalog"
+ }
+ },
+ "obj:2 0 R": {
+ "value": {
+ "/Count": 1,
+ "/Kids": [
+ "3 0 R"
+ ],
+ "/Type": "/Pages"
+ }
+ },
+ "obj:3 0 R": {
+ "value": {
+ "/Contents": "4 0 R",
+ "/MediaBox": [
+ 0,
+ 0,
+ 612,
+ 792
+ ],
+ "/Parent": "2 0 R",
+ "/Resources": {
+ "/Font": {
+ "/F1": "6 0 R"
+ },
+ "/ProcSet": "5 0 R"
+ },
+ "/Type": "/Page"
+ }
+ },
+ "obj:4 0 R": {
+ "stream": {
+ "data": "QlQKICAvRjEgMjQgVGYKICA3MiA3MjAgVGQKICAoUG90YXRvKSBUagpFVAo=",
+ "dict": {}
+ }
+ },
+ "obj:5 0 R": {
+ "value": [
+ "/PDF",
+ "/Text"
+ ]
+ },
+ "obj:6 0 R": {
+ "value": {
+ "/BaseFont": "/Helvetica",
+ "/Encoding": "/WinAnsiEncoding",
+ "/Name": "/F1",
+ "/Subtype": "/Type1",
+ "/Type": "/Font"
+ }
+ },
+ "trailer": {
+ "value": {
+ "/Root": "1 0 R",
+ "/Size": 7
+ }
+ }
+ }
+ ]
+}
diff --git a/qpdf/qtest/qpdf/qpdf-ctest-47-4 b/qpdf/qtest/qpdf/qpdf-ctest-47-4
new file mode 100644
index 00000000..b74381fa
--- /dev/null
+++ b/qpdf/qtest/qpdf/qpdf-ctest-47-4
@@ -0,0 +1,5 @@
+BT
+ /F1 24 Tf
+ 72 720 Td
+ (Potato) Tj
+ET
diff --git a/qpdf/qtest/qpdf/qpdf-ctest-47.json b/qpdf/qtest/qpdf/qpdf-ctest-47.json
new file mode 100644
index 00000000..44f8b500
--- /dev/null
+++ b/qpdf/qtest/qpdf/qpdf-ctest-47.json
@@ -0,0 +1,25 @@
+{
+ "qpdf": [
+ {
+ "jsonversion": 2,
+ "pdfversion": "1.3",
+ "pushedinheritedpageresources": false,
+ "calledgetallpages": false,
+ "maxobjectid": 6
+ },
+ {
+ "obj:4 0 R": {
+ "stream": {
+ "datafile": "auto-4",
+ "dict": {}
+ }
+ },
+ "trailer": {
+ "value": {
+ "/Root": "1 0 R",
+ "/Size": 7
+ }
+ }
+ }
+ ]
+}