Merge branch 'jw' from #1146 into work

This commit is contained in:
Jay Berkenbilt 2024-02-17 14:15:48 -05:00
commit e362bce8e8
49 changed files with 2555 additions and 327 deletions

View File

@ -290,8 +290,11 @@ class JSON
QPDF_DLL QPDF_DLL
qpdf_offset_t getEnd() const; 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: private:
static std::string encode_string(std::string const& utf8);
static void writeClose(Pipeline* p, bool first, size_t depth, char const* delimeter); static void writeClose(Pipeline* p, bool first, size_t depth, char const* delimeter);
enum value_type_e { enum value_type_e {

View File

@ -1411,19 +1411,6 @@ class QPDF
// JSON import // JSON import
void importJSON(std::shared_ptr<InputSource>, bool must_be_complete); 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 // Type conversion helper methods
template <typename T> template <typename T>
static qpdf_offset_t static qpdf_offset_t

View File

@ -551,7 +551,6 @@ class QPDFJob
// JSON // JSON
void doJSON(QPDF& pdf, Pipeline*); void doJSON(QPDF& pdf, Pipeline*);
QPDFObjGen::set getWantedJSONObjects(); QPDFObjGen::set getWantedJSONObjects();
void doJSONObject(Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle&);
void doJSONObjects(Pipeline* p, bool& first, QPDF& pdf); void doJSONObjects(Pipeline* p, bool& first, QPDF& pdf);
void doJSONObjectinfo(Pipeline* p, bool& first, QPDF& pdf); void doJSONObjectinfo(Pipeline* p, bool& first, QPDF& pdf);
void doJSONPages(Pipeline* p, bool& first, QPDF& pdf); void doJSONPages(Pipeline* p, bool& first, QPDF& pdf);

View File

@ -1197,6 +1197,13 @@ class QPDFObjectHandle
QPDF_DLL QPDF_DLL
JSON getJSON(int json_version, bool dereference_indirect = false); 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. // Deprecated version uses v1 for backward compatibility.
// ABI: remove for qpdf 12 // ABI: remove for qpdf 12
[[deprecated("Use getJSON(int version)")]] QPDF_DLL JSON [[deprecated("Use getJSON(int version)")]] QPDF_DLL JSON
@ -1353,6 +1360,8 @@ class QPDFObjectHandle
return obj.get(); return obj.get();
} }
void writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect = false);
private: private:
QPDF_Array* asArray(); QPDF_Array* asArray();
QPDF_Bool* asBool(); QPDF_Bool* asBool();

View File

@ -1,5 +1,7 @@
#include <qpdf/JSON.hh> #include <qpdf/JSON.hh>
#include <qpdf/JSON_writer.hh>
#include <qpdf/BufferInputSource.hh> #include <qpdf/BufferInputSource.hh>
#include <qpdf/Pl_Base64.hh> #include <qpdf/Pl_Base64.hh>
#include <qpdf/Pl_Concatenate.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::JSON_string::JSON_string(std::string const& utf8) :
JSON_value(vt_string), JSON_value(vt_string),
utf8(utf8), utf8(utf8),
encoded(encode_string(utf8)) encoded(Writer::encode_string(utf8))
{ {
} }
@ -211,7 +213,7 @@ JSON::unparse() const
} }
std::string std::string
JSON::encode_string(std::string const& str) JSON::Writer::encode_string(std::string const& str)
{ {
static auto constexpr hexchars = "0123456789abcdef"; static auto constexpr hexchars = "0123456789abcdef";
@ -279,7 +281,7 @@ JSON
JSON::addDictionaryMember(std::string const& key, JSON const& val) JSON::addDictionaryMember(std::string const& key, JSON const& val)
{ {
if (auto* obj = m ? dynamic_cast<JSON_dictionary*>(m->value.get()) : nullptr) { 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 { } else {
throw std::runtime_error("JSON::addDictionaryMember called on non-dictionary"); throw std::runtime_error("JSON::addDictionaryMember called on non-dictionary");
} }

View File

@ -954,23 +954,6 @@ QPDFJob::getWantedJSONObjects()
return wanted_og; return wanted_og;
} }
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 void
QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf) QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf)
{ {
@ -982,16 +965,17 @@ QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf)
auto wanted_og = getWantedJSONObjects(); auto wanted_og = getWantedJSONObjects();
for (auto& obj: pdf.getAllObjects()) { for (auto& obj: pdf.getAllObjects()) {
std::string key = obj.unparse(); std::string key = obj.unparse();
if (m->json_version > 1) {
key = "obj:" + key;
}
if (all_objects || wanted_og.count(obj.getObjGen())) { 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")) { if (all_objects || m->json_objects.count("trailer")) {
auto trailer = pdf.getTrailer(); JSON::writeDictionaryKey(p, first_object, "trailer", 2);
doJSONObject(p, first_object, "trailer", trailer); pdf.getTrailer().writeJSON(1, p, true, 2);
first_object = false;
} }
JSON::writeDictionaryClose(p, first_object, 1); JSON::writeDictionaryClose(p, first_object, 1);
} else { } else {
@ -3097,9 +3081,10 @@ QPDFJob::writeOutfile(QPDF& pdf)
try { try {
QUtil::remove_file(backup.c_str()); QUtil::remove_file(backup.c_str());
} catch (QPDFSystemError& e) { } catch (QPDFSystemError& e) {
*m->log->getError() << m->message_prefix << ": unable to delete original file (" *m->log->getError()
<< e.what() << ");" << " original file left in " << backup << m->message_prefix << ": unable to delete original file (" << e.what() << ");"
<< ", but the input was successfully replaced\n"; << " original file left in " << backup
<< ", but the input was successfully replaced\n";
} }
} }
} }

