Commit f1a2d3160a1dbaf735cce597d0f6f40e76f7f223

Authored by Jay Berkenbilt
1 parent 66f1fd2a

Add JSON v2 support to C API

ChangeLog
1 1 2022-09-08 Jay Berkenbilt <ejb@ql.org>
2 2  
  3 + * Added new functions to the C API to support qpdf JSON:
  4 + qpdf_create_from_json_file, qpdf_create_from_json_data,
  5 + qpdf_update_from_json_file, qpdf_update_from_json_data, and
  6 + qpdf_write_json. Examples can be found in qpdf-ctest.c (in the
  7 + source tree), tests 42 through 47.
  8 +
3 9 * Add QPDFObjectHandle::isDestroyed() to test whether an indirect
4 10 object was from a QPDF that has been destroyed.
5 11  
... ...
... ... @@ -8,12 +8,6 @@ Always
8 8 Next
9 9 ====
10 10  
11   -Before Release:
12   -
13   -* Support json v2 in the C API. At a minimum, write_json,
14   - create_from_json, and update_from_json need to be there and should
15   - take the same kinds of functions as the C API for logger.
16   -
17 11 Pending changes:
18 12  
19 13 * Consider also exposing a way to set a new logger and to get the
... ...
include/qpdf/qpdf-c.h
... ... @@ -257,8 +257,6 @@ extern &quot;C&quot; {
257 257 QPDF_DLL
258 258 QPDF_ERROR_CODE qpdf_check_pdf(qpdf_data qpdf);
259 259  
260   - /* READ FUNCTIONS */
261   -
262 260 /* READ PARAMETER FUNCTIONS -- must be called before qpdf_read */
263 261  
264 262 QPDF_DLL
... ... @@ -267,6 +265,10 @@ extern &quot;C&quot; {
267 265 QPDF_DLL
268 266 void qpdf_set_attempt_recovery(qpdf_data qpdf, QPDF_BOOL value);
269 267  
  268 + /* PROCESS FUNCTIONS */
  269 +
  270 + /* This functions process a PDF or JSON input source. */
  271 +
270 272 /* Calling qpdf_read causes processFile to be called in the C++
271 273 * API. Basic parsing is performed, but data from the file is
272 274 * only read as needed. For files without passwords, pass a null
... ... @@ -297,8 +299,40 @@ extern &quot;C&quot; {
297 299 QPDF_DLL
298 300 QPDF_ERROR_CODE qpdf_empty_pdf(qpdf_data qpdf);
299 301  
300   - /* Read functions below must be called after qpdf_read or
301   - * qpdf_read_memory. */
  302 + /* Create a PDF from a JSON file. This calls createFromJSON in the
  303 + * C++ API.
  304 + */
  305 + QPDF_DLL
  306 + QPDF_ERROR_CODE
  307 + qpdf_create_from_json_file(qpdf_data qpdf, char const* filename);
  308 +
  309 + /* Create a PDF from JSON data in a null-terminated string. This
  310 + * calls createFromJSON in the C++ API.
  311 + */
  312 + QPDF_DLL
  313 + QPDF_ERROR_CODE
  314 + qpdf_create_from_json_data(
  315 + qpdf_data qpdf, char const* buffer, unsigned long long size);
  316 +
  317 + /* JSON UPDATE FUNCTIONS */
  318 +
  319 + /* Update a QPDF object from a JSON file or buffer. These
  320 + * functions call updateFromJSON. One of the other processing
  321 + * functions has to be called first so that the QPDF object is
  322 + * initialized with PDF data.
  323 + */
  324 + QPDF_DLL
  325 + QPDF_ERROR_CODE
  326 + qpdf_update_from_json_file(qpdf_data qpdf, char const* filename);
  327 + QPDF_DLL
  328 + QPDF_ERROR_CODE
  329 + qpdf_update_from_json_data(
  330 + qpdf_data qpdf, char const* buffer, unsigned long long size);
  331 +
  332 + /* READ FUNCTIONS */
  333 +
  334 + /* Read functions below must be called after qpdf_read or any of
  335 + * the other functions that process a PDF. */
302 336  
303 337 /*
304 338 * NOTE: Functions that return char* are returning a pointer to an
... ... @@ -371,6 +405,39 @@ extern &quot;C&quot; {
371 405 QPDF_DLL
372 406 QPDF_BOOL qpdf_allow_modify_all(qpdf_data qpdf);
373 407  
  408 + /* JSON WRITE FUNCTIONS */
  409 +
  410 + /* This function serializes the PDF to JSON. This calls writeJSON
  411 + * from the C++ API.
  412 + *
  413 + * - version: the JSON version, currently must be 2
  414 + * - fn: a function that will be called with blocks of JSON data;
  415 + * will be called with data, a length, and the value of the
  416 + * udata parameter to this function
  417 + * - udata: will be passed as the third argument to fn with each
  418 + * call; use this for your own tracking or pass a null pointer
  419 + * if you don't need it
  420 + * - For decode_level, json_stream_data, file_prefix, and
  421 + * wanted_objects, see comments in QPDF.hh. For this API,
  422 + * wanted_objects should be a null-terminated array of
  423 + * null-terminated strings. Pass a null pointer if you want all
  424 + * objects.
  425 + */
  426 +
  427 + /* Function should return 0 on success. */
  428 + typedef int (*qpdf_write_fn_t)(char const* data, size_t len, void* udata);
  429 +
  430 + QPDF_DLL
  431 + QPDF_ERROR_CODE qpdf_write_json(
  432 + qpdf_data qpdf,
  433 + int version,
  434 + qpdf_write_fn_t fn,
  435 + void* udata,
  436 + enum qpdf_stream_decode_level_e decode_level,
  437 + enum qpdf_json_stream_data_e json_stream_data,
  438 + char const* file_prefix,
  439 + char const* const* wanted_objects);
  440 +
374 441 /* WRITE FUNCTIONS */
375 442  
376 443 /* Set up for writing. No writing is actually performed until the
... ...
libqpdf/qpdf-c.cc
... ... @@ -2,8 +2,10 @@
2 2  
3 3 #include <qpdf/QPDF.hh>
4 4  
  5 +#include <qpdf/BufferInputSource.hh>
5 6 #include <qpdf/Pl_Buffer.hh>
6 7 #include <qpdf/Pl_Discard.hh>
  8 +#include <qpdf/Pl_Function.hh>
7 9 #include <qpdf/QIntC.hh>
8 10 #include <qpdf/QPDFExc.hh>
9 11 #include <qpdf/QPDFLogger.hh>
... ... @@ -2029,3 +2031,89 @@ qpdf_remove_page(qpdf_data qpdf, qpdf_oh page)
2029 2031 auto p = qpdf_oh_item_internal(qpdf, page);
2030 2032 return trap_errors(qpdf, [&p](qpdf_data q) { q->qpdf->removePage(p); });
2031 2033 }
  2034 +
  2035 +QPDF_ERROR_CODE
  2036 +qpdf_create_from_json_file(qpdf_data qpdf, char const* filename)
  2037 +{
  2038 + QPDF_ERROR_CODE status = QPDF_SUCCESS;
  2039 + qpdf->filename = filename;
  2040 + status = trap_errors(
  2041 + qpdf, [](qpdf_data q) { q->qpdf->createFromJSON(q->filename); });
  2042 + return status;
  2043 +}
  2044 +
  2045 +QPDF_ERROR_CODE
  2046 +qpdf_create_from_json_data(
  2047 + qpdf_data qpdf, char const* buffer, unsigned long long size)
  2048 +{
  2049 + QPDF_ERROR_CODE status = QPDF_SUCCESS;
  2050 + qpdf->filename = "json buffer";
  2051 + qpdf->buffer = buffer;
  2052 + qpdf->size = size;
  2053 + auto b =
  2054 + new Buffer(QUtil::unsigned_char_pointer(buffer), QIntC::to_size(size));
  2055 + auto is = std::make_shared<BufferInputSource>(qpdf->filename, b, true);
  2056 + status =
  2057 + trap_errors(qpdf, [&is](qpdf_data q) { q->qpdf->createFromJSON(is); });
  2058 + return status;
  2059 +}
  2060 +
  2061 +QPDF_ERROR_CODE
  2062 +qpdf_update_from_json_file(qpdf_data qpdf, char const* filename)
  2063 +{
  2064 + QPDF_ERROR_CODE status = QPDF_SUCCESS;
  2065 + status = trap_errors(
  2066 + qpdf, [filename](qpdf_data q) { q->qpdf->updateFromJSON(filename); });
  2067 + return status;
  2068 +}
  2069 +
  2070 +QPDF_ERROR_CODE
  2071 +qpdf_update_from_json_data(
  2072 + qpdf_data qpdf, char const* buffer, unsigned long long size)
  2073 +{
  2074 + QPDF_ERROR_CODE status = QPDF_SUCCESS;
  2075 + auto b =
  2076 + new Buffer(QUtil::unsigned_char_pointer(buffer), QIntC::to_size(size));
  2077 + auto is = std::make_shared<BufferInputSource>(qpdf->filename, b, true);
  2078 + status =
  2079 + trap_errors(qpdf, [&is](qpdf_data q) { q->qpdf->updateFromJSON(is); });
  2080 + return status;
  2081 +}
  2082 +
  2083 +QPDF_ERROR_CODE
  2084 +qpdf_write_json(
  2085 + qpdf_data qpdf,
  2086 + int version,
  2087 + qpdf_write_fn_t fn,
  2088 + void* udata,
  2089 + enum qpdf_stream_decode_level_e decode_level,
  2090 + enum qpdf_json_stream_data_e json_stream_data,
  2091 + char const* file_prefix,
  2092 + char const* const* wanted_objects)
  2093 +{
  2094 + QPDF_ERROR_CODE status = QPDF_SUCCESS;
  2095 + auto p = std::make_shared<Pl_Function>("write_json", nullptr, fn, udata);
  2096 + std::set<std::string> wanted_objects_set;
  2097 + if (wanted_objects) {
  2098 + for (auto i = wanted_objects; *i; ++i) {
  2099 + wanted_objects_set.insert(*i);
  2100 + }
  2101 + }
  2102 + status = trap_errors(
  2103 + qpdf,
  2104 + [version,
  2105 + p,
  2106 + decode_level,
  2107 + json_stream_data,
  2108 + file_prefix,
  2109 + &wanted_objects_set](qpdf_data q) {
  2110 + q->qpdf->writeJSON(
  2111 + version,
  2112 + p.get(),
  2113 + decode_level,
  2114 + json_stream_data,
  2115 + file_prefix,
  2116 + wanted_objects_set);
  2117 + });
  2118 + return status;
  2119 +}
... ...
manual/release-notes.rst
... ... @@ -40,6 +40,10 @@ For a detailed list of changes, please see the file
40 40 - New C++ API calls: ``QPDF::writeJSON``,
41 41 ``QPDF::createFromJSON``, ``QPDF::updateFromJSON``
42 42  
  43 + - New C API calls: ``qpdf_create_from_json_file``,
  44 + ``qpdf_create_from_json_data``, ``qpdf_update_from_json_file``,
  45 + ``qpdf_update_from_json_data``, and ``qpdf_write_json``.
  46 +
43 47 - Complete documentation can be found at :ref:`json`. A
44 48 comprehensive list of changes from version 1 to version 2 can be
45 49 found at :ref:`json-v2-changes`.
... ...
qpdf/qpdf-ctest.c
... ... @@ -133,6 +133,13 @@ count_progress(int percent, void* data)
133 133 ++(*(int*)data);
134 134 }
135 135  
  136 +static int
  137 +write_to_file(char const* data, size_t size, void* udata)
  138 +{
  139 + FILE* f = (FILE*)udata;
  140 + return fwrite(data, 1, size, f) != size;
  141 +}
  142 +
136 143 static void
137 144 test01(
138 145 char const* infile,
... ... @@ -1458,7 +1465,7 @@ test41(
1458 1465 char const* outfile,
1459 1466 char const* xarg)
1460 1467 {
1461   - /* Empty PDF -- infile is ignored*/
  1468 + /* Empty PDF -- infile is ignored */
1462 1469 assert(qpdf_empty_pdf(qpdf) == 0);
1463 1470 qpdf_init_write(qpdf, outfile);
1464 1471 qpdf_set_static_ID(qpdf, QPDF_TRUE);
... ... @@ -1466,6 +1473,110 @@ test41(
1466 1473 report_errors();
1467 1474 }
1468 1475  
  1476 +static void
  1477 +test42(
  1478 + char const* infile,
  1479 + char const* password,
  1480 + char const* outfile,
  1481 + char const* xarg)
  1482 +{
  1483 + assert(qpdf_create_from_json_file(qpdf, infile) == QPDF_SUCCESS);
  1484 + qpdf_init_write(qpdf, outfile);
  1485 + qpdf_set_static_ID(qpdf, QPDF_TRUE);
  1486 + qpdf_write(qpdf);
  1487 + report_errors();
  1488 +}
  1489 +
  1490 +static void
  1491 +test43(
  1492 + char const* infile,
  1493 + char const* password,
  1494 + char const* outfile,
  1495 + char const* xarg)
  1496 +{
  1497 + char* buf = NULL;
  1498 + unsigned long size = 0;
  1499 + read_file_into_memory(infile, &buf, &size);
  1500 + assert(qpdf_create_from_json_data(qpdf, buf, size) == QPDF_SUCCESS);
  1501 + qpdf_init_write(qpdf, outfile);
  1502 + qpdf_set_static_ID(qpdf, QPDF_TRUE);
  1503 + qpdf_write(qpdf);
  1504 + report_errors();
  1505 + free(buf);
  1506 +}
  1507 +
  1508 +static void
  1509 +test44(
  1510 + char const* infile,
  1511 + char const* password,
  1512 + char const* outfile,
  1513 + char const* xarg)
  1514 +{
  1515 + assert(qpdf_read(qpdf, infile, password) == 0);
  1516 + assert(qpdf_update_from_json_file(qpdf, xarg) == QPDF_SUCCESS);
  1517 + qpdf_init_write(qpdf, outfile);
  1518 + qpdf_set_static_ID(qpdf, QPDF_TRUE);
  1519 + qpdf_write(qpdf);
  1520 + report_errors();
  1521 +}
  1522 +
  1523 +static void
  1524 +test45(
  1525 + char const* infile,
  1526 + char const* password,
  1527 + char const* outfile,
  1528 + char const* xarg)
  1529 +{
  1530 + char* buf = NULL;
  1531 + unsigned long size = 0;
  1532 + read_file_into_memory(xarg, &buf, &size);
  1533 + assert(qpdf_read(qpdf, infile, password) == 0);
  1534 + assert(qpdf_update_from_json_data(qpdf, buf, size) == QPDF_SUCCESS);
  1535 + qpdf_init_write(qpdf, outfile);
  1536 + qpdf_set_static_ID(qpdf, QPDF_TRUE);
  1537 + qpdf_write(qpdf);
  1538 + report_errors();
  1539 + free(buf);
  1540 +}
  1541 +
  1542 +static void
  1543 +test46(
  1544 + char const* infile,
  1545 + char const* password,
  1546 + char const* outfile,
  1547 + char const* xarg)
  1548 +{
  1549 + FILE* f = safe_fopen(outfile, "wb");
  1550 + assert(qpdf_read(qpdf, infile, password) == 0);
  1551 + qpdf_write_json(
  1552 + qpdf, 2, write_to_file, f, qpdf_dl_none, qpdf_sj_inline, "", NULL);
  1553 + fclose(f);
  1554 + report_errors();
  1555 +}
  1556 +
  1557 +static void
  1558 +test47(
  1559 + char const* infile,
  1560 + char const* password,
  1561 + char const* outfile,
  1562 + char const* xarg)
  1563 +{
  1564 + FILE* f = safe_fopen(outfile, "wb");
  1565 + assert(qpdf_read(qpdf, infile, password) == 0);
  1566 + char const* wanted_objects[] = {"obj:4 0 R", "trailer", NULL};
  1567 + qpdf_write_json(
  1568 + qpdf,
  1569 + 2,
  1570 + write_to_file,
  1571 + f,
  1572 + qpdf_dl_specialized,
  1573 + qpdf_sj_file,
  1574 + xarg,
  1575 + wanted_objects);
  1576 + fclose(f);
  1577 + report_errors();
  1578 +}
  1579 +
1469 1580 int
1470 1581 main(int argc, char* argv[])
1471 1582 {
... ... @@ -1542,6 +1653,12 @@ main(int argc, char* argv[])
1542 1653 : (n == 39) ? test39
1543 1654 : (n == 40) ? test40
1544 1655 : (n == 41) ? test41
  1656 + : (n == 42) ? test42
  1657 + : (n == 43) ? test43
  1658 + : (n == 44) ? test44
  1659 + : (n == 45) ? test45
  1660 + : (n == 46) ? test46
  1661 + : (n == 47) ? test47
1545 1662 : 0);
1546 1663  
1547 1664 if (fn == 0) {
... ...
qpdf/qtest/qpdf-json.test
... ... @@ -288,5 +288,58 @@ $td-&gt;runtest(&quot;simple version of writeJSON&quot;,
288 288 {$td->FILE => "minimal-write-json.json", $td->EXIT_STATUS => 0},
289 289 $td->NORMALIZE_NEWLINES);
290 290  
  291 +$n_tests += 13;
  292 +$td->runtest("C API create from json file",
  293 + {$td->COMMAND => "qpdf-ctest 42 minimal.json '' a.pdf"},
  294 + {$td->STRING => "C test 42 done\n", $td->EXIT_STATUS => 0},
  295 + $td->NORMALIZE_NEWLINES);
  296 +$td->runtest("check C API create from file",
  297 + {$td->FILE => "a.pdf"},
  298 + {$td->FILE => "qpdf-ctest-42-43.pdf"});
  299 +$td->runtest("C API create from json buffer",
  300 + {$td->COMMAND => "qpdf-ctest 43 minimal.json '' a.pdf"},
  301 + {$td->STRING => "C test 43 done\n", $td->EXIT_STATUS => 0},
  302 + $td->NORMALIZE_NEWLINES);
  303 +$td->runtest("check C API create from buffer",
  304 + {$td->FILE => "a.pdf"},
  305 + {$td->FILE => "qpdf-ctest-42-43.pdf"});
  306 +$td->runtest("C API update from json file",
  307 + {$td->COMMAND =>
  308 + "qpdf-ctest 44 minimal.pdf '' a.pdf minimal-update.json"},
  309 + {$td->STRING => "C test 44 done\n", $td->EXIT_STATUS => 0},
  310 + $td->NORMALIZE_NEWLINES);
  311 +$td->runtest("check C API update from file",
  312 + {$td->FILE => "a.pdf"},
  313 + {$td->FILE => "qpdf-ctest-44-45.pdf"});
  314 +$td->runtest("C API update from json buffer",
  315 + {$td->COMMAND =>
  316 + "qpdf-ctest 45 minimal.pdf '' a.pdf minimal-update.json"},
  317 + {$td->STRING => "C test 45 done\n", $td->EXIT_STATUS => 0},
  318 + $td->NORMALIZE_NEWLINES);
  319 +$td->runtest("check C API update from buffer",
  320 + {$td->FILE => "a.pdf"},
  321 + {$td->FILE => "qpdf-ctest-44-45.pdf"});
  322 +$td->runtest("C API write to JSON 1",
  323 + {$td->COMMAND =>
  324 + "qpdf-ctest 46 minimal.pdf '' a.json"},
  325 + {$td->STRING => "C test 46 done\n", $td->EXIT_STATUS => 0},
  326 + $td->NORMALIZE_NEWLINES);
  327 +$td->runtest("check C API write to JSON 1",
  328 + {$td->FILE => "a.json"},
  329 + {$td->FILE => "qpdf-ctest-46.json"},
  330 + $td->NORMALIZE_NEWLINES);
  331 +$td->runtest("C API write to JSON 2",
  332 + {$td->COMMAND =>
  333 + "qpdf-ctest 47 minimal.pdf '' a.json auto"},
  334 + {$td->STRING => "C test 47 done\n", $td->EXIT_STATUS => 0},
  335 + $td->NORMALIZE_NEWLINES);
  336 +$td->runtest("check C API write to JSON 2",
  337 + {$td->FILE => "a.json"},
  338 + {$td->FILE => "qpdf-ctest-47.json"},
  339 + $td->NORMALIZE_NEWLINES);
  340 +$td->runtest("check C API write to JSON stream",
  341 + {$td->FILE => "auto-4"},
  342 + {$td->FILE => "qpdf-ctest-47-4"});
  343 +
291 344 cleanup();
292 345 $td->report($n_tests);
... ...
qpdf/qtest/qpdf/minimal-update.json 0 → 100644
  1 +{
  2 + "qpdf": [
  3 + {
  4 + "jsonversion": 2,
  5 + "pushedinheritedpageresources": false,
  6 + "calledgetallpages": false
  7 + },
  8 + {
  9 + "obj:4 0 R": {
  10 + "stream": {
  11 + "data": "QlQKICAvRjEgMjQgVGYKICA3MiA3MjAgVGQKICAoU2FsYWQpIFRqCkVUCg==",
  12 + "dict": {}
  13 + }
  14 + }
  15 + }
  16 + ]
  17 +}
... ...
qpdf/qtest/qpdf/minimal.json 0 → 100644
  1 +{
  2 + "qpdf": [
  3 + {
  4 + "jsonversion": 2,
  5 + "pdfversion": "1.3",
  6 + "pushedinheritedpageresources": false,
  7 + "calledgetallpages": false,
  8 + "maxobjectid": 6
  9 + },
  10 + {
  11 + "obj:1 0 R": {
  12 + "value": {
  13 + "/Pages": "2 0 R",
  14 + "/Type": "/Catalog"
  15 + }
  16 + },
  17 + "obj:2 0 R": {
  18 + "value": {
  19 + "/Count": 1,
  20 + "/Kids": [
  21 + "3 0 R"
  22 + ],
  23 + "/Type": "/Pages"
  24 + }
  25 + },
  26 + "obj:3 0 R": {
  27 + "value": {
  28 + "/Contents": "4 0 R",
  29 + "/MediaBox": [
  30 + 0,
  31 + 0,
  32 + 612,
  33 + 792
  34 + ],
  35 + "/Parent": "2 0 R",
  36 + "/Resources": {
  37 + "/Font": {
  38 + "/F1": "6 0 R"
  39 + },
  40 + "/ProcSet": "5 0 R"
  41 + },
  42 + "/Type": "/Page"
  43 + }
  44 + },
  45 + "obj:4 0 R": {
  46 + "stream": {
  47 + "data": "QlQKICAvRjEgMjQgVGYKICA3MiA3MjAgVGQKICAoUG90YXRvKSBUagpFVAo=",
  48 + "dict": {}
  49 + }
  50 + },
  51 + "obj:5 0 R": {
  52 + "value": [
  53 + "/PDF",
  54 + "/Text"
  55 + ]
  56 + },
  57 + "obj:6 0 R": {
  58 + "value": {
  59 + "/BaseFont": "/Helvetica",
  60 + "/Encoding": "/WinAnsiEncoding",
  61 + "/Name": "/F1",
  62 + "/Subtype": "/Type1",
  63 + "/Type": "/Font"
  64 + }
  65 + },
  66 + "trailer": {
  67 + "value": {
  68 + "/Root": "1 0 R",
  69 + "/Size": 7
  70 + }
  71 + }
  72 + }
  73 + ]
  74 +}
... ...
qpdf/qtest/qpdf/qpdf-ctest-42-43.pdf 0 → 100644
No preview for this file type
qpdf/qtest/qpdf/qpdf-ctest-44-45.pdf 0 → 100644
No preview for this file type
qpdf/qtest/qpdf/qpdf-ctest-46.json 0 → 100644
  1 +{
  2 + "qpdf": [
  3 + {
  4 + "jsonversion": 2,
  5 + "pdfversion": "1.3",
  6 + "pushedinheritedpageresources": false,
  7 + "calledgetallpages": false,
  8 + "maxobjectid": 6
  9 + },
  10 + {
  11 + "obj:1 0 R": {
  12 + "value": {
  13 + "/Pages": "2 0 R",
  14 + "/Type": "/Catalog"
  15 + }
  16 + },
  17 + "obj:2 0 R": {
  18 + "value": {
  19 + "/Count": 1,
  20 + "/Kids": [
  21 + "3 0 R"
  22 + ],
  23 + "/Type": "/Pages"
  24 + }
  25 + },
  26 + "obj:3 0 R": {
  27 + "value": {
  28 + "/Contents": "4 0 R",
  29 + "/MediaBox": [
  30 + 0,
  31 + 0,
  32 + 612,
  33 + 792
  34 + ],
  35 + "/Parent": "2 0 R",
  36 + "/Resources": {
  37 + "/Font": {
  38 + "/F1": "6 0 R"
  39 + },
  40 + "/ProcSet": "5 0 R"
  41 + },
  42 + "/Type": "/Page"
  43 + }
  44 + },
  45 + "obj:4 0 R": {
  46 + "stream": {
  47 + "data": "QlQKICAvRjEgMjQgVGYKICA3MiA3MjAgVGQKICAoUG90YXRvKSBUagpFVAo=",
  48 + "dict": {}
  49 + }
  50 + },
  51 + "obj:5 0 R": {
  52 + "value": [
  53 + "/PDF",
  54 + "/Text"
  55 + ]
  56 + },
  57 + "obj:6 0 R": {
  58 + "value": {
  59 + "/BaseFont": "/Helvetica",
  60 + "/Encoding": "/WinAnsiEncoding",
  61 + "/Name": "/F1",
  62 + "/Subtype": "/Type1",
  63 + "/Type": "/Font"
  64 + }
  65 + },
  66 + "trailer": {
  67 + "value": {
  68 + "/Root": "1 0 R",
  69 + "/Size": 7
  70 + }
  71 + }
  72 + }
  73 + ]
  74 +}
... ...
qpdf/qtest/qpdf/qpdf-ctest-47-4 0 → 100644
  1 +BT
  2 + /F1 24 Tf
  3 + 72 720 Td
  4 + (Potato) Tj
  5 +ET
... ...
qpdf/qtest/qpdf/qpdf-ctest-47.json 0 → 100644
  1 +{
  2 + "qpdf": [
  3 + {
  4 + "jsonversion": 2,
  5 + "pdfversion": "1.3",
  6 + "pushedinheritedpageresources": false,
  7 + "calledgetallpages": false,
  8 + "maxobjectid": 6
  9 + },
  10 + {
  11 + "obj:4 0 R": {
  12 + "stream": {
  13 + "datafile": "auto-4",
  14 + "dict": {}
  15 + }
  16 + },
  17 + "trailer": {
  18 + "value": {
  19 + "/Root": "1 0 R",
  20 + "/Size": 7
  21 + }
  22 + }
  23 + }
  24 + ]
  25 +}
... ...