Commit e362bce8e86f4912eaa008bac06f9e2c19b72d3f

Authored by Jay Berkenbilt
2 parents 5a29b7f9 413aba5b

Merge branch 'jw' from #1146 into work

include/qpdf/JSON.hh
... ... @@ -290,8 +290,11 @@ class JSON
290 290 QPDF_DLL
291 291 qpdf_offset_t getEnd() const;
292 292  
  293 + // The following class does not form part of the public API and is for internal use only.
  294 +
  295 + class Writer;
  296 +
293 297 private:
294   - static std::string encode_string(std::string const& utf8);
295 298 static void writeClose(Pipeline* p, bool first, size_t depth, char const* delimeter);
296 299  
297 300 enum value_type_e {
... ...
include/qpdf/QPDF.hh
... ... @@ -1411,19 +1411,6 @@ class QPDF
1411 1411 // JSON import
1412 1412 void importJSON(std::shared_ptr<InputSource>, bool must_be_complete);
1413 1413  
1414   - // JSON write
1415   - void writeJSONStream(
1416   - int version,
1417   - Pipeline* p,
1418   - bool& first,
1419   - std::string const& key,
1420   - QPDFObjectHandle&,
1421   - qpdf_stream_decode_level_e,
1422   - qpdf_json_stream_data_e,
1423   - std::string const& file_prefix);
1424   - void writeJSONObject(
1425   - int version, Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle&);
1426   -
1427 1414 // Type conversion helper methods
1428 1415 template <typename T>
1429 1416 static qpdf_offset_t
... ...
include/qpdf/QPDFJob.hh
... ... @@ -551,7 +551,6 @@ class QPDFJob
551 551 // JSON
552 552 void doJSON(QPDF& pdf, Pipeline*);
553 553 QPDFObjGen::set getWantedJSONObjects();
554   - void doJSONObject(Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle&);
555 554 void doJSONObjects(Pipeline* p, bool& first, QPDF& pdf);
556 555 void doJSONObjectinfo(Pipeline* p, bool& first, QPDF& pdf);
557 556 void doJSONPages(Pipeline* p, bool& first, QPDF& pdf);
... ...
include/qpdf/QPDFObjectHandle.hh
... ... @@ -1197,6 +1197,13 @@ class QPDFObjectHandle
1197 1197 QPDF_DLL
1198 1198 JSON getJSON(int json_version, bool dereference_indirect = false);
1199 1199  
  1200 + // Write the object encoded as JSON to a pipeline. This is equivalent to, but more efficient
  1201 + // than, calling getJSON(json_version, dereference_indirect).write(p, depth). See the
  1202 + // documentation for getJSON and JSON::write for further detail.
  1203 + QPDF_DLL
  1204 + void
  1205 + writeJSON(int json_version, Pipeline* p, bool dereference_indirect = false, size_t depth = 0);
  1206 +
1200 1207 // Deprecated version uses v1 for backward compatibility.
1201 1208 // ABI: remove for qpdf 12
1202 1209 [[deprecated("Use getJSON(int version)")]] QPDF_DLL JSON
... ... @@ -1353,6 +1360,8 @@ class QPDFObjectHandle
1353 1360 return obj.get();
1354 1361 }
1355 1362  
  1363 + void writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect = false);
  1364 +
1356 1365 private:
1357 1366 QPDF_Array* asArray();
1358 1367 QPDF_Bool* asBool();
... ...
libqpdf/JSON.cc
1 1 #include <qpdf/JSON.hh>
2 2  
  3 +#include <qpdf/JSON_writer.hh>
  4 +
3 5 #include <qpdf/BufferInputSource.hh>
4 6 #include <qpdf/Pl_Base64.hh>
5 7 #include <qpdf/Pl_Concatenate.hh>
... ... @@ -119,7 +121,7 @@ JSON::JSON_array::write(Pipeline* p, size_t depth) const
119 121 JSON::JSON_string::JSON_string(std::string const& utf8) :
120 122 JSON_value(vt_string),
121 123 utf8(utf8),
122   - encoded(encode_string(utf8))
  124 + encoded(Writer::encode_string(utf8))
123 125 {
124 126 }
125 127  
... ... @@ -211,7 +213,7 @@ JSON::unparse() const
211 213 }
212 214  
213 215 std::string
214   -JSON::encode_string(std::string const& str)
  216 +JSON::Writer::encode_string(std::string const& str)
215 217 {
216 218 static auto constexpr hexchars = "0123456789abcdef";
217 219  
... ... @@ -279,7 +281,7 @@ JSON
279 281 JSON::addDictionaryMember(std::string const& key, JSON const& val)
280 282 {
281 283 if (auto* obj = m ? dynamic_cast<JSON_dictionary*>(m->value.get()) : nullptr) {
282   - return obj->members[encode_string(key)] = val.m ? val : makeNull();
  284 + return obj->members[Writer::encode_string(key)] = val.m ? val : makeNull();
283 285 } else {
284 286 throw std::runtime_error("JSON::addDictionaryMember called on non-dictionary");
285 287 }
... ...
libqpdf/QPDFJob.cc
... ... @@ -955,23 +955,6 @@ QPDFJob::getWantedJSONObjects()
955 955 }
956 956  
957 957 void
958   -QPDFJob::doJSONObject(Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle& obj)
959   -{
960   - if (m->json_version == 1) {
961   - JSON::writeDictionaryItem(p, first, key, obj.getJSON(1, true), 2);
962   - } else {
963   - auto j = JSON::makeDictionary();
964   - if (obj.isStream()) {
965   - j.addDictionaryMember("stream", JSON::makeDictionary())
966   - .addDictionaryMember("dict", obj.getDict().getJSON(m->json_version, true));
967   - } else {
968   - j.addDictionaryMember("value", obj.getJSON(m->json_version, true));
969   - }
970   - JSON::writeDictionaryItem(p, first, key, j, 2);
971   - }
972   -}
973   -
974   -void
975 958 QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf)
976 959 {
977 960 if (m->json_version == 1) {
... ... @@ -982,16 +965,17 @@ QPDFJob::doJSONObjects(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
982 965 auto wanted_og = getWantedJSONObjects();
983 966 for (auto& obj: pdf.getAllObjects()) {
984 967 std::string key = obj.unparse();
985   - if (m->json_version > 1) {
986   - key = "obj:" + key;
987   - }
  968 +
988 969 if (all_objects || wanted_og.count(obj.getObjGen())) {
989   - doJSONObject(p, first_object, key, obj);
  970 + JSON::writeDictionaryKey(p, first_object, obj.unparse(), 2);
  971 + obj.writeJSON(1, p, true, 2);
  972 + first_object = false;
990 973 }
991 974 }
992 975 if (all_objects || m->json_objects.count("trailer")) {
993   - auto trailer = pdf.getTrailer();
994   - doJSONObject(p, first_object, "trailer", trailer);
  976 + JSON::writeDictionaryKey(p, first_object, "trailer", 2);
  977 + pdf.getTrailer().writeJSON(1, p, true, 2);
  978 + first_object = false;
995 979 }
996 980 JSON::writeDictionaryClose(p, first_object, 1);
997 981 } else {
... ... @@ -3097,9 +3081,10 @@ QPDFJob::writeOutfile(QPDF&amp; pdf)
3097 3081 try {
3098 3082 QUtil::remove_file(backup.c_str());
3099 3083 } catch (QPDFSystemError& e) {
3100   - *m->log->getError() << m->message_prefix << ": unable to delete original file ("
3101   - << e.what() << ");" << " original file left in " << backup
3102   - << ", but the input was successfully replaced\n";
  3084 + *m->log->getError()
  3085 + << m->message_prefix << ": unable to delete original file (" << e.what() << ");"
  3086 + << " original file left in " << backup
  3087 + << ", but the input was successfully replaced\n";
3103 3088 }
3104 3089 }
3105 3090 }
... ...
libqpdf/QPDFObjectHandle.cc
... ... @@ -3,6 +3,7 @@
3 3 #include <qpdf/BufferInputSource.hh>
4 4 #include <qpdf/Pl_Buffer.hh>
5 5 #include <qpdf/Pl_QPDFTokenizer.hh>
  6 +#include <qpdf/JSON_writer.hh>
6 7 #include <qpdf/QPDF.hh>
7 8 #include <qpdf/QPDFExc.hh>
8 9 #include <qpdf/QPDFLogger.hh>
... ... @@ -1617,10 +1618,33 @@ QPDFObjectHandle::getJSON(int json_version, bool dereference_indirect)
1617 1618 } else if (!dereference()) {
1618 1619 throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1619 1620 } else {
1620   - return obj->getJSON(json_version);
  1621 + Pl_Buffer p{"json"};
  1622 + JSON::Writer jw{&p, 0};
  1623 + writeJSON(json_version, jw, dereference_indirect);
  1624 + p.finish();
  1625 + return JSON::parse(p.getString());
1621 1626 }
1622 1627 }
1623 1628  
  1629 +void
  1630 +QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect)
  1631 +{
  1632 + if (!dereference_indirect && isIndirect()) {
  1633 + p << "\"" << getObjGen().unparse(' ') << " R\"";
  1634 + } else if (!dereference()) {
  1635 + throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
  1636 + } else {
  1637 + obj->writeJSON(json_version, p);
  1638 + }
  1639 +}
  1640 +
  1641 +void
  1642 +QPDFObjectHandle::writeJSON(int json_version, Pipeline* p, bool dereference_indirect, size_t depth)
  1643 +{
  1644 + JSON::Writer jw{p, depth};
  1645 + writeJSON(json_version, jw, dereference_indirect);
  1646 +}
  1647 +
1624 1648 JSON
1625 1649 QPDFObjectHandle::getStreamJSON(
1626 1650 int json_version,
... ...
libqpdf/QPDF_Array.cc
1 1 #include <qpdf/QPDF_Array.hh>
2 2  
  3 +#include <qpdf/JSON_writer.hh>
3 4 #include <qpdf/QPDFObjectHandle.hh>
4 5 #include <qpdf/QPDFObject_private.hh>
5 6 #include <qpdf/QTC.hh>
... ... @@ -148,36 +149,41 @@ QPDF_Array::unparse()
148 149 return result;
149 150 }
150 151  
151   -JSON
152   -QPDF_Array::getJSON(int json_version)
  152 +void
  153 +QPDF_Array::writeJSON(int json_version, JSON::Writer& p)
153 154 {
154   - static const JSON j_null = JSON::makeNull();
155   - JSON j_array = JSON::makeArray();
  155 + p.writeStart('[');
156 156 if (sp) {
157 157 int next = 0;
158 158 for (auto& item: sp->elements) {
159 159 int key = item.first;
160 160 for (int j = next; j < key; ++j) {
161   - j_array.addArrayElement(j_null);
  161 + p.writeNext() << "null";
162 162 }
  163 + p.writeNext();
163 164 auto og = item.second->getObjGen();
164   - j_array.addArrayElement(
165   - og.isIndirect() ? JSON::makeString(og.unparse(' ') + " R")
166   - : item.second->getJSON(json_version));
  165 + if (og.isIndirect()) {
  166 + p << "\"" << og.unparse(' ') << " R\"";
  167 + } else {
  168 + item.second->writeJSON(json_version, p);
  169 + }
167 170 next = ++key;
168 171 }
169 172 for (int j = next; j < sp->size; ++j) {
170   - j_array.addArrayElement(j_null);
  173 + p.writeNext() << "null";
171 174 }
172 175 } else {
173 176 for (auto const& item: elements) {
  177 + p.writeNext();
174 178 auto og = item->getObjGen();
175   - j_array.addArrayElement(
176   - og.isIndirect() ? JSON::makeString(og.unparse(' ') + " R")
177   - : item->getJSON(json_version));
  179 + if (og.isIndirect()) {
  180 + p << "\"" << og.unparse(' ') << " R\"";
  181 + } else {
  182 + item->writeJSON(json_version, p);
  183 + }
178 184 }
179 185 }
180   - return j_array;
  186 + p.writeEnd(']');
181 187 }
182 188  
183 189 QPDFObjectHandle
... ...
libqpdf/QPDF_Bool.cc
1 1 #include <qpdf/QPDF_Bool.hh>
2 2  
  3 +#include <qpdf/JSON_writer.hh>
  4 +
3 5 QPDF_Bool::QPDF_Bool(bool val) :
4 6 QPDFValue(::ot_boolean, "boolean"),
5 7 val(val)
... ... @@ -24,10 +26,10 @@ QPDF_Bool::unparse()
24 26 return (val ? "true" : "false");
25 27 }
26 28  
27   -JSON
28   -QPDF_Bool::getJSON(int json_version)
  29 +void
  30 +QPDF_Bool::writeJSON(int json_version, JSON::Writer& p)
29 31 {
30   - return JSON::makeBool(this->val);
  32 + p << val;
31 33 }
32 34  
33 35 bool
... ...
libqpdf/QPDF_Destroyed.cc
... ... @@ -28,9 +28,8 @@ QPDF_Destroyed::unparse()
28 28 return "";
29 29 }
30 30  
31   -JSON
32   -QPDF_Destroyed::getJSON(int json_version)
  31 +void
  32 +QPDF_Destroyed::writeJSON(int json_version, JSON::Writer& p)
33 33 {
34 34 throw std::logic_error("attempted to get JSON from a QPDFObjectHandle from a destroyed QPDF");
35   - return JSON::makeNull();
36   -}
  35 +}
37 36 \ No newline at end of file
... ...
libqpdf/QPDF_Dictionary.cc
1 1 #include <qpdf/QPDF_Dictionary.hh>
2 2  
  3 +#include <qpdf/JSON_writer.hh>