View File

@ -3,6 +3,7 @@
#include <qpdf/BufferInputSource.hh> #include <qpdf/BufferInputSource.hh>
#include <qpdf/Pl_Buffer.hh> #include <qpdf/Pl_Buffer.hh>
#include <qpdf/Pl_QPDFTokenizer.hh> #include <qpdf/Pl_QPDFTokenizer.hh>
#include <qpdf/JSON_writer.hh>
#include <qpdf/QPDF.hh> #include <qpdf/QPDF.hh>
#include <qpdf/QPDFExc.hh> #include <qpdf/QPDFExc.hh>
#include <qpdf/QPDFLogger.hh> #include <qpdf/QPDFLogger.hh>
@ -1617,10 +1618,33 @@ QPDFObjectHandle::getJSON(int json_version, bool dereference_indirect)
} else if (!dereference()) { } else if (!dereference()) {
throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle"); throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
} else { } 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 JSON
QPDFObjectHandle::getStreamJSON( QPDFObjectHandle::getStreamJSON(
int json_version, int json_version,

View File

@ -1,5 +1,6 @@
#include <qpdf/QPDF_Array.hh> #include <qpdf/QPDF_Array.hh>
#include <qpdf/JSON_writer.hh>
#include <qpdf/QPDFObjectHandle.hh> #include <qpdf/QPDFObjectHandle.hh>
#include <qpdf/QPDFObject_private.hh> #include <qpdf/QPDFObject_private.hh>
#include <qpdf/QTC.hh> #include <qpdf/QTC.hh>
@ -148,36 +149,41 @@ QPDF_Array::unparse()
return result; return result;
} }
JSON void
QPDF_Array::getJSON(int json_version) QPDF_Array::writeJSON(int json_version, JSON::Writer& p)
{ {
static const JSON j_null = JSON::makeNull(); p.writeStart('[');
JSON j_array = JSON::makeArray();
if (sp) { if (sp) {
int next = 0; int next = 0;
for (auto& item: sp->elements) { for (auto& item: sp->elements) {
int key = item.first; int key = item.first;
for (int j = next; j < key; ++j) { for (int j = next; j < key; ++j) {
j_array.addArrayElement(j_null); p.writeNext() << "null";
} }
p.writeNext();
auto og = item.second->getObjGen(); auto og = item.second->getObjGen();
j_array.addArrayElement( if (og.isIndirect()) {
og.isIndirect() ? JSON::makeString(og.unparse(' ') + " R") p << "\"" << og.unparse(' ') << " R\"";
: item.second->getJSON(json_version)); } else {
item.second->writeJSON(json_version, p);
}
next = ++key; next = ++key;
} }
for (int j = next; j < sp->size; ++j) { for (int j = next; j < sp->size; ++j) {
j_array.addArrayElement(j_null); p.writeNext() << "null";
} }
} else { } else {
for (auto const& item: elements) { for (auto const& item: elements) {
p.writeNext();
auto og = item->getObjGen(); auto og = item->getObjGen();
j_array.addArrayElement( if (og.isIndirect()) {
og.isIndirect() ? JSON::makeString(og.unparse(' ') + " R") p << "\"" << og.unparse(' ') << " R\"";
: item->getJSON(json_version)); } else {
item->writeJSON(json_version, p);
}
} }
} }
return j_array; p.writeEnd(']');
} }
QPDFObjectHandle QPDFObjectHandle

View File

@ -1,5 +1,7 @@
#include <qpdf/QPDF_Bool.hh> #include <qpdf/QPDF_Bool.hh>
#include <qpdf/JSON_writer.hh>
QPDF_Bool::QPDF_Bool(bool val) : QPDF_Bool::QPDF_Bool(bool val) :
QPDFValue(::ot_boolean, "boolean"), QPDFValue(::ot_boolean, "boolean"),
val(val) val(val)
@ -24,10 +26,10 @@ QPDF_Bool::unparse()
return (val ? "true" : "false"); return (val ? "true" : "false");
} }
JSON void
QPDF_Bool::getJSON(int json_version) QPDF_Bool::writeJSON(int json_version, JSON::Writer& p)
{ {
return JSON::makeBool(this->val); p << val;
} }
bool bool

View File

@ -28,9 +28,8 @@ QPDF_Destroyed::unparse()
return ""; return "";
} }
JSON void
QPDF_Destroyed::getJSON(int json_version) QPDF_Destroyed::writeJSON(int json_version, JSON::Writer& p)
{ {
throw std::logic_error("attempted to get JSON from a QPDFObjectHandle from a destroyed QPDF"); throw std::logic_error("attempted to get JSON from a QPDFObjectHandle from a destroyed QPDF");
return JSON::makeNull(); }
}

View File

@ -1,5 +1,6 @@
#include <qpdf/QPDF_Dictionary.hh> #include <qpdf/QPDF_Dictionary.hh>
#include <qpdf/JSON_writer.hh>
#include <qpdf/QPDFObject_private.hh> #include <qpdf/QPDFObject_private.hh>
#include <qpdf/QPDF_Name.hh> #include <qpdf/QPDF_Name.hh>
#include <qpdf/QPDF_Null.hh> #include <qpdf/QPDF_Null.hh>
@ -67,28 +68,30 @@ QPDF_Dictionary::unparse()
return result; return result;
} }
JSON void
QPDF_Dictionary::getJSON(int json_version) QPDF_Dictionary::writeJSON(int json_version, JSON::Writer& p)
{ {
JSON j = JSON::makeDictionary(); p.writeStart('{');
for (auto& iter: this->items) { for (auto& iter: this->items) {
if (!iter.second.isNull()) { if (!iter.second.isNull()) {
p.writeNext();
if (json_version == 1) { if (json_version == 1) {
j.addDictionaryMember( p << "\"" << JSON::Writer::encode_string(QPDF_Name::normalizeName(iter.first))
QPDF_Name::normalizeName(iter.first), iter.second.getJSON(json_version)); << "\": ";
} 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 { } else {
bool has_8bit_chars; p << "\"n:" << JSON::Writer::encode_string(QPDF_Name::normalizeName(iter.first))
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));
} }
iter.second.writeJSON(json_version, p);
} }
} }
return j; p.writeEnd('}');
} }
bool bool

