aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2024-02-17 20:15:48 +0100
committerJay Berkenbilt <ejb@ql.org>2024-02-17 20:15:48 +0100
commite362bce8e86f4912eaa008bac06f9e2c19b72d3f (patch)
tree42fcd9eed699714f98ddee634763e4d670a2652b
parent5a29b7f9dd353c7baf2ca738bd2a56582a6525ea (diff)
parent413aba5bf2ef239d3abf024de3c819e153171035 (diff)
downloadqpdf-e362bce8e86f4912eaa008bac06f9e2c19b72d3f.tar.zst
Merge branch 'jw' from #1146 into work
-rw-r--r--include/qpdf/JSON.hh5
-rw-r--r--include/qpdf/QPDF.hh13
-rw-r--r--include/qpdf/QPDFJob.hh1
-rw-r--r--include/qpdf/QPDFObjectHandle.hh9
-rw-r--r--libqpdf/JSON.cc8
-rw-r--r--libqpdf/QPDFJob.cc37
-rw-r--r--libqpdf/QPDFObjectHandle.cc26
-rw-r--r--libqpdf/QPDF_Array.cc32
-rw-r--r--libqpdf/QPDF_Bool.cc8
-rw-r--r--libqpdf/QPDF_Destroyed.cc7
-rw-r--r--libqpdf/QPDF_Dictionary.cc31
-rw-r--r--libqpdf/QPDF_InlineImage.cc8
-rw-r--r--libqpdf/QPDF_Integer.cc7
-rw-r--r--libqpdf/QPDF_Name.cc74
-rw-r--r--libqpdf/QPDF_Null.cc8
-rw-r--r--libqpdf/QPDF_Operator.cc8
-rw-r--r--libqpdf/QPDF_Real.cc19
-rw-r--r--libqpdf/QPDF_Reserved.cc5
-rw-r--r--libqpdf/QPDF_Stream.cc143
-rw-r--r--libqpdf/QPDF_String.cc42
-rw-r--r--libqpdf/QPDF_Unresolved.cc5
-rw-r--r--libqpdf/QPDF_json.cc168
-rw-r--r--libqpdf/qpdf/JSON_writer.hh137
-rw-r--r--libqpdf/qpdf/QPDFObject_private.hh6
-rw-r--r--libqpdf/qpdf/QPDFValue.hh2
-rw-r--r--libqpdf/qpdf/QPDF_Array.hh2
-rw-r--r--libqpdf/qpdf/QPDF_Bool.hh3
-rw-r--r--libqpdf/qpdf/QPDF_Destroyed.hh2
-rw-r--r--libqpdf/qpdf/QPDF_Dictionary.hh2
-rw-r--r--libqpdf/qpdf/QPDF_InlineImage.hh2
-rw-r--r--libqpdf/qpdf/QPDF_Integer.hh2
-rw-r--r--libqpdf/qpdf/QPDF_Name.hh7
-rw-r--r--libqpdf/qpdf/QPDF_Null.hh2
-rw-r--r--libqpdf/qpdf/QPDF_Operator.hh2
-rw-r--r--libqpdf/qpdf/QPDF_Real.hh2
-rw-r--r--libqpdf/qpdf/QPDF_Reserved.hh2
-rw-r--r--libqpdf/qpdf/QPDF_Stream.hh10
-rw-r--r--libqpdf/qpdf/QPDF_String.hh2
-rw-r--r--libqpdf/qpdf/QPDF_Unresolved.hh2
-rw-r--r--qpdf/qtest/many-nulls.test19
-rw-r--r--qpdf/qtest/qpdf-json.test15
-rw-r--r--qpdf/qtest/qpdf/minimal-nulls-1.json453
-rw-r--r--qpdf/qtest/qpdf/minimal-nulls-2.json424
-rw-r--r--qpdf/qtest/qpdf/minimal-nulls.pdf387
-rw-r--r--qpdf/qtest/qpdf/weird-tokens-alt.json136
-rw-r--r--qpdf/qtest/qpdf/weird-tokens-v1.json295
-rw-r--r--qpdf/qtest/qpdf/weird-tokens.json136
-rw-r--r--qpdf/qtest/qpdf/weird-tokens.pdf148
-rw-r--r--qpdf/test_driver.cc18
49 files changed, 2555 insertions, 327 deletions
diff --git a/include/qpdf/JSON.hh b/include/qpdf/JSON.hh
index e3c8a7dc..3272800d 100644
--- a/include/qpdf/JSON.hh
+++ b/include/qpdf/JSON.hh
@@ -290,8 +290,11 @@ class JSON
QPDF_DLL
qpdf_offset_t getEnd() const;
+ // The following class does not form part of the public API and is for internal use only.
+
+ class Writer;
+
private:
- static std::string encode_string(std::string const& utf8);
static void writeClose(Pipeline* p, bool first, size_t depth, char const* delimeter);
enum value_type_e {
diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh
index a836c3c9..04b11cba 100644
--- a/include/qpdf/QPDF.hh
+++ b/include/qpdf/QPDF.hh
@@ -1411,19 +1411,6 @@ class QPDF
// JSON import
void importJSON(std::shared_ptr<InputSource>, bool must_be_complete);
- // JSON write
- void writeJSONStream(
- int version,
- Pipeline* p,
- bool& first,
- std::string const& key,
- QPDFObjectHandle&,
- qpdf_stream_decode_level_e,
- qpdf_json_stream_data_e,
- std::string const& file_prefix);
- void writeJSONObject(
- int version, Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle&);
-
// Type conversion helper methods
template <typename T>
static qpdf_offset_t
diff --git a/include/qpdf/QPDFJob.hh b/include/qpdf/QPDFJob.hh
index c291d1e8..b26a9dcf 100644
--- a/include/qpdf/QPDFJob.hh
+++ b/include/qpdf/QPDFJob.hh
@@ -551,7 +551,6 @@ class QPDFJob
// JSON
void doJSON(QPDF& pdf, Pipeline*);
QPDFObjGen::set getWantedJSONObjects();
- void doJSONObject(Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle&);
void doJSONObjects(Pipeline* p, bool& first, QPDF& pdf);
void doJSONObjectinfo(Pipeline* p, bool& first, QPDF& pdf);
void doJSONPages(Pipeline* p, bool& first, QPDF& pdf);
diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh
index b2835495..9ea329ff 100644
--- a/include/qpdf/QPDFObjectHandle.hh
+++ b/include/qpdf/QPDFObjectHandle.hh
@@ -1197,6 +1197,13 @@ class QPDFObjectHandle
QPDF_DLL
JSON getJSON(int json_version, bool dereference_indirect = false);
+ // Write the object encoded as JSON to a pipeline. This is equivalent to, but more efficient
+ // than, calling getJSON(json_version, dereference_indirect).write(p, depth). See the
+ // documentation for getJSON and JSON::write for further detail.
+ QPDF_DLL
+ void
+ writeJSON(int json_version, Pipeline* p, bool dereference_indirect = false, size_t depth = 0);
+
// Deprecated version uses v1 for backward compatibility.
// ABI: remove for qpdf 12
[[deprecated("Use getJSON(int version)")]] QPDF_DLL JSON
@@ -1353,6 +1360,8 @@ class QPDFObjectHandle
return obj.get();
}
+ void writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect = false);
+
private:
QPDF_Array* asArray();
QPDF_Bool* asBool();
diff --git a/libqpdf/JSON.cc b/libqpdf/JSON.cc
index 27405df7..bc28f236 100644
--- a/libqpdf/JSON.cc
+++ b/libqpdf/JSON.cc
@@ -1,5 +1,7 @@
#include <qpdf/JSON.hh>
+#include <qpdf/JSON_writer.hh>
+
#include <qpdf/BufferInputSource.hh>
#include <qpdf/Pl_Base64.hh>
#include <qpdf/Pl_Concatenate.hh>
@@ -119,7 +121,7 @@ JSON::JSON_array::write(Pipeline* p, size_t depth) const
JSON::JSON_string::JSON_string(std::string const& utf8) :
JSON_value(vt_string),
utf8(utf8),
- encoded(encode_string(utf8))
+ encoded(Writer::encode_string(utf8))
{
}
@@ -211,7 +213,7 @@ JSON::unparse() const
}
std::string
-JSON::encode_string(std::string const& str)
+JSON::Writer::encode_string(std::string const& str)
{
static auto constexpr hexchars = "0123456789abcdef";
@@ -279,7 +281,7 @@ JSON
JSON::addDictionaryMember(std::string const& key, JSON const& val)
{
if (auto* obj = m ? dynamic_cast<JSON_dictionary*>(m->value.get()) : nullptr) {
- return obj->members[encode_string(key)] = val.m ? val : makeNull();
+ return obj->members[Writer::encode_string(key)] = val.m ? val : makeNull();
} else {
throw std::runtime_error("JSON::addDictionaryMember called on non-dictionary");
}
diff --git a/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc
index a699e38d..a85ff0a2 100644
--- a/libqpdf/QPDFJob.cc
+++ b/libqpdf/QPDFJob.cc
@@ -955,23 +955,6 @@ QPDFJob::getWantedJSONObjects()
}
void
-QPDFJob::doJSONObject(Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle& obj)
-{
- if (m->json_version == 1) {
- JSON::writeDictionaryItem(p, first, key, obj.getJSON(1, true), 2);
- } else {
- auto j = JSON::makeDictionary();
- if (obj.isStream()) {
- j.addDictionaryMember("stream", JSON::makeDictionary())
- .addDictionaryMember("dict", obj.getDict().getJSON(m->json_version, true));
- } else {
- j.addDictionaryMember("value", obj.getJSON(m->json_version, true));
- }
- JSON::writeDictionaryItem(p, first, key, j, 2);
- }
-}
-
-void
QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf)
{
if (m->json_version == 1) {
@@ -982,16 +965,17 @@ QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf)
auto wanted_og = getWantedJSONObjects();
for (auto& obj: pdf.getAllObjects()) {
std::string key = obj.unparse();
- if (m->json_version > 1) {
- key = "obj:" + key;
- }
+
if (all_objects || wanted_og.count(obj.getObjGen())) {
- doJSONObject(p, first_object, key, obj);
+ JSON::writeDictionaryKey(p, first_object, obj.unparse(), 2);
+ obj.writeJSON(1, p, true, 2);
+ first_object = false;
}
}
if (all_objects || m->json_objects.count("trailer")) {
- auto trailer = pdf.getTrailer();
- doJSONObject(p, first_object, "trailer", trailer);
+ JSON::writeDictionaryKey(p, first_object, "trailer", 2);
+ pdf.getTrailer().writeJSON(1, p, true, 2);
+ first_object = false;
}
JSON::writeDictionaryClose(p, first_object, 1);
} else {
@@ -3097,9 +3081,10 @@ QPDFJob::writeOutfile(QPDF& pdf)
try {
QUtil::remove_file(backup.c_str());
} catch (QPDFSystemError& e) {
- *m->log->getError() << m->message_prefix << ": unable to delete original file ("
- << e.what() << ");" << " original file left in " << backup
- << ", but the input was successfully replaced\n";
+ *m->log->getError()
+ << m->message_prefix << ": unable to delete original file (" << e.what() << ");"
+ << " original file left in " << backup
+ << ", but the input was successfully replaced\n";
}
}
}
diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc
index d543f98e..9b9849a3 100644
--- a/libqpdf/QPDFObjectHandle.cc
+++ b/libqpdf/QPDFObjectHandle.cc
@@ -3,6 +3,7 @@
#include <qpdf/BufferInputSource.hh>
#include <qpdf/Pl_Buffer.hh>
#include <qpdf/Pl_QPDFTokenizer.hh>
+#include <qpdf/JSON_writer.hh>
#include <qpdf/QPDF.hh>
#include <qpdf/QPDFExc.hh>
#include <qpdf/QPDFLogger.hh>
@@ -1617,10 +1618,33 @@ QPDFObjectHandle::getJSON(int json_version, bool dereference_indirect)
} else if (!dereference()) {
throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
} else {
- return obj->getJSON(json_version);
+ Pl_Buffer p{"json"};
+ JSON::Writer jw{&p, 0};
+ writeJSON(json_version, jw, dereference_indirect);
+ p.finish();
+ return JSON::parse(p.getString());
}
}
+void
+QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect)
+{
+ if (!dereference_indirect && isIndirect()) {
+ p << "\"" << getObjGen().unparse(' ') << " R\"";
+ } else if (!dereference()) {
+ throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
+ } else {
+ obj->writeJSON(json_version, p);
+ }
+}
+
+void
+QPDFObjectHandle::writeJSON(int json_version, Pipeline* p, bool dereference_indirect, size_t depth)
+{
+ JSON::Writer jw{p, depth};
+ writeJSON(json_version, jw, dereference_indirect);
+}
+
JSON
QPDFObjectHandle::getStreamJSON(
int json_version,
diff --git a/libqpdf/QPDF_Array.cc b/libqpdf/QPDF_Array.cc
index 789acc35..ab2a4c8c 100644
--- a/libqpdf/QPDF_Array.cc
+++ b/libqpdf/QPDF_Array.cc
@@ -1,5 +1,6 @@
#include <qpdf/QPDF_Array.hh>
+#include <qpdf/JSON_writer.hh>
#include <qpdf/QPDFObjectHandle.hh>
#include <qpdf/QPDFObject_private.hh>
#include <qpdf/QTC.hh>
@@ -148,36 +149,41 @@ QPDF_Array::unparse()
return result;
}
-JSON
-QPDF_Array::getJSON(int json_version)
+void
+QPDF_Array::writeJSON(int json_version, JSON::Writer& p)
{
- static const JSON j_null = JSON::makeNull();
- JSON j_array = JSON::makeArray();
+ p.writeStart('[');
if (sp) {
int next = 0;
for (auto& item: sp->elements) {
int key = item.first;
for (int j = next; j < key; ++j) {
- j_array.addArrayElement(j_null);
+ p.writeNext() << "null";
}
+ p.writeNext();
auto og = item.second->getObjGen();
- j_array.addArrayElement(
- og.isIndirect() ? JSON::makeString(og.unparse(' ') + " R")
- : item.second->getJSON(json_version));
+ if (og.isIndirect()) {
+ p << "\"" << og.unparse(' ') << " R\"";
+ } else {
+ item.second->writeJSON(json_version, p);
+ }
next = ++key;
}
for (int j = next; j < sp->size; ++j) {
- j_array.addArrayElement(j_null);
+ p.writeNext() << "null";
}
} else {
for (auto const& item: elements) {
+ p.writeNext();
auto og = item->getObjGen();
- j_array.addArrayElement(
- og.isIndirect() ? JSON::makeString(og.unparse(' ') + " R")
- : item->getJSON(json_version));
+ if (og.isIndirect()) {
+ p << "\"" << og.unparse(' ') << " R\"";
+ } else {
+ item->writeJSON(json_version, p);
+ }
}
}
- return j_array;
+ p.writeEnd(']');
}
QPDFObjectHandle
diff --git a/libqpdf/QPDF_Bool.cc b/libqpdf/QPDF_Bool.cc
index 05c52f22..97f47881 100644
--- a/libqpdf/QPDF_Bool.cc
+++ b/libqpdf/QPDF_Bool.cc
@@ -1,5 +1,7 @@
#include <qpdf/QPDF_Bool.hh>
+#include <qpdf/JSON_writer.hh>
+
QPDF_Bool::QPDF_Bool(bool val) :
QPDFValue(::ot_boolean, "boolean"),
val(val)
@@ -24,10 +26,10 @@ QPDF_Bool::unparse()
return (val ? "true" : "false");
}
-JSON
-QPDF_Bool::getJSON(int json_version)
+void
+QPDF_Bool::writeJSON(int json_version, JSON::Writer& p)
{
- return JSON::makeBool(this->val);
+ p << val;
}
bool
diff --git a/libqpdf/QPDF_Destroyed.cc b/libqpdf/QPDF_Destroyed.cc
index 4e34b508..5e04566a 100644
--- a/libqpdf/QPDF_Destroyed.cc
+++ b/libqpdf/QPDF_Destroyed.cc
@@ -28,9 +28,8 @@ QPDF_Destroyed::unparse()
return "";
}
-JSON
-QPDF_Destroyed::getJSON(int json_version)
+void
+QPDF_Destroyed::writeJSON(int json_version, JSON::Writer& p)
{
throw std::logic_error("attempted to get JSON from a QPDFObjectHandle from a destroyed QPDF");
- return JSON::makeNull();
-}
+} \ No newline at end of file
diff --git a/libqpdf/QPDF_Dictionary.cc b/libqpdf/QPDF_Dictionary.cc
index f7e32fc9..9332b1d3 100644
--- a/libqpdf/QPDF_Dictionary.cc
+++ b/libqpdf/QPDF_Dictionary.cc
@@ -1,5 +1,6 @@
#include <qpdf/QPDF_Dictionary.hh>
+#include <qpdf/JSON_writer.hh>
#include <qpdf/QPDFObject_private.hh>
#include <qpdf/QPDF_Name.hh>
#include <qpdf/QPDF_Null.hh>
@@ -67,28 +68,30 @@ QPDF_Dictionary::unparse()
return result;
}
-JSON
-QPDF_Dictionary::getJSON(int json_version)
+void
+QPDF_Dictionary::writeJSON(int json_version, JSON::Writer& p)
{
- JSON j = JSON::makeDictionary();
+ p.writeStart('{');
for (auto& iter: this->items) {
if (!iter.second.isNull()) {
+ p.writeNext();
if (json_version == 1) {
- j.addDictionaryMember(
- QPDF_Name::normalizeName(iter.first), iter.second.getJSON(json_version));
+ p << "\"" << JSON::Writer::encode_string(QPDF_Name::normalizeName(iter.first))
+ << "\": ";
+ } else if (auto res = QPDF_Name::analyzeJSONEncoding(iter.first); res.first) {
+ if (res.second) {
+ p << "\"" << iter.first << "\": ";
+ } else {
+ p << "\"" << JSON::Writer::encode_string(iter.first) << "\": ";
+ }
} else {
- bool has_8bit_chars;
- bool is_valid_utf8;
- bool is_utf16;
- QUtil::analyze_encoding(iter.first, has_8bit_chars, is_valid_utf8, is_utf16);
- std::string key = !has_8bit_chars || is_valid_utf8
- ? iter.first
- : "n:" + QPDF_Name::normalizeName(iter.first);
- j.addDictionaryMember(key, iter.second.getJSON(json_version));
+ p << "\"n:" << JSON::Writer::encode_string(QPDF_Name::normalizeName(iter.first))
+ << "\": ";
}
+ iter.second.writeJSON(json_version, p);
}
}
- return j;
+ p.writeEnd('}');
}
bool
diff --git a/libqpdf/QPDF_InlineImage.cc b/libqpdf/QPDF_InlineImage.cc
index 18f2fed6..2d62071d 100644
--- a/libqpdf/QPDF_InlineImage.cc
+++ b/libqpdf/QPDF_InlineImage.cc
@@ -1,5 +1,7 @@
#include <qpdf/QPDF_InlineImage.hh>
+#include <qpdf/JSON_writer.hh>
+
QPDF_InlineImage::QPDF_InlineImage(std::string const& val) :
QPDFValue(::ot_inlineimage, "inline-image"),
val(val)
@@ -24,8 +26,8 @@ QPDF_InlineImage::unparse()
return this->val;
}
-JSON
-QPDF_InlineImage::getJSON(int json_version)
+void
+QPDF_InlineImage::writeJSON(int json_version, JSON::Writer& p)
{
- return JSON::makeNull();
+ p << "null";
}
diff --git a/libqpdf/QPDF_Integer.cc b/libqpdf/QPDF_Integer.cc
index 716a11e0..aa0437a1 100644
--- a/libqpdf/QPDF_Integer.cc
+++ b/libqpdf/QPDF_Integer.cc
@@ -1,5 +1,6 @@
#include <qpdf/QPDF_Integer.hh>
+#include <qpdf/JSON_writer.hh>
#include <qpdf/QUtil.hh>
QPDF_Integer::QPDF_Integer(long long val) :
@@ -26,10 +27,10 @@ QPDF_Integer::unparse()
return std::to_string(this->val);
}
-JSON
-QPDF_Integer::getJSON(int json_version)
+void
+QPDF_Integer::writeJSON(int json_version, JSON::Writer& p)
{
- return JSON::makeInt(this->val);
+ p << std::to_string(this->val);
}
long long
diff --git a/libqpdf/QPDF_Name.cc b/libqpdf/QPDF_Name.cc
index 5fde9c65..0e7f441a 100644
--- a/libqpdf/QPDF_Name.cc
+++ b/libqpdf/QPDF_Name.cc
@@ -1,7 +1,10 @@
#include <qpdf/QPDF_Name.hh>
+#include <qpdf/JSON_writer.hh>
#include <qpdf/QUtil.hh>
+#include <string_view>
+
QPDF_Name::QPDF_Name(std::string const& name) :
QPDFValue(::ot_name, "name"),
name(name)
@@ -51,20 +54,71 @@ QPDF_Name::unparse()
return normalizeName(this->name);
}
-JSON
-QPDF_Name::getJSON(int json_version)
+std::pair<bool, bool>
+QPDF_Name::analyzeJSONEncoding(const std::string& name)
+{
+ std::basic_string_view<unsigned char> view{
+ reinterpret_cast<const unsigned char*>(name.data()), name.size()};
+
+ int tail = 0; // Number of continuation characters expected.
+ bool tail2 = false; // Potential overlong 3 octet utf-8.
+ bool tail3 = false; // potential overlong 4 octet
+ bool needs_escaping = false;
+ for (auto const& c: view) {
+ if (tail) {
+ if ((c & 0xc0) != 0x80) {
+ return {false, false};
+ }
+ if (tail2) {
+ if ((c & 0xe0) == 0x80) {
+ return {false, false};
+ }
+ tail2 = false;
+ } else if (tail3) {
+ if ((c & 0xf0) == 0x80) {
+ return {false, false};
+ }
+ tail3 = false;
+ }
+ tail--;
+ } else if (c < 0x80) {
+ if (!needs_escaping) {
+ needs_escaping = !((c > 34 && c != '\\') || c == ' ' || c == 33);
+ }
+ } else if ((c & 0xe0) == 0xc0) {
+ if ((c & 0xfe) == 0xc0) {
+ return {false, false};
+ }
+ tail = 1;
+ } else if ((c & 0xf0) == 0xe0) {
+ tail2 = (c == 0xe0);
+ tail = 2;
+ } else if ((c & 0xf8) == 0xf0) {
+ tail3 = (c == 0xf0);
+ tail = 3;
+ } else {
+ return {false, false};
+ }
+ }
+ return {tail == 0, !needs_escaping};
+}
+
+void
+QPDF_Name::writeJSON(int json_version, JSON::Writer& p)
{
+ // For performance reasons this code is duplicated in QPDF_Dictionary::writeJSON. When updating
+ // this method make sure QPDF_Dictionary is also update.
if (json_version == 1) {
- return JSON::makeString(normalizeName(this->name));
+ p << "\"" << JSON::Writer::encode_string(normalizeName(name)) << "\"";
} else {
- bool has_8bit_chars;
- bool is_valid_utf8;
- bool is_utf16;
- QUtil::analyze_encoding(this->name, has_8bit_chars, is_valid_utf8, is_utf16);
- if (!has_8bit_chars || is_valid_utf8) {
- return JSON::makeString(this->name);
+ if (auto res = analyzeJSONEncoding(name); res.first) {
+ if (res.second) {
+ p << "\"" << name << "\"";
+ } else {
+ p << "\"" << JSON::Writer::encode_string(name) << "\"";
+ }
} else {
- return JSON::makeString("n:" + normalizeName(this->name));
+ p << "\"n:" << JSON::Writer::encode_string(normalizeName(name)) << "\"";
}
}
}
diff --git a/libqpdf/QPDF_Null.cc b/libqpdf/QPDF_Null.cc
index fdabdfa7..fa4b6cab 100644
--- a/libqpdf/QPDF_Null.cc
+++ b/libqpdf/QPDF_Null.cc
@@ -1,5 +1,6 @@
#include <qpdf/QPDF_Null.hh>
+#include <qpdf/JSON_writer.hh>
#include <qpdf/QPDFObject_private.hh>
QPDF_Null::QPDF_Null() :
@@ -43,9 +44,8 @@ QPDF_Null::unparse()
return "null";
}
-JSON
-QPDF_Null::getJSON(int json_version)
+void
+QPDF_Null::writeJSON(int json_version, JSON::Writer& p)
{
- // If this is updated, QPDF_Array::getJSON must also be updated.
- return JSON::makeNull();
+ p << "null";
}
diff --git a/libqpdf/QPDF_Operator.cc b/libqpdf/QPDF_Operator.cc
index d1b2969d..c35a8391 100644
--- a/libqpdf/QPDF_Operator.cc
+++ b/libqpdf/QPDF_Operator.cc
@@ -1,5 +1,7 @@
#include <qpdf/QPDF_Operator.hh>
+#include <qpdf/JSON_writer.hh>
+
QPDF_Operator::QPDF_Operator(std::string const& val) :
QPDFValue(::ot_operator, "operator"),
val(val)
@@ -24,8 +26,8 @@ QPDF_Operator::unparse()
return val;
}
-JSON
-QPDF_Operator::getJSON(int json_version)
+void
+QPDF_Operator::writeJSON(int json_version, JSON::Writer& p)
{
- return JSON::makeNull();
+ p << "null";
}
diff --git a/libqpdf/QPDF_Real.cc b/libqpdf/QPDF_Real.cc
index 1d954dcd..f4304397 100644
--- a/libqpdf/QPDF_Real.cc
+++ b/libqpdf/QPDF_Real.cc
@@ -1,5 +1,6 @@
#include <qpdf/QPDF_Real.hh>
+#include <qpdf/JSON_writer.hh>
#include <qpdf/QUtil.hh>
QPDF_Real::QPDF_Real(std::string const& val) :
@@ -38,21 +39,17 @@ QPDF_Real::unparse()
return this->val;
}
-JSON
-QPDF_Real::getJSON(int json_version)
+void
+QPDF_Real::writeJSON(int json_version, JSON::Writer& p)
{
- // While PDF allows .x or -.x, JSON does not. Rather than converting from string to double and
- // back, just handle this as a special case for JSON.
- std::string result;
if (this->val.length() == 0) {
// Can't really happen...
- result = "0";
+ p << "0";
} else if (this->val.at(0) == '.') {
- result = "0" + this->val;
- } else if ((this->val.length() >= 2) && (this->val.at(0) == '-') && (this->val.at(1) == '.')) {
- result = "-0." + this->val.substr(2);
+ p << "0" << this->val;
+ } else if (this->val.length() >= 2 && this->val.at(0) == '-' && this->val.at(1) == '.') {
+ p << "-0." << this->val.substr(2);
} else {
- result = this->val;
+ p << this->val;
}
- return JSON::makeNumber(result);
}
diff --git a/libqpdf/QPDF_Reserved.cc b/libqpdf/QPDF_Reserved.cc
index 845d6ebc..da112acc 100644
--- a/libqpdf/QPDF_Reserved.cc
+++ b/libqpdf/QPDF_Reserved.cc
@@ -26,9 +26,8 @@ QPDF_Reserved::unparse()
return "";
}
-JSON
-QPDF_Reserved::getJSON(int json_version)
+void
+QPDF_Reserved::writeJSON(int json_version, JSON::Writer& p)
{
throw std::logic_error("QPDFObjectHandle: attempting to get JSON from a reserved object");
- return JSON::makeNull();
}
diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc
index a43d91ff..6cfcdd46 100644
--- a/libqpdf/QPDF_Stream.cc
+++ b/libqpdf/QPDF_Stream.cc
@@ -1,6 +1,7 @@
#include <qpdf/QPDF_Stream.hh>
#include <qpdf/ContentNormalizer.hh>
+#include <qpdf/JSON_writer.hh>
#include <qpdf/Pipeline.hh>
#include <qpdf/Pl_Base64.hh>
#include <qpdf/Pl_Buffer.hh>
@@ -176,13 +177,10 @@ QPDF_Stream::unparse()
return og.unparse(' ') + " R";
}
-JSON
-QPDF_Stream::getJSON(int json_version)
+void
+QPDF_Stream::writeJSON(int json_version, JSON::Writer& jw)
{
- if (json_version == 1) {
- return this->stream_dict.getJSON(json_version);
- }
- return getStreamJSON(json_version, qpdf_sj_none, qpdf_dl_none, nullptr, "");
+ stream_dict.writeJSON(json_version, jw);
}
JSON
@@ -193,77 +191,108 @@ QPDF_Stream::getStreamJSON(
Pipeline* p,
std::string const& data_filename)
{
+ Pl_Buffer pb{"streamjson"};
+ JSON::Writer jw{&pb, 0};
+ decode_level =
+ writeStreamJSON(json_version, jw, json_data, decode_level, p, data_filename, true);
+ pb.finish();
+ auto result = JSON::parse(pb.getString());
+ if (json_data == qpdf_sj_inline) {
+ result.addDictionaryMember("data", JSON::makeBlob(StreamBlobProvider(this, decode_level)));
+ }
+ return result;
+}
+
+qpdf_stream_decode_level_e
+QPDF_Stream::writeStreamJSON(
+ int json_version,
+ JSON::Writer& jw,
+ qpdf_json_stream_data_e json_data,
+ qpdf_stream_decode_level_e decode_level,
+ Pipeline* p,
+ std::string const& data_filename,
+ bool no_data_key)
+{
switch (json_data) {
case qpdf_sj_none:
case qpdf_sj_inline:
if (p != nullptr) {
- throw std::logic_error("QPDF_Stream::getStreamJSON: pipeline should only be supplied "
+ throw std::logic_error("QPDF_Stream::writeStreamJSON: pipeline should only be supplied "
"when json_data is file");
}
break;
case qpdf_sj_file:
if (p == nullptr) {
throw std::logic_error(
- "QPDF_Stream::getStreamJSON: pipeline must be supplied when json_data is file");
+ "QPDF_Stream::writeStreamJSON: pipeline must be supplied when json_data is file");
}
if (data_filename.empty()) {
- throw std::logic_error("QPDF_Stream::getStreamJSON: data_filename must be supplied "
+ throw std::logic_error("QPDF_Stream::writeStreamJSON: data_filename must be supplied "
"when json_data is file");
}
break;
}
- auto dict = this->stream_dict;
- JSON result = JSON::makeDictionary();
- if (json_data != qpdf_sj_none) {
- Pl_Discard discard;
- Pl_Buffer buf_pl{"stream data"};
- // buf_pl contains valid data and is ready for retrieval of the data.
- bool buf_pl_ready = false;
- bool filtered = false;
- bool filter = (decode_level != qpdf_dl_none);
- for (int attempt = 1; attempt <= 2; ++attempt) {
- Pipeline* data_pipeline = &discard;
- if (json_data == qpdf_sj_file) {
- // We need to capture the data to write
- data_pipeline = &buf_pl;
- }
- bool succeeded =
- pipeStreamData(data_pipeline, &filtered, 0, decode_level, false, (attempt == 1));
- if (!succeeded || (filter && !filtered)) {
- // Try again
- filter = false;
- decode_level = qpdf_dl_none;
- buf_pl.getString(); // reset buf_pl
- } else {
- if (json_data == qpdf_sj_file) {
- buf_pl_ready = true;
- }
- break;
- }
- }
- // We can use unsafeShallowCopy because we are only touching top-level keys.
- dict = this->stream_dict.unsafeShallowCopy();
- dict.removeKey("/Length");
- if (filter && filtered) {
- dict.removeKey("/Filter");
- dict.removeKey("/DecodeParms");
- }
- if (json_data == qpdf_sj_file) {
- result.addDictionaryMember("datafile", JSON::makeString(data_filename));
- if (!buf_pl_ready) {
- throw std::logic_error("QPDF_Stream: failed to get stream data in json file mode");
- }
- p->writeString(buf_pl.getString());
- } else if (json_data == qpdf_sj_inline) {
- result.addDictionaryMember(
- "data", JSON::makeBlob(StreamBlobProvider(this, decode_level)));
+ jw.writeStart('{');
+
+ if (json_data == qpdf_sj_none) {
+ jw.writeNext();
+ jw << R"("dict": )";
+ stream_dict.writeJSON(json_version, jw);
+ jw.writeEnd('}');
+ return decode_level;
+ }
+
+ Pl_Discard discard;
+ Pl_Buffer buf_pl{"stream data"};
+ Pipeline* data_pipeline = &buf_pl;
+ if (no_data_key && json_data == qpdf_sj_inline) {
+ data_pipeline = &discard;
+ }
+ // pipeStreamData produced valid data.
+ bool buf_pl_ready = false;
+ bool filtered = false;
+ bool filter = (decode_level != qpdf_dl_none);
+ for (int attempt = 1; attempt <= 2; ++attempt) {
+ bool succeeded =
+ pipeStreamData(data_pipeline, &filtered, 0, decode_level, false, (attempt == 1));
+ if (!succeeded || (filter && !filtered)) {
+ // Try again
+ filter = false;
+ decode_level = qpdf_dl_none;
+ buf_pl.getString(); // reset buf_pl
} else {
- throw std::logic_error("QPDF_Stream: unexpected value of json_data");
+ buf_pl_ready = true;
+ break;
}
}
- result.addDictionaryMember("dict", dict.getJSON(json_version));
- return result;
+ if (!buf_pl_ready) {
+ throw std::logic_error("QPDF_Stream: failed to get stream data");
+ }
+ // We can use unsafeShallowCopy because we are only touching top-level keys.
+ auto dict = stream_dict.unsafeShallowCopy();
+ dict.removeKey("/Length");
+ if (filter && filtered) {
+ dict.removeKey("/Filter");
+ dict.removeKey("/DecodeParms");
+ }
+ if (json_data == qpdf_sj_file) {
+ jw.writeNext() << R"("datafile": ")" << JSON::Writer::encode_string(data_filename) << "\"";
+ p->writeString(buf_pl.getString());
+ } else if (json_data == qpdf_sj_inline) {
+ if (!no_data_key) {
+ jw.writeNext() << R"("data": ")";
+ jw.writeBase64(buf_pl.getString()) << "\"";
+ }
+ } else {
+ throw std::logic_error("QPDF_Stream::writeStreamJSON : unexpected value of json_data");
+ }
+
+ jw.writeNext() << R"("dict": )";
+ dict.writeJSON(json_version, jw);
+ jw.writeEnd('}');
+
+ return decode_level;
}
void
diff --git a/libqpdf/QPDF_String.cc b/libqpdf/QPDF_String.cc
index 3886b399..f425b313 100644
--- a/libqpdf/QPDF_String.cc
+++ b/libqpdf/QPDF_String.cc
@@ -1,5 +1,6 @@
#include <qpdf/QPDF_String.hh>
+#include <qpdf/JSON_writer.hh>
#include <qpdf/QUtil.hh>
// 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()
return unparse(false);
}
-JSON
-QPDF_String::getJSON(int json_version)
+void
+QPDF_String::writeJSON(int json_version, JSON::Writer& p)
{
+ auto candidate = getUTF8Val();
if (json_version == 1) {
- return JSON::makeString(getUTF8Val());
- }
- // See if we can unambiguously represent as Unicode.
- bool is_unicode = false;
- std::string result;
- std::string candidate = getUTF8Val();
- if (QUtil::is_utf16(this->val) || QUtil::is_explicit_utf8(this->val)) {
- is_unicode = true;
- result = candidate;
- } else if (!useHexString()) {
- std::string test;
- if (QUtil::utf8_to_pdf_doc(candidate, test, '?') && (test == this->val)) {
- // This is a PDF-doc string that can be losslessly encoded as Unicode.
- is_unicode = true;
- result = candidate;
- }
- }
- if (is_unicode) {
- result = "u:" + result;
+
+ p << "\"" << JSON::Writer::encode_string(candidate) << "\"";
} else {
- result = "b:" + QUtil::hex_encode(this->val);
+ // See if we can unambiguously represent as Unicode.
+ if (QUtil::is_utf16(this->val) || QUtil::is_explicit_utf8(this->val)) {
+ p << "\"u:" << JSON::Writer::encode_string(candidate) <<"\"";
+ return;
+ } else if (!useHexString()) {
+ std::string test;
+ if (QUtil::utf8_to_pdf_doc(candidate, test, '?') && (test == this->val)) {
+ // This is a PDF-doc string that can be losslessly encoded as Unicode.
+ p << "\"u:" << JSON::Writer::encode_string(candidate) <<"\"";
+ return;
+ }
+ }
+ p << "\"b:" << QUtil::hex_encode(val) <<"\"";
}
- return JSON::makeString(result);
}
bool
diff --git a/libqpdf/QPDF_Unresolved.cc b/libqpdf/QPDF_Unresolved.cc
index fbf5e15f..dc14c2f2 100644
--- a/libqpdf/QPDF_Unresolved.cc
+++ b/libqpdf/QPDF_Unresolved.cc
@@ -27,9 +27,8 @@ QPDF_Unresolved::unparse()
return "";
}
-JSON
-QPDF_Unresolved::getJSON(int json_version)
+void
+QPDF_Unresolved::writeJSON(int json_version, JSON::Writer& p)
{
throw std::logic_error("attempted to get JSON from an unresolved QPDFObjectHandle");
- return JSON::makeNull();
}
diff --git a/libqpdf/QPDF_json.cc b/libqpdf/QPDF_json.cc
index 8326e6a5..b06b70c9 100644
--- a/libqpdf/QPDF_json.cc
+++ b/libqpdf/QPDF_json.cc
@@ -1,12 +1,14 @@
#include <qpdf/QPDF.hh>
#include <qpdf/FileInputSource.hh>
+#include <qpdf/JSON_writer.hh>
#include <qpdf/Pl_Base64.hh>
#include <qpdf/Pl_StdioFile.hh>
#include <qpdf/QIntC.hh>
#include <qpdf/QPDFObject_private.hh>
#include <qpdf/QPDFValue.hh>
#include <qpdf/QPDF_Null.hh>
+#include <qpdf/QPDF_Stream.hh>
#include <qpdf/QTC.hh>
#include <qpdf/QUtil.hh>
#include <algorithm>
@@ -442,7 +444,9 @@ void
QPDF::JSONReactor::replaceObject(QPDFObjectHandle&& replacement, JSON const& value)
{
if (replacement.isIndirect()) {
- error(replacement.getParsedOffset(), "the value of an object may not be an indirect object reference");
+ error(
+ replacement.getParsedOffset(),
+ "the value of an object may not be an indirect object reference");
return;
}
auto& tos = stack.back();
@@ -828,45 +832,20 @@ QPDF::importJSON(std::shared_ptr<InputSource> is, bool must_be_complete)
}
void
-QPDF::writeJSONStream(
+writeJSONStreamFile(
int version,
- Pipeline* p,
- bool& first,
- std::string const& key,
- QPDFObjectHandle& obj,
+ JSON::Writer& jw,
+ QPDF_Stream& stream,
+ int id,
qpdf_stream_decode_level_e decode_level,
- qpdf_json_stream_data_e json_stream_data,
std::string const& file_prefix)
{
- Pipeline* stream_p = nullptr;
- FILE* f = nullptr;
- std::shared_ptr<Pl_StdioFile> f_pl;
- std::string filename;
- if (json_stream_data == qpdf_sj_file) {
- filename = file_prefix + "-" + std::to_string(obj.getObjectID());
- f = QUtil::safe_fopen(filename.c_str(), "wb");
- f_pl = std::make_shared<Pl_StdioFile>("stream data", f);
- stream_p = f_pl.get();
- }
- auto j = JSON::makeDictionary();
- j.addDictionaryMember(
- "stream", obj.getStreamJSON(version, json_stream_data, decode_level, stream_p, filename));
-
- JSON::writeDictionaryItem(p, first, key, j, 3);
- if (f) {
- f_pl->finish();
- f_pl = nullptr;
- fclose(f);
- }
-}
-
-void
-QPDF::writeJSONObject(
- int version, Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle& obj)
-{
- auto j = JSON::makeDictionary();
- j.addDictionaryMember("value", obj.getJSON(version, true));
- JSON::writeDictionaryItem(p, first, key, j, 3);
+ auto filename = file_prefix + "-" + std::to_string(id);
+ auto* f = QUtil::safe_fopen(filename.c_str(), "wb");
+ Pl_StdioFile f_pl{"stream data", f};
+ stream.writeStreamJSON(version, jw, qpdf_sj_file, decode_level, &f_pl, filename);
+ f_pl.finish();
+ fclose(f);
}
void
@@ -893,80 +872,75 @@ QPDF::writeJSON(
std::string const& file_prefix,
std::set<std::string> wanted_objects)
{
- int const depth_outer = 1;
- int const depth_top = 1;
- int const depth_qpdf = 2;
- int const depth_qpdf_inner = 3;
-
if (version != 2) {
throw std::runtime_error("QPDF::writeJSON: only version 2 is supported");
}
- bool first = true;
+ JSON::Writer jw{p, 4};
if (complete) {
- JSON::writeDictionaryOpen(p, first, depth_outer);
- } else {
- first = first_key;
- }
- JSON::writeDictionaryKey(p, first, "qpdf", depth_top);
- bool first_qpdf = true;
- JSON::writeArrayOpen(p, first_qpdf, depth_top);
- JSON::writeNext(p, first_qpdf, depth_qpdf);
- bool first_qpdf_inner = true;
- JSON::writeDictionaryOpen(p, first_qpdf_inner, depth_qpdf);
- JSON::writeDictionaryItem(
- p, first_qpdf_inner, "jsonversion", JSON::makeInt(version), depth_qpdf_inner);
- JSON::writeDictionaryItem(
- p, first_qpdf_inner, "pdfversion", JSON::makeString(getPDFVersion()), depth_qpdf_inner);
- JSON::writeDictionaryItem(
- p,
- first_qpdf_inner,
- "pushedinheritedpageresources",
- JSON::makeBool(everPushedInheritedAttributesToPages()),
- depth_qpdf_inner);
- JSON::writeDictionaryItem(
- p,
- first_qpdf_inner,
- "calledgetallpages",
- JSON::makeBool(everCalledGetAllPages()),
- depth_qpdf_inner);
- JSON::writeDictionaryItem(
- p,
- first_qpdf_inner,
- "maxobjectid",
- JSON::makeInt(QIntC::to_longlong(getObjectCount())),
- depth_qpdf_inner);
- JSON::writeDictionaryClose(p, first_qpdf_inner, depth_qpdf);
- JSON::writeNext(p, first_qpdf, depth_qpdf);
- JSON::writeDictionaryOpen(p, first_qpdf_inner, depth_qpdf);
+ jw << "{";
+ } else if (!first_key) {
+ jw << ",";
+ }
+ first_key = false;
+
+ /* clang-format off */
+ jw << "\n"
+ " \"qpdf\": [\n"
+ " {\n"
+ " \"jsonversion\": " << std::to_string(version) << ",\n"
+ " \"pdfversion\": \"" << getPDFVersion() << "\",\n"
+ " \"pushedinheritedpageresources\": " << (everPushedInheritedAttributesToPages() ? "true" : "false") << ",\n"
+ " \"calledgetallpages\": " << (everCalledGetAllPages() ? "true" : "false") << ",\n"
+ " \"maxobjectid\": " << std::to_string(getObjectCount()) << "\n"
+ " },\n"
+ " {";
+ /* clang-format on */
+
bool all_objects = wanted_objects.empty();
+ bool first = true;
for (auto& obj: getAllObjects()) {
- std::string key = "obj:" + obj.unparse();
+ auto const og = obj.getObjGen();
+ std::string key = "obj:" + og.unparse(' ') + " R";
if (all_objects || wanted_objects.count(key)) {
- if (obj.isStream()) {
- writeJSONStream(
- version,
- p,
- first_qpdf_inner,
- key,
- obj,
- decode_level,
- json_stream_data,
- file_prefix);
+ if (first) {
+ jw << "\n \"" << key;
+ first = false;
} else {
- writeJSONObject(version, p, first_qpdf_inner, key, obj);
+ jw << "\n },\n \"" << key;
+ }
+ if (auto* stream = obj.getObjectPtr()->as<QPDF_Stream>()) {
+ jw << "\": {\n \"stream\": ";
+ if (json_stream_data == qpdf_sj_file) {
+ writeJSONStreamFile(
+ version, jw, *stream, og.getObj(), decode_level, file_prefix);
+ } else {
+ stream->writeStreamJSON(
+ version, jw, json_stream_data, decode_level, nullptr, "");
+ }
+ } else {
+ jw << "\": {\n \"value\": ";
+ obj.writeJSON(version, jw, true);
}
}
}
if (all_objects || wanted_objects.count("trailer")) {
- auto trailer = getTrailer();
- writeJSONObject(version, p, first_qpdf_inner, "trailer", trailer);
- }
- JSON::writeDictionaryClose(p, first_qpdf_inner, depth_qpdf);
- JSON::writeArrayClose(p, first_qpdf, depth_top);
+ if (!first) {
+ jw << "\n },";
+ }
+ jw << "\n \"trailer\": {\n \"value\": ";
+ getTrailer().writeJSON(version, jw, true);
+ first = false;
+ }
+ if (!first) {
+ jw << "\n }";
+ }
+ /* clang-format off */
+ jw << "\n"
+ " }\n"
+ " ]";
+ /* clang-format on */
if (complete) {
- JSON::writeDictionaryClose(p, first, 0);
- *p << "\n";
+ jw << "\n}\n";
p->finish();
}
- first_key = false;
}
diff --git a/libqpdf/qpdf/JSON_writer.hh b/libqpdf/qpdf/JSON_writer.hh
new file mode 100644
index 00000000..3f770c58
--- /dev/null
+++ b/libqpdf/qpdf/JSON_writer.hh
@@ -0,0 +1,137 @@
+#ifndef JSON_WRITER_HH
+#define JSON_WRITER_HH
+
+#include <qpdf/JSON.hh>
+#include <qpdf/Pipeline.hh>
+#include <qpdf/Pl_Base64.hh>
+#include <qpdf/Pl_Concatenate.hh>
+
+#include <string_view>
+
+// Writer is a small utility class to aid writing JSON to a pipeline. Methods are designed to allow
+// chaining of calls.
+//
+// Some uses of the class have a significant performance impact. The class is intended purely for
+// internal use to allow it to be adapted as needed to maintain performance.
+class JSON::Writer
+{
+ public:
+ Writer(Pipeline* p, size_t depth) :
+ p(p),
+ indent(2 * depth)
+ {
+ }
+
+ Writer&
+ write(char const* data, size_t len)
+ {
+ p->write(reinterpret_cast<unsigned char const*>(data), len);
+ return *this;
+ }
+
+ Writer&
+ writeBase64(std::string_view sv)
+ {
+ Pl_Concatenate cat{"writer concat", p};
+ Pl_Base64 base{"writer base64", &cat, Pl_Base64::a_encode};
+ base.write(reinterpret_cast<unsigned char const*>(sv.data()), sv.size());
+ base.finish();
+ return *this;
+ }
+
+ Writer&
+ writeNext()
+ {
+ auto n = indent;
+ if (first) {
+ first = false;
+ write(&spaces[1], n % n_spaces + 1);
+ } else {
+ write(&spaces[0], n % n_spaces + 2);
+ }
+ while (n >= n_spaces) {
+ write(&spaces[2], n_spaces);
+ n -= n_spaces;
+ }
+ return *this;
+ }
+
+ Writer&
+ writeStart(char const& c)
+ {
+ write(&c, 1);
+ first = true;
+ indent += 2;
+ return *this;
+ }
+
+ Writer&
+ writeEnd(char const& c)
+ {
+ if (indent > 1) {
+ indent -= 2;
+ }
+ if (!first) {
+ first = true;
+ writeNext();
+ }
+ first = false;
+ write(&c, 1);
+ return *this;
+ }
+
+ Writer&
+ operator<<(std::string_view sv)
+ {
+ p->write(reinterpret_cast<unsigned char const*>(sv.data()), sv.size());
+ return *this;
+ }
+
+ Writer&
+ operator<<(char const* s)
+ {
+ *this << std::string_view{s};
+ return *this;
+ }
+
+ Writer&
+ operator<<(bool val)
+ {
+ *this << (val ? "true" : "false");
+ return *this;
+ }
+
+ Writer&
+ operator<<(int val)
+ {
+ *this << std::to_string(val);
+ return *this;
+ }
+
+ Writer&
+ operator<<(size_t val)
+ {
+ *this << std::to_string(val);
+ return *this;
+ }
+
+ Writer&
+ operator<<(JSON&& j)
+ {
+ j.write(p, indent / 2);
+ return *this;
+ }
+
+ static std::string encode_string(std::string const& utf8);
+
+ private:
+ Pipeline* p;
+ bool first{true};
+ size_t indent;
+
+ static constexpr std::string_view spaces =
+ ",\n ";
+ static constexpr auto n_spaces = spaces.size() - 2;
+};
+
+#endif // JSON_WRITER_HH
diff --git a/libqpdf/qpdf/QPDFObject_private.hh b/libqpdf/qpdf/QPDFObject_private.hh
index 5e87c215..1c3dadc9 100644
--- a/libqpdf/qpdf/QPDFObject_private.hh
+++ b/libqpdf/qpdf/QPDFObject_private.hh
@@ -33,10 +33,10 @@ class QPDFObject
{
return value->unparse();
}
- JSON
- getJSON(int json_version)
+ void
+ writeJSON(int json_version, JSON::Writer& p)
{
- return value->getJSON(json_version);
+ return value->writeJSON(json_version, p);
}
std::string
getStringValue() const
diff --git a/libqpdf/qpdf/QPDFValue.hh b/libqpdf/qpdf/QPDFValue.hh
index db8fb923..28abf147 100644
--- a/libqpdf/qpdf/QPDFValue.hh
+++ b/libqpdf/qpdf/QPDFValue.hh
@@ -24,7 +24,7 @@ class QPDFValue: public std::enable_shared_from_this<QPDFValue>
virtual std::shared_ptr<QPDFObject> copy(bool shallow = false) = 0;
virtual std::string unparse() = 0;
- virtual JSON getJSON(int json_version) = 0;
+ virtual void writeJSON(int json_version, JSON::Writer& p) = 0;
struct JSON_Descr
{
diff --git a/libqpdf/qpdf/QPDF_Array.hh b/libqpdf/qpdf/QPDF_Array.hh
index 281e1d48..88b2a3d3 100644
--- a/libqpdf/qpdf/QPDF_Array.hh
+++ b/libqpdf/qpdf/QPDF_Array.hh
@@ -22,7 +22,7 @@ class QPDF_Array: public QPDFValue
create(std::vector<std::shared_ptr<QPDFObject>>&& items, bool sparse);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override;
- JSON getJSON(int json_version) override;
+ void writeJSON(int json_version, JSON::Writer& p) override;
void disconnect() override;
int
diff --git a/libqpdf/qpdf/QPDF_Bool.hh b/libqpdf/qpdf/QPDF_Bool.hh
index 0b4a71fd..1692bdc4 100644
--- a/libqpdf/qpdf/QPDF_Bool.hh
+++ b/libqpdf/qpdf/QPDF_Bool.hh
@@ -10,7 +10,8 @@ class QPDF_Bool: public QPDFValue
static std::shared_ptr<QPDFObject> create(bool val);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override;
- JSON getJSON(int json_version) override;
+ void writeJSON(int json_version, JSON::Writer& p) override;
+
bool getVal() const;
private:
diff --git a/libqpdf/qpdf/QPDF_Destroyed.hh b/libqpdf/qpdf/QPDF_Destroyed.hh
index 72e9130a..9259a2dc 100644
--- a/libqpdf/qpdf/QPDF_Destroyed.hh
+++ b/libqpdf/qpdf/QPDF_Destroyed.hh
@@ -9,7 +9,7 @@ class QPDF_Destroyed: public QPDFValue
~QPDF_Destroyed() override = default;
std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override;
- JSON getJSON(int json_version) override;
+ void writeJSON(int json_version, JSON::Writer& p) override;
static std::shared_ptr<QPDFValue> getInstance();
private:
diff --git a/libqpdf/qpdf/QPDF_Dictionary.hh b/libqpdf/qpdf/QPDF_Dictionary.hh
index 0fb6636e..8713a450 100644
--- a/libqpdf/qpdf/QPDF_Dictionary.hh
+++ b/libqpdf/qpdf/QPDF_Dictionary.hh
@@ -16,7 +16,7 @@ class QPDF_Dictionary: public QPDFValue
static std::shared_ptr<QPDFObject> create(std::map<std::string, QPDFObjectHandle>&& items);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override;
- JSON getJSON(int json_version) override;
+ void writeJSON(int json_version, JSON::Writer& p) override;
void disconnect() override;
// hasKey() and getKeys() treat keys with null values as if they aren't there. getKey() returns
diff --git a/libqpdf/qpdf/QPDF_InlineImage.hh b/libqpdf/qpdf/QPDF_InlineImage.hh
index bee12354..c06662d7 100644
--- a/libqpdf/qpdf/QPDF_InlineImage.hh
+++ b/libqpdf/qpdf/QPDF_InlineImage.hh
@@ -10,7 +10,7 @@ class QPDF_InlineImage: public QPDFValue
static std::shared_ptr<QPDFObject> create(std::string const& val);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override;
- JSON getJSON(int json_version) override;
+ void writeJSON(int json_version, JSON::Writer& p) override;
std::string
getStringValue() const override
{
diff --git a/libqpdf/qpdf/QPDF_Integer.hh b/libqpdf/qpdf/QPDF_Integer.hh
index 2c2cf2f9..ae7f7893 100644
--- a/libqpdf/qpdf/QPDF_Integer.hh
+++ b/libqpdf/qpdf/QPDF_Integer.hh
@@ -10,7 +10,7 @@ class QPDF_Integer: public QPDFValue
static std::shared_ptr<QPDFObject> create(long long value);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override;
- JSON getJSON(int json_version) override;
+ void writeJSON(int json_version, JSON::Writer& p) override;
long long getVal() const;
private:
diff --git a/libqpdf/qpdf/QPDF_Name.hh b/libqpdf/qpdf/QPDF_Name.hh
index c14d8659..b5d3c318 100644
--- a/libqpdf/qpdf/QPDF_Name.hh
+++ b/libqpdf/qpdf/QPDF_Name.hh
@@ -10,10 +10,15 @@ class QPDF_Name: public QPDFValue
static std::shared_ptr<QPDFObject> create(std::string const& name);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override;
- JSON getJSON(int json_version) override;
+ void writeJSON(int json_version, JSON::Writer& p) override;
// Put # into strings with characters unsuitable for name token
static std::string normalizeName(std::string const& name);
+
+ // Check whether name is valid utf-8 and whether it contains characters that require escaping.
+ // Return {false, false} if the name is not valid utf-8, otherwise return {true, true} if no
+ // characters require or {true, false} if escaping is required.
+ static std::pair<bool, bool> analyzeJSONEncoding(std::string const& name);
std::string
getStringValue() const override
{
diff --git a/libqpdf/qpdf/QPDF_Null.hh b/libqpdf/qpdf/QPDF_Null.hh
index a59b7509..fc6e0b5f 100644
--- a/libqpdf/qpdf/QPDF_Null.hh
+++ b/libqpdf/qpdf/QPDF_Null.hh
@@ -18,7 +18,7 @@ class QPDF_Null: public QPDFValue
std::string var_descr);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override;
- JSON getJSON(int json_version) override;
+ void writeJSON(int json_version, JSON::Writer& p) override;
private:
QPDF_Null();
diff --git a/libqpdf/qpdf/QPDF_Operator.hh b/libqpdf/qpdf/QPDF_Operator.hh
index 77aa5a17..b9b040d6 100644
--- a/libqpdf/qpdf/QPDF_Operator.hh
+++ b/libqpdf/qpdf/QPDF_Operator.hh
@@ -10,7 +10,7 @@ class QPDF_Operator: public QPDFValue
static std::shared_ptr<QPDFObject> create(std::string const& val);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override;
- JSON getJSON(int json_version) override;
+ void writeJSON(int json_version, JSON::Writer& p) override;
std::string
getStringValue() const override
{
diff --git a/libqpdf/qpdf/QPDF_Real.hh b/libqpdf/qpdf/QPDF_Real.hh
index db5e0940..aa9baa5f 100644
--- a/libqpdf/qpdf/QPDF_Real.hh
+++ b/libqpdf/qpdf/QPDF_Real.hh
@@ -12,7 +12,7 @@ class QPDF_Real: public QPDFValue
create(double value, int decimal_places, bool trim_trailing_zeroes);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override;
- JSON getJSON(int json_version) override;
+ void writeJSON(int json_version, JSON::Writer& p) override;
std::string
getStringValue() const override
{
diff --git a/libqpdf/qpdf/QPDF_Reserved.hh b/libqpdf/qpdf/QPDF_Reserved.hh
index 9ba855b7..801987e4 100644
--- a/libqpdf/qpdf/QPDF_Reserved.hh
+++ b/libqpdf/qpdf/QPDF_Reserved.hh
@@ -10,7 +10,7 @@ class QPDF_Reserved: public QPDFValue
static std::shared_ptr<QPDFObject> create();
std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override;
- JSON getJSON(int json_version) override;
+ void writeJSON(int json_version, JSON::Writer& p) override;
private:
QPDF_Reserved();
diff --git a/libqpdf/qpdf/QPDF_Stream.hh b/libqpdf/qpdf/QPDF_Stream.hh
index 8488e157..3fcbc428 100644
--- a/libqpdf/qpdf/QPDF_Stream.hh
+++ b/libqpdf/qpdf/QPDF_Stream.hh
@@ -25,7 +25,7 @@ class QPDF_Stream: public QPDFValue
size_t length);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override;
- JSON getJSON(int json_version) override;
+ void writeJSON(int json_version, JSON::Writer& p) override;
void setDescription(
QPDF*, std::shared_ptr<QPDFValue::Description>& description, qpdf_offset_t offset) override;
void disconnect() override;
@@ -64,6 +64,14 @@ class QPDF_Stream: public QPDFValue
qpdf_stream_decode_level_e decode_level,
Pipeline* p,
std::string const& data_filename);
+ qpdf_stream_decode_level_e writeStreamJSON(
+ int json_version,
+ JSON::Writer& jw,
+ qpdf_json_stream_data_e json_data,
+ qpdf_stream_decode_level_e decode_level,
+ Pipeline* p,
+ std::string const& data_filename,
+ bool no_data_key = false);
void replaceDict(QPDFObjectHandle const& new_dict);
diff --git a/libqpdf/qpdf/QPDF_String.hh b/libqpdf/qpdf/QPDF_String.hh
index c34cafef..967b2d30 100644
--- a/libqpdf/qpdf/QPDF_String.hh
+++ b/libqpdf/qpdf/QPDF_String.hh
@@ -16,7 +16,7 @@ class QPDF_String: public QPDFValue
std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override;
std::string unparse(bool force_binary);
- JSON getJSON(int json_version) override;
+ void writeJSON(int json_version, JSON::Writer& p) override;
std::string getUTF8Val() const;
std::string
getStringValue() const override
diff --git a/libqpdf/qpdf/QPDF_Unresolved.hh b/libqpdf/qpdf/QPDF_Unresolved.hh
index 0a1fa9a5..b4c1d9e4 100644
--- a/libqpdf/qpdf/QPDF_Unresolved.hh
+++ b/libqpdf/qpdf/QPDF_Unresolved.hh
@@ -10,7 +10,7 @@ class QPDF_Unresolved: public QPDFValue
static std::shared_ptr<QPDFObject> create(QPDF* qpdf, QPDFObjGen const& og);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override;
- JSON getJSON(int json_version) override;
+ void writeJSON(int json_version, JSON::Writer& p) override;
private:
QPDF_Unresolved(QPDF* qpdf, QPDFObjGen const& og);
diff --git a/qpdf/qtest/many-nulls.test b/qpdf/qtest/many-nulls.test
index 8a723d53..26ce5f8a 100644
--- a/qpdf/qtest/many-nulls.test
+++ b/qpdf/qtest/many-nulls.test
@@ -33,5 +33,22 @@ $td->runtest("copy sparse array",
{$td->COMMAND => "test_driver 97 many-nulls.pdf"},
{$td->STRING => "test 97 done\n", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
+$td->runtest("copy file with many nulls",
+ {$td->COMMAND =>
+ "qpdf minimal-nulls.pdf --qdf --static-id --no-original-object-ids a.pdf"},
+ {$td->STRING => "", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("compare files",
+ {$td->FILE => "a.pdf"},
+ {$td->FILE => "minimal-nulls.pdf"});
+$td->runtest("file with many nulls to JSON v1",
+ {$td->COMMAND => "qpdf minimal-nulls.pdf --json=1 -"},
+ {$td->FILE => "minimal-nulls-1.json", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("file with many nulls to JSON v2",
+ {$td->COMMAND => "qpdf minimal-nulls.pdf --json=2 -"},
+ {$td->FILE => "minimal-nulls-2.json", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+
cleanup();
-$td->report(4);
+$td->report(8);
diff --git a/qpdf/qtest/qpdf-json.test b/qpdf/qtest/qpdf-json.test
index 9542bccf..299bcd7e 100644
--- a/qpdf/qtest/qpdf-json.test
+++ b/qpdf/qtest/qpdf-json.test
@@ -350,7 +350,7 @@ $td->runtest("check C API write to JSON stream",
# (using #xx) would generate invalid JSON, even though qpdf's own JSON
# parser would accept it. Also, the JSON spec allows real numbers in
# scientific notation, but the PDF spec does not.
-$n_tests += 4;
+$n_tests += 7;
$td->runtest("handle binary names",
{$td->COMMAND =>
"qpdf --json-output weird-tokens.pdf a.json"},
@@ -371,6 +371,17 @@ $td->runtest("weird tokens with scientific notation",
"qpdf --json-input --json-output weird-tokens-alt.json -"},
{$td->FILE => "weird-tokens.json", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
-
+$td->runtest("handle binary names (JSON v1)",
+ {$td->COMMAND =>
+ "qpdf --json=1 weird-tokens.pdf a.json"},
+ {$td->STRING => "", $td->EXIT_STATUS => 0});
+$td->runtest("check json",
+ {$td->FILE => "a.json"},
+ {$td->FILE => "weird-tokens-v1.json"},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("write JSON to pipeline",
+ {$td->COMMAND => "test_driver 98 minimal.pdf ''"},
+ {$td->STRING => "test 98 done\n", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
cleanup();
$td->report($n_tests);
diff --git a/qpdf/qtest/qpdf/minimal-nulls-1.json b/qpdf/qtest/qpdf/minimal-nulls-1.json
new file mode 100644
index 00000000..0b8f6e53
--- /dev/null
+++ b/qpdf/qtest/qpdf/minimal-nulls-1.json
@@ -0,0 +1,453 @@
+{
+ "version": 1,
+ "parameters": {
+ "decodelevel": "generalized"
+ },
+ "pages": [
+ {
+ "contents": [
+ "4 0 R"
+ ],
+ "images": [],
+ "label": null,
+ "object": "3 0 R",
+ "outlines": [],
+ "pageposfrom1": 1
+ }
+ ],
+ "pagelabels": [],
+ "acroform": {
+ "fields": [],
+ "hasacroform": false,
+ "needappearances": false
+ },
+ "attachments": {},
+ "encrypt": {
+ "capabilities": {
+ "accessibility": true,
+ "extract": true,
+ "moddifyannotations": true,
+ "modify": true,
+ "modifyassembly": true,
+ "modifyforms": true,
+ "modifyother": true,
+ "printhigh": true,
+ "printlow": true
+ },
+ "encrypted": false,
+ "ownerpasswordmatched": false,
+ "parameters": {
+ "P": 0,
+ "R": 0,
+ "V": 0,
+ "bits": 0,
+ "filemethod": "none",
+ "key": null,
+ "method": "none",
+ "streammethod": "none",
+ "stringmethod": "none"
+ },
+ "recovereduserpassword": null,
+ "userpasswordmatched": false
+ },
+ "outlines": [],
+ "objects": {
+ "1 0 R": {
+ "/Pages": "2 0 R",
+ "/Type": "/Catalog"
+ },
+ "2 0 R": {
+ "/Count": 1,
+ "/Kids": [
+ "3 0 R"
+ ],
+ "/Type": "/Pages"
+ },
+ "3 0 R": {
+ "/Contents": "4 0 R",
+ "/MediaBox": [
+ 0,
+ 0,
+ 612,
+ 792
+ ],
+ "/Nulls": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ "6 0 R",
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ "/Parent": "2 0 R",
+ "/Resources": {
+ "/Font": {
+ "/F1": "7 0 R"
+ },
+ "/ProcSet": "8 0 R"
+ },
+ "/Type": "/Page"
+ },
+ "4 0 R": {
+ "/Length": "5 0 R"
+ },
+ "5 0 R": 44,
+ "6 0 R": null,
+ "7 0 R": {
+ "/BaseFont": "/Helvetica",
+ "/Encoding": "/WinAnsiEncoding",
+ "/Name": "/F1",
+ "/Subtype": "/Type1",
+ "/Type": "/Font"
+ },
+ "8 0 R": [
+ "/PDF",
+ "/Text"
+ ],
+ "trailer": {
+ "/ID": [
+ "ÏîgE�EMÛ‹Êߢ$²\u0005#",
+ "1AY&SXŠfi#—bd3…'Ł"
+ ],
+ "/Root": "1 0 R",
+ "/Size": 9
+ }
+ },
+ "objectinfo": {
+ "1 0 R": {
+ "stream": {
+ "filter": null,
+ "is": false,
+ "length": null
+ }
+ },
+ "2 0 R": {
+ "stream": {
+ "filter": null,
+ "is": false,
+ "length": null
+ }
+ },
+ "3 0 R": {
+ "stream": {
+ "filter": null,
+ "is": false,
+ "length": null
+ }
+ },
+ "4 0 R": {
+ "stream": {
+ "filter": null,
+ "is": true,
+ "length": 44
+ }
+ },
+ "5 0 R": {
+ "stream": {
+ "filter": null,
+ "is": false,
+ "length": null
+ }
+ },
+ "6 0 R": {
+ "stream": {
+ "filter": null,
+ "is": false,
+ "length": null
+ }
+ },
+ "7 0 R": {
+ "stream": {
+ "filter": null,
+ "is": false,
+ "length": null
+ }
+ },
+ "8 0 R": {
+ "stream": {
+ "filter": null,
+ "is": false,
+ "length": null
+ }
+ }
+ }
+}
diff --git a/qpdf/qtest/qpdf/minimal-nulls-2.json b/qpdf/qtest/qpdf/minimal-nulls-2.json
new file mode 100644
index 00000000..6bf61ea7
--- /dev/null
+++ b/qpdf/qtest/qpdf/minimal-nulls-2.json
@@ -0,0 +1,424 @@
+{
+ "version": 2,
+ "parameters": {
+ "decodelevel": "generalized"
+ },
+ "pages": [
+ {
+ "contents": [
+ "4 0 R"
+ ],
+ "images": [],
+ "label": null,
+ "object": "3 0 R",
+ "outlines": [],
+ "pageposfrom1": 1
+ }
+ ],
+ "pagelabels": [],
+ "acroform": {
+ "fields": [],
+ "hasacroform": false,
+ "needappearances": false
+ },
+ "attachments": {},
+ "encrypt": {
+ "capabilities": {
+ "accessibility": true,
+ "extract": true,
+ "modify": true,
+ "modifyannotations": true,
+ "modifyassembly": true,
+ "modifyforms": true,
+ "modifyother": true,
+ "printhigh": true,
+ "printlow": true
+ },
+ "encrypted": false,
+ "ownerpasswordmatched": false,
+ "parameters": {
+ "P": 0,
+ "R": 0,
+ "V": 0,
+ "bits": 0,
+ "filemethod": "none",
+ "key": null,
+ "method": "none",
+ "streammethod": "none",
+ "stringmethod": "none"
+ },
+ "recovereduserpassword": null,
+ "userpasswordmatched": false
+ },
+ "outlines": [],
+ "qpdf": [
+ {
+ "jsonversion": 2,
+ "pdfversion": "1.3",
+ "pushedinheritedpageresources": false,
+ "calledgetallpages": true,
+ "maxobjectid": 8
+ },
+ {
+ "obj:1 0 R": {
+ "value": {
+ "/Pages": "2 0 R",
+ "/Type": "/Catalog"
+ }
+ },
+ "obj:2 0 R": {
+ "value": {
+ "/Count": 1,
+ "/Kids": [
+ "3 0 R"
+ ],
+ "/Type": "/Pages"
+ }
+ },
+ "obj:3 0 R": {
+ "value": {
+ "/Contents": "4 0 R",
+ "/MediaBox": [
+ 0,
+ 0,
+ 612,
+ 792
+ ],
+ "/Nulls": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ "6 0 R",
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ 10,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ],
+ "/Parent": "2 0 R",
+ "/Resources": {
+ "/Font": {
+ "/F1": "7 0 R"
+ },
+ "/ProcSet": "8 0 R"
+ },
+ "/Type": "/Page"
+ }
+ },
+ "obj:4 0 R": {
+ "stream": {
+ "dict": {
+ "/Length": "5 0 R"
+ }
+ }
+ },
+ "obj:5 0 R": {
+ "value": 44
+ },
+ "obj:6 0 R": {
+ "value": null
+ },
+ "obj:7 0 R": {
+ "value": {
+ "/BaseFont": "/Helvetica",
+ "/Encoding": "/WinAnsiEncoding",
+ "/Name": "/F1",
+ "/Subtype": "/Type1",
+ "/Type": "/Font"
+ }
+ },
+ "obj:8 0 R": {
+ "value": [
+ "/PDF",
+ "/Text"
+ ]
+ },
+ "trailer": {
+ "value": {
+ "/ID": [
+ "b:cfee6745ad454ddb88cadfa224b20523",
+ "b:31415926535897932384626433832795"
+ ],
+ "/Root": "1 0 R",
+ "/Size": 9
+ }
+ }
+ }
+ ]
+}
diff --git a/qpdf/qtest/qpdf/minimal-nulls.pdf b/qpdf/qtest/qpdf/minimal-nulls.pdf
new file mode 100644
index 00000000..491d0b30
--- /dev/null
+++ b/qpdf/qtest/qpdf/minimal-nulls.pdf
@@ -0,0 +1,387 @@
+%PDF-1.3
+%
+%QDF-1.0
+
+1 0 obj
+<<
+ /Pages 2 0 R
+ /Type /Catalog
+>>
+endobj
+
+2 0 obj
+<<
+ /Count 1
+ /Kids [
+ 3 0 R
+ ]
+ /Type /Pages
+>>
+endobj
+
+%% Page 1
+3 0 obj
+<<
+ /Contents 4 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Nulls [
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 6 0 R
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ 10
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ null
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 7 0 R
+ >>
+ /ProcSet 8 0 R
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Contents for page 1
+4 0 obj
+<<
+ /Length 5 0 R
+>>
+stream
+BT
+ /F1 24 Tf
+ 72 720 Td
+ (Potato) Tj
+ET
+endstream
+endobj
+
+5 0 obj
+44
+endobj
+
+6 0 obj
+null
+endobj
+
+7 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+8 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+xref
+0 9
+0000000000 65535 f
+0000000025 00000 n
+0000000079 00000 n
+0000000161 00000 n
+0000002909 00000 n
+0000003008 00000 n
+0000003027 00000 n
+0000003048 00000 n
+0000003166 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 9
+ /ID [<cfee6745ad454ddb88cadfa224b20523><31415926535897932384626433832795>]
+>>
+startxref
+3201
+%%EOF
diff --git a/qpdf/qtest/qpdf/weird-tokens-alt.json b/qpdf/qtest/qpdf/weird-tokens-alt.json
index 607bdd55..7fbff908 100644
--- a/qpdf/qtest/qpdf/weird-tokens-alt.json
+++ b/qpdf/qtest/qpdf/weird-tokens-alt.json
@@ -10,21 +10,155 @@
{
"obj:1 0 R": {
"value": {
+ "/Escape\\Key": 42,
"/Extra": [
"u:Names with binary data",
"n:/ABCDEF+#ba#da#cc#e5",
"n:/OVERLONG+#c0#81",
+ "n:/OVERLONG+#c1#ff",
+ "/Ok+€",
"n:/OVERLONG+#e0#81#82",
+ "n:/OVERLONG+#e0#9f#ff",
+ "/Ok+ࠀ",
"n:/OVERLONG+#f0#81#82#83",
+ "n:/OVERLONG+#f0#8f#ff#ff",
+ "/Ok+𐀀",
"n:/range+#01",
"n:/low+#18",
"/ABCEDEF+π",
"n:/one+#a0two",
"n:/text#2fplain",
+ "u:Names requiring escaping in JSON",
+ "/Back\\shlash",
+ "/Low\u0022",
+ "/Low\u001f",
+ "/ExceptSpace ",
+ "/Except!",
"u:Very small/large reals",
1e-05,
1e12
],
+ "/Nested": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": 42
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/Pages": "2 0 R",
"/Type": "/Catalog",
"n:/WeirdKey+#ba#da#cc#e5": 42
@@ -78,7 +212,7 @@
"value": {
"/ID": [
"b:42841c13bbf709d79a200fa1691836f8",
- "b:728c020f464c3cf7e02c12605fa7d88b"
+ "b:31415926535897932384626433832795"
],
"/Root": "1 0 R",
"/Size": 7
diff --git a/qpdf/qtest/qpdf/weird-tokens-v1.json b/qpdf/qtest/qpdf/weird-tokens-v1.json
new file mode 100644
index 00000000..8b8c194c
--- /dev/null
+++ b/qpdf/qtest/qpdf/weird-tokens-v1.json
@@ -0,0 +1,295 @@
+{
+ "version": 1,
+ "parameters": {
+ "decodelevel": "generalized"
+ },
+ "pages": [
+ {
+ "contents": [
+ "4 0 R"
+ ],
+ "images": [],
+ "label": null,
+ "object": "3 0 R",
+ "outlines": [],
+ "pageposfrom1": 1
+ }
+ ],
+ "pagelabels": [],
+ "acroform": {
+ "fields": [],
+ "hasacroform": false,
+ "needappearances": false
+ },
+ "attachments": {},
+ "encrypt": {
+ "capabilities": {
+ "accessibility": true,
+ "extract": true,
+ "moddifyannotations": true,
+ "modify": true,
+ "modifyassembly": true,
+ "modifyforms": true,
+ "modifyother": true,
+ "printhigh": true,
+ "printlow": true
+ },
+ "encrypted": false,
+ "ownerpasswordmatched": false,
+ "parameters": {
+ "P": 0,
+ "R": 0,
+ "V": 0,
+ "bits": 0,
+ "filemethod": "none",
+ "key": null,
+ "method": "none",
+ "streammethod": "none",
+ "stringmethod": "none"
+ },
+ "recovereduserpassword": null,
+ "userpasswordmatched": false
+ },
+ "outlines": [],
+ "objects": {
+ "1 0 R": {
+ "/Escape\\Key": 42,
+ "/Extra": [
+ "Names with binary data",
+ "/ABCDEF+#ba#da#cc#e5",
+ "/OVERLONG+#c0#81",
+ "/OVERLONG+#c1#ff",
+ "/Ok+#c2#80",
+ "/OVERLONG+#e0#81#82",
+ "/OVERLONG+#e0#9f#ff",
+ "/Ok+#e0#a0#80",
+ "/OVERLONG+#f0#81#82#83",
+ "/OVERLONG+#f0#8f#ff#ff",
+ "/Ok+#f0#90#80#80",
+ "/range+#01",
+ "/low+#18",
+ "/ABCEDEF+#cf#80",
+ "/one+#a0two",
+ "/text#2fplain",
+ "Names requiring escaping in JSON",
+ "/Back\\shlash",
+ "/Low\"",
+ "/Low#1f",
+ "/ExceptSpace#20",
+ "/Except!",
+ "Very small/large reals",
+ 0.00001,
+ 1000000000000
+ ],
+ "/Nested": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": 42
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/Pages": "2 0 R",
+ "/Type": "/Catalog",
+ "/WeirdKey+#ba#da#cc#e5": 42
+ },
+ "2 0 R": {
+ "/Count": 1,
+ "/Kids": [
+ "3 0 R"
+ ],
+ "/Type": "/Pages"
+ },
+ "3 0 R": {
+ "/Contents": "4 0 R",
+ "/MediaBox": [
+ 0,
+ 0,
+ 612,
+ 792
+ ],
+ "/Parent": "2 0 R",
+ "/Resources": {
+ "/Font": {
+ "/F1": "6 0 R"
+ }
+ },
+ "/Type": "/Page"
+ },
+ "4 0 R": {
+ "/Length": "5 0 R"
+ },
+ "5 0 R": 44,
+ "6 0 R": {
+ "/BaseFont": "/Helvetica",
+ "/Encoding": "/WinAnsiEncoding",
+ "/Subtype": "/Type1",
+ "/Type": "/Font"
+ },
+ "trailer": {
+ "/ID": [
+ "B—˝\u0013»÷\t×ı \u000f¡i˘6ø",
+ "1AY&SXŠfi#—bd3…'Ł"
+ ],
+ "/Root": "1 0 R",
+ "/Size": 7
+ }
+ },
+ "objectinfo": {
+ "1 0 R": {
+ "stream": {
+ "filter": null,
+ "is": false,
+ "length": null
+ }
+ },
+ "2 0 R": {
+ "stream": {
+ "filter": null,
+ "is": false,
+ "length": null
+ }
+ },
+ "3 0 R": {
+ "stream": {
+ "filter": null,
+ "is": false,
+ "length": null
+ }
+ },
+ "4 0 R": {
+ "stream": {
+ "filter": null,
+ "is": true,
+ "length": 44
+ }
+ },
+ "5 0 R": {
+ "stream": {
+ "filter": null,
+ "is": false,
+ "length": null
+ }
+ },
+ "6 0 R": {
+ "stream": {
+ "filter": null,
+ "is": false,
+ "length": null
+ }
+ }
+ }
+}
diff --git a/qpdf/qtest/qpdf/weird-tokens.json b/qpdf/qtest/qpdf/weird-tokens.json
index 6aca6a5a..4ad926e7 100644
--- a/qpdf/qtest/qpdf/weird-tokens.json
+++ b/qpdf/qtest/qpdf/weird-tokens.json
@@ -10,21 +10,155 @@
{
"obj:1 0 R": {
"value": {
+ "/Escape\\Key": 42,
"/Extra": [
"u:Names with binary data",
"n:/ABCDEF+#ba#da#cc#e5",
"n:/OVERLONG+#c0#81",
+ "n:/OVERLONG+#c1#ff",
+ "/Ok+€",
"n:/OVERLONG+#e0#81#82",
+ "n:/OVERLONG+#e0#9f#ff",
+ "/Ok+ࠀ",
"n:/OVERLONG+#f0#81#82#83",
+ "n:/OVERLONG+#f0#8f#ff#ff",
+ "/Ok+𐀀",
"/range+\u0001",
"/low+\u0018",
"/ABCEDEF+π",
"n:/one+#a0two",
"/text/plain",
+ "u:Names requiring escaping in JSON",
+ "/Back\\shlash",
+ "/Low\"",
+ "/Low\u001f",
+ "/ExceptSpace ",
+ "/Except!",
"u:Very small/large reals",
0.00001,
1000000000000
],
+ "/Nested": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": {
+ "/1": {
+ "/2": {
+ "/3": {
+ "/4": {
+ "/5": {
+ "/6": {
+ "/7": {
+ "/8": {
+ "/9": {
+ "/10": 42
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/Pages": "2 0 R",
"/Type": "/Catalog",
"n:/WeirdKey+#ba#da#cc#e5": 42
@@ -78,7 +212,7 @@
"value": {
"/ID": [
"b:42841c13bbf709d79a200fa1691836f8",
- "b:728c020f464c3cf7e02c12605fa7d88b"
+ "b:31415926535897932384626433832795"
],
"/Root": "1 0 R",
"/Size": 7
diff --git a/qpdf/qtest/qpdf/weird-tokens.pdf b/qpdf/qtest/qpdf/weird-tokens.pdf
index 27415a46..c49a046f 100644
--- a/qpdf/qtest/qpdf/weird-tokens.pdf
+++ b/qpdf/qtest/qpdf/weird-tokens.pdf
@@ -4,21 +4,155 @@
1 0 obj
<<
+ /Escape\Key 42
/Extra [
(Names with binary data)
/ABCDEF+#ba#da#cc#e5
/OVERLONG+#c0#81
+ /OVERLONG+#c1#ff
+ /Ok+#c2#80
/OVERLONG+#e0#81#82
+ /OVERLONG+#e0#9f#ff
+ /Ok+#e0#a0#80
/OVERLONG+#f0#81#82#83
+ /OVERLONG+#f0#8f#ff#ff
+ /Ok+#f0#90#80#80
/range+#01
/low+#18
/ABCEDEF+#cf#80
/one+#a0two
/text#2fplain
+ (Names requiring escaping in JSON)
+ /Back\shlash
+ /Low"
+ /Low#1f
+ /ExceptSpace#20
+ /Except!
(Very small/large reals)
0.00001
1000000000000
]
+ /Nested <<
+ /1 <<
+ /2 <<
+ /3 <<
+ /4 <<
+ /5 <<
+ /6 <<
+ /7 <<
+ /8 <<
+ /9 <<
+ /10 <<
+ /1 <<
+ /2 <<
+ /3 <<
+ /4 <<
+ /5 <<
+ /6 <<
+ /7 <<
+ /8 <<
+ /9 <<
+ /10 <<
+ /1 <<
+ /2 <<
+ /3 <<
+ /4 <<
+ /5 <<
+ /6 <<
+ /7 <<
+ /8 <<
+ /9 <<
+ /10 <<
+ /1 <<
+ /2 <<
+ /3 <<
+ /4 <<
+ /5 <<
+ /6 <<
+ /7 <<
+ /8 <<
+ /9 <<
+ /10 <<
+ /1 <<
+ /2 <<
+ /3 <<
+ /4 <<
+ /5 <<
+ /6 <<
+ /7 <<
+ /8 <<
+ /9 <<
+ /10 <<
+ /1 <<
+ /2 <<
+ /3 <<
+ /4 <<
+ /5 <<
+ /6 <<
+ /7 <<
+ /8 <<
+ /9 <<
+ /10 42
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
+ >>
/Pages 2 0 R
/Type /Catalog
/WeirdKey+#ba#da#cc#e5 42
@@ -86,16 +220,16 @@ xref
0 7
0000000000 65535 f
0000000025 00000 n
-0000000389 00000 n
-0000000471 00000 n
-0000000667 00000 n
-0000000766 00000 n
-0000000785 00000 n
+0000008642 00000 n
+0000008724 00000 n
+0000008920 00000 n
+0000009019 00000 n
+0000009038 00000 n
trailer <<
/Root 1 0 R
/Size 7
- /ID [<42841c13bbf709d79a200fa1691836f8><728c020f464c3cf7e02c12605fa7d88b>]
+ /ID [<42841c13bbf709d79a200fa1691836f8><31415926535897932384626433832795>]
>>
startxref
-891
+9144
%%EOF
diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc
index e7451576..565b517d 100644
--- a/qpdf/test_driver.cc
+++ b/qpdf/test_driver.cc
@@ -3382,6 +3382,22 @@ test_97(QPDF& pdf, char const* arg2)
assert(nulls.unparse() == nulls2.unparse());
}
+static void
+test_98(QPDF& pdf, char const* arg2)
+{
+ // Test QPDFObjectHandle::writeJSON. This test is built for minimal.pdf.
+ for (int i = 1; i < 7; ++i) {
+ auto oh = pdf.getObject(i, 0);
+ Pl_Buffer bf1{"write", nullptr};
+ Pl_Buffer bf2{"get", nullptr};
+ oh.writeJSON(JSON::LATEST, &bf1, true, 7);
+ bf1.finish();
+ oh.getJSON(JSON::LATEST, true).write(&bf2, 7);
+ bf2.finish();
+ assert(bf1.getString() == bf2.getString());
+ }
+}
+
void
runtest(int n, char const* filename1, char const* arg2)
{
@@ -3483,7 +3499,7 @@ runtest(int n, char const* filename1, char const* arg2)
{78, test_78}, {79, test_79}, {80, test_80}, {81, test_81}, {82, test_82}, {83, test_83},
{84, test_84}, {85, test_85}, {86, test_86}, {87, test_87}, {88, test_88}, {89, test_89},
{90, test_90}, {91, test_91}, {92, test_92}, {93, test_93}, {94, test_94}, {95, test_95},
- {96, test_96}, {97, test_97}};
+ {96, test_96}, {97, test_97}, {98, test_98}};
auto fn = test_functions.find(n);
if (fn == test_functions.end()) {