3 4 #include <qpdf/QPDFObject_private.hh>
4 5 #include <qpdf/QPDF_Name.hh>
5 6 #include <qpdf/QPDF_Null.hh>
... ... @@ -67,28 +68,30 @@ QPDF_Dictionary::unparse()
67 68 return result;
68 69 }
69 70  
70   -JSON
71   -QPDF_Dictionary::getJSON(int json_version)
  71 +void
  72 +QPDF_Dictionary::writeJSON(int json_version, JSON::Writer& p)
72 73 {
73   - JSON j = JSON::makeDictionary();
  74 + p.writeStart('{');
74 75 for (auto& iter: this->items) {
75 76 if (!iter.second.isNull()) {
  77 + p.writeNext();
76 78 if (json_version == 1) {
77   - j.addDictionaryMember(
78   - QPDF_Name::normalizeName(iter.first), iter.second.getJSON(json_version));
  79 + p << "\"" << JSON::Writer::encode_string(QPDF_Name::normalizeName(iter.first))
  80 + << "\": ";
  81 + } else if (auto res = QPDF_Name::analyzeJSONEncoding(iter.first); res.first) {
  82 + if (res.second) {
  83 + p << "\"" << iter.first << "\": ";
  84 + } else {
  85 + p << "\"" << JSON::Writer::encode_string(iter.first) << "\": ";
  86 + }
79 87 } else {
80   - bool has_8bit_chars;
81   - bool is_valid_utf8;
82   - bool is_utf16;
83   - QUtil::analyze_encoding(iter.first, has_8bit_chars, is_valid_utf8, is_utf16);
84   - std::string key = !has_8bit_chars || is_valid_utf8
85   - ? iter.first
86   - : "n:" + QPDF_Name::normalizeName(iter.first);
87   - j.addDictionaryMember(key, iter.second.getJSON(json_version));
  88 + p << "\"n:" << JSON::Writer::encode_string(QPDF_Name::normalizeName(iter.first))
  89 + << "\": ";
88 90 }
  91 + iter.second.writeJSON(json_version, p);
89 92 }
90 93 }
91   - return j;
  94 + p.writeEnd('}');
92 95 }
93 96  
94 97 bool
... ...
libqpdf/QPDF_InlineImage.cc
1 1 #include <qpdf/QPDF_InlineImage.hh>
2 2  
  3 +#include <qpdf/JSON_writer.hh>
  4 +
3 5 QPDF_InlineImage::QPDF_InlineImage(std::string const& val) :
4 6 QPDFValue(::ot_inlineimage, "inline-image"),
5 7 val(val)
... ... @@ -24,8 +26,8 @@ QPDF_InlineImage::unparse()
24 26 return this->val;
25 27 }
26 28  
27   -JSON
28   -QPDF_InlineImage::getJSON(int json_version)
  29 +void
  30 +QPDF_InlineImage::writeJSON(int json_version, JSON::Writer& p)
29 31 {
30   - return JSON::makeNull();
  32 + p << "null";
31 33 }
... ...
libqpdf/QPDF_Integer.cc
1 1 #include <qpdf/QPDF_Integer.hh>
2 2  
  3 +#include <qpdf/JSON_writer.hh>
3 4 #include <qpdf/QUtil.hh>
4 5  
5 6 QPDF_Integer::QPDF_Integer(long long val) :
... ... @@ -26,10 +27,10 @@ QPDF_Integer::unparse()
26 27 return std::to_string(this->val);
27 28 }
28 29  
29   -JSON
30   -QPDF_Integer::getJSON(int json_version)
  30 +void
  31 +QPDF_Integer::writeJSON(int json_version, JSON::Writer& p)
31 32 {
32   - return JSON::makeInt(this->val);
  33 + p << std::to_string(this->val);
33 34 }
34 35  
35 36 long long
... ...
libqpdf/QPDF_Name.cc
1 1 #include <qpdf/QPDF_Name.hh>
2 2  
  3 +#include <qpdf/JSON_writer.hh>
3 4 #include <qpdf/QUtil.hh>
4 5  
  6 +#include <string_view>
  7 +
5 8 QPDF_Name::QPDF_Name(std::string const& name) :
6 9 QPDFValue(::ot_name, "name"),
7 10 name(name)
... ... @@ -51,20 +54,71 @@ QPDF_Name::unparse()
51 54 return normalizeName(this->name);
52 55 }
53 56  
54   -JSON
55   -QPDF_Name::getJSON(int json_version)
  57 +std::pair<bool, bool>
  58 +QPDF_Name::analyzeJSONEncoding(const std::string& name)
  59 +{
  60 + std::basic_string_view<unsigned char> view{
  61 + reinterpret_cast<const unsigned char*>(name.data()), name.size()};
  62 +
  63 + int tail = 0; // Number of continuation characters expected.
  64 + bool tail2 = false; // Potential overlong 3 octet utf-8.
  65 + bool tail3 = false; // potential overlong 4 octet
  66 + bool needs_escaping = false;
  67 + for (auto const& c: view) {
  68 + if (tail) {
  69 + if ((c & 0xc0) != 0x80) {
  70 + return {false, false};
  71 + }
  72 + if (tail2) {
  73 + if ((c & 0xe0) == 0x80) {
  74 + return {false, false};
  75 + }
  76 + tail2 = false;
  77 + } else if (tail3) {
  78 + if ((c & 0xf0) == 0x80) {
  79 + return {false, false};
  80 + }
  81 + tail3 = false;
  82 + }
  83 + tail--;
  84 + } else if (c < 0x80) {
  85 + if (!needs_escaping) {
  86 + needs_escaping = !((c > 34 && c != '\\') || c == ' ' || c == 33);
  87 + }
  88 + } else if ((c & 0xe0) == 0xc0) {
  89 + if ((c & 0xfe) == 0xc0) {
  90 + return {false, false};
  91 + }
  92 + tail = 1;
  93 + } else if ((c & 0xf0) == 0xe0) {
  94 + tail2 = (c == 0xe0);
  95 + tail = 2;
  96 + } else if ((c & 0xf8) == 0xf0) {
  97 + tail3 = (c == 0xf0);
  98 + tail = 3;
  99 + } else {
  100 + return {false, false};
  101 + }
  102 + }
  103 + return {tail == 0, !needs_escaping};
  104 +}
  105 +
  106 +void
  107 +QPDF_Name::writeJSON(int json_version, JSON::Writer& p)
56 108 {
  109 + // For performance reasons this code is duplicated in QPDF_Dictionary::writeJSON. When updating
  110 + // this method make sure QPDF_Dictionary is also update.
57 111 if (json_version == 1) {
58   - return JSON::makeString(normalizeName(this->name));
  112 + p << "\"" << JSON::Writer::encode_string(normalizeName(name)) << "\"";
59 113 } else {
60   - bool has_8bit_chars;
61   - bool is_valid_utf8;
62   - bool is_utf16;
63   - QUtil::analyze_encoding(this->name, has_8bit_chars, is_valid_utf8, is_utf16);
64   - if (!has_8bit_chars || is_valid_utf8) {
65   - return JSON::makeString(this->name);
  114 + if (auto res = analyzeJSONEncoding(name); res.first) {
  115 + if (res.second) {
  116 + p << "\"" << name << "\"";
  117 + } else {
  118 + p << "\"" << JSON::Writer::encode_string(name) << "\"";
  119 + }
66 120 } else {
67   - return JSON::makeString("n:" + normalizeName(this->name));
  121 + p << "\"n:" << JSON::Writer::encode_string(normalizeName(name)) << "\"";
68 122 }
69 123 }
70 124 }
... ...
libqpdf/QPDF_Null.cc
1 1 #include <qpdf/QPDF_Null.hh>
2 2  
  3 +#include <qpdf/JSON_writer.hh>
3 4 #include <qpdf/QPDFObject_private.hh>
4 5  
5 6 QPDF_Null::QPDF_Null() :
... ... @@ -43,9 +44,8 @@ QPDF_Null::unparse()
43 44 return "null";
44 45 }
45 46  
46   -JSON
47   -QPDF_Null::getJSON(int json_version)
  47 +void
  48 +QPDF_Null::writeJSON(int json_version, JSON::Writer& p)
48 49 {
49   - // If this is updated, QPDF_Array::getJSON must also be updated.
50   - return JSON::makeNull();
  50 + p << "null";
51 51 }
... ...
libqpdf/QPDF_Operator.cc
1 1 #include <qpdf/QPDF_Operator.hh>
2 2  
  3 +#include <qpdf/JSON_writer.hh>
  4 +
3 5 QPDF_Operator::QPDF_Operator(std::string const& val) :
4 6 QPDFValue(::ot_operator, "operator"),
5 7 val(val)
... ... @@ -24,8 +26,8 @@ QPDF_Operator::unparse()
24 26 return val;
25 27 }
26 28  
27   -JSON
28   -QPDF_Operator::getJSON(int json_version)
  29 +void
  30 +QPDF_Operator::writeJSON(int json_version, JSON::Writer& p)
29 31 {
30   - return JSON::makeNull();
  32 + p << "null";
31 33 }
... ...
libqpdf/QPDF_Real.cc
1 1 #include <qpdf/QPDF_Real.hh>
2 2  
  3 +#include <qpdf/JSON_writer.hh>
3 4 #include <qpdf/QUtil.hh>
4 5  
5 6 QPDF_Real::QPDF_Real(std::string const& val) :
... ... @@ -38,21 +39,17 @@ QPDF_Real::unparse()
38 39 return this->val;
39 40 }
40 41  
41   -JSON
42   -QPDF_Real::getJSON(int json_version)
  42 +void
  43 +QPDF_Real::writeJSON(int json_version, JSON::Writer& p)
43 44 {
44   - // While PDF allows .x or -.x, JSON does not. Rather than converting from string to double and
45   - // back, just handle this as a special case for JSON.
46   - std::string result;
47 45 if (this->val.length() == 0) {
48 46 // Can't really happen...
49   - result = "0";
  47 + p << "0";
50 48 } else if (this->val.at(0) == '.') {
51   - result = "0" + this->val;
52   - } else if ((this->val.length() >= 2) && (this->val.at(0) == '-') && (this->val.at(1) == '.')) {
53   - result = "-0." + this->val.substr(2);
  49 + p << "0" << this->val;
  50 + } else if (this->val.length() >= 2 && this->val.at(0) == '-' && this->val.at(1) == '.') {
  51 + p << "-0." << this->val.substr(2);
54 52 } else {
55   - result = this->val;
  53 + p << this->val;
56 54 }
57   - return JSON::makeNumber(result);
58 55 }
... ...
libqpdf/QPDF_Reserved.cc
... ... @@ -26,9 +26,8 @@ QPDF_Reserved::unparse()
26 26 return "";
27 27 }
28 28  
29   -JSON
30   -QPDF_Reserved::getJSON(int json_version)
  29 +void
  30 +QPDF_Reserved::writeJSON(int json_version, JSON::Writer& p)
31 31 {
32 32 throw std::logic_error("QPDFObjectHandle: attempting to get JSON from a reserved object");
33   - return JSON::makeNull();
34 33 }
... ...
libqpdf/QPDF_Stream.cc
1 1 #include <qpdf/QPDF_Stream.hh>
2 2  
3 3 #include <qpdf/ContentNormalizer.hh>
  4 +#include <qpdf/JSON_writer.hh>
4 5 #include <qpdf/Pipeline.hh>
5 6 #include <qpdf/Pl_Base64.hh>
6 7 #include <qpdf/Pl_Buffer.hh>
... ... @@ -176,13 +177,10 @@ QPDF_Stream::unparse()
176 177 return og.unparse(' ') + " R";
177 178 }
178 179  
179   -JSON
180   -QPDF_Stream::getJSON(int json_version)
  180 +void
  181 +QPDF_Stream::writeJSON(int json_version, JSON::Writer& jw)