View File

@ -1,5 +1,7 @@
#include <qpdf/QPDF_InlineImage.hh> #include <qpdf/QPDF_InlineImage.hh>
#include <qpdf/JSON_writer.hh>
QPDF_InlineImage::QPDF_InlineImage(std::string const& val) : QPDF_InlineImage::QPDF_InlineImage(std::string const& val) :
QPDFValue(::ot_inlineimage, "inline-image"), QPDFValue(::ot_inlineimage, "inline-image"),
val(val) val(val)
@ -24,8 +26,8 @@ QPDF_InlineImage::unparse()
return this->val; return this->val;
} }
JSON void
QPDF_InlineImage::getJSON(int json_version) QPDF_InlineImage::writeJSON(int json_version, JSON::Writer& p)
{ {
return JSON::makeNull(); p << "null";
} }

View File

@ -1,5 +1,6 @@
#include <qpdf/QPDF_Integer.hh> #include <qpdf/QPDF_Integer.hh>
#include <qpdf/JSON_writer.hh>
#include <qpdf/QUtil.hh> #include <qpdf/QUtil.hh>
QPDF_Integer::QPDF_Integer(long long val) : QPDF_Integer::QPDF_Integer(long long val) :
@ -26,10 +27,10 @@ QPDF_Integer::unparse()
return std::to_string(this->val); return std::to_string(this->val);
} }
JSON void
QPDF_Integer::getJSON(int json_version) QPDF_Integer::writeJSON(int json_version, JSON::Writer& p)
{ {
return JSON::makeInt(this->val); p << std::to_string(this->val);
} }
long long long long

View File

@ -1,7 +1,10 @@
#include <qpdf/QPDF_Name.hh> #include <qpdf/QPDF_Name.hh>
#include <qpdf/JSON_writer.hh>
#include <qpdf/QUtil.hh> #include <qpdf/QUtil.hh>
#include <string_view>
QPDF_Name::QPDF_Name(std::string const& name) : QPDF_Name::QPDF_Name(std::string const& name) :
QPDFValue(::ot_name, "name"), QPDFValue(::ot_name, "name"),
name(name) name(name)
@ -51,20 +54,71 @@ QPDF_Name::unparse()
return normalizeName(this->name); return normalizeName(this->name);
} }
JSON std::pair<bool, bool>
QPDF_Name::getJSON(int json_version) QPDF_Name::analyzeJSONEncoding(const std::string& name)
{ {
if (json_version == 1) { std::basic_string_view<unsigned char> view{
return JSON::makeString(normalizeName(this->name)); reinterpret_cast<const unsigned char*>(name.data()), name.size()};
} else {
bool has_8bit_chars; int tail = 0; // Number of continuation characters expected.
bool is_valid_utf8; bool tail2 = false; // Potential overlong 3 octet utf-8.
bool is_utf16; bool tail3 = false; // potential overlong 4 octet
QUtil::analyze_encoding(this->name, has_8bit_chars, is_valid_utf8, is_utf16); bool needs_escaping = false;
if (!has_8bit_chars || is_valid_utf8) { for (auto const& c: view) {
return JSON::makeString(this->name); 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 { } else {
return JSON::makeString("n:" + normalizeName(this->name)); 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) {
p << "\"" << JSON::Writer::encode_string(normalizeName(name)) << "\"";
} else {
if (auto res = analyzeJSONEncoding(name); res.first) {
if (res.second) {
p << "\"" << name << "\"";
} else {
p << "\"" << JSON::Writer::encode_string(name) << "\"";
}
} else {
p << "\"n:" << JSON::Writer::encode_string(normalizeName(name)) << "\"";
} }
} }
} }

View File

@ -1,5 +1,6 @@
#include <qpdf/QPDF_Null.hh> #include <qpdf/QPDF_Null.hh>
#include <qpdf/JSON_writer.hh>
#include <qpdf/QPDFObject_private.hh> #include <qpdf/QPDFObject_private.hh>
QPDF_Null::QPDF_Null() : QPDF_Null::QPDF_Null() :
@ -43,9 +44,8 @@ QPDF_Null::unparse()
return "null"; return "null";
} }
JSON void
QPDF_Null::getJSON(int json_version) QPDF_Null::writeJSON(int json_version, JSON::Writer& p)
{ {
// If this is updated, QPDF_Array::getJSON must also be updated. p << "null";
return JSON::makeNull();
} }

View File

@ -1,5 +1,7 @@
#include <qpdf/QPDF_Operator.hh> #include <qpdf/QPDF_Operator.hh>
#include <qpdf/JSON_writer.hh>
QPDF_Operator::QPDF_Operator(std::string const& val) : QPDF_Operator::QPDF_Operator(std::string const& val) :
QPDFValue(::ot_operator, "operator"), QPDFValue(::ot_operator, "operator"),
val(val) val(val)
@ -24,8 +26,8 @@ QPDF_Operator::unparse()
return val; return val;
} }
JSON void
QPDF_Operator::getJSON(int json_version) QPDF_Operator::writeJSON(int json_version, JSON::Writer& p)
{ {
return JSON::makeNull(); p << "null";
} }

View File

@ -1,5 +1,6 @@
#include <qpdf/QPDF_Real.hh> #include <qpdf/QPDF_Real.hh>
#include <qpdf/JSON_writer.hh>
#include <qpdf/QUtil.hh> #include <qpdf/QUtil.hh>
QPDF_Real::QPDF_Real(std::string const& val) : QPDF_Real::QPDF_Real(std::string const& val) :
@ -38,21 +39,17 @@ QPDF_Real::unparse()
return this->val; return this->val;
} }
JSON void
QPDF_Real::getJSON(int json_version) 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) { if (this->val.length() == 0) {
// Can't really happen... // Can't really happen...
result = "0"; p << "0";
} else if (this->val.at(0) == '.') { } else if (this->val.at(0) == '.') {
result = "0" + this->val; p << "0" << this->val;
} else if ((this->val.length() >= 2) && (this->val.at(0) == '-') && (this->val.at(1) == '.')) { } else if (this->val.length() >= 2 && this->val.at(0) == '-' && this->val.at(1) == '.') {
result = "-0." + this->val.substr(2); p << "-0." << this->val.substr(2);
} else { } else {
result = this->val; p << this->val;
} }
return JSON::makeNumber(result);
} }

View File

@ -26,9 +26,8 @@ QPDF_Reserved::unparse()
return ""; return "";
} }
JSON void
QPDF_Reserved::getJSON(int json_version) QPDF_Reserved::writeJSON(int json_version, JSON::Writer& p)
{ {
throw std::logic_error("QPDFObjectHandle: attempting to get JSON from a reserved object"); throw std::logic_error("QPDFObjectHandle: attempting to get JSON from a reserved object");
return JSON::makeNull();
} }

View File

@ -1,6 +1,7 @@
#include <qpdf/QPDF_Stream.hh> #include <qpdf/QPDF_Stream.hh>
#include <qpdf/ContentNormalizer.hh> #include <qpdf/ContentNormalizer.hh>
#include <qpdf/JSON_writer.hh>
#include <qpdf/Pipeline.hh> #include <qpdf/Pipeline.hh>
#include <qpdf/Pl_Base64.hh> #include <qpdf/Pl_Base64.hh>
#include <qpdf/Pl_Buffer.hh> #include <qpdf/Pl_Buffer.hh>
@ -176,13 +177,10 @@ QPDF_Stream::unparse()
return og.unparse(' ') + " R"; return og.unparse(' ') + " R";
} }
JSON void
QPDF_Stream::getJSON(int json_version) QPDF_Stream::writeJSON(int json_version, JSON::Writer& jw)
{ {
if (json_version == 1) { stream_dict.writeJSON(json_version, jw);
return this->stream_dict.getJSON(json_version);
}
return getStreamJSON(json_version, qpdf_sj_none, qpdf_dl_none, nullptr, "");
} }
JSON JSON
@ -192,78 +190,109 @@ QPDF_Stream::getStreamJSON(
qpdf_stream_decode_level_e decode_level, qpdf_stream_decode_level_e decode_level,
Pipeline* p, Pipeline* p,
std::string const& data_filename) 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) { switch (json_data) {
case qpdf_sj_none: case qpdf_sj_none:
case qpdf_sj_inline: case qpdf_sj_inline:
if (p != nullptr) { 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"); "when json_data is file");
} }
break; break;
case qpdf_sj_file: case qpdf_sj_file:
if (p == nullptr) { if (p == nullptr) {
throw std::logic_error( 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()) { 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"); "when json_data is file");
} }
break; break;
} }
auto dict = this->stream_dict; jw.writeStart('{');
JSON result = JSON::makeDictionary();
if (json_data != qpdf_sj_none) { if (json_data == qpdf_sj_none) {
Pl_Discard discard; jw.writeNext();
Pl_Buffer buf_pl{"stream data"}; jw << R"("dict": )";
// buf_pl contains valid data and is ready for retrieval of the data. stream_dict.writeJSON(json_version, jw);
bool buf_pl_ready = false; jw.writeEnd('}');
bool filtered = false; return decode_level;
bool filter = (decode_level != qpdf_dl_none); }
for (int attempt = 1; attempt <= 2; ++attempt) {
Pipeline* data_pipeline = &discard; Pl_Discard discard;
if (json_data == qpdf_sj_file) { Pl_Buffer buf_pl{"stream data"};
// We need to capture the data to write Pipeline* data_pipeline = &buf_pl;
data_pipeline = &buf_pl; if (no_data_key && json_data == qpdf_sj_inline) {
} data_pipeline = &discard;
bool succeeded = }
pipeStreamData(data_pipeline, &filtered, 0, decode_level, false, (attempt == 1)); // pipeStreamData produced valid data.
if (!succeeded || (filter && !filtered)) { bool buf_pl_ready = false;
// Try again bool filtered = false;
filter = false; bool filter = (decode_level != qpdf_dl_none);
decode_level = qpdf_dl_none; for (int attempt = 1; attempt <= 2; ++attempt) {
buf_pl.getString(); // reset buf_pl bool succeeded =
} else { pipeStreamData(data_pipeline, &filtered, 0, decode_level, false, (attempt == 1));
if (json_data == qpdf_sj_file) { if (!succeeded || (filter && !filtered)) {
buf_pl_ready = true; // Try again
} filter = false;
break; decode_level = qpdf_dl_none;
} buf_pl.getString(); // reset buf_pl
}
// 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)));
} else { } else {
throw std::logic_error("QPDF_Stream: unexpected value of json_data"); buf_pl_ready = true;
break;
} }
} }
result.addDictionaryMember("dict", dict.getJSON(json_version)); if (!buf_pl_ready) {
return result; 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 void

View File

@ -1,5 +1,6 @@
#include <qpdf/QPDF_String.hh> #include <qpdf/QPDF_String.hh>
#include <qpdf/JSON_writer.hh>
#include <qpdf/QUtil.hh> #include <qpdf/QUtil.hh>
// DO NOT USE ctype -- it is locale dependent for some things, and it's not worth the risk of // 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); return unparse(false);
} }
JSON void
QPDF_String::getJSON(int json_version) QPDF_String::writeJSON(int json_version, JSON::Writer& p)
{ {
auto candidate = getUTF8Val();
if (json_version == 1) { if (json_version == 1) {
return JSON::makeString(getUTF8Val());
} p << "\"" << JSON::Writer::encode_string(candidate) << "\"";
// 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;
} else { } 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 bool

View File

@ -27,9 +27,8 @@ QPDF_Unresolved::unparse()
return ""; return "";
} }
JSON void
QPDF_Unresolved::getJSON(int json_version) QPDF_Unresolved::writeJSON(int json_version, JSON::Writer& p)
{ {
throw std::logic_error("attempted to get JSON from an unresolved QPDFObjectHandle"); throw std::logic_error("attempted to get JSON from an unresolved QPDFObjectHandle");
return JSON::makeNull();
} }