181 182 {
182   - if (json_version == 1) {
183   - return this->stream_dict.getJSON(json_version);
184   - }
185   - return getStreamJSON(json_version, qpdf_sj_none, qpdf_dl_none, nullptr, "");
  183 + stream_dict.writeJSON(json_version, jw);
186 184 }
187 185  
188 186 JSON
... ... @@ -193,77 +191,108 @@ QPDF_Stream::getStreamJSON(
193 191 Pipeline* p,
194 192 std::string const& data_filename)
195 193 {
  194 + Pl_Buffer pb{"streamjson"};
  195 + JSON::Writer jw{&pb, 0};
  196 + decode_level =
  197 + writeStreamJSON(json_version, jw, json_data, decode_level, p, data_filename, true);
  198 + pb.finish();
  199 + auto result = JSON::parse(pb.getString());
  200 + if (json_data == qpdf_sj_inline) {
  201 + result.addDictionaryMember("data", JSON::makeBlob(StreamBlobProvider(this, decode_level)));
  202 + }
  203 + return result;
  204 +}
  205 +
  206 +qpdf_stream_decode_level_e
  207 +QPDF_Stream::writeStreamJSON(
  208 + int json_version,
  209 + JSON::Writer& jw,
  210 + qpdf_json_stream_data_e json_data,
  211 + qpdf_stream_decode_level_e decode_level,
  212 + Pipeline* p,
  213 + std::string const& data_filename,
  214 + bool no_data_key)
  215 +{
196 216 switch (json_data) {
197 217 case qpdf_sj_none:
198 218 case qpdf_sj_inline:
199 219 if (p != nullptr) {
200   - throw std::logic_error("QPDF_Stream::getStreamJSON: pipeline should only be supplied "
  220 + throw std::logic_error("QPDF_Stream::writeStreamJSON: pipeline should only be supplied "
201 221 "when json_data is file");
202 222 }
203 223 break;
204 224 case qpdf_sj_file:
205 225 if (p == nullptr) {
206 226 throw std::logic_error(
207   - "QPDF_Stream::getStreamJSON: pipeline must be supplied when json_data is file");
  227 + "QPDF_Stream::writeStreamJSON: pipeline must be supplied when json_data is file");
208 228 }
209 229 if (data_filename.empty()) {
210   - throw std::logic_error("QPDF_Stream::getStreamJSON: data_filename must be supplied "
  230 + throw std::logic_error("QPDF_Stream::writeStreamJSON: data_filename must be supplied "
211 231 "when json_data is file");
212 232 }
213 233 break;
214 234 }
215 235  
216   - auto dict = this->stream_dict;
217   - JSON result = JSON::makeDictionary();
218   - if (json_data != qpdf_sj_none) {
219   - Pl_Discard discard;
220   - Pl_Buffer buf_pl{"stream data"};
221   - // buf_pl contains valid data and is ready for retrieval of the data.
222   - bool buf_pl_ready = false;
223   - bool filtered = false;
224   - bool filter = (decode_level != qpdf_dl_none);
225   - for (int attempt = 1; attempt <= 2; ++attempt) {
226   - Pipeline* data_pipeline = &discard;
227   - if (json_data == qpdf_sj_file) {
228   - // We need to capture the data to write
229   - data_pipeline = &buf_pl;
230   - }
231   - bool succeeded =
232   - pipeStreamData(data_pipeline, &filtered, 0, decode_level, false, (attempt == 1));
233   - if (!succeeded || (filter && !filtered)) {
234   - // Try again
235   - filter = false;
236   - decode_level = qpdf_dl_none;
237   - buf_pl.getString(); // reset buf_pl
238   - } else {
239   - if (json_data == qpdf_sj_file) {
240   - buf_pl_ready = true;
241   - }
242   - break;
243   - }
244   - }
245   - // We can use unsafeShallowCopy because we are only touching top-level keys.
246   - dict = this->stream_dict.unsafeShallowCopy();
247   - dict.removeKey("/Length");
248   - if (filter && filtered) {
249   - dict.removeKey("/Filter");
250   - dict.removeKey("/DecodeParms");
251   - }
252   - if (json_data == qpdf_sj_file) {
253   - result.addDictionaryMember("datafile", JSON::makeString(data_filename));
254   - if (!buf_pl_ready) {
255   - throw std::logic_error("QPDF_Stream: failed to get stream data in json file mode");
256   - }
257   - p->writeString(buf_pl.getString());
258   - } else if (json_data == qpdf_sj_inline) {
259   - result.addDictionaryMember(
260   - "data", JSON::makeBlob(StreamBlobProvider(this, decode_level)));
  236 + jw.writeStart('{');
  237 +
  238 + if (json_data == qpdf_sj_none) {
  239 + jw.writeNext();
  240 + jw << R"("dict": )";
  241 + stream_dict.writeJSON(json_version, jw);
  242 + jw.writeEnd('}');
  243 + return decode_level;
  244 + }
  245 +
  246 + Pl_Discard discard;
  247 + Pl_Buffer buf_pl{"stream data"};
  248 + Pipeline* data_pipeline = &buf_pl;
  249 + if (no_data_key && json_data == qpdf_sj_inline) {
  250 + data_pipeline = &discard;
  251 + }
  252 + // pipeStreamData produced valid data.
  253 + bool buf_pl_ready = false;
  254 + bool filtered = false;
  255 + bool filter = (decode_level != qpdf_dl_none);
  256 + for (int attempt = 1; attempt <= 2; ++attempt) {
  257 + bool succeeded =
  258 + pipeStreamData(data_pipeline, &filtered, 0, decode_level, false, (attempt == 1));
  259 + if (!succeeded || (filter && !filtered)) {
  260 + // Try again
  261 + filter = false;
  262 + decode_level = qpdf_dl_none;
  263 + buf_pl.getString(); // reset buf_pl
261 264 } else {
262   - throw std::logic_error("QPDF_Stream: unexpected value of json_data");
  265 + buf_pl_ready = true;
  266 + break;
263 267 }
264 268 }
265   - result.addDictionaryMember("dict", dict.getJSON(json_version));
266   - return result;
  269 + if (!buf_pl_ready) {
  270 + throw std::logic_error("QPDF_Stream: failed to get stream data");
  271 + }
  272 + // We can use unsafeShallowCopy because we are only touching top-level keys.
  273 + auto dict = stream_dict.unsafeShallowCopy();
  274 + dict.removeKey("/Length");
  275 + if (filter && filtered) {
  276 + dict.removeKey("/Filter");
  277 + dict.removeKey("/DecodeParms");
  278 + }
  279 + if (json_data == qpdf_sj_file) {
  280 + jw.writeNext() << R"("datafile": ")" << JSON::Writer::encode_string(data_filename) << "\"";
  281 + p->writeString(buf_pl.getString());
  282 + } else if (json_data == qpdf_sj_inline) {
  283 + if (!no_data_key) {
  284 + jw.writeNext() << R"("data": ")";
  285 + jw.writeBase64(buf_pl.getString()) << "\"";
  286 + }
  287 + } else {
  288 + throw std::logic_error("QPDF_Stream::writeStreamJSON : unexpected value of json_data");
  289 + }
  290 +
  291 + jw.writeNext() << R"("dict": )";
  292 + dict.writeJSON(json_version, jw);
  293 + jw.writeEnd('}');
  294 +
  295 + return decode_level;
267 296 }
268 297  
269 298 void
... ...
libqpdf/QPDF_String.cc
1 1 #include <qpdf/QPDF_String.hh>
2 2  
  3 +#include <qpdf/JSON_writer.hh>
3 4 #include <qpdf/QUtil.hh>
4 5  
5 6 // DO NOT USE ctype -- it is locale dependent for some things, and it's not worth the risk of
... ... @@ -45,33 +46,28 @@ QPDF_String::unparse()
45 46 return unparse(false);
46 47 }
47 48  
48   -JSON
49   -QPDF_String::getJSON(int json_version)
  49 +void
  50 +QPDF_String::writeJSON(int json_version, JSON::Writer& p)
50 51 {
  52 + auto candidate = getUTF8Val();
51 53 if (json_version == 1) {
52   - return JSON::makeString(getUTF8Val());
53   - }
54   - // See if we can unambiguously represent as Unicode.
55   - bool is_unicode = false;
56   - std::string result;
57   - std::string candidate = getUTF8Val();
58   - if (QUtil::is_utf16(this->val) || QUtil::is_explicit_utf8(this->val)) {
59   - is_unicode = true;
60   - result = candidate;
61   - } else if (!useHexString()) {
62   - std::string test;
63   - if (QUtil::utf8_to_pdf_doc(candidate, test, '?') && (test == this->val)) {
64   - // This is a PDF-doc string that can be losslessly encoded as Unicode.
65   - is_unicode = true;
66   - result = candidate;
67   - }
68   - }
69   - if (is_unicode) {
70   - result = "u:" + result;
  54 +
  55 + p << "\"" << JSON::Writer::encode_string(candidate) << "\"";
71 56 } else {
72   - result = "b:" + QUtil::hex_encode(this->val);
  57 + // See if we can unambiguously represent as Unicode.
  58 + if (QUtil::is_utf16(this->val) || QUtil::is_explicit_utf8(this->val)) {
  59 + p << "\"u:" << JSON::Writer::encode_string(candidate) <<"\"";
  60 + return;
  61 + } else if (!useHexString()) {
  62 + std::string test;
  63 + if (QUtil::utf8_to_pdf_doc(candidate, test, '?') && (test == this->val)) {
  64 + // This is a PDF-doc string that can be losslessly encoded as Unicode.
  65 + p << "\"u:" << JSON::Writer::encode_string(candidate) <<"\"";
  66 + return;
  67 + }
  68 + }
  69 + p << "\"b:" << QUtil::hex_encode(val) <<"\"";
73 70 }
74   - return JSON::makeString(result);
75 71 }
76 72  
77 73 bool
... ...
libqpdf/QPDF_Unresolved.cc
... ... @@ -27,9 +27,8 @@ QPDF_Unresolved::unparse()
27 27 return "";
28 28 }
29 29  
30   -JSON
31   -QPDF_Unresolved::getJSON(int json_version)
  30 +void
  31 +QPDF_Unresolved::writeJSON(int json_version, JSON::Writer& p)
32 32 {
33 33 throw std::logic_error("attempted to get JSON from an unresolved QPDFObjectHandle");
34   - return JSON::makeNull();
35 34 }
... ...
libqpdf/QPDF_json.cc
1 1 #include <qpdf/QPDF.hh>
2 2  
3 3 #include <qpdf/FileInputSource.hh>
  4 +#include <qpdf/JSON_writer.hh>
4 5 #include <qpdf/Pl_Base64.hh>
5 6 #include <qpdf/Pl_StdioFile.hh>
6 7 #include <qpdf/QIntC.hh>
7 8 #include <qpdf/QPDFObject_private.hh>
8 9 #include <qpdf/QPDFValue.hh>
9 10 #include <qpdf/QPDF_Null.hh>
  11 +#include <qpdf/QPDF_Stream.hh>