View File

@ -1,12 +1,14 @@
#include <qpdf/QPDF.hh> #include <qpdf/QPDF.hh>
#include <qpdf/FileInputSource.hh> #include <qpdf/FileInputSource.hh>
#include <qpdf/JSON_writer.hh>
#include <qpdf/Pl_Base64.hh> #include <qpdf/Pl_Base64.hh>
#include <qpdf/Pl_StdioFile.hh> #include <qpdf/Pl_StdioFile.hh>
#include <qpdf/QIntC.hh> #include <qpdf/QIntC.hh>
#include <qpdf/QPDFObject_private.hh> #include <qpdf/QPDFObject_private.hh>
#include <qpdf/QPDFValue.hh> #include <qpdf/QPDFValue.hh>
#include <qpdf/QPDF_Null.hh> #include <qpdf/QPDF_Null.hh>
#include <qpdf/QPDF_Stream.hh>
#include <qpdf/QTC.hh> #include <qpdf/QTC.hh>
#include <qpdf/QUtil.hh> #include <qpdf/QUtil.hh>
#include <algorithm> #include <algorithm>
@ -442,7 +444,9 @@ void
QPDF::JSONReactor::replaceObject(QPDFObjectHandle&& replacement, JSON const& value) QPDF::JSONReactor::replaceObject(QPDFObjectHandle&& replacement, JSON const& value)
{ {
if (replacement.isIndirect()) { 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; return;
} }
auto& tos = stack.back(); auto& tos = stack.back();
@ -828,45 +832,20 @@ QPDF::importJSON(std::shared_ptr<InputSource> is, bool must_be_complete)
} }
void void
QPDF::writeJSONStream( writeJSONStreamFile(
int version, int version,
Pipeline* p, JSON::Writer& jw,
bool& first, QPDF_Stream& stream,
std::string const& key, int id,
QPDFObjectHandle& obj,
qpdf_stream_decode_level_e decode_level, qpdf_stream_decode_level_e decode_level,
qpdf_json_stream_data_e json_stream_data,
std::string const& file_prefix) std::string const& file_prefix)
{ {
Pipeline* stream_p = nullptr; auto filename = file_prefix + "-" + std::to_string(id);
FILE* f = nullptr; auto* f = QUtil::safe_fopen(filename.c_str(), "wb");
std::shared_ptr<Pl_StdioFile> f_pl; Pl_StdioFile f_pl{"stream data", f};
std::string filename; stream.writeStreamJSON(version, jw, qpdf_sj_file, decode_level, &f_pl, filename);
if (json_stream_data == qpdf_sj_file) { f_pl.finish();
filename = file_prefix + "-" + std::to_string(obj.getObjectID()); fclose(f);
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);
} }
void void
@ -893,80 +872,75 @@ QPDF::writeJSON(
std::string const& file_prefix, std::string const& file_prefix,
std::set<std::string> wanted_objects) 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) { if (version != 2) {
throw std::runtime_error("QPDF::writeJSON: only version 2 is supported"); throw std::runtime_error("QPDF::writeJSON: only version 2 is supported");
} }
bool first = true; JSON::Writer jw{p, 4};
if (complete) { if (complete) {
JSON::writeDictionaryOpen(p, first, depth_outer); jw << "{";
} else { } else if (!first_key) {
first = first_key; jw << ",";
} }
JSON::writeDictionaryKey(p, first, "qpdf", depth_top); first_key = false;
bool first_qpdf = true;
JSON::writeArrayOpen(p, first_qpdf, depth_top); /* clang-format off */
JSON::writeNext(p, first_qpdf, depth_qpdf); jw << "\n"
bool first_qpdf_inner = true; " \"qpdf\": [\n"
JSON::writeDictionaryOpen(p, first_qpdf_inner, depth_qpdf); " {\n"
JSON::writeDictionaryItem( " \"jsonversion\": " << std::to_string(version) << ",\n"
p, first_qpdf_inner, "jsonversion", JSON::makeInt(version), depth_qpdf_inner); " \"pdfversion\": \"" << getPDFVersion() << "\",\n"
JSON::writeDictionaryItem( " \"pushedinheritedpageresources\": " << (everPushedInheritedAttributesToPages() ? "true" : "false") << ",\n"
p, first_qpdf_inner, "pdfversion", JSON::makeString(getPDFVersion()), depth_qpdf_inner); " \"calledgetallpages\": " << (everCalledGetAllPages() ? "true" : "false") << ",\n"
JSON::writeDictionaryItem( " \"maxobjectid\": " << std::to_string(getObjectCount()) << "\n"
p, " },\n"
first_qpdf_inner, " {";
"pushedinheritedpageresources", /* clang-format on */
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);
bool all_objects = wanted_objects.empty(); bool all_objects = wanted_objects.empty();
bool first = true;
for (auto& obj: getAllObjects()) { 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 (all_objects || wanted_objects.count(key)) {
if (obj.isStream()) { if (first) {
writeJSONStream( jw << "\n \"" << key;
version, first = false;
p,
first_qpdf_inner,
key,
obj,
decode_level,
json_stream_data,
file_prefix);
} else { } 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")) { if (all_objects || wanted_objects.count("trailer")) {
auto trailer = getTrailer(); if (!first) {
writeJSONObject(version, p, first_qpdf_inner, "trailer", trailer); jw << "\n },";
}
jw << "\n \"trailer\": {\n \"value\": ";
getTrailer().writeJSON(version, jw, true);
first = false;
} }
JSON::writeDictionaryClose(p, first_qpdf_inner, depth_qpdf); if (!first) {
JSON::writeArrayClose(p, first_qpdf, depth_top); jw << "\n }";
}
/* clang-format off */
jw << "\n"
" }\n"
" ]";
/* clang-format on */
if (complete) { if (complete) {
JSON::writeDictionaryClose(p, first, 0); jw << "\n}\n";
*p << "\n";
p->finish(); p->finish();
} }
first_key = false;
} }