10 12 #include <qpdf/QTC.hh>
11 13 #include <qpdf/QUtil.hh>
12 14 #include <algorithm>
... ... @@ -442,7 +444,9 @@ void
442 444 QPDF::JSONReactor::replaceObject(QPDFObjectHandle&& replacement, JSON const& value)
443 445 {
444 446 if (replacement.isIndirect()) {
445   - error(replacement.getParsedOffset(), "the value of an object may not be an indirect object reference");
  447 + error(
  448 + replacement.getParsedOffset(),
  449 + "the value of an object may not be an indirect object reference");
446 450 return;
447 451 }
448 452 auto& tos = stack.back();
... ... @@ -828,45 +832,20 @@ QPDF::importJSON(std::shared_ptr&lt;InputSource&gt; is, bool must_be_complete)
828 832 }
829 833  
830 834 void
831   -QPDF::writeJSONStream(
  835 +writeJSONStreamFile(
832 836 int version,
833   - Pipeline* p,
834   - bool& first,
835   - std::string const& key,
836   - QPDFObjectHandle& obj,
  837 + JSON::Writer& jw,
  838 + QPDF_Stream& stream,
  839 + int id,
837 840 qpdf_stream_decode_level_e decode_level,
838   - qpdf_json_stream_data_e json_stream_data,
839 841 std::string const& file_prefix)
840 842 {
841   - Pipeline* stream_p = nullptr;
842   - FILE* f = nullptr;
843   - std::shared_ptr<Pl_StdioFile> f_pl;
844   - std::string filename;
845   - if (json_stream_data == qpdf_sj_file) {
846   - filename = file_prefix + "-" + std::to_string(obj.getObjectID());
847   - f = QUtil::safe_fopen(filename.c_str(), "wb");
848   - f_pl = std::make_shared<Pl_StdioFile>("stream data", f);
849   - stream_p = f_pl.get();
850   - }
851   - auto j = JSON::makeDictionary();
852   - j.addDictionaryMember(
853   - "stream", obj.getStreamJSON(version, json_stream_data, decode_level, stream_p, filename));
854   -
855   - JSON::writeDictionaryItem(p, first, key, j, 3);
856   - if (f) {
857   - f_pl->finish();
858   - f_pl = nullptr;
859   - fclose(f);
860   - }
861   -}
862   -
863   -void
864   -QPDF::writeJSONObject(
865   - int version, Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle& obj)
866   -{
867   - auto j = JSON::makeDictionary();
868   - j.addDictionaryMember("value", obj.getJSON(version, true));
869   - JSON::writeDictionaryItem(p, first, key, j, 3);
  843 + auto filename = file_prefix + "-" + std::to_string(id);
  844 + auto* f = QUtil::safe_fopen(filename.c_str(), "wb");
  845 + Pl_StdioFile f_pl{"stream data", f};
  846 + stream.writeStreamJSON(version, jw, qpdf_sj_file, decode_level, &f_pl, filename);
  847 + f_pl.finish();
  848 + fclose(f);
870 849 }
871 850  
872 851 void
... ... @@ -893,80 +872,75 @@ QPDF::writeJSON(
893 872 std::string const& file_prefix,
894 873 std::set<std::string> wanted_objects)
895 874 {
896   - int const depth_outer = 1;
897   - int const depth_top = 1;
898   - int const depth_qpdf = 2;
899   - int const depth_qpdf_inner = 3;
900   -
901 875 if (version != 2) {
902 876 throw std::runtime_error("QPDF::writeJSON: only version 2 is supported");
903 877 }
904   - bool first = true;
  878 + JSON::Writer jw{p, 4};
905 879 if (complete) {
906   - JSON::writeDictionaryOpen(p, first, depth_outer);
907   - } else {
908   - first = first_key;
909   - }
910   - JSON::writeDictionaryKey(p, first, "qpdf", depth_top);
911   - bool first_qpdf = true;
912   - JSON::writeArrayOpen(p, first_qpdf, depth_top);
913   - JSON::writeNext(p, first_qpdf, depth_qpdf);
914   - bool first_qpdf_inner = true;
915   - JSON::writeDictionaryOpen(p, first_qpdf_inner, depth_qpdf);
916   - JSON::writeDictionaryItem(
917   - p, first_qpdf_inner, "jsonversion", JSON::makeInt(version), depth_qpdf_inner);
918   - JSON::writeDictionaryItem(
919   - p, first_qpdf_inner, "pdfversion", JSON::makeString(getPDFVersion()), depth_qpdf_inner);
920   - JSON::writeDictionaryItem(
921   - p,
922   - first_qpdf_inner,
923   - "pushedinheritedpageresources",
924   - JSON::makeBool(everPushedInheritedAttributesToPages()),
925   - depth_qpdf_inner);
926   - JSON::writeDictionaryItem(
927   - p,
928   - first_qpdf_inner,
929   - "calledgetallpages",
930   - JSON::makeBool(everCalledGetAllPages()),
931   - depth_qpdf_inner);
932   - JSON::writeDictionaryItem(
933   - p,
934   - first_qpdf_inner,
935   - "maxobjectid",
936   - JSON::makeInt(QIntC::to_longlong(getObjectCount())),
937   - depth_qpdf_inner);
938   - JSON::writeDictionaryClose(p, first_qpdf_inner, depth_qpdf);
939   - JSON::writeNext(p, first_qpdf, depth_qpdf);
940   - JSON::writeDictionaryOpen(p, first_qpdf_inner, depth_qpdf);
  880 + jw << "{";
  881 + } else if (!first_key) {
  882 + jw << ",";
  883 + }
  884 + first_key = false;
  885 +
  886 + /* clang-format off */
  887 + jw << "\n"
  888 + " \"qpdf\": [\n"
  889 + " {\n"
  890 + " \"jsonversion\": " << std::to_string(version) << ",\n"
  891 + " \"pdfversion\": \"" << getPDFVersion() << "\",\n"
  892 + " \"pushedinheritedpageresources\": " << (everPushedInheritedAttributesToPages() ? "true" : "false") << ",\n"
  893 + " \"calledgetallpages\": " << (everCalledGetAllPages() ? "true" : "false") << ",\n"
  894 + " \"maxobjectid\": " << std::to_string(getObjectCount()) << "\n"
  895 + " },\n"
  896 + " {";
  897 + /* clang-format on */
  898 +
941 899 bool all_objects = wanted_objects.empty();
  900 + bool first = true;
942 901 for (auto& obj: getAllObjects()) {
943   - std::string key = "obj:" + obj.unparse();
  902 + auto const og = obj.getObjGen();
  903 + std::string key = "obj:" + og.unparse(' ') + " R";
944 904 if (all_objects || wanted_objects.count(key)) {
945   - if (obj.isStream()) {
946   - writeJSONStream(
947   - version,
948   - p,
949   - first_qpdf_inner,
950   - key,
951   - obj,
952   - decode_level,
953   - json_stream_data,
954   - file_prefix);
  905 + if (first) {
  906 + jw << "\n \"" << key;
  907 + first = false;
955 908 } else {
956   - writeJSONObject(version, p, first_qpdf_inner, key, obj);
  909 + jw << "\n },\n \"" << key;
  910 + }
  911 + if (auto* stream = obj.getObjectPtr()->as<QPDF_Stream>()) {
  912 + jw << "\": {\n \"stream\": ";
  913 + if (json_stream_data == qpdf_sj_file) {
  914 + writeJSONStreamFile(
  915 + version, jw, *stream, og.getObj(), decode_level, file_prefix);
  916 + } else {
  917 + stream->writeStreamJSON(
  918 + version, jw, json_stream_data, decode_level, nullptr, "");
  919 + }
  920 + } else {
  921 + jw << "\": {\n \"value\": ";
  922 + obj.writeJSON(version, jw, true);
957 923 }
958 924 }
959 925 }
960 926 if (all_objects || wanted_objects.count("trailer")) {
961   - auto trailer = getTrailer();
962   - writeJSONObject(version, p, first_qpdf_inner, "trailer", trailer);
963   - }
964   - JSON::writeDictionaryClose(p, first_qpdf_inner, depth_qpdf);
965   - JSON::writeArrayClose(p, first_qpdf, depth_top);
  927 + if (!first) {
  928 + jw << "\n },";
  929 + }
  930 + jw << "\n \"trailer\": {\n \"value\": ";
  931 + getTrailer().writeJSON(version, jw, true);
  932 + first = false;
  933 + }
  934 + if (!first) {
  935 + jw << "\n }";
  936 + }
  937 + /* clang-format off */
  938 + jw << "\n"
  939 + " }\n"
  940 + " ]";
  941 + /* clang-format on */
966 942 if (complete) {
967   - JSON::writeDictionaryClose(p, first, 0);
968   - *p << "\n";
  943 + jw << "\n}\n";
969 944 p->finish();
970 945 }
971   - first_key = false;
972 946 }
... ...
libqpdf/qpdf/JSON_writer.hh 0 โ†’ 100644
  1 +#ifndef JSON_WRITER_HH
  2 +#define JSON_WRITER_HH
  3 +
  4 +#include <qpdf/JSON.hh>
  5 +#include <qpdf/Pipeline.hh>
  6 +#include <qpdf/Pl_Base64.hh>
  7 +#include <qpdf/Pl_Concatenate.hh>
  8 +
  9 +#include <string_view>
  10 +
  11 +// Writer is a small utility class to aid writing JSON to a pipeline. Methods are designed to allow
  12 +// chaining of calls.
  13 +//
  14 +// Some uses of the class have a significant performance impact. The class is intended purely for
  15 +// internal use to allow it to be adapted as needed to maintain performance.
  16 +class JSON::Writer
  17 +{
  18 + public:
  19 + Writer(Pipeline* p, size_t depth) :
  20 + p(p),
  21 + indent(2 * depth)
  22 + {
  23 + }
  24 +
  25 + Writer&
  26 + write(char const* data, size_t len)
  27 + {
  28 + p->write(reinterpret_cast<unsigned char const*>(data), len);
  29 + return *this;
  30 + }
  31 +
  32 + Writer&
  33 + writeBase64(std::string_view sv)
  34 + {
  35 + Pl_Concatenate cat{"writer concat", p};
  36 + Pl_Base64 base{"writer base64", &cat, Pl_Base64::a_encode};
  37 + base.write(reinterpret_cast<unsigned char const*>(sv.data()), sv.size());
  38 + base.finish();
  39 + return *this;
  40 + }
  41 +
  42 + Writer&
  43 + writeNext()
  44 + {
  45 + auto n = indent;
  46 + if (first) {
  47 + first = false;
  48 + write(&spaces[1], n % n_spaces + 1);
  49 + } else {
  50 + write(&spaces[0], n % n_spaces + 2);
  51 + }
  52 + while (n >= n_spaces) {
  53 + write(&spaces[2], n_spaces);
  54 + n -= n_spaces;
  55 + }
  56 + return *this;
  57 + }
  58 +
  59 + Writer&
  60 + writeStart(char const& c)
  61 + {
  62 + write(&c, 1);
  63 + first = true;
  64 + indent += 2;
  65 + return *this;
  66 + }
  67 +
  68 + Writer&
  69 + writeEnd(char const& c)
  70 + {
  71 + if (indent > 1) {
  72 + indent -= 2;
  73 + }
  74 + if (!first) {
  75 + first = true;
  76 + writeNext();
  77 + }
  78 + first = false;
  79 + write(&c, 1);
  80 + return *this;
  81 + }
  82 +
  83 + Writer&
  84 + operator<<(std::string_view sv)
  85 + {
  86 + p->write(reinterpret_cast<unsigned char const*>(sv.data()), sv.size());
  87 + return *this;
  88 + }
  89 +
  90 + Writer&
  91 + operator<<(char const* s)
  92 + {
  93 + *this << std::string_view{s};
  94 + return *this;
  95 + }
  96 +
  97 + Writer&
  98 + operator<<(bool val)
  99 + {
  100 + *this << (val ? "true" : "false");
  101 + return *this;
  102 + }
  103 +
  104 + Writer&
  105 + operator<<(int val)
  106 + {
  107 + *this << std::to_string(val);
  108 + return *this;
  109 + }
  110 +
  111 + Writer&
  112 + operator<<(size_t val)
  113 + {
  114 + *this << std::to_string(val);
  115 + return *this;
  116 + }
  117 +
  118 + Writer&
  119 + operator<<(JSON&& j)
  120 + {
  121 + j.write(p, indent / 2);
  122 + return *this;
  123 + }
  124 +
  125 + static std::string encode_string(std::string const& utf8);
  126 +
  127 + private:
  128 + Pipeline* p;
  129 + bool first{true};
  130 + size_t indent;
  131 +
  132 + static constexpr std::string_view spaces =
  133 + ",\n ";
  134 + static constexpr auto n_spaces = spaces.size() - 2;
  135 +};
  136 +
  137 +#endif // JSON_WRITER_HH
... ...
libqpdf/qpdf/QPDFObject_private.hh
... ... @@ -33,10 +33,10 @@ class QPDFObject
33 33 {
34 34 return value->unparse();
35 35 }
36   - JSON
37   - getJSON(int json_version)
  36 + void
  37 + writeJSON(int json_version, JSON::Writer& p)
38 38 {
39   - return value->getJSON(json_version);
  39 + return value->writeJSON(json_version, p);
40 40 }
41 41 std::string
42 42 getStringValue() const
... ...
libqpdf/qpdf/QPDFValue.hh
... ... @@ -24,7 +24,7 @@ class QPDFValue: public std::enable_shared_from_this&lt;QPDFValue&gt;
24 24  
25 25 virtual std::shared_ptr<QPDFObject> copy(bool shallow = false) = 0;
26 26 virtual std::string unparse() = 0;
27   - virtual JSON getJSON(int json_version) = 0;
  27 + virtual void writeJSON(int json_version, JSON::Writer& p) = 0;