137
libqpdf/qpdf/JSON_writer.hh Normal file
View File

@ -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

View File

@ -33,10 +33,10 @@ class QPDFObject
{ {
return value->unparse(); return value->unparse();
} }
JSON void
getJSON(int json_version) writeJSON(int json_version, JSON::Writer& p)
{ {
return value->getJSON(json_version); return value->writeJSON(json_version, p);
} }
std::string std::string
getStringValue() const getStringValue() const

View File

@ -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::shared_ptr<QPDFObject> copy(bool shallow = false) = 0;
virtual std::string unparse() = 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 struct JSON_Descr
{ {

View File

@ -22,7 +22,7 @@ class QPDF_Array: public QPDFValue
create(std::vector<std::shared_ptr<QPDFObject>>&& items, bool sparse); create(std::vector<std::shared_ptr<QPDFObject>>&& items, bool sparse);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override; std::string unparse() override;
JSON getJSON(int json_version) override; void writeJSON(int json_version, JSON::Writer& p) override;
void disconnect() override; void disconnect() override;
int int

View File

@ -10,7 +10,8 @@ class QPDF_Bool: public QPDFValue
static std::shared_ptr<QPDFObject> create(bool val); static std::shared_ptr<QPDFObject> create(bool val);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override; std::string unparse() override;
JSON getJSON(int json_version) override; void writeJSON(int json_version, JSON::Writer& p) override;
bool getVal() const; bool getVal() const;
private: private:

View File

@ -9,7 +9,7 @@ class QPDF_Destroyed: public QPDFValue
~QPDF_Destroyed() override = default; ~QPDF_Destroyed() override = default;
std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() 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(); static std::shared_ptr<QPDFValue> getInstance();
private: private:

View File

@ -16,7 +16,7 @@ class QPDF_Dictionary: public QPDFValue
static std::shared_ptr<QPDFObject> create(std::map<std::string, QPDFObjectHandle>&& items); static std::shared_ptr<QPDFObject> create(std::map<std::string, QPDFObjectHandle>&& items);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override; std::string unparse() override;
JSON getJSON(int json_version) override; void writeJSON(int json_version, JSON::Writer& p) override;
void disconnect() override; void disconnect() override;
// hasKey() and getKeys() treat keys with null values as if they aren't there. getKey() returns // hasKey() and getKeys() treat keys with null values as if they aren't there. getKey() returns

View File

@ -10,7 +10,7 @@ class QPDF_InlineImage: public QPDFValue
static std::shared_ptr<QPDFObject> create(std::string const& val); static std::shared_ptr<QPDFObject> create(std::string const& val);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override; std::string unparse() override;
JSON getJSON(int json_version) override; void writeJSON(int json_version, JSON::Writer& p) override;
std::string std::string
getStringValue() const override getStringValue() const override
{ {

View File

@ -10,7 +10,7 @@ class QPDF_Integer: public QPDFValue
static std::shared_ptr<QPDFObject> create(long long value); static std::shared_ptr<QPDFObject> create(long long value);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override; std::string unparse() override;
JSON getJSON(int json_version) override; void writeJSON(int json_version, JSON::Writer& p) override;
long long getVal() const; long long getVal() const;
private: private:

View File

@ -10,10 +10,15 @@ class QPDF_Name: public QPDFValue
static std::shared_ptr<QPDFObject> create(std::string const& name); static std::shared_ptr<QPDFObject> create(std::string const& name);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() 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 // Put # into strings with characters unsuitable for name token
static std::string normalizeName(std::string const& name); 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 std::string
getStringValue() const override getStringValue() const override
{ {

View File

@ -18,7 +18,7 @@ class QPDF_Null: public QPDFValue
std::string var_descr); std::string var_descr);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override; std::string unparse() override;
JSON getJSON(int json_version) override; void writeJSON(int json_version, JSON::Writer& p) override;
private: private:
QPDF_Null(); QPDF_Null();

View File

@ -10,7 +10,7 @@ class QPDF_Operator: public QPDFValue
static std::shared_ptr<QPDFObject> create(std::string const& val); static std::shared_ptr<QPDFObject> create(std::string const& val);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override; std::string unparse() override;
JSON getJSON(int json_version) override; void writeJSON(int json_version, JSON::Writer& p) override;
std::string std::string
getStringValue() const override getStringValue() const override
{ {

View File

@ -12,7 +12,7 @@ class QPDF_Real: public QPDFValue
create(double value, int decimal_places, bool trim_trailing_zeroes); create(double value, int decimal_places, bool trim_trailing_zeroes);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override; std::string unparse() override;
JSON getJSON(int json_version) override; void writeJSON(int json_version, JSON::Writer& p) override;
std::string std::string
getStringValue() const override getStringValue() const override
{ {

View File

@ -10,7 +10,7 @@ class QPDF_Reserved: public QPDFValue
static std::shared_ptr<QPDFObject> create(); static std::shared_ptr<QPDFObject> create();
std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override; std::string unparse() override;
JSON getJSON(int json_version) override; void writeJSON(int json_version, JSON::Writer& p) override;
private: private:
QPDF_Reserved(); QPDF_Reserved();

View File

@ -25,7 +25,7 @@ class QPDF_Stream: public QPDFValue
size_t length); size_t length);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override; std::string unparse() override;
JSON getJSON(int json_version) override; void writeJSON(int json_version, JSON::Writer& p) override;
void setDescription( void setDescription(
QPDF*, std::shared_ptr<QPDFValue::Description>& description, qpdf_offset_t offset) override; QPDF*, std::shared_ptr<QPDFValue::Description>& description, qpdf_offset_t offset) override;
void disconnect() override; void disconnect() override;
@ -64,6 +64,14 @@ class QPDF_Stream: public QPDFValue
qpdf_stream_decode_level_e decode_level, qpdf_stream_decode_level_e decode_level,
Pipeline* p, Pipeline* p,
std::string const& data_filename); 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); void replaceDict(QPDFObjectHandle const& new_dict);

View File

@ -16,7 +16,7 @@ class QPDF_String: public QPDFValue
std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override; std::string unparse() override;
std::string unparse(bool force_binary); 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 getUTF8Val() const;
std::string std::string
getStringValue() const override getStringValue() const override

View File

@ -10,7 +10,7 @@ class QPDF_Unresolved: public QPDFValue
static std::shared_ptr<QPDFObject> create(QPDF* qpdf, QPDFObjGen const& og); static std::shared_ptr<QPDFObject> create(QPDF* qpdf, QPDFObjGen const& og);
std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::shared_ptr<QPDFObject> copy(bool shallow = false) override;
std::string unparse() override; std::string unparse() override;
JSON getJSON(int json_version) override; void writeJSON(int json_version, JSON::Writer& p) override;
private: private:
QPDF_Unresolved(QPDF* qpdf, QPDFObjGen const& og); QPDF_Unresolved(QPDF* qpdf, QPDFObjGen const& og);

View File

@ -33,5 +33,22 @@ $td->runtest("copy sparse array",
{$td->COMMAND => "test_driver 97 many-nulls.pdf"}, {$td->COMMAND => "test_driver 97 many-nulls.pdf"},
{$td->STRING => "test 97 done\n", $td->EXIT_STATUS => 0}, {$td->STRING => "test 97 done\n", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES); $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(); cleanup();
$td->report(4); $td->report(8);

View File

@ -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 # (using #xx) would generate invalid JSON, even though qpdf's own JSON
# parser would accept it. Also, the JSON spec allows real numbers in # parser would accept it. Also, the JSON spec allows real numbers in
# scientific notation, but the PDF spec does not. # scientific notation, but the PDF spec does not.
$n_tests += 4; $n_tests += 7;
$td->runtest("handle binary names", $td->runtest("handle binary names",
{$td->COMMAND => {$td->COMMAND =>
"qpdf --json-output weird-tokens.pdf a.json"}, "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 -"}, "qpdf --json-input --json-output weird-tokens-alt.json -"},
{$td->FILE => "weird-tokens.json", $td->EXIT_STATUS => 0}, {$td->FILE => "weird-tokens.json", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES); $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(); cleanup();
$td->report($n_tests); $td->report($n_tests);

View File

@ -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<67>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
}
}
}
}

View File

@ -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
}
}
}
]
}

View File

@ -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

View File

@ -10,21 +10,155 @@
{ {
"obj:1 0 R": { "obj:1 0 R": {
"value": { "value": {
"/Escape\\Key": 42,
"/Extra": [ "/Extra": [
"u:Names with binary data", "u:Names with binary data",
"n:/ABCDEF+#ba#da#cc#e5", "n:/ABCDEF+#ba#da#cc#e5",
"n:/OVERLONG+#c0#81", "n:/OVERLONG+#c0#81",
"n:/OVERLONG+#c1#ff",
"/Ok+€",
"n:/OVERLONG+#e0#81#82", "n:/OVERLONG+#e0#81#82",
"n:/OVERLONG+#e0#9f#ff",
"/Ok+ࠀ",
"n:/OVERLONG+#f0#81#82#83", "n:/OVERLONG+#f0#81#82#83",
"n:/OVERLONG+#f0#8f#ff#ff",
"/Ok+𐀀",
"n:/range+#01", "n:/range+#01",
"n:/low+#18", "n:/low+#18",
"/ABCEDEF+π", "/ABCEDEF+π",
"n:/one+#a0two", "n:/one+#a0two",
"n:/text#2fplain", "n:/text#2fplain",
"u:Names requiring escaping in JSON",
"/Back\\shlash",
"/Low\u0022",
"/Low\u001f",
"/ExceptSpace ",
"/Except!",
"u:Very small/large reals", "u:Very small/large reals",
1e-05, 1e-05,
1e12 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", "/Pages": "2 0 R",
"/Type": "/Catalog", "/Type": "/Catalog",
"n:/WeirdKey+#ba#da#cc#e5": 42 "n:/WeirdKey+#ba#da#cc#e5": 42
@ -78,7 +212,7 @@
"value": { "value": {
"/ID": [ "/ID": [
"b:42841c13bbf709d79a200fa1691836f8", "b:42841c13bbf709d79a200fa1691836f8",
"b:728c020f464c3cf7e02c12605fa7d88b" "b:31415926535897932384626433832795"
], ],
"/Root": "1 0 R", "/Root": "1 0 R",
"/Size": 7 "/Size": 7

View File

@ -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
}
}
}
}

View File

@ -10,21 +10,155 @@
{ {
"obj:1 0 R": { "obj:1 0 R": {
"value": { "value": {
"/Escape\\Key": 42,
"/Extra": [ "/Extra": [
"u:Names with binary data", "u:Names with binary data",
"n:/ABCDEF+#ba#da#cc#e5", "n:/ABCDEF+#ba#da#cc#e5",
"n:/OVERLONG+#c0#81", "n:/OVERLONG+#c0#81",
"n:/OVERLONG+#c1#ff",
"/Ok+€",
"n:/OVERLONG+#e0#81#82", "n:/OVERLONG+#e0#81#82",
"n:/OVERLONG+#e0#9f#ff",
"/Ok+ࠀ",
"n:/OVERLONG+#f0#81#82#83", "n:/OVERLONG+#f0#81#82#83",
"n:/OVERLONG+#f0#8f#ff#ff",
"/Ok+𐀀",
"/range+\u0001", "/range+\u0001",
"/low+\u0018", "/low+\u0018",
"/ABCEDEF+π", "/ABCEDEF+π",
"n:/one+#a0two", "n:/one+#a0two",
"/text/plain", "/text/plain",
"u:Names requiring escaping in JSON",
"/Back\\shlash",
"/Low\"",
"/Low\u001f",
"/ExceptSpace ",
"/Except!",
"u:Very small/large reals", "u:Very small/large reals",
0.00001, 0.00001,
1000000000000 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", "/Pages": "2 0 R",
"/Type": "/Catalog", "/Type": "/Catalog",
"n:/WeirdKey+#ba#da#cc#e5": 42 "n:/WeirdKey+#ba#da#cc#e5": 42
@ -78,7 +212,7 @@
"value": { "value": {
"/ID": [ "/ID": [
"b:42841c13bbf709d79a200fa1691836f8", "b:42841c13bbf709d79a200fa1691836f8",
"b:728c020f464c3cf7e02c12605fa7d88b" "b:31415926535897932384626433832795"
], ],
"/Root": "1 0 R", "/Root": "1 0 R",
"/Size": 7 "/Size": 7

View File

@ -4,21 +4,155 @@
1 0 obj 1 0 obj
<< <<
/Escape\Key 42
/Extra [ /Extra [
(Names with binary data) (Names with binary data)
/ABCDEF+#ba#da#cc#e5 /ABCDEF+#ba#da#cc#e5
/OVERLONG+#c0#81 /OVERLONG+#c0#81
/OVERLONG+#c1#ff
/Ok+#c2#80
/OVERLONG+#e0#81#82 /OVERLONG+#e0#81#82
/OVERLONG+#e0#9f#ff
/Ok+#e0#a0#80
/OVERLONG+#f0#81#82#83 /OVERLONG+#f0#81#82#83
/OVERLONG+#f0#8f#ff#ff
/Ok+#f0#90#80#80
/range+#01 /range+#01
/low+#18 /low+#18
/ABCEDEF+#cf#80 /ABCEDEF+#cf#80
/one+#a0two /one+#a0two
/text#2fplain /text#2fplain
(Names requiring escaping in JSON)
/Back\shlash
/Low"
/Low#1f
/ExceptSpace#20
/Except!
(Very small/large reals) (Very small/large reals)
0.00001 0.00001
1000000000000 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 /Pages 2 0 R
/Type /Catalog /Type /Catalog
/WeirdKey+#ba#da#cc#e5 42 /WeirdKey+#ba#da#cc#e5 42
@ -86,16 +220,16 @@ xref
0 7 0 7
0000000000 65535 f 0000000000 65535 f
0000000025 00000 n 0000000025 00000 n
0000000389 00000 n 0000008642 00000 n
0000000471 00000 n 0000008724 00000 n
0000000667 00000 n 0000008920 00000 n
0000000766 00000 n 0000009019 00000 n
0000000785 00000 n 0000009038 00000 n
trailer << trailer <<
/Root 1 0 R /Root 1 0 R
/Size 7 /Size 7
/ID [<42841c13bbf709d79a200fa1691836f8><728c020f464c3cf7e02c12605fa7d88b>] /ID [<42841c13bbf709d79a200fa1691836f8><31415926535897932384626433832795>]
>> >>
startxref startxref
891 9144
%%EOF %%EOF

View File

@ -3382,6 +3382,22 @@ test_97(QPDF& pdf, char const* arg2)
assert(nulls.unparse() == nulls2.unparse()); 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 void
runtest(int n, char const* filename1, char const* arg2) 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}, {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}, {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}, {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); auto fn = test_functions.find(n);
if (fn == test_functions.end()) { if (fn == test_functions.end()) {