28 28  
29 29 struct JSON_Descr
30 30 {
... ...
libqpdf/qpdf/QPDF_Array.hh
... ... @@ -22,7 +22,7 @@ class QPDF_Array: public QPDFValue
22 22 create(std::vector<std::shared_ptr<QPDFObject>>&& items, bool sparse);
23 23 std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
24 24 std::string unparse() override;
25   - JSON getJSON(int json_version) override;
  25 + void writeJSON(int json_version, JSON::Writer& p) override;
26 26 void disconnect() override;
27 27  
28 28 int
... ...
libqpdf/qpdf/QPDF_Bool.hh
... ... @@ -10,7 +10,8 @@ class QPDF_Bool: public QPDFValue
10 10 static std::shared_ptr<QPDFObject> create(bool val);
11 11 std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
12 12 std::string unparse() override;
13   - JSON getJSON(int json_version) override;
  13 + void writeJSON(int json_version, JSON::Writer& p) override;
  14 +
14 15 bool getVal() const;
15 16  
16 17 private:
... ...
libqpdf/qpdf/QPDF_Destroyed.hh
... ... @@ -9,7 +9,7 @@ class QPDF_Destroyed: public QPDFValue
9 9 ~QPDF_Destroyed() override = default;
10 10 std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
11 11 std::string unparse() override;
12   - JSON getJSON(int json_version) override;
  12 + void writeJSON(int json_version, JSON::Writer& p) override;
13 13 static std::shared_ptr<QPDFValue> getInstance();
14 14  
15 15 private:
... ...
libqpdf/qpdf/QPDF_Dictionary.hh
... ... @@ -16,7 +16,7 @@ class QPDF_Dictionary: public QPDFValue
16 16 static std::shared_ptr<QPDFObject> create(std::map<std::string, QPDFObjectHandle>&& items);
17 17 std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
18 18 std::string unparse() override;
19   - JSON getJSON(int json_version) override;
  19 + void writeJSON(int json_version, JSON::Writer& p) override;
20 20 void disconnect() override;
21 21  
22 22 // hasKey() and getKeys() treat keys with null values as if they aren't there. getKey() returns
... ...
libqpdf/qpdf/QPDF_InlineImage.hh
... ... @@ -10,7 +10,7 @@ class QPDF_InlineImage: public QPDFValue
10 10 static std::shared_ptr<QPDFObject> create(std::string const& val);
11 11 std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
12 12 std::string unparse() override;
13   - JSON getJSON(int json_version) override;
  13 + void writeJSON(int json_version, JSON::Writer& p) override;
14 14 std::string
15 15 getStringValue() const override
16 16 {
... ...
libqpdf/qpdf/QPDF_Integer.hh
... ... @@ -10,7 +10,7 @@ class QPDF_Integer: public QPDFValue
10 10 static std::shared_ptr<QPDFObject> create(long long value);
11 11 std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
12 12 std::string unparse() override;
13   - JSON getJSON(int json_version) override;
  13 + void writeJSON(int json_version, JSON::Writer& p) override;
14 14 long long getVal() const;
15 15  
16 16 private:
... ...
libqpdf/qpdf/QPDF_Name.hh
... ... @@ -10,10 +10,15 @@ class QPDF_Name: public QPDFValue
10 10 static std::shared_ptr<QPDFObject> create(std::string const& name);
11 11 std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
12 12 std::string unparse() override;
13   - JSON getJSON(int json_version) override;
  13 + void writeJSON(int json_version, JSON::Writer& p) override;
14 14  
15 15 // Put # into strings with characters unsuitable for name token
16 16 static std::string normalizeName(std::string const& name);
  17 +
  18 + // Check whether name is valid utf-8 and whether it contains characters that require escaping.
  19 + // Return {false, false} if the name is not valid utf-8, otherwise return {true, true} if no
  20 + // characters require or {true, false} if escaping is required.
  21 + static std::pair<bool, bool> analyzeJSONEncoding(std::string const& name);
17 22 std::string
18 23 getStringValue() const override
19 24 {
... ...
libqpdf/qpdf/QPDF_Null.hh
... ... @@ -18,7 +18,7 @@ class QPDF_Null: public QPDFValue
18 18 std::string var_descr);
19 19 std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
20 20 std::string unparse() override;
21   - JSON getJSON(int json_version) override;
  21 + void writeJSON(int json_version, JSON::Writer& p) override;
22 22  
23 23 private:
24 24 QPDF_Null();
... ...
libqpdf/qpdf/QPDF_Operator.hh
... ... @@ -10,7 +10,7 @@ class QPDF_Operator: public QPDFValue
10 10 static std::shared_ptr<QPDFObject> create(std::string const& val);
11 11 std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
12 12 std::string unparse() override;
13   - JSON getJSON(int json_version) override;
  13 + void writeJSON(int json_version, JSON::Writer& p) override;
14 14 std::string
15 15 getStringValue() const override
16 16 {
... ...
libqpdf/qpdf/QPDF_Real.hh
... ... @@ -12,7 +12,7 @@ class QPDF_Real: public QPDFValue
12 12 create(double value, int decimal_places, bool trim_trailing_zeroes);
13 13 std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
14 14 std::string unparse() override;
15   - JSON getJSON(int json_version) override;
  15 + void writeJSON(int json_version, JSON::Writer& p) override;
16 16 std::string
17 17 getStringValue() const override
18 18 {
... ...
libqpdf/qpdf/QPDF_Reserved.hh
... ... @@ -10,7 +10,7 @@ class QPDF_Reserved: public QPDFValue
10 10 static std::shared_ptr<QPDFObject> create();
11 11 std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
12 12 std::string unparse() override;
13   - JSON getJSON(int json_version) override;
  13 + void writeJSON(int json_version, JSON::Writer& p) override;
14 14  
15 15 private:
16 16 QPDF_Reserved();
... ...
libqpdf/qpdf/QPDF_Stream.hh
... ... @@ -25,7 +25,7 @@ class QPDF_Stream: public QPDFValue
25 25 size_t length);
26 26 std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
27 27 std::string unparse() override;
28   - JSON getJSON(int json_version) override;
  28 + void writeJSON(int json_version, JSON::Writer& p) override;
29 29 void setDescription(
30 30 QPDF*, std::shared_ptr<QPDFValue::Description>& description, qpdf_offset_t offset) override;
31 31 void disconnect() override;
... ... @@ -64,6 +64,14 @@ class QPDF_Stream: public QPDFValue
64 64 qpdf_stream_decode_level_e decode_level,
65 65 Pipeline* p,
66 66 std::string const& data_filename);
  67 + qpdf_stream_decode_level_e writeStreamJSON(
  68 + int json_version,
  69 + JSON::Writer& jw,
  70 + qpdf_json_stream_data_e json_data,
  71 + qpdf_stream_decode_level_e decode_level,
  72 + Pipeline* p,
  73 + std::string const& data_filename,
  74 + bool no_data_key = false);
67 75  
68 76 void replaceDict(QPDFObjectHandle const& new_dict);
69 77  
... ...
libqpdf/qpdf/QPDF_String.hh
... ... @@ -16,7 +16,7 @@ class QPDF_String: public QPDFValue
16 16 std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
17 17 std::string unparse() override;
18 18 std::string unparse(bool force_binary);
19   - JSON getJSON(int json_version) override;
  19 + void writeJSON(int json_version, JSON::Writer& p) override;
20 20 std::string getUTF8Val() const;
21 21 std::string
22 22 getStringValue() const override
... ...
libqpdf/qpdf/QPDF_Unresolved.hh
... ... @@ -10,7 +10,7 @@ class QPDF_Unresolved: public QPDFValue
10 10 static std::shared_ptr<QPDFObject> create(QPDF* qpdf, QPDFObjGen const& og);
11 11 std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
12 12 std::string unparse() override;
13   - JSON getJSON(int json_version) override;
  13 + void writeJSON(int json_version, JSON::Writer& p) override;
14 14  
15 15 private:
16 16 QPDF_Unresolved(QPDF* qpdf, QPDFObjGen const& og);
... ...
qpdf/qtest/many-nulls.test
... ... @@ -33,5 +33,22 @@ $td-&gt;runtest(&quot;copy sparse array&quot;,
33 33 {$td->COMMAND => "test_driver 97 many-nulls.pdf"},
34 34 {$td->STRING => "test 97 done\n", $td->EXIT_STATUS => 0},
35 35 $td->NORMALIZE_NEWLINES);
  36 +$td->runtest("copy file with many nulls",
  37 + {$td->COMMAND =>
  38 + "qpdf minimal-nulls.pdf --qdf --static-id --no-original-object-ids a.pdf"},
  39 + {$td->STRING => "", $td->EXIT_STATUS => 0},
  40 + $td->NORMALIZE_NEWLINES);
  41 +$td->runtest("compare files",
  42 + {$td->FILE => "a.pdf"},
  43 + {$td->FILE => "minimal-nulls.pdf"});
  44 +$td->runtest("file with many nulls to JSON v1",
  45 + {$td->COMMAND => "qpdf minimal-nulls.pdf --json=1 -"},
  46 + {$td->FILE => "minimal-nulls-1.json", $td->EXIT_STATUS => 0},
  47 + $td->NORMALIZE_NEWLINES);
  48 +$td->runtest("file with many nulls to JSON v2",
  49 + {$td->COMMAND => "qpdf minimal-nulls.pdf --json=2 -"},
  50 + {$td->FILE => "minimal-nulls-2.json", $td->EXIT_STATUS => 0},
  51 + $td->NORMALIZE_NEWLINES);
  52 +
36 53 cleanup();
37   -$td->report(4);
  54 +$td->report(8);
... ...
qpdf/qtest/qpdf-json.test
... ... @@ -350,7 +350,7 @@ $td-&gt;runtest(&quot;check C API write to JSON stream&quot;,
350 350 # (using #xx) would generate invalid JSON, even though qpdf's own JSON
351 351 # parser would accept it. Also, the JSON spec allows real numbers in
352 352 # scientific notation, but the PDF spec does not.
353   -$n_tests += 4;
  353 +$n_tests += 7;
354 354 $td->runtest("handle binary names",
355 355 {$td->COMMAND =>
356 356 "qpdf --json-output weird-tokens.pdf a.json"},
... ... @@ -371,6 +371,17 @@ $td-&gt;runtest(&quot;weird tokens with scientific notation&quot;,
371 371 "qpdf --json-input --json-output weird-tokens-alt.json -"},
372 372 {$td->FILE => "weird-tokens.json", $td->EXIT_STATUS => 0},
373 373 $td->NORMALIZE_NEWLINES);
374   -
  374 +$td->runtest("handle binary names (JSON v1)",
  375 + {$td->COMMAND =>
  376 + "qpdf --json=1 weird-tokens.pdf a.json"},
  377 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  378 +$td->runtest("check json",
  379 + {$td->FILE => "a.json"},
  380 + {$td->FILE => "weird-tokens-v1.json"},
  381 + $td->NORMALIZE_NEWLINES);
  382 +$td->runtest("write JSON to pipeline",
  383 + {$td->COMMAND => "test_driver 98 minimal.pdf ''"},
  384 + {$td->STRING => "test 98 done\n", $td->EXIT_STATUS => 0},
  385 + $td->NORMALIZE_NEWLINES);
375 386 cleanup();
376 387 $td->report($n_tests);
... ...
qpdf/qtest/qpdf/minimal-nulls-1.json 0 โ†’ 100644
  1 +{
  2 + "version": 1,
  3 + "parameters": {
  4 + "decodelevel": "generalized"
  5 + },
  6 + "pages": [
  7 + {
  8 + "contents": [
  9 + "4 0 R"
  10 + ],
  11 + "images": [],
  12 + "label": null,
  13 + "object": "3 0 R",
  14 + "outlines": [],
  15 + "pageposfrom1": 1
  16 + }
  17 + ],
  18 + "pagelabels": [],
  19 + "acroform": {
  20 + "fields": [],
  21 + "hasacroform": false,
  22 + "needappearances": false
  23 + },
  24 + "attachments": {},
  25 + "encrypt": {
  26 + "capabilities": {
  27 + "accessibility": true,
  28 + "extract": true,
  29 + "moddifyannotations": true,
  30 + "modify": true,
  31 + "modifyassembly": true,
  32 + "modifyforms": true,
  33 + "modifyother": true,
  34 + "printhigh": true,
  35 + "printlow": true
  36 + },
  37 + "encrypted": false,
  38 + "ownerpasswordmatched": false,
  39 + "parameters": {
  40 + "P": 0,
  41 + "R": 0,
  42 + "V": 0,
  43 + "bits": 0,
  44 + "filemethod": "none",
  45 + "key": null,
  46 + "method": "none",
  47 + "streammethod": "none",
  48 + "stringmethod": "none"
  49 + },
  50 + "recovereduserpassword": null,
  51 + "userpasswordmatched": false
  52 + },
  53 + "outlines": [],
  54 + "objects": {
  55 + "1 0 R": {
  56 + "/Pages": "2 0 R",
  57 + "/Type": "/Catalog"
  58 + },
  59 + "2 0 R": {
  60 + "/Count": 1,
  61 + "/Kids": [
  62 + "3 0 R"
  63 + ],
  64 + "/Type": "/Pages"
  65 + },
  66 + "3 0 R": {
  67 + "/Contents": "4 0 R",
  68 + "/MediaBox": [
  69 + 0,
  70 + 0,
  71 + 612,
  72 + 792
  73 + ],
  74 + "/Nulls": [
  75 + null,
  76 + null,
  77 + null,
  78 + null,
  79 + null,
  80 + null,
  81 + null,
  82 + null,
  83 + null,
  84 + null,
  85 + "6 0 R",
  86 + null,
  87 + null,
  88 + null,
  89 + null,
  90 + null,
  91 + null,
  92 + null,
  93 + null,
  94 + null,
  95 + null,
  96 + 10,
  97 + null,
  98 + null,
  99 + null,
  100 + null,
  101 + null,
  102 + null,
  103 + null,
  104 + null,
  105 + null,
  106 + null,
  107 + 10,
  108 + null,
  109 + null,
  110 + null,
  111 + null,
  112 + null,
  113 + null,
  114 + null,
  115 + null,
  116 + null,
  117 + null,
  118 + 10,
  119 + null,
  120 + null,
  121 + null,
  122 + null,
  123 + null,
  124 + null,
  125 + null,
  126 + null,
  127 + null,
  128 + null,
  129 + 10,
  130 + null,
  131 + null,
  132 + null,
  133 + null,
  134 + null,
  135 + null,
  136 + null,
  137 + null,
  138 + null,
  139 + null,
  140 + 10,
  141 + null,
  142 + null,
  143 + null,
  144 + null,
  145 + null,
  146 + null,
  147 + null,
  148 + null,
  149 + null,
  150 + null,
  151 + 10,
  152 + null,
  153 + null,
  154 + null,
  155 + null,
  156 + null,
  157 + null,
  158 + null,
  159 + null,
  160 + null,
  161 + null,
  162 + 10,
  163 + null,
  164 + null,
  165 + null,
  166 + null,
  167 + null,
  168 + null,
  169 + null,
  170 + null,
  171 + null,
  172 + null,
  173 + 10,
  174 + null,
  175 + null,
  176 + null,
  177 + null,
  178 + null,
  179 + null,
  180 + null,
  181 + null,
  182 + null,
  183 + null,
  184 + 10,
  185 + null,
  186 + null,
  187 + null,
  188 + null,
  189 + null,
  190 + null,
  191 + null,
  192 + null,
  193 + null,
  194 + null,
  195 + 10,
  196 + null,
  197 + null,
  198 + null,
  199 + null,
  200 + null,
  201 + null,
  202 + null,
  203 + null,
  204 + null,
  205 + null,
  206 + 10,
  207 + null,
  208 + null,
  209 + null,
  210 + null,
  211 + null,
  212 + null,
  213 + null,
  214 + null,
  215 + null,
  216 + null,
  217 + 10,
  218 + null,
  219 + null,
  220 + null,
  221 + null,
  222 + null,
  223 + null,
  224 + null,
  225 + null,
  226 + null,
  227 + null,
  228 + 10,
  229 + null,
  230 + null,
  231 + null,
  232 + null,
  233 + null,
  234 + null,
  235 + null,
  236 + null,
  237 + null,
  238 + null,
  239 + 10,
  240 + null,
  241 + null,
  242 + null,
  243 + null,
  244 + null,
  245 + null,
  246 + null,
  247 + null,
  248 + null,
  249 + null,
  250 + 10,
  251 + null,
  252 + null,
  253 + null,
  254 + null,
  255 + null,
  256 + null,
  257 + null,
  258 + null,
  259 + null,
  260 + null,
  261 + 10,
  262 + null,
  263 + null,
  264 + null,
  265 + null,
  266 + null,
  267 + null,
  268 + null,
  269 + null,
  270 + null,
  271 + null,
  272 + 10,
  273 + null,
  274 + null,
  275 + null,
  276 + null,
  277 + null,
  278 + null,
  279 + null,
  280 + null,
  281 + null,
  282 + null,
  283 + 10,
  284 + null,
  285 + null,
  286 + null,
  287 + null,
  288 + null,
  289 + null,
  290 + null,
  291 + null,
  292 + null,
  293 + null,
  294 + 10,
  295 + null,
  296 + null,
  297 + null,
  298 + null,
  299 + null,
  300 + null,
  301 + null,
  302 + null,
  303 + null,
  304 + null,
  305 + 10,
  306 + null,
  307 + null,
  308 + null,
  309 + null,
  310 + null,
  311 + null,
  312 + null,
  313 + null,
  314 + null,
  315 + null,
  316 + 10,
  317 + null,
  318 + null,
  319 + null,
  320 + null,
  321 + null,
  322 + null,
  323 + null,
  324 + null,
  325 + null,
  326 + null,
  327 + 10,
  328 + null,
  329 + null,
  330 + null,
  331 + null,
  332 + null,
  333 + null,
  334 + null,
  335 + null,
  336 + null,
  337 + null,
  338 + 10,
  339 + null,
  340 + null,
  341 + null,
  342 + null,
  343 + null,
  344 + null,
  345 + null,
  346 + null,
  347 + null,
  348 + null,
  349 + 10,
  350 + null,
  351 + null,
  352 + null,
  353 + null,
  354 + null,
  355 + null,
  356 + null,
  357 + null,
  358 + null,
  359 + null
  360 + ],
  361 + "/Parent": "2 0 R",
  362 + "/Resources": {
  363 + "/Font": {
  364 + "/F1": "7 0 R"
  365 + },
  366 + "/ProcSet": "8 0 R"
  367 + },
  368 + "/Type": "/Page"
  369 + },
  370 + "4 0 R": {
  371 + "/Length": "5 0 R"
  372 + },
  373 + "5 0 R": 44,
  374 + "6 0 R": null,
  375 + "7 0 R": {
  376 + "/BaseFont": "/Helvetica",
  377 + "/Encoding": "/WinAnsiEncoding",
  378 + "/Name": "/F1",
  379 + "/Subtype": "/Type1",
  380 + "/Type": "/Font"
  381 + },
  382 + "8 0 R": [
  383 + "/PDF",
  384 + "/Text"
  385 + ],
  386 + "trailer": {
  387 + "/ID": [
  388 + "รรฎgE๏ฟฝEMร›โ€นรŠรŸยข$ยฒ\u0005#",
  389 + "1AY&SXล ๏ฌ#โ€”bd3โ€ฆ'ล"
  390 + ],
  391 + "/Root": "1 0 R",
  392 + "/Size": 9
  393 + }
  394 + },
  395 + "objectinfo": {
  396 + "1 0 R": {
  397 + "stream": {
  398 + "filter": null,
  399 + "is": false,
  400 + "length": null
  401 + }
  402 + },
  403 + "2 0 R": {
  404 + "stream": {
  405 + "filter": null,
  406 + "is": false,
  407 + "length": null
  408 + }
  409 + },
  410 + "3 0 R": {
  411 + "stream": {
  412 + "filter": null,
  413 + "is": false,
  414 + "length": null
  415 + }
  416 + },
  417 + "4 0 R": {
  418 + "stream": {
  419 + "filter": null,
  420 + "is": true,
  421 + "length": 44
  422 + }
  423 + },
  424 + "5 0 R": {
  425 + "stream": {
  426 + "filter": null,
  427 + "is": false,
  428 + "length": null
  429 + }
  430 + },
  431 + "6 0 R": {
  432 + "stream": {
  433 + "filter": null,
  434 + "is": false,
  435 + "length": null
  436 + }
  437 + },
  438 + "7 0 R": {
  439 + "stream": {
  440 + "filter": null,
  441 + "is": false,
  442 + "length": null
  443 + }
  444 + },
  445 + "8 0 R": {
  446 + "stream": {
  447 + "filter": null,
  448 + "is": false,
  449 + "length": null
  450 + }
  451 + }
  452 + }
  453 +}
... ...
qpdf/qtest/qpdf/minimal-nulls-2.json 0 โ†’ 100644
  1 +{
  2 + "version": 2,
  3 + "parameters": {
  4 + "decodelevel": "generalized"
  5 + },
  6 + "pages": [
  7 + {
  8 + "contents": [
  9 + "4 0 R"
  10 + ],
  11 + "images": [],
  12 + "label": null,
  13 + "object": "3 0 R",
  14 + "outlines": [],
  15 + "pageposfrom1": 1
  16 + }
  17 + ],
  18 + "pagelabels": [],
  19 + "acroform": {
  20 + "fields": [],
  21 + "hasacroform": false,
  22 + "needappearances": false
  23 + },
  24 + "attachments": {},
  25 + "encrypt": {
  26 + "capabilities": {
  27 + "accessibility": true,
  28 + "extract": true,
  29 + "modify": true,
  30 + "modifyannotations": true,
  31 + "modifyassembly": true,
  32 + "modifyforms": true,
  33 + "modifyother": true,
  34 + "printhigh": true,
  35 + "printlow": true
  36 + },
  37 + "encrypted": false,
  38 + "ownerpasswordmatched": false,
  39 + "parameters": {
  40 + "P": 0,
  41 + "R": 0,
  42 + "V": 0,
  43 + "bits": 0,
  44 + "filemethod": "none",
  45 + "key": null,
  46 + "method": "none",
  47 + "streammethod": "none",
  48 + "stringmethod": "none"
  49 + },
  50 + "recovereduserpassword": null,
  51 + "userpasswordmatched": false
  52 + },
  53 + "outlines": [],
  54 + "qpdf": [
  55 + {
  56 + "jsonversion": 2,
  57 + "pdfversion": "1.3",
  58 + "pushedinheritedpageresources": false,
  59 + "calledgetallpages": true,
  60 + "maxobjectid": 8
  61 + },
  62 + {
  63 + "obj:1 0 R": {
  64 + "value": {
  65 + "/Pages": "2 0 R",
  66 + "/Type": "/Catalog"
  67 + }
  68 + },
  69 + "obj:2 0 R": {
  70 + "value": {
  71 + "/Count": 1,
  72 + "/Kids": [
  73 + "3 0 R"
  74 + ],
  75 + "/Type": "/Pages"
  76 + }
  77 + },
  78 + "obj:3 0 R": {
  79 + "value": {
  80 + "/Contents": "4 0 R",
  81 + "/MediaBox": [
  82 + 0,
  83 + 0,
  84 + 612,
  85 + 792
  86 + ],
  87 + "/Nulls": [
  88 + null,
  89 + null,
  90 + null,
  91 + null,
  92 + null,
  93 + null,
  94 + null,
  95 + null,
  96 + null,
  97 + null,
  98 + "6 0 R",
  99 + null,
  100 + null,
  101 + null,
  102 + null,
  103 + null,
  104 + null,
  105 + null,
  106 + null,
  107 + null,
  108 + null,
  109 + 10,
  110 + null,
  111 + null,
  112 + null,
  113 + null,
  114 + null,
  115 + null,
  116 + null,
  117 + null,
  118 + null,
  119 + null,
  120 + 10,
  121 + null,
  122 + null,
  123 + null,
  124 + null,
  125 + null,
  126 + null,
  127 + null,
  128 + null,
  129 + null,
  130 + null,
  131 + 10,
  132 + null,
  133 + null,
  134 + null,
  135 + null,
  136 + null,
  137 + null,
  138 + null,
  139 + null,
  140 + null,
  141 + null,
  142 + 10,
  143 + null,
  144 + null,
  145 + null,
  146 + null,
  147 + null,
  148 + null,
  149 + null,
  150 + null,
  151 + null,
  152 + null,
  153 + 10,
  154 + null,
  155 + null,
  156 + null,
  157 + null,
  158 + null,
  159 + null,
  160 + null,
  161 + null,
  162 + null,
  163 + null,
  164 + 10,
  165 + null,
  166 + null,
  167 + null,
  168 + null,
  169 + null,
  170 + null,
  171 + null,
  172 + null,
  173 + null,
  174 + null,
  175 + 10,
  176 + null,
  177 + null,
  178 + null,
  179 + null,
  180 + null,
  181 + null,
  182 + null,
  183 + null,
  184 + null,
  185 + null,
  186 + 10,
  187 + null,
  188 + null,
  189 + null,
  190 + null,
  191 + null,
  192 + null,
  193 + null,
  194 + null,
  195 + null,
  196 + null,
  197 + 10,
  198 + null,
  199 + null,
  200 + null,
  201 + null,
  202 + null,
  203 + null,
  204 + null,
  205 + null,
  206 + null,
  207 + null,
  208 + 10,
  209 + null,
  210 + null,
  211 + null,
  212 + null,
  213 + null,
  214 + null,
  215 + null,
  216 + null,
  217 + null,
  218 + null,
  219 + 10,
  220 + null,
  221 + null,
  222 + null,
  223 + null,
  224 + null,
  225 + null,
  226 + null,
  227 + null,
  228 + null,
  229 + null,
  230 + 10,
  231 + null,
  232 + null,
  233 + null,
  234 + null,
  235 + null,
  236 + null,
  237 + null,
  238 + null,
  239 + null,
  240 + null,
  241 + 10,
  242 + null,
  243 + null,
  244 + null,
  245 + null,
  246 + null,
  247 + null,
  248 + null,
  249 + null,
  250 + null,
  251 + null,
  252 + 10,
  253 + null,
  254 + null,
  255 + null,
  256 + null,
  257 + null,
  258 + null,
  259 + null,
  260 + null,
  261 + null,
  262 + null,
  263 + 10,
  264 + null,
  265 + null,
  266 + null,
  267 + null,
  268 + null,
  269 + null,
  270 + null,
  271 + null,
  272 + null,
  273 + null,
  274 + 10,
  275 + null,
  276 + null,
  277 + null,
  278 + null,
  279 + null,
  280 + null,
  281 + null,
  282 + null,
  283 + null,
  284 + null,
  285 + 10,
  286 + null,
  287 + null,
  288 + null,
  289 + null,
  290 + null,
  291 + null,
  292 + null,
  293 + null,
  294 + null,
  295 + null,
  296 + 10,
  297 + null,
  298 + null,
  299 + null,
  300 + null,
  301 + null,
  302 + null,
  303 + null,
  304 + null,
  305 + null,
  306 + null,
  307 + 10,
  308 + null,
  309 + null,
  310 + null,
  311 + null,
  312 + null,
  313 + null,
  314 + null,
  315 + null,
  316 + null,
  317 + null,
  318 + 10,
  319 + null,
  320 + null,
  321 + null,
  322 + null,
  323 + null,
  324 + null,
  325 + null,
  326 + null,
  327 + null,
  328 + null,
  329 + 10,
  330 + null,
  331 + null,
  332 + null,
  333 + null,
  334 + null,
  335 + null,
  336 + null,
  337 + null,
  338 + null,
  339 + null,
  340 + 10,
  341 + null,
  342 + null,
  343 + null,
  344 + null,
  345 + null,
  346 + null,
  347 + null,
  348 + null,
  349 + null,
  350 + null,
  351 + 10,
  352 + null,
  353 + null,
  354 + null,
  355 + null,
  356 + null,
  357 + null,
  358 + null,
  359 + null,
  360 + null,
  361 + null,
  362 + 10,
  363 + null,
  364 + null,
  365 + null,
  366 + null,
  367 + null,
  368 + null,
  369 + null,
  370 + null,
  371 + null,
  372 + null
  373 + ],
  374 + "/Parent": "2 0 R",
  375 + "/Resources": {
  376 + "/Font": {
  377 + "/F1": "7 0 R"
  378 + },
  379 + "/ProcSet": "8 0 R"
  380 + },
  381 + "/Type": "/Page"
  382 + }
  383 + },
  384 + "obj:4 0 R": {
  385 + "stream": {
  386 + "dict": {
  387 + "/Length": "5 0 R"
  388 + }
  389 + }
  390 + },
  391 + "obj:5 0 R": {
  392 + "value": 44
  393 + },
  394 + "obj:6 0 R": {
  395 + "value": null
  396 + },
  397 + "obj:7 0 R": {
  398 + "value": {
  399 + "/BaseFont": "/Helvetica",
  400 + "/Encoding": "/WinAnsiEncoding",
  401 + "/Name": "/F1",
  402 + "/Subtype": "/Type1",
  403 + "/Type": "/Font"
  404 + }
  405 + },
  406 + "obj:8 0 R": {
  407 + "value": [
  408 + "/PDF",
  409 + "/Text"
  410 + ]
  411 + },
  412 + "trailer": {
  413 + "value": {
  414 + "/ID": [
  415 + "b:cfee6745ad454ddb88cadfa224b20523",
  416 + "b:31415926535897932384626433832795"
  417 + ],
  418 + "/Root": "1 0 R",
  419 + "/Size": 9
  420 + }
  421 + }
  422 + }
  423 + ]
  424 +}
... ...
qpdf/qtest/qpdf/minimal-nulls.pdf 0 โ†’ 100644
  1 +%PDF-1.3
  2 +%ยฟรทยขรพ
  3 +%QDF-1.0
  4 +
  5 +1 0 obj
  6 +<<
  7 + /Pages 2 0 R
  8 + /Type /Catalog
  9 +>>
  10 +endobj
  11 +
  12 +2 0 obj
  13 +<<
  14 + /Count 1
  15 + /Kids [
  16 + 3 0 R
  17 + ]
  18 + /Type /Pages
  19 +>>
  20 +endobj
  21 +
  22 +%% Page 1
  23 +3 0 obj
  24 +<<
  25 + /Contents 4 0 R
  26 + /MediaBox [
  27 + 0
  28 + 0
  29 + 612
  30 + 792
  31 + ]
  32 + /Nulls [
  33 + null
  34 + null
  35 + null
  36 + null
  37 + null
  38 + null
  39 + null
  40 + null
  41 + null
  42 + null
  43 + 6 0 R
  44 + null
  45 + null
  46 + null
  47 + null
  48 + null
  49 + null
  50 + null
  51 + null
  52 + null
  53 + null
  54 + 10
  55 + null
  56 + null
  57 + null
  58 + null
  59 + null
  60 + null
  61 + null
  62 + null
  63 + null
  64 + null
  65 + 10
  66 + null
  67 + null
  68 + null
  69 + null
  70 + null
  71 + null
  72 + null
  73 + null
  74 + null
  75 + null
  76 + 10
  77 + null
  78 + null
  79 + null
  80 + null
  81 + null
  82 + null
  83 + null
  84 + null
  85 + null
  86 + null
  87 + 10
  88 + null
  89 + null
  90 + null
  91 + null
  92 + null
  93 + null
  94 + null
  95 + null
  96 + null
  97 + null
  98 + 10
  99 + null
  100 + null
  101 + null
  102 + null
  103 + null
  104 + null
  105 + null
  106 + null
  107 + null
  108 + null
  109 + 10
  110 + null
  111 + null
  112 + null
  113 + null
  114 + null
  115 + null
  116 + null
  117 + null
  118 + null
  119 + null
  120 + 10
  121 + null
  122 + null
  123 + null
  124 + null
  125 + null
  126 + null
  127 + null
  128 + null
  129 + null
  130 + null
  131 + 10
  132 + null
  133 + null
  134 + null
  135 + null
  136 + null
  137 + null
  138 + null
  139 + null
  140 + null
  141 + null
  142 + 10
  143 + null
  144 + null
  145 + null
  146 + null
  147 + null
  148 + null
  149 + null
  150 + null
  151 + null
  152 + null
  153 + 10
  154 + null
  155 + null
  156 + null
  157 + null
  158 + null
  159 + null
  160 + null
  161 + null
  162 + null
  163 + null
  164 + 10
  165 + null
  166 + null
  167 + null
  168 + null
  169 + null
  170 + null
  171 + null
  172 + null
  173 + null
  174 + null
  175 + 10
  176 + null
  177 + null
  178 + null
  179 + null
  180 + null
  181 + null
  182 + null
  183 + null
  184 + null
  185 + null
  186 + 10
  187 + null
  188 + null
  189 + null
  190 + null
  191 + null
  192 + null
  193 + null
  194 + null
  195 + null
  196 + null
  197 + 10
  198 + null
  199 + null
  200 + null
  201 + null
  202 + null
  203 + null
  204 + null
  205 + null
  206 + null
  207 + null
  208 + 10
  209 + null
  210 + null
  211 + null
  212 + null
  213 + null
  214 + null
  215 + null
  216 + null
  217 + null
  218 + null
  219 + 10
  220 + null
  221 + null
  222 + null
  223 + null
  224 + null
  225 + null
  226 + null
  227 + null
  228 + null
  229 + null
  230 + 10
  231 + null
  232 + null
  233 + null
  234 + null
  235 + null
  236 + null
  237 + null
  238 + null
  239 + null
  240 + null
  241 + 10
  242 + null
  243 + null
  244 + null
  245 + null
  246 + null
  247 + null
  248 + null
  249 + null
  250 + null
  251 + null
  252 + 10
  253 + null
  254 + null
  255 + null
  256 + null
  257 + null
  258 + null
  259 + null
  260 + null
  261 + null
  262 + null
  263 + 10
  264 + null
  265 + null
  266 + null
  267 + null
  268 + null
  269 + null
  270 + null
  271 + null
  272 + null
  273 + null
  274 + 10
  275 + null
  276 + null
  277 + null
  278 + null
  279 + null
  280 + null
  281 + null
  282 + null
  283 + null
  284 + null
  285 + 10
  286 + null
  287 + null
  288 + null
  289 + null
  290 + null
  291 + null
  292 + null
  293 + null
  294 + null
  295 + null
  296 + 10
  297 + null
  298 + null
  299 + null
  300 + null
  301 + null
  302 + null
  303 + null
  304 + null
  305 + null
  306 + null
  307 + 10
  308 + null
  309 + null
  310 + null
  311 + null
  312 + null
  313 + null
  314 + null
  315 + null
  316 + null
  317 + null
  318 + ]
  319 + /Parent 2 0 R
  320 + /Resources <<
  321 + /Font <<
  322 + /F1 7 0 R
  323 + >>
  324 + /ProcSet 8 0 R
  325 + >>
  326 + /Type /Page
  327 +>>
  328 +endobj
  329 +
  330 +%% Contents for page 1
  331 +4 0 obj
  332 +<<
  333 + /Length 5 0 R
  334 +>>
  335 +stream
  336 +BT
  337 + /F1 24 Tf
  338 + 72 720 Td
  339 + (Potato) Tj
  340 +ET
  341 +endstream
  342 +endobj
  343 +
  344 +5 0 obj
  345 +44
  346 +endobj
  347 +
  348 +6 0 obj
  349 +null
  350 +endobj
  351 +
  352 +7 0 obj
  353 +<<
  354 + /BaseFont /Helvetica
  355 + /Encoding /WinAnsiEncoding
  356 + /Name /F1
  357 + /Subtype /Type1
  358 + /Type /Font
  359 +>>
  360 +endobj
  361 +
  362 +8 0 obj
  363 +[
  364 + /PDF
  365 + /Text
  366 +]
  367 +endobj
  368 +
  369 +xref
  370 +0 9
  371 +0000000000 65535 f
  372 +0000000025 00000 n
  373 +0000000079 00000 n
  374 +0000000161 00000 n
  375 +0000002909 00000 n
  376 +0000003008 00000 n
  377 +0000003027 00000 n
  378 +0000003048 00000 n
  379 +0000003166 00000 n
  380 +trailer <<
  381 + /Root 1 0 R
  382 + /Size 9
  383 + /ID [<cfee6745ad454ddb88cadfa224b20523><31415926535897932384626433832795>]
  384 +>>
  385 +startxref
  386 +3201
  387 +%%EOF
... ...
qpdf/qtest/qpdf/weird-tokens-alt.json
... ... @@ -10,21 +10,155 @@
10 10 {
11 11 "obj:1 0 R": {
12 12 "value": {
  13 + "/Escape\\Key": 42,
13 14 "/Extra": [
14 15 "u:Names with binary data",
15 16 "n:/ABCDEF+#ba#da#cc#e5",
16 17 "n:/OVERLONG+#c0#81",
  18 + "n:/OVERLONG+#c1#ff",
  19 + "/Ok+ย€",
17 20 "n:/OVERLONG+#e0#81#82",
  21 + "n:/OVERLONG+#e0#9f#ff",
  22 + "/Ok+เ €",
18 23 "n:/OVERLONG+#f0#81#82#83",
  24 + "n:/OVERLONG+#f0#8f#ff#ff",
  25 + "/Ok+๐€€",
19 26 "n:/range+#01",
20 27 "n:/low+#18",
21 28 "/ABCEDEF+ฯ€",
22 29 "n:/one+#a0two",
23 30 "n:/text#2fplain",
  31 + "u:Names requiring escaping in JSON",
  32 + "/Back\\shlash",
  33 + "/Low\u0022",
  34 + "/Low\u001f",
  35 + "/ExceptSpace ",
  36 + "/Except!",
24 37 "u:Very small/large reals",
25 38 1e-05,
26 39 1e12
27 40 ],
  41 + "/Nested": {
  42 + "/1": {
  43 + "/2": {
  44 + "/3": {
  45 + "/4": {
  46 + "/5": {
  47 + "/6": {
  48 + "/7": {
  49 + "/8": {
  50 + "/9": {
  51 + "/10": {
  52 + "/1": {
  53 + "/2": {
  54 + "/3": {
  55 + "/4": {
  56 + "/5": {
  57 + "/6": {
  58 + "/7": {
  59 + "/8": {
  60 + "/9": {
  61 + "/10": {
  62 + "/1": {
  63 + "/2": {
  64 + "/3": {
  65 + "/4": {
  66 + "/5": {
  67 + "/6": {
  68 + "/7": {
  69 + "/8": {
  70 + "/9": {
  71 + "/10": {
  72 + "/1": {
  73 + "/2": {
  74 + "/3": {
  75 + "/4": {
  76 + "/5": {
  77 + "/6": {
  78 + "/7": {
  79 + "/8": {
  80 + "/9": {
  81 + "/10": {
  82 + "/1": {
  83 + "/2": {
  84 + "/3": {
  85 + "/4": {
  86 + "/5": {
  87 + "/6": {
  88 + "/7": {
  89 + "/8": {
  90 + "/9": {
  91 + "/10": {
  92 + "/1": {
  93 + "/2": {
  94 + "/3": {
  95 + "/4": {
  96 + "/5": {
  97 + "/6": {
  98 + "/7": {
  99 + "/8": {
  100 + "/9": {
  101 + "/10": 42
  102 + }
  103 + }
  104 + }
  105 + }
  106 + }
  107 + }
  108 + }
  109 + }
  110 + }
  111 + }
  112 + }
  113 + }
  114 + }
  115 + }
  116 + }
  117 + }
  118 + }
  119 + }
  120 + }
  121 + }
  122 + }
  123 + }
  124 + }
  125 + }
  126 + }
  127 + }
  128 + }
  129 + }
  130 + }
  131 + }
  132 + }
  133 + }
  134 + }
  135 + }
  136 + }
  137 + }
  138 + }
  139 + }
  140 + }
  141 + }
  142 + }
  143 + }
  144 + }
  145 + }
  146 + }
  147 + }
  148 + }
  149 + }
  150 + }
  151 + }
  152 + }
  153 + }
  154 + }
  155 + }
  156 + }
  157 + }
  158 + }
  159 + }
  160 + }
  161 + },
28 162 "/Pages": "2 0 R",
29 163 "/Type": "/Catalog",
30 164 "n:/WeirdKey+#ba#da#cc#e5": 42
... ... @@ -78,7 +212,7 @@
78 212 "value": {
79 213 "/ID": [
80 214 "b:42841c13bbf709d79a200fa1691836f8",
81   - "b:728c020f464c3cf7e02c12605fa7d88b"
  215 + "b:31415926535897932384626433832795"
82 216 ],
83 217 "/Root": "1 0 R",
84 218 "/Size": 7
... ...
qpdf/qtest/qpdf/weird-tokens-v1.json 0 โ†’ 100644
  1 +{
  2 + "version": 1,
  3 + "parameters": {
  4 + "decodelevel": "generalized"
  5 + },
  6 + "pages": [
  7 + {
  8 + "contents": [
  9 + "4 0 R"
  10 + ],
  11 + "images": [],
  12 + "label": null,
  13 + "object": "3 0 R",
  14 + "outlines": [],
  15 + "pageposfrom1": 1
  16 + }
  17 + ],
  18 + "pagelabels": [],
  19 + "acroform": {
  20 + "fields": [],
  21 + "hasacroform": false,
  22 + "needappearances": false
  23 + },
  24 + "attachments": {},
  25 + "encrypt": {
  26 + "capabilities": {
  27 + "accessibility": true,
  28 + "extract": true,
  29 + "moddifyannotations": true,
  30 + "modify": true,
  31 + "modifyassembly": true,
  32 + "modifyforms": true,
  33 + "modifyother": true,
  34 + "printhigh": true,
  35 + "printlow": true
  36 + },
  37 + "encrypted": false,
  38 + "ownerpasswordmatched": false,
  39 + "parameters": {
  40 + "P": 0,
  41 + "R": 0,
  42 + "V": 0,
  43 + "bits": 0,
  44 + "filemethod": "none",
  45 + "key": null,
  46 + "method": "none",
  47 + "streammethod": "none",
  48 + "stringmethod": "none"
  49 + },
  50 + "recovereduserpassword": null,
  51 + "userpasswordmatched": false
  52 + },
  53 + "outlines": [],
  54 + "objects": {
  55 + "1 0 R": {
  56 + "/Escape\\Key": 42,
  57 + "/Extra": [
  58 + "Names with binary data",
  59 + "/ABCDEF+#ba#da#cc#e5",
  60 + "/OVERLONG+#c0#81",
  61 + "/OVERLONG+#c1#ff",
  62 + "/Ok+#c2#80",
  63 + "/OVERLONG+#e0#81#82",
  64 + "/OVERLONG+#e0#9f#ff",
  65 + "/Ok+#e0#a0#80",
  66 + "/OVERLONG+#f0#81#82#83",
  67 + "/OVERLONG+#f0#8f#ff#ff",
  68 + "/Ok+#f0#90#80#80",
  69 + "/range+#01",
  70 + "/low+#18",
  71 + "/ABCEDEF+#cf#80",
  72 + "/one+#a0two",
  73 + "/text#2fplain",
  74 + "Names requiring escaping in JSON",
  75 + "/Back\\shlash",
  76 + "/Low\"",
  77 + "/Low#1f",
  78 + "/ExceptSpace#20",
  79 + "/Except!",
  80 + "Very small/large reals",
  81 + 0.00001,
  82 + 1000000000000
  83 + ],
  84 + "/Nested": {
  85 + "/1": {
  86 + "/2": {
  87 + "/3": {
  88 + "/4": {
  89 + "/5": {
  90 + "/6": {
  91 + "/7": {
  92 + "/8": {
  93 + "/9": {
  94 + "/10": {
  95 + "/1": {
  96 + "/2": {
  97 + "/3": {
  98 + "/4": {
  99 + "/5": {
  100 + "/6": {
  101 + "/7": {
  102 + "/8": {
  103 + "/9": {
  104 + "/10": {
  105 + "/1": {
  106 + "/2": {
  107 + "/3": {
  108 + "/4": {
  109 + "/5": {
  110 + "/6": {
  111 + "/7": {
  112 + "/8": {
  113 + "/9": {
  114 + "/10": {
  115 + "/1": {
  116 + "/2": {
  117 + "/3": {
  118 + "/4": {
  119 + "/5": {
  120 + "/6": {
  121 + "/7": {
  122 + "/8": {
  123 + "/9": {
  124 + "/10": {
  125 + "/1": {
  126 + "/2": {
  127 + "/3": {
  128 + "/4": {
  129 + "/5": {
  130 + "/6": {
  131 + "/7": {
  132 + "/8": {
  133 + "/9": {
  134 + "/10": {
  135 + "/1": {
  136 + "/2": {
  137 + "/3": {
  138 + "/4": {
  139 + "/5": {
  140 + "/6": {
  141 + "/7": {
  142 + "/8": {
  143 + "/9": {
  144 + "/10": 42
  145 + }
  146 + }
  147 + }
  148 + }
  149 + }
  150 + }
  151 + }
  152 + }
  153 + }
  154 + }
  155 + }
  156 + }
  157 + }
  158 + }
  159 + }
  160 + }
  161 + }
  162 + }
  163 + }
  164 + }
  165 + }
  166 + }
  167 + }
  168 + }
  169 + }
  170 + }
  171 + }
  172 + }
  173 + }
  174 + }
  175 + }
  176 + }
  177 + }
  178 + }
  179 + }
  180 + }
  181 + }
  182 + }
  183 + }
  184 + }
  185 + }
  186 + }
  187 + }
  188 + }
  189 + }
  190 + }
  191 + }
  192 + }
  193 + }
  194 + }
  195 + }
  196 + }
  197 + }
  198 + }
  199 + }
  200 + }
  201 + }
  202 + }
  203 + }
  204 + },
  205 + "/Pages": "2 0 R",
  206 + "/Type": "/Catalog",
  207 + "/WeirdKey+#ba#da#cc#e5": 42
  208 + },
  209 + "2 0 R": {
  210 + "/Count": 1,
  211 + "/Kids": [
  212 + "3 0 R"
  213 + ],
  214 + "/Type": "/Pages"
  215 + },
  216 + "3 0 R": {
  217 + "/Contents": "4 0 R",
  218 + "/MediaBox": [
  219 + 0,
  220 + 0,
  221 + 612,
  222 + 792
  223 + ],
  224 + "/Parent": "2 0 R",
  225 + "/Resources": {
  226 + "/Font": {
  227 + "/F1": "6 0 R"
  228 + }
  229 + },
  230 + "/Type": "/Page"
  231 + },
  232 + "4 0 R": {
  233 + "/Length": "5 0 R"
  234 + },
  235 + "5 0 R": 44,
  236 + "6 0 R": {
  237 + "/BaseFont": "/Helvetica",
  238 + "/Encoding": "/WinAnsiEncoding",
  239 + "/Subtype": "/Type1",
  240 + "/Type": "/Font"
  241 + },
  242 + "trailer": {
  243 + "/ID": [
  244 + "Bโ€”ห\u0013ยปรท\tร—ฤฑ \u000fยกiห˜6รธ",
  245 + "1AY&SXล ๏ฌ#โ€”bd3โ€ฆ'ล"
  246 + ],
  247 + "/Root": "1 0 R",
  248 + "/Size": 7
  249 + }
  250 + },
  251 + "objectinfo": {
  252 + "1 0 R": {
  253 + "stream": {
  254 + "filter": null,
  255 + "is": false,
  256 + "length": null
  257 + }
  258 + },
  259 + "2 0 R": {
  260 + "stream": {
  261 + "filter": null,
  262 + "is": false,
  263 + "length": null
  264 + }
  265 + },
  266 + "3 0 R": {
  267 + "stream": {
  268 + "filter": null,
  269 + "is": false,
  270 + "length": null
  271 + }
  272 + },
  273 + "4 0 R": {
  274 + "stream": {
  275 + "filter": null,
  276 + "is": true,
  277 + "length": 44
  278 + }
  279 + },
  280 + "5 0 R": {
  281 + "stream": {
  282 + "filter": null,
  283 + "is": false,
  284 + "length": null
  285 + }
  286 + },
  287 + "6 0 R": {
  288 + "stream": {
  289 + "filter": null,
  290 + "is": false,
  291 + "length": null
  292 + }
  293 + }
  294 + }
  295 +}
... ...
qpdf/qtest/qpdf/weird-tokens.json
... ... @@ -10,21 +10,155 @@
10 10 {
11 11 "obj:1 0 R": {
12 12 "value": {
  13 + "/Escape\\Key": 42,
13 14 "/Extra": [
14 15 "u:Names with binary data",
15 16 "n:/ABCDEF+#ba#da#cc#e5",
16 17 "n:/OVERLONG+#c0#81",
  18 + "n:/OVERLONG+#c1#ff",
  19 + "/Ok+ย€",
17 20 "n:/OVERLONG+#e0#81#82",
  21 + "n:/OVERLONG+#e0#9f#ff",
  22 + "/Ok+เ €",
18 23 "n:/OVERLONG+#f0#81#82#83",
  24 + "n:/OVERLONG+#f0#8f#ff#ff",
  25 + "/Ok+๐€€",
19 26 "/range+\u0001",
20 27 "/low+\u0018",
21 28 "/ABCEDEF+ฯ€",
22 29 "n:/one+#a0two",
23 30 "/text/plain",
  31 + "u:Names requiring escaping in JSON",
  32 + "/Back\\shlash",
  33 + "/Low\"",
  34 + "/Low\u001f",
  35 + "/ExceptSpace ",
  36 + "/Except!",
24 37 "u:Very small/large reals",
25 38 0.00001,
26 39 1000000000000
27 40 ],
  41 + "/Nested": {
  42 + "/1": {
  43 + "/2": {
  44 + "/3": {
  45 + "/4": {
  46 + "/5": {
  47 + "/6": {
  48 + "/7": {
  49 + "/8": {
  50 + "/9": {
  51 + "/10": {
  52 + "/1": {
  53 + "/2": {
  54 + "/3": {
  55 + "/4": {
  56 + "/5": {
  57 + "/6": {
  58 + "/7": {
  59 + "/8": {
  60 + "/9": {
  61 + "/10": {
  62 + "/1": {
  63 + "/2": {
  64 + "/3": {
  65 + "/4": {
  66 + "/5": {
  67 + "/6": {
  68 + "/7": {
  69 + "/8": {
  70 + "/9": {
  71 + "/10": {
  72 + "/1": {
  73 + "/2": {
  74 + "/3": {
  75 + "/4": {
  76 + "/5": {
  77 + "/6": {
  78 + "/7": {
  79 + "/8": {
  80 + "/9": {
  81 + "/10": {
  82 + "/1": {
  83 + "/2": {
  84 + "/3": {
  85 + "/4": {
  86 + "/5": {
  87 + "/6": {
  88 + "/7": {
  89 + "/8": {
  90 + "/9": {
  91 + "/10": {
  92 + "/1": {
  93 + "/2": {
  94 + "/3": {
  95 + "/4": {
  96 + "/5": {
  97 + "/6": {
  98 + "/7": {
  99 + "/8": {
  100 + "/9": {
  101 + "/10": 42
  102 + }
  103 + }
  104 + }
  105 + }
  106 + }
  107 + }
  108 + }
  109 + }
  110 + }
  111 + }
  112 + }
  113 + }
  114 + }
  115 + }
  116 + }
  117 + }
  118 + }
  119 + }
  120 + }
  121 + }
  122 + }
  123 + }
  124 + }
  125 + }
  126 + }
  127 + }
  128 + }
  129 + }
  130 + }
  131 + }
  132 + }
  133 + }
  134 + }
  135 + }
  136 + }
  137 + }
  138 + }
  139 + }
  140 + }
  141 + }
  142 + }
  143 + }
  144 + }
  145 + }
  146 + }
  147 + }
  148 + }
  149 + }
  150 + }
  151 + }
  152 + }
  153 + }
  154 + }
  155 + }
  156 + }
  157 + }
  158 + }
  159 + }
  160 + }
  161 + },
28 162 "/Pages": "2 0 R",
29 163 "/Type": "/Catalog",
30 164 "n:/WeirdKey+#ba#da#cc#e5": 42
... ... @@ -78,7 +212,7 @@
78 212 "value": {
79 213 "/ID": [
80 214 "b:42841c13bbf709d79a200fa1691836f8",
81   - "b:728c020f464c3cf7e02c12605fa7d88b"
  215 + "b:31415926535897932384626433832795"
82 216 ],
83 217 "/Root": "1 0 R",
84 218 "/Size": 7
... ...
qpdf/qtest/qpdf/weird-tokens.pdf
... ... @@ -4,21 +4,155 @@
4 4  
5 5 1 0 obj
6 6 <<
  7 + /Escape\Key 42
7 8 /Extra [
8 9 (Names with binary data)
9 10 /ABCDEF+#ba#da#cc#e5
10 11 /OVERLONG+#c0#81
  12 + /OVERLONG+#c1#ff
  13 + /Ok+#c2#80
11 14 /OVERLONG+#e0#81#82
  15 + /OVERLONG+#e0#9f#ff
  16 + /Ok+#e0#a0#80
12 17 /OVERLONG+#f0#81#82#83
  18 + /OVERLONG+#f0#8f#ff#ff
  19 + /Ok+#f0#90#80#80
13 20 /range+#01
14 21 /low+#18
15 22 /ABCEDEF+#cf#80
16 23 /one+#a0two
17 24 /text#2fplain
  25 + (Names requiring escaping in JSON)
  26 + /Back\shlash
  27 + /Low"
  28 + /Low#1f
  29 + /ExceptSpace#20
  30 + /Except!
18 31 (Very small/large reals)
19 32 0.00001
20 33 1000000000000
21 34 ]
  35 + /Nested <<
  36 + /1 <<
  37 + /2 <<
  38 + /3 <<
  39 + /4 <<
  40 + /5 <<
  41 + /6 <<
  42 + /7 <<
  43 + /8 <<
  44 + /9 <<
  45 + /10 <<
  46 + /1 <<
  47 + /2 <<
  48 + /3 <<
  49 + /4 <<
  50 + /5 <<
  51 + /6 <<
  52 + /7 <<
  53 + /8 <<
  54 + /9 <<
  55 + /10 <<
  56 + /1 <<
  57 + /2 <<
  58 + /3 <<
  59 + /4 <<
  60 + /5 <<
  61 + /6 <<
  62 + /7 <<
  63 + /8 <<
  64 + /9 <<
  65 + /10 <<
  66 + /1 <<
  67 + /2 <<
  68 + /3 <<
  69 + /4 <<
  70 + /5 <<
  71 + /6 <<
  72 + /7 <<
  73 + /8 <<
  74 + /9 <<
  75 + /10 <<
  76 + /1 <<
  77 + /2 <<
  78 + /3 <<
  79 + /4 <<
  80 + /5 <<
  81 + /6 <<
  82 + /7 <<
  83 + /8 <<
  84 + /9 <<
  85 + /10 <<
  86 + /1 <<
  87 + /2 <<
  88 + /3 <<
  89 + /4 <<
  90 + /5 <<
  91 + /6 <<
  92 + /7 <<
  93 + /8 <<
  94 + /9 <<
  95 + /10 42
  96 + >>
  97 + >>
  98 + >>
  99 + >>
  100 + >>
  101 + >>
  102 + >>
  103 + >>
  104 + >>
  105 + >>
  106 + >>
  107 + >>
  108 + >>
  109 + >>
  110 + >>
  111 + >>
  112 + >>
  113 + >>
  114 + >>
  115 + >>
  116 + >>
  117 + >>
  118 + >>
  119 + >>
  120 + >>
  121 + >>
  122 + >>
  123 + >>
  124 + >>
  125 + >>
  126 + >>
  127 + >>
  128 + >>
  129 + >>
  130 + >>
  131 + >>
  132 + >>
  133 + >>
  134 + >>
  135 + >>
  136 + >>
  137 + >>
  138 + >>
  139 + >>
  140 + >>
  141 + >>
  142 + >>
  143 + >>
  144 + >>
  145 + >>
  146 + >>
  147 + >>
  148 + >>
  149 + >>
  150 + >>
  151 + >>
  152 + >>
  153 + >>
  154 + >>
  155 + >>
22 156 /Pages 2 0 R
23 157 /Type /Catalog
24 158 /WeirdKey+#ba#da#cc#e5 42
... ... @@ -86,16 +220,16 @@ xref
86 220 0 7
87 221 0000000000 65535 f
88 222 0000000025 00000 n
89   -0000000389 00000 n
90   -0000000471 00000 n
91   -0000000667 00000 n
92   -0000000766 00000 n
93   -0000000785 00000 n
  223 +0000008642 00000 n
  224 +0000008724 00000 n
  225 +0000008920 00000 n
  226 +0000009019 00000 n
  227 +0000009038 00000 n
94 228 trailer <<
95 229 /Root 1 0 R
96 230 /Size 7
97   - /ID [<42841c13bbf709d79a200fa1691836f8><728c020f464c3cf7e02c12605fa7d88b>]
  231 + /ID [<42841c13bbf709d79a200fa1691836f8><31415926535897932384626433832795>]
98 232 >>
99 233 startxref
100   -891
  234 +9144
101 235 %%EOF
... ...
qpdf/test_driver.cc
... ... @@ -3382,6 +3382,22 @@ test_97(QPDF&amp; pdf, char const* arg2)
3382 3382 assert(nulls.unparse() == nulls2.unparse());
3383 3383 }
3384 3384  
  3385 +static void
  3386 +test_98(QPDF& pdf, char const* arg2)
  3387 +{
  3388 + // Test QPDFObjectHandle::writeJSON. This test is built for minimal.pdf.
  3389 + for (int i = 1; i < 7; ++i) {
  3390 + auto oh = pdf.getObject(i, 0);
  3391 + Pl_Buffer bf1{"write", nullptr};
  3392 + Pl_Buffer bf2{"get", nullptr};
  3393 + oh.writeJSON(JSON::LATEST, &bf1, true, 7);
  3394 + bf1.finish();
  3395 + oh.getJSON(JSON::LATEST, true).write(&bf2, 7);
  3396 + bf2.finish();
  3397 + assert(bf1.getString() == bf2.getString());
  3398 + }
  3399 +}
  3400 +
3385 3401 void
3386 3402 runtest(int n, char const* filename1, char const* arg2)
3387 3403 {
... ... @@ -3483,7 +3499,7 @@ runtest(int n, char const* filename1, char const* arg2)
3483 3499 {78, test_78}, {79, test_79}, {80, test_80}, {81, test_81}, {82, test_82}, {83, test_83},
3484 3500 {84, test_84}, {85, test_85}, {86, test_86}, {87, test_87}, {88, test_88}, {89, test_89},
3485 3501 {90, test_90}, {91, test_91}, {92, test_92}, {93, test_93}, {94, test_94}, {95, test_95},
3486   - {96, test_96}, {97, test_97}};
  3502 + {96, test_96}, {97, test_97}, {98, test_98}};
3487 3503  
3488 3504 auto fn = test_functions.find(n);
3489 3505 if (fn == test_functions.end()) {
... ...