From 9e90007a4a490dd1335b63079fc3e2a74420911f Mon Sep 17 00:00:00 2001 From: m-holger Date: Fri, 9 Feb 2024 13:03:53 +0000 Subject: [PATCH 01/13] Add new private class JSON::Writer Create a simple utility class for writing JSON to a pipeline. --- include/qpdf/JSON.hh | 5 +- libqpdf/JSON.cc | 8 ++- libqpdf/qpdf/JSON_writer.hh | 125 ++++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 libqpdf/qpdf/JSON_writer.hh 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/libqpdf/JSON.cc b/libqpdf/JSON.cc index 27405df7..bc28f236 100644 --- a/libqpdf/JSON.cc +++ b/libqpdf/JSON.cc @@ -1,5 +1,7 @@ #include +#include + #include #include #include @@ -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(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/qpdf/JSON_writer.hh b/libqpdf/qpdf/JSON_writer.hh new file mode 100644 index 00000000..6d870b88 --- /dev/null +++ b/libqpdf/qpdf/JSON_writer.hh @@ -0,0 +1,125 @@ +#ifndef JSON_WRITER_HH +#define JSON_WRITER_HH + +#include +#include + +#include + +// 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(data), len); + 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(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 From e2737ab646bff6aa07ba72e0cc15cc955d9afcc0 Mon Sep 17 00:00:00 2001 From: m-holger Date: Fri, 9 Feb 2024 13:09:08 +0000 Subject: [PATCH 02/13] Add new writeJSON methods Create an alternative to getJSON to allow an object handle to be written as JSON without the overhead of creating a JSON object. --- include/qpdf/QPDFObjectHandle.hh | 2 ++ libqpdf/QPDFObjectHandle.cc | 13 ++++++++++ libqpdf/QPDF_Array.cc | 38 ++++++++++++++++++++++++++++++ libqpdf/QPDF_Bool.cc | 8 +++++++ libqpdf/QPDF_Destroyed.cc | 6 +++++ libqpdf/QPDF_Dictionary.cc | 28 ++++++++++++++++++++++ libqpdf/QPDF_InlineImage.cc | 8 +++++++ libqpdf/QPDF_Integer.cc | 7 ++++++ libqpdf/QPDF_Name.cc | 19 +++++++++++++++ libqpdf/QPDF_Null.cc | 7 ++++++ libqpdf/QPDF_Operator.cc | 8 +++++++ libqpdf/QPDF_Real.cc | 16 +++++++++++++ libqpdf/QPDF_Reserved.cc | 6 +++++ libqpdf/QPDF_Stream.cc | 7 ++++++ libqpdf/QPDF_String.cc | 25 ++++++++++++++++++++ libqpdf/QPDF_Unresolved.cc | 6 +++++ libqpdf/QPDF_json.cc | 13 +++++++--- libqpdf/qpdf/QPDFObject_private.hh | 5 ++++ libqpdf/qpdf/QPDFValue.hh | 1 + libqpdf/qpdf/QPDF_Array.hh | 1 + libqpdf/qpdf/QPDF_Bool.hh | 2 ++ libqpdf/qpdf/QPDF_Destroyed.hh | 1 + libqpdf/qpdf/QPDF_Dictionary.hh | 1 + libqpdf/qpdf/QPDF_InlineImage.hh | 1 + libqpdf/qpdf/QPDF_Integer.hh | 1 + libqpdf/qpdf/QPDF_Name.hh | 1 + libqpdf/qpdf/QPDF_Null.hh | 1 + libqpdf/qpdf/QPDF_Operator.hh | 1 + libqpdf/qpdf/QPDF_Real.hh | 1 + libqpdf/qpdf/QPDF_Reserved.hh | 1 + libqpdf/qpdf/QPDF_Stream.hh | 1 + libqpdf/qpdf/QPDF_String.hh | 1 + libqpdf/qpdf/QPDF_Unresolved.hh | 1 + 33 files changed, 235 insertions(+), 3 deletions(-) diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh index b2835495..a965f468 100644 --- a/include/qpdf/QPDFObjectHandle.hh +++ b/include/qpdf/QPDFObjectHandle.hh @@ -1353,6 +1353,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/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index d543f98e..7af83a68 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -1621,6 +1622,18 @@ QPDFObjectHandle::getJSON(int json_version, bool dereference_indirect) } } +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); + } +} + JSON QPDFObjectHandle::getStreamJSON( int json_version, diff --git a/libqpdf/QPDF_Array.cc b/libqpdf/QPDF_Array.cc index 789acc35..6e50a781 100644 --- a/libqpdf/QPDF_Array.cc +++ b/libqpdf/QPDF_Array.cc @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -180,6 +181,43 @@ QPDF_Array::getJSON(int json_version) return j_array; } +void +QPDF_Array::writeJSON(int json_version, JSON::Writer& p) +{ + p.writeStart('['); + if (sp) { + int next = 0; + for (auto& item: sp->elements) { + int key = item.first; + for (int j = next; j < key; ++j) { + p.writeNext() << "null"; + } + p.writeNext(); + auto og = item.second->getObjGen(); + if (og.isIndirect()) { + p << "\"" << og.unparse(' ') << " R\""; + } else { + item.second->writeJSON(json_version, p); + } + next = ++key; + } + for (int j = next; j < sp->size; ++j) { + p.writeNext() << "null"; + } + } else { + for (auto const& item: elements) { + p.writeNext(); + auto og = item->getObjGen(); + if (og.isIndirect()) { + p << "\"" << og.unparse(' ') << " R\""; + } else { + item->writeJSON(json_version, p); + } + } + } + p.writeEnd(']'); +} + QPDFObjectHandle QPDF_Array::at(int n) const noexcept { diff --git a/libqpdf/QPDF_Bool.cc b/libqpdf/QPDF_Bool.cc index 05c52f22..9e57e68e 100644 --- a/libqpdf/QPDF_Bool.cc +++ b/libqpdf/QPDF_Bool.cc @@ -1,5 +1,7 @@ #include +#include + QPDF_Bool::QPDF_Bool(bool val) : QPDFValue(::ot_boolean, "boolean"), val(val) @@ -30,6 +32,12 @@ QPDF_Bool::getJSON(int json_version) return JSON::makeBool(this->val); } +void +QPDF_Bool::writeJSON(int json_version, JSON::Writer& p) +{ + p << val; +} + bool QPDF_Bool::getVal() const { diff --git a/libqpdf/QPDF_Destroyed.cc b/libqpdf/QPDF_Destroyed.cc index 4e34b508..06d1a83c 100644 --- a/libqpdf/QPDF_Destroyed.cc +++ b/libqpdf/QPDF_Destroyed.cc @@ -34,3 +34,9 @@ QPDF_Destroyed::getJSON(int json_version) throw std::logic_error("attempted to get JSON from a QPDFObjectHandle from a destroyed QPDF"); return JSON::makeNull(); } + +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"); +} \ No newline at end of file diff --git a/libqpdf/QPDF_Dictionary.cc b/libqpdf/QPDF_Dictionary.cc index f7e32fc9..53d78a2b 100644 --- a/libqpdf/QPDF_Dictionary.cc +++ b/libqpdf/QPDF_Dictionary.cc @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -91,6 +92,33 @@ QPDF_Dictionary::getJSON(int json_version) return j; } +void +QPDF_Dictionary::writeJSON(int json_version, JSON::Writer& p) +{ + p.writeStart('{'); + for (auto& iter: this->items) { + if (!iter.second.isNull()) { + p.writeNext(); + if (json_version == 1) { + p << "\"" << JSON::Writer::encode_string(QPDF_Name::normalizeName(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); + if (!has_8bit_chars || is_valid_utf8) { + p << "\"" << JSON::Writer::encode_string(iter.first) << "\": "; + } else { + p << "\"n:" << JSON::Writer::encode_string(QPDF_Name::normalizeName(iter.first)) + << "\": "; + } + } + iter.second.writeJSON(json_version, p); + } + } + p.writeEnd('}'); +} + bool QPDF_Dictionary::hasKey(std::string const& key) { diff --git a/libqpdf/QPDF_InlineImage.cc b/libqpdf/QPDF_InlineImage.cc index 18f2fed6..bb5c4d0f 100644 --- a/libqpdf/QPDF_InlineImage.cc +++ b/libqpdf/QPDF_InlineImage.cc @@ -1,5 +1,7 @@ #include +#include + QPDF_InlineImage::QPDF_InlineImage(std::string const& val) : QPDFValue(::ot_inlineimage, "inline-image"), val(val) @@ -29,3 +31,9 @@ QPDF_InlineImage::getJSON(int json_version) { return JSON::makeNull(); } + +void +QPDF_InlineImage::writeJSON(int json_version, JSON::Writer& p) +{ + p << "null"; +} diff --git a/libqpdf/QPDF_Integer.cc b/libqpdf/QPDF_Integer.cc index 716a11e0..d65b3478 100644 --- a/libqpdf/QPDF_Integer.cc +++ b/libqpdf/QPDF_Integer.cc @@ -1,5 +1,6 @@ #include +#include #include QPDF_Integer::QPDF_Integer(long long val) : @@ -32,6 +33,12 @@ QPDF_Integer::getJSON(int json_version) return JSON::makeInt(this->val); } +void +QPDF_Integer::writeJSON(int json_version, JSON::Writer& p) +{ + p << std::to_string(this->val); +} + long long QPDF_Integer::getVal() const { diff --git a/libqpdf/QPDF_Name.cc b/libqpdf/QPDF_Name.cc index 5fde9c65..458b1428 100644 --- a/libqpdf/QPDF_Name.cc +++ b/libqpdf/QPDF_Name.cc @@ -1,5 +1,6 @@ #include +#include #include QPDF_Name::QPDF_Name(std::string const& name) : @@ -68,3 +69,21 @@ QPDF_Name::getJSON(int json_version) } } } + +void +QPDF_Name::writeJSON(int json_version, JSON::Writer& p) +{ + if (json_version == 1) { + 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) { + p << "\"" << JSON::Writer::encode_string(name) << "\""; + } else { + p << "\"n:" << JSON::Writer::encode_string(normalizeName(name)) << "\""; + } + } +} diff --git a/libqpdf/QPDF_Null.cc b/libqpdf/QPDF_Null.cc index fdabdfa7..13118e8c 100644 --- a/libqpdf/QPDF_Null.cc +++ b/libqpdf/QPDF_Null.cc @@ -1,5 +1,6 @@ #include +#include #include QPDF_Null::QPDF_Null() : @@ -49,3 +50,9 @@ QPDF_Null::getJSON(int json_version) // If this is updated, QPDF_Array::getJSON must also be updated. return JSON::makeNull(); } + +void +QPDF_Null::writeJSON(int json_version, JSON::Writer& p) +{ + p << "null"; +} diff --git a/libqpdf/QPDF_Operator.cc b/libqpdf/QPDF_Operator.cc index d1b2969d..763c4f12 100644 --- a/libqpdf/QPDF_Operator.cc +++ b/libqpdf/QPDF_Operator.cc @@ -1,5 +1,7 @@ #include +#include + QPDF_Operator::QPDF_Operator(std::string const& val) : QPDFValue(::ot_operator, "operator"), val(val) @@ -29,3 +31,9 @@ QPDF_Operator::getJSON(int json_version) { return JSON::makeNull(); } + +void +QPDF_Operator::writeJSON(int json_version, JSON::Writer& p) +{ + p << "null"; +} diff --git a/libqpdf/QPDF_Real.cc b/libqpdf/QPDF_Real.cc index 1d954dcd..af96c9df 100644 --- a/libqpdf/QPDF_Real.cc +++ b/libqpdf/QPDF_Real.cc @@ -1,5 +1,6 @@ #include +#include #include QPDF_Real::QPDF_Real(std::string const& val) : @@ -56,3 +57,18 @@ QPDF_Real::getJSON(int json_version) } return JSON::makeNumber(result); } + +void +QPDF_Real::writeJSON(int json_version, JSON::Writer& p) +{ + if (this->val.length() == 0) { + // Can't really happen... + p << "0"; + } else if (this->val.at(0) == '.') { + 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 { + p << this->val; + } +} diff --git a/libqpdf/QPDF_Reserved.cc b/libqpdf/QPDF_Reserved.cc index 845d6ebc..0be60c98 100644 --- a/libqpdf/QPDF_Reserved.cc +++ b/libqpdf/QPDF_Reserved.cc @@ -32,3 +32,9 @@ QPDF_Reserved::getJSON(int json_version) throw std::logic_error("QPDFObjectHandle: attempting to get JSON from a reserved object"); return JSON::makeNull(); } + +void +QPDF_Reserved::writeJSON(int json_version, JSON::Writer& p) +{ + throw std::logic_error("QPDFObjectHandle: attempting to get JSON from a reserved object"); +} diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc index a43d91ff..bedc96b2 100644 --- a/libqpdf/QPDF_Stream.cc +++ b/libqpdf/QPDF_Stream.cc @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -185,6 +186,12 @@ QPDF_Stream::getJSON(int json_version) return getStreamJSON(json_version, qpdf_sj_none, qpdf_dl_none, nullptr, ""); } +void +QPDF_Stream::writeJSON(int json_version, JSON::Writer& p) +{ + stream_dict.writeJSON(json_version, p); +} + JSON QPDF_Stream::getStreamJSON( int json_version, diff --git a/libqpdf/QPDF_String.cc b/libqpdf/QPDF_String.cc index 3886b399..d3fcaaef 100644 --- a/libqpdf/QPDF_String.cc +++ b/libqpdf/QPDF_String.cc @@ -1,5 +1,6 @@ #include +#include #include // DO NOT USE ctype -- it is locale dependent for some things, and it's not worth the risk of @@ -74,6 +75,30 @@ QPDF_String::getJSON(int json_version) return JSON::makeString(result); } +void +QPDF_String::writeJSON(int json_version, JSON::Writer& p) +{ + auto candidate = getUTF8Val(); + if (json_version == 1) { + + p << "\"" << JSON::Writer::encode_string(candidate) << "\""; + } else { + // 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) <<"\""; + } +} + bool QPDF_String::useHexString() const { diff --git a/libqpdf/QPDF_Unresolved.cc b/libqpdf/QPDF_Unresolved.cc index fbf5e15f..c2a50dbf 100644 --- a/libqpdf/QPDF_Unresolved.cc +++ b/libqpdf/QPDF_Unresolved.cc @@ -33,3 +33,9 @@ QPDF_Unresolved::getJSON(int json_version) throw std::logic_error("attempted to get JSON from an unresolved QPDFObjectHandle"); return JSON::makeNull(); } + +void +QPDF_Unresolved::writeJSON(int json_version, JSON::Writer& p) +{ + throw std::logic_error("attempted to get JSON from an unresolved QPDFObjectHandle"); +} diff --git a/libqpdf/QPDF_json.cc b/libqpdf/QPDF_json.cc index 8326e6a5..5560afec 100644 --- a/libqpdf/QPDF_json.cc +++ b/libqpdf/QPDF_json.cc @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -864,9 +865,15 @@ 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); + if (first) { + *p << "\n \"" << key << "\": {\n \"value\": "; + first = false; + } else { + *p << ",\n \"" << key << "\": {\n \"value\": "; + } + auto w = JSON::Writer(p, 4); + obj.writeJSON(version, w, true); + *p << "\n }"; } void diff --git a/libqpdf/qpdf/QPDFObject_private.hh b/libqpdf/qpdf/QPDFObject_private.hh index 5e87c215..b873bcd0 100644 --- a/libqpdf/qpdf/QPDFObject_private.hh +++ b/libqpdf/qpdf/QPDFObject_private.hh @@ -38,6 +38,11 @@ class QPDFObject { return value->getJSON(json_version); } + void + writeJSON(int json_version, JSON::Writer& p) + { + return value->writeJSON(json_version, p); + } std::string getStringValue() const { diff --git a/libqpdf/qpdf/QPDFValue.hh b/libqpdf/qpdf/QPDFValue.hh index db8fb923..f7783466 100644 --- a/libqpdf/qpdf/QPDFValue.hh +++ b/libqpdf/qpdf/QPDFValue.hh @@ -25,6 +25,7 @@ class QPDFValue: public std::enable_shared_from_this virtual std::shared_ptr 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..7ee69b8e 100644 --- a/libqpdf/qpdf/QPDF_Array.hh +++ b/libqpdf/qpdf/QPDF_Array.hh @@ -23,6 +23,7 @@ class QPDF_Array: public QPDFValue std::shared_ptr 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..f4ce0b1c 100644 --- a/libqpdf/qpdf/QPDF_Bool.hh +++ b/libqpdf/qpdf/QPDF_Bool.hh @@ -11,6 +11,8 @@ class QPDF_Bool: public QPDFValue std::shared_ptr 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..400921e3 100644 --- a/libqpdf/qpdf/QPDF_Destroyed.hh +++ b/libqpdf/qpdf/QPDF_Destroyed.hh @@ -10,6 +10,7 @@ class QPDF_Destroyed: public QPDFValue std::shared_ptr 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 getInstance(); private: diff --git a/libqpdf/qpdf/QPDF_Dictionary.hh b/libqpdf/qpdf/QPDF_Dictionary.hh index 0fb6636e..32c926a3 100644 --- a/libqpdf/qpdf/QPDF_Dictionary.hh +++ b/libqpdf/qpdf/QPDF_Dictionary.hh @@ -17,6 +17,7 @@ class QPDF_Dictionary: public QPDFValue std::shared_ptr 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..cc4dc31a 100644 --- a/libqpdf/qpdf/QPDF_InlineImage.hh +++ b/libqpdf/qpdf/QPDF_InlineImage.hh @@ -11,6 +11,7 @@ class QPDF_InlineImage: public QPDFValue std::shared_ptr 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..dd2f9e0d 100644 --- a/libqpdf/qpdf/QPDF_Integer.hh +++ b/libqpdf/qpdf/QPDF_Integer.hh @@ -11,6 +11,7 @@ class QPDF_Integer: public QPDFValue std::shared_ptr 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..167ddef5 100644 --- a/libqpdf/qpdf/QPDF_Name.hh +++ b/libqpdf/qpdf/QPDF_Name.hh @@ -11,6 +11,7 @@ class QPDF_Name: public QPDFValue std::shared_ptr 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); diff --git a/libqpdf/qpdf/QPDF_Null.hh b/libqpdf/qpdf/QPDF_Null.hh index a59b7509..2a099bf9 100644 --- a/libqpdf/qpdf/QPDF_Null.hh +++ b/libqpdf/qpdf/QPDF_Null.hh @@ -19,6 +19,7 @@ class QPDF_Null: public QPDFValue std::shared_ptr 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..d8b99e17 100644 --- a/libqpdf/qpdf/QPDF_Operator.hh +++ b/libqpdf/qpdf/QPDF_Operator.hh @@ -11,6 +11,7 @@ class QPDF_Operator: public QPDFValue std::shared_ptr 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..5fec919d 100644 --- a/libqpdf/qpdf/QPDF_Real.hh +++ b/libqpdf/qpdf/QPDF_Real.hh @@ -13,6 +13,7 @@ class QPDF_Real: public QPDFValue std::shared_ptr 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..4503fb9e 100644 --- a/libqpdf/qpdf/QPDF_Reserved.hh +++ b/libqpdf/qpdf/QPDF_Reserved.hh @@ -11,6 +11,7 @@ class QPDF_Reserved: public QPDFValue std::shared_ptr 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..597a48a8 100644 --- a/libqpdf/qpdf/QPDF_Stream.hh +++ b/libqpdf/qpdf/QPDF_Stream.hh @@ -26,6 +26,7 @@ class QPDF_Stream: public QPDFValue std::shared_ptr 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& description, qpdf_offset_t offset) override; void disconnect() override; diff --git a/libqpdf/qpdf/QPDF_String.hh b/libqpdf/qpdf/QPDF_String.hh index c34cafef..e7a87a53 100644 --- a/libqpdf/qpdf/QPDF_String.hh +++ b/libqpdf/qpdf/QPDF_String.hh @@ -17,6 +17,7 @@ class QPDF_String: public QPDFValue 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..669e314e 100644 --- a/libqpdf/qpdf/QPDF_Unresolved.hh +++ b/libqpdf/qpdf/QPDF_Unresolved.hh @@ -11,6 +11,7 @@ class QPDF_Unresolved: public QPDFValue std::shared_ptr 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); From 431987475b392daf4094570565881e1ebfc9528a Mon Sep 17 00:00:00 2001 From: m-holger Date: Sat, 10 Feb 2024 12:03:28 +0000 Subject: [PATCH 03/13] Add new method QPDF_Name::analyzeJSONEncoding Provide a custom method to check whether a name is valid utf8. Integrate checking for characters that need to be escaped in JSON. --- libqpdf/QPDF_Dictionary.cc | 31 +++++++--------- libqpdf/QPDF_Name.cc | 73 +++++++++++++++++++++++++++++++------- libqpdf/qpdf/QPDF_Name.hh | 5 +++ 3 files changed, 78 insertions(+), 31 deletions(-) diff --git a/libqpdf/QPDF_Dictionary.cc b/libqpdf/QPDF_Dictionary.cc index 53d78a2b..ca7fa04a 100644 --- a/libqpdf/QPDF_Dictionary.cc +++ b/libqpdf/QPDF_Dictionary.cc @@ -77,15 +77,11 @@ QPDF_Dictionary::getJSON(int json_version) if (json_version == 1) { j.addDictionaryMember( QPDF_Name::normalizeName(iter.first), iter.second.getJSON(json_version)); + } else if (auto res = QPDF_Name::analyzeJSONEncoding(iter.first); res.first) { + j.addDictionaryMember(iter.first, iter.second.getJSON(json_version)); } 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)); + j.addDictionaryMember( + "n:" + QPDF_Name::normalizeName(iter.first), iter.second.getJSON(json_version)); } } } @@ -100,18 +96,17 @@ QPDF_Dictionary::writeJSON(int json_version, JSON::Writer& p) if (!iter.second.isNull()) { p.writeNext(); if (json_version == 1) { - p << "\"" << JSON::Writer::encode_string(QPDF_Name::normalizeName(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); - if (!has_8bit_chars || is_valid_utf8) { - p << "\"" << JSON::Writer::encode_string(iter.first) << "\": "; + 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 << "\"n:" << JSON::Writer::encode_string(QPDF_Name::normalizeName(iter.first)) - << "\": "; + p << "\"" << JSON::Writer::encode_string(iter.first) << "\": "; } + } else { + p << "\"n:" << JSON::Writer::encode_string(QPDF_Name::normalizeName(iter.first)) + << "\": "; } iter.second.writeJSON(json_version, p); } diff --git a/libqpdf/QPDF_Name.cc b/libqpdf/QPDF_Name.cc index 458b1428..04614769 100644 --- a/libqpdf/QPDF_Name.cc +++ b/libqpdf/QPDF_Name.cc @@ -3,6 +3,8 @@ #include #include +#include + QPDF_Name::QPDF_Name(std::string const& name) : QPDFValue(::ot_name, "name"), name(name) @@ -52,20 +54,65 @@ QPDF_Name::unparse() return normalizeName(this->name); } +std::pair +QPDF_Name::analyzeJSONEncoding(const std::string& name) +{ + std::basic_string_view view{ + reinterpret_cast(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}; +} + JSON QPDF_Name::getJSON(int json_version) { if (json_version == 1) { return JSON::makeString(normalizeName(this->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) { + return JSON::makeString(name); } else { - return JSON::makeString("n:" + normalizeName(this->name)); + return JSON::makeString("n:" + normalizeName(name)); } } } @@ -76,12 +123,12 @@ QPDF_Name::writeJSON(int json_version, JSON::Writer& p) if (json_version == 1) { 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) { - p << "\"" << JSON::Writer::encode_string(name) << "\""; + 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)) << "\""; } diff --git a/libqpdf/qpdf/QPDF_Name.hh b/libqpdf/qpdf/QPDF_Name.hh index 167ddef5..fd4ac458 100644 --- a/libqpdf/qpdf/QPDF_Name.hh +++ b/libqpdf/qpdf/QPDF_Name.hh @@ -15,6 +15,11 @@ class QPDF_Name: public QPDFValue // 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 analyzeJSONEncoding(std::string const& name); std::string getStringValue() const override { From d28969bf375d75ff7c1b911d84708727510a7a8a Mon Sep 17 00:00:00 2001 From: m-holger Date: Sat, 10 Feb 2024 16:59:20 +0000 Subject: [PATCH 04/13] Add additional sparse array JSON tests --- qpdf/qtest/many-nulls.test | 19 +- qpdf/qtest/qpdf/minimal-nulls-1.json | 453 +++++++++++++++++++++++++++ qpdf/qtest/qpdf/minimal-nulls-2.json | 424 +++++++++++++++++++++++++ qpdf/qtest/qpdf/minimal-nulls.pdf | 387 +++++++++++++++++++++++ 4 files changed, 1282 insertions(+), 1 deletion(-) create mode 100644 qpdf/qtest/qpdf/minimal-nulls-1.json create mode 100644 qpdf/qtest/qpdf/minimal-nulls-2.json create mode 100644 qpdf/qtest/qpdf/minimal-nulls.pdf 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/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 [<31415926535897932384626433832795>] +>> +startxref +3201 +%%EOF From 9379b768118f465e94f826bed1daacbbc94938fc Mon Sep 17 00:00:00 2001 From: m-holger Date: Sat, 10 Feb 2024 19:26:40 +0000 Subject: [PATCH 05/13] Add additional name token JSON tests Also, test writing JSON v1 files and files with deeply nested containers. --- qpdf/qtest/qpdf-json.test | 10 +- qpdf/qtest/qpdf/weird-tokens-alt.json | 136 +++++++++++- qpdf/qtest/qpdf/weird-tokens-v1.json | 295 ++++++++++++++++++++++++++ qpdf/qtest/qpdf/weird-tokens.json | 136 +++++++++++- qpdf/qtest/qpdf/weird-tokens.pdf | 148 ++++++++++++- 5 files changed, 715 insertions(+), 10 deletions(-) create mode 100644 qpdf/qtest/qpdf/weird-tokens-v1.json diff --git a/qpdf/qtest/qpdf-json.test b/qpdf/qtest/qpdf-json.test index 9542bccf..defabbe7 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 += 6; $td->runtest("handle binary names", {$td->COMMAND => "qpdf --json-output weird-tokens.pdf a.json"}, @@ -371,6 +371,14 @@ $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); cleanup(); $td->report($n_tests); 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 From 9589fad1e5bde7c51f01c0de16ac0b5ccb7051e0 Mon Sep 17 00:00:00 2001 From: m-holger Date: Mon, 12 Feb 2024 18:40:21 +0000 Subject: [PATCH 06/13] Reimplement QPDFObjectHandle::getJSON in terms of writeJSON --- libqpdf/QPDFObjectHandle.cc | 6 +++++- libqpdf/QPDF_Array.cc | 32 ------------------------------ libqpdf/QPDF_Bool.cc | 6 ------ libqpdf/QPDF_Destroyed.cc | 7 ------- libqpdf/QPDF_Dictionary.cc | 20 ------------------- libqpdf/QPDF_InlineImage.cc | 6 ------ libqpdf/QPDF_Integer.cc | 6 ------ libqpdf/QPDF_Name.cc | 14 ------------- libqpdf/QPDF_Null.cc | 7 ------- libqpdf/QPDF_Operator.cc | 6 ------ libqpdf/QPDF_Real.cc | 19 ------------------ libqpdf/QPDF_Reserved.cc | 7 ------- libqpdf/QPDF_Stream.cc | 9 --------- libqpdf/QPDF_String.cc | 29 --------------------------- libqpdf/QPDF_Unresolved.cc | 7 ------- libqpdf/qpdf/QPDFObject_private.hh | 5 ----- libqpdf/qpdf/QPDFValue.hh | 1 - libqpdf/qpdf/QPDF_Array.hh | 1 - libqpdf/qpdf/QPDF_Bool.hh | 1 - libqpdf/qpdf/QPDF_Destroyed.hh | 1 - libqpdf/qpdf/QPDF_Dictionary.hh | 1 - libqpdf/qpdf/QPDF_InlineImage.hh | 1 - libqpdf/qpdf/QPDF_Integer.hh | 1 - libqpdf/qpdf/QPDF_Name.hh | 1 - libqpdf/qpdf/QPDF_Null.hh | 1 - libqpdf/qpdf/QPDF_Operator.hh | 1 - libqpdf/qpdf/QPDF_Real.hh | 1 - libqpdf/qpdf/QPDF_Reserved.hh | 1 - libqpdf/qpdf/QPDF_Stream.hh | 1 - libqpdf/qpdf/QPDF_String.hh | 1 - libqpdf/qpdf/QPDF_Unresolved.hh | 1 - 31 files changed, 5 insertions(+), 196 deletions(-) diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index 7af83a68..4fda24f5 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -1618,7 +1618,11 @@ 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()); } } diff --git a/libqpdf/QPDF_Array.cc b/libqpdf/QPDF_Array.cc index 6e50a781..ab2a4c8c 100644 --- a/libqpdf/QPDF_Array.cc +++ b/libqpdf/QPDF_Array.cc @@ -149,38 +149,6 @@ QPDF_Array::unparse() return result; } -JSON -QPDF_Array::getJSON(int json_version) -{ - static const JSON j_null = JSON::makeNull(); - JSON j_array = JSON::makeArray(); - 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); - } - auto og = item.second->getObjGen(); - j_array.addArrayElement( - og.isIndirect() ? JSON::makeString(og.unparse(' ') + " R") - : item.second->getJSON(json_version)); - next = ++key; - } - for (int j = next; j < sp->size; ++j) { - j_array.addArrayElement(j_null); - } - } else { - for (auto const& item: elements) { - auto og = item->getObjGen(); - j_array.addArrayElement( - og.isIndirect() ? JSON::makeString(og.unparse(' ') + " R") - : item->getJSON(json_version)); - } - } - return j_array; -} - void QPDF_Array::writeJSON(int json_version, JSON::Writer& p) { diff --git a/libqpdf/QPDF_Bool.cc b/libqpdf/QPDF_Bool.cc index 9e57e68e..97f47881 100644 --- a/libqpdf/QPDF_Bool.cc +++ b/libqpdf/QPDF_Bool.cc @@ -26,12 +26,6 @@ QPDF_Bool::unparse() return (val ? "true" : "false"); } -JSON -QPDF_Bool::getJSON(int json_version) -{ - return JSON::makeBool(this->val); -} - void QPDF_Bool::writeJSON(int json_version, JSON::Writer& p) { diff --git a/libqpdf/QPDF_Destroyed.cc b/libqpdf/QPDF_Destroyed.cc index 06d1a83c..5e04566a 100644 --- a/libqpdf/QPDF_Destroyed.cc +++ b/libqpdf/QPDF_Destroyed.cc @@ -28,13 +28,6 @@ QPDF_Destroyed::unparse() return ""; } -JSON -QPDF_Destroyed::getJSON(int json_version) -{ - throw std::logic_error("attempted to get JSON from a QPDFObjectHandle from a destroyed QPDF"); - return JSON::makeNull(); -} - void QPDF_Destroyed::writeJSON(int json_version, JSON::Writer& p) { diff --git a/libqpdf/QPDF_Dictionary.cc b/libqpdf/QPDF_Dictionary.cc index ca7fa04a..9332b1d3 100644 --- a/libqpdf/QPDF_Dictionary.cc +++ b/libqpdf/QPDF_Dictionary.cc @@ -68,26 +68,6 @@ QPDF_Dictionary::unparse() return result; } -JSON -QPDF_Dictionary::getJSON(int json_version) -{ - JSON j = JSON::makeDictionary(); - for (auto& iter: this->items) { - if (!iter.second.isNull()) { - if (json_version == 1) { - j.addDictionaryMember( - QPDF_Name::normalizeName(iter.first), iter.second.getJSON(json_version)); - } else if (auto res = QPDF_Name::analyzeJSONEncoding(iter.first); res.first) { - j.addDictionaryMember(iter.first, iter.second.getJSON(json_version)); - } else { - j.addDictionaryMember( - "n:" + QPDF_Name::normalizeName(iter.first), iter.second.getJSON(json_version)); - } - } - } - return j; -} - void QPDF_Dictionary::writeJSON(int json_version, JSON::Writer& p) { diff --git a/libqpdf/QPDF_InlineImage.cc b/libqpdf/QPDF_InlineImage.cc index bb5c4d0f..2d62071d 100644 --- a/libqpdf/QPDF_InlineImage.cc +++ b/libqpdf/QPDF_InlineImage.cc @@ -26,12 +26,6 @@ QPDF_InlineImage::unparse() return this->val; } -JSON -QPDF_InlineImage::getJSON(int json_version) -{ - return JSON::makeNull(); -} - void QPDF_InlineImage::writeJSON(int json_version, JSON::Writer& p) { diff --git a/libqpdf/QPDF_Integer.cc b/libqpdf/QPDF_Integer.cc index d65b3478..aa0437a1 100644 --- a/libqpdf/QPDF_Integer.cc +++ b/libqpdf/QPDF_Integer.cc @@ -27,12 +27,6 @@ QPDF_Integer::unparse() return std::to_string(this->val); } -JSON -QPDF_Integer::getJSON(int json_version) -{ - return JSON::makeInt(this->val); -} - void QPDF_Integer::writeJSON(int json_version, JSON::Writer& p) { diff --git a/libqpdf/QPDF_Name.cc b/libqpdf/QPDF_Name.cc index 04614769..bcc9498b 100644 --- a/libqpdf/QPDF_Name.cc +++ b/libqpdf/QPDF_Name.cc @@ -103,20 +103,6 @@ QPDF_Name::analyzeJSONEncoding(const std::string& name) return {tail == 0, !needs_escaping}; } -JSON -QPDF_Name::getJSON(int json_version) -{ - if (json_version == 1) { - return JSON::makeString(normalizeName(this->name)); - } else { - if (auto res = analyzeJSONEncoding(name); res.first) { - return JSON::makeString(name); - } else { - return JSON::makeString("n:" + normalizeName(name)); - } - } -} - void QPDF_Name::writeJSON(int json_version, JSON::Writer& p) { diff --git a/libqpdf/QPDF_Null.cc b/libqpdf/QPDF_Null.cc index 13118e8c..fa4b6cab 100644 --- a/libqpdf/QPDF_Null.cc +++ b/libqpdf/QPDF_Null.cc @@ -44,13 +44,6 @@ QPDF_Null::unparse() return "null"; } -JSON -QPDF_Null::getJSON(int json_version) -{ - // If this is updated, QPDF_Array::getJSON must also be updated. - return JSON::makeNull(); -} - void QPDF_Null::writeJSON(int json_version, JSON::Writer& p) { diff --git a/libqpdf/QPDF_Operator.cc b/libqpdf/QPDF_Operator.cc index 763c4f12..c35a8391 100644 --- a/libqpdf/QPDF_Operator.cc +++ b/libqpdf/QPDF_Operator.cc @@ -26,12 +26,6 @@ QPDF_Operator::unparse() return val; } -JSON -QPDF_Operator::getJSON(int json_version) -{ - return JSON::makeNull(); -} - void QPDF_Operator::writeJSON(int json_version, JSON::Writer& p) { diff --git a/libqpdf/QPDF_Real.cc b/libqpdf/QPDF_Real.cc index af96c9df..f4304397 100644 --- a/libqpdf/QPDF_Real.cc +++ b/libqpdf/QPDF_Real.cc @@ -39,25 +39,6 @@ QPDF_Real::unparse() return this->val; } -JSON -QPDF_Real::getJSON(int json_version) -{ - // 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"; - } 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); - } else { - result = this->val; - } - return JSON::makeNumber(result); -} - void QPDF_Real::writeJSON(int json_version, JSON::Writer& p) { diff --git a/libqpdf/QPDF_Reserved.cc b/libqpdf/QPDF_Reserved.cc index 0be60c98..da112acc 100644 --- a/libqpdf/QPDF_Reserved.cc +++ b/libqpdf/QPDF_Reserved.cc @@ -26,13 +26,6 @@ QPDF_Reserved::unparse() return ""; } -JSON -QPDF_Reserved::getJSON(int json_version) -{ - throw std::logic_error("QPDFObjectHandle: attempting to get JSON from a reserved object"); - return JSON::makeNull(); -} - void QPDF_Reserved::writeJSON(int json_version, JSON::Writer& p) { diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc index bedc96b2..8189ae7b 100644 --- a/libqpdf/QPDF_Stream.cc +++ b/libqpdf/QPDF_Stream.cc @@ -177,15 +177,6 @@ QPDF_Stream::unparse() return og.unparse(' ') + " R"; } -JSON -QPDF_Stream::getJSON(int json_version) -{ - if (json_version == 1) { - return this->stream_dict.getJSON(json_version); - } - return getStreamJSON(json_version, qpdf_sj_none, qpdf_dl_none, nullptr, ""); -} - void QPDF_Stream::writeJSON(int json_version, JSON::Writer& p) { diff --git a/libqpdf/QPDF_String.cc b/libqpdf/QPDF_String.cc index d3fcaaef..f425b313 100644 --- a/libqpdf/QPDF_String.cc +++ b/libqpdf/QPDF_String.cc @@ -46,35 +46,6 @@ QPDF_String::unparse() return unparse(false); } -JSON -QPDF_String::getJSON(int json_version) -{ - 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; - } else { - result = "b:" + QUtil::hex_encode(this->val); - } - return JSON::makeString(result); -} - void QPDF_String::writeJSON(int json_version, JSON::Writer& p) { diff --git a/libqpdf/QPDF_Unresolved.cc b/libqpdf/QPDF_Unresolved.cc index c2a50dbf..dc14c2f2 100644 --- a/libqpdf/QPDF_Unresolved.cc +++ b/libqpdf/QPDF_Unresolved.cc @@ -27,13 +27,6 @@ QPDF_Unresolved::unparse() return ""; } -JSON -QPDF_Unresolved::getJSON(int json_version) -{ - throw std::logic_error("attempted to get JSON from an unresolved QPDFObjectHandle"); - return JSON::makeNull(); -} - void QPDF_Unresolved::writeJSON(int json_version, JSON::Writer& p) { diff --git a/libqpdf/qpdf/QPDFObject_private.hh b/libqpdf/qpdf/QPDFObject_private.hh index b873bcd0..1c3dadc9 100644 --- a/libqpdf/qpdf/QPDFObject_private.hh +++ b/libqpdf/qpdf/QPDFObject_private.hh @@ -33,11 +33,6 @@ class QPDFObject { return value->unparse(); } - JSON - getJSON(int json_version) - { - return value->getJSON(json_version); - } void writeJSON(int json_version, JSON::Writer& p) { diff --git a/libqpdf/qpdf/QPDFValue.hh b/libqpdf/qpdf/QPDFValue.hh index f7783466..28abf147 100644 --- a/libqpdf/qpdf/QPDFValue.hh +++ b/libqpdf/qpdf/QPDFValue.hh @@ -24,7 +24,6 @@ class QPDFValue: public std::enable_shared_from_this virtual std::shared_ptr 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 7ee69b8e..88b2a3d3 100644 --- a/libqpdf/qpdf/QPDF_Array.hh +++ b/libqpdf/qpdf/QPDF_Array.hh @@ -22,7 +22,6 @@ class QPDF_Array: public QPDFValue create(std::vector>&& items, bool sparse); std::shared_ptr 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; diff --git a/libqpdf/qpdf/QPDF_Bool.hh b/libqpdf/qpdf/QPDF_Bool.hh index f4ce0b1c..1692bdc4 100644 --- a/libqpdf/qpdf/QPDF_Bool.hh +++ b/libqpdf/qpdf/QPDF_Bool.hh @@ -10,7 +10,6 @@ class QPDF_Bool: public QPDFValue static std::shared_ptr create(bool val); std::shared_ptr 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; diff --git a/libqpdf/qpdf/QPDF_Destroyed.hh b/libqpdf/qpdf/QPDF_Destroyed.hh index 400921e3..9259a2dc 100644 --- a/libqpdf/qpdf/QPDF_Destroyed.hh +++ b/libqpdf/qpdf/QPDF_Destroyed.hh @@ -9,7 +9,6 @@ class QPDF_Destroyed: public QPDFValue ~QPDF_Destroyed() override = default; std::shared_ptr 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 getInstance(); diff --git a/libqpdf/qpdf/QPDF_Dictionary.hh b/libqpdf/qpdf/QPDF_Dictionary.hh index 32c926a3..8713a450 100644 --- a/libqpdf/qpdf/QPDF_Dictionary.hh +++ b/libqpdf/qpdf/QPDF_Dictionary.hh @@ -16,7 +16,6 @@ class QPDF_Dictionary: public QPDFValue static std::shared_ptr create(std::map&& items); std::shared_ptr 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; diff --git a/libqpdf/qpdf/QPDF_InlineImage.hh b/libqpdf/qpdf/QPDF_InlineImage.hh index cc4dc31a..c06662d7 100644 --- a/libqpdf/qpdf/QPDF_InlineImage.hh +++ b/libqpdf/qpdf/QPDF_InlineImage.hh @@ -10,7 +10,6 @@ class QPDF_InlineImage: public QPDFValue static std::shared_ptr create(std::string const& val); std::shared_ptr 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 dd2f9e0d..ae7f7893 100644 --- a/libqpdf/qpdf/QPDF_Integer.hh +++ b/libqpdf/qpdf/QPDF_Integer.hh @@ -10,7 +10,6 @@ class QPDF_Integer: public QPDFValue static std::shared_ptr create(long long value); std::shared_ptr 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; diff --git a/libqpdf/qpdf/QPDF_Name.hh b/libqpdf/qpdf/QPDF_Name.hh index fd4ac458..b5d3c318 100644 --- a/libqpdf/qpdf/QPDF_Name.hh +++ b/libqpdf/qpdf/QPDF_Name.hh @@ -10,7 +10,6 @@ class QPDF_Name: public QPDFValue static std::shared_ptr create(std::string const& name); std::shared_ptr 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 diff --git a/libqpdf/qpdf/QPDF_Null.hh b/libqpdf/qpdf/QPDF_Null.hh index 2a099bf9..fc6e0b5f 100644 --- a/libqpdf/qpdf/QPDF_Null.hh +++ b/libqpdf/qpdf/QPDF_Null.hh @@ -18,7 +18,6 @@ class QPDF_Null: public QPDFValue std::string var_descr); std::shared_ptr 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: diff --git a/libqpdf/qpdf/QPDF_Operator.hh b/libqpdf/qpdf/QPDF_Operator.hh index d8b99e17..b9b040d6 100644 --- a/libqpdf/qpdf/QPDF_Operator.hh +++ b/libqpdf/qpdf/QPDF_Operator.hh @@ -10,7 +10,6 @@ class QPDF_Operator: public QPDFValue static std::shared_ptr create(std::string const& val); std::shared_ptr 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 5fec919d..aa9baa5f 100644 --- a/libqpdf/qpdf/QPDF_Real.hh +++ b/libqpdf/qpdf/QPDF_Real.hh @@ -12,7 +12,6 @@ class QPDF_Real: public QPDFValue create(double value, int decimal_places, bool trim_trailing_zeroes); std::shared_ptr 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 4503fb9e..801987e4 100644 --- a/libqpdf/qpdf/QPDF_Reserved.hh +++ b/libqpdf/qpdf/QPDF_Reserved.hh @@ -10,7 +10,6 @@ class QPDF_Reserved: public QPDFValue static std::shared_ptr create(); std::shared_ptr 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: diff --git a/libqpdf/qpdf/QPDF_Stream.hh b/libqpdf/qpdf/QPDF_Stream.hh index 597a48a8..d8d1d16a 100644 --- a/libqpdf/qpdf/QPDF_Stream.hh +++ b/libqpdf/qpdf/QPDF_Stream.hh @@ -25,7 +25,6 @@ class QPDF_Stream: public QPDFValue size_t length); std::shared_ptr 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& description, qpdf_offset_t offset) override; diff --git a/libqpdf/qpdf/QPDF_String.hh b/libqpdf/qpdf/QPDF_String.hh index e7a87a53..967b2d30 100644 --- a/libqpdf/qpdf/QPDF_String.hh +++ b/libqpdf/qpdf/QPDF_String.hh @@ -16,7 +16,6 @@ class QPDF_String: public QPDFValue std::shared_ptr 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 diff --git a/libqpdf/qpdf/QPDF_Unresolved.hh b/libqpdf/qpdf/QPDF_Unresolved.hh index 669e314e..b4c1d9e4 100644 --- a/libqpdf/qpdf/QPDF_Unresolved.hh +++ b/libqpdf/qpdf/QPDF_Unresolved.hh @@ -10,7 +10,6 @@ class QPDF_Unresolved: public QPDFValue static std::shared_ptr create(QPDF* qpdf, QPDFObjGen const& og); std::shared_ptr 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: From 920e929864aecf1e9e16c96565ac28a9121e278e Mon Sep 17 00:00:00 2001 From: m-holger Date: Wed, 14 Feb 2024 15:59:00 +0000 Subject: [PATCH 07/13] Reimplement QPDF_Stream::getStreamJSON in terms of writeStreamJSON writeStreamJSON is a temporary implementation minimally adapted from getStreamJSON. --- libqpdf/QPDF_Stream.cc | 48 +++++++++++++++++++++++++++---------- libqpdf/qpdf/QPDF_Stream.hh | 8 +++++++ 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc index 8189ae7b..d7087819 100644 --- a/libqpdf/QPDF_Stream.cc +++ b/libqpdf/QPDF_Stream.cc @@ -190,22 +190,44 @@ QPDF_Stream::getStreamJSON( qpdf_stream_decode_level_e decode_level, 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; @@ -234,12 +256,13 @@ QPDF_Stream::getStreamJSON( decode_level = qpdf_dl_none; buf_pl.getString(); // reset buf_pl } else { - if (json_data == qpdf_sj_file) { - buf_pl_ready = true; - } + buf_pl_ready = true; break; } } + 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. dict = this->stream_dict.unsafeShallowCopy(); dict.removeKey("/Length"); @@ -249,19 +272,20 @@ QPDF_Stream::getStreamJSON( } 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))); + if (!no_data_key) { + result.addDictionaryMember( + "data", JSON::makeBlob(StreamBlobProvider(this, decode_level))); + } } else { throw std::logic_error("QPDF_Stream: unexpected value of json_data"); } } result.addDictionaryMember("dict", dict.getJSON(json_version)); - return result; + jw << std::move(result); + return decode_level; } void diff --git a/libqpdf/qpdf/QPDF_Stream.hh b/libqpdf/qpdf/QPDF_Stream.hh index d8d1d16a..3fcbc428 100644 --- a/libqpdf/qpdf/QPDF_Stream.hh +++ b/libqpdf/qpdf/QPDF_Stream.hh @@ -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); From b15d0bf6e1305982450879b498a116d3387b705b Mon Sep 17 00:00:00 2001 From: m-holger Date: Thu, 15 Feb 2024 12:33:31 +0000 Subject: [PATCH 08/13] Add new method QPDF_Stream::writeStreamJSON (Replacing the temporary implementation from the last commit.) --- libqpdf/QPDF_Stream.cc | 107 +++++++++++++++++++----------------- libqpdf/qpdf/JSON_writer.hh | 12 ++++ 2 files changed, 69 insertions(+), 50 deletions(-) diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc index d7087819..6cfcdd46 100644 --- a/libqpdf/QPDF_Stream.cc +++ b/libqpdf/QPDF_Stream.cc @@ -178,9 +178,9 @@ QPDF_Stream::unparse() } void -QPDF_Stream::writeJSON(int json_version, JSON::Writer& p) +QPDF_Stream::writeJSON(int json_version, JSON::Writer& jw) { - stream_dict.writeJSON(json_version, p); + stream_dict.writeJSON(json_version, jw); } JSON @@ -233,58 +233,65 @@ QPDF_Stream::writeStreamJSON( 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 { - buf_pl_ready = true; - break; - } - } - 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. - 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)); + jw.writeStart('{'); - p->writeString(buf_pl.getString()); - } else if (json_data == qpdf_sj_inline) { - if (!no_data_key) { - result.addDictionaryMember( - "data", JSON::makeBlob(StreamBlobProvider(this, decode_level))); - } + 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)); - jw << std::move(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; } diff --git a/libqpdf/qpdf/JSON_writer.hh b/libqpdf/qpdf/JSON_writer.hh index 6d870b88..3f770c58 100644 --- a/libqpdf/qpdf/JSON_writer.hh +++ b/libqpdf/qpdf/JSON_writer.hh @@ -3,6 +3,8 @@ #include #include +#include +#include #include @@ -27,6 +29,16 @@ class JSON::Writer 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(sv.data()), sv.size()); + base.finish(); + return *this; + } + Writer& writeNext() { From b9bc05356ac6823e3e105ea25908e826d7b731fd Mon Sep 17 00:00:00 2001 From: m-holger Date: Thu, 15 Feb 2024 14:47:08 +0000 Subject: [PATCH 09/13] Refactor QPDF::writeJSONStream Use QPDF_Stream::writeStreamJSON. Factor out the json_stream_data == qpdf_sj_file case. --- include/qpdf/QPDF.hh | 13 --------- libqpdf/QPDF_json.cc | 66 +++++++++++++++++++++++++++----------------- 2 files changed, 41 insertions(+), 38 deletions(-) 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, 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 static qpdf_offset_t diff --git a/libqpdf/QPDF_json.cc b/libqpdf/QPDF_json.cc index 5560afec..5fc02ad5 100644 --- a/libqpdf/QPDF_json.cc +++ b/libqpdf/QPDF_json.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -443,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(); @@ -829,40 +832,51 @@ QPDF::importJSON(std::shared_ptr is, bool must_be_complete) } void -QPDF::writeJSONStream( +writeJSONStreamFile( + int version, + JSON::Writer& jw, + QPDF_Stream& stream, + int id, + qpdf_stream_decode_level_e decode_level, + std::string const& file_prefix) +{ + 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 +writeJSONStream( int version, Pipeline* p, bool& first, std::string const& key, - QPDFObjectHandle& obj, + 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 f_pl; - std::string filename; + if (first) { + *p << "\n \"" << key << "\": {\n \"stream\": "; + first = false; + } else { + *p << ",\n \"" << key << "\": {\n \"stream\": "; + } + JSON::Writer jw{p, 4}; 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("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); + writeJSONStreamFile(version, jw, stream, id, decode_level, file_prefix); + } else { + stream.writeStreamJSON(version, jw, json_stream_data, decode_level, nullptr, ""); } + *p << "\n }"; } void -QPDF::writeJSONObject( +writeJSONObject( int version, Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle& obj) { if (first) { @@ -947,15 +961,17 @@ QPDF::writeJSON( JSON::writeDictionaryOpen(p, first_qpdf_inner, depth_qpdf); bool all_objects = wanted_objects.empty(); 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()) { + if (auto* stream = obj.getObjectPtr()->as()) { writeJSONStream( version, p, first_qpdf_inner, key, - obj, + *stream, + og.getObj(), decode_level, json_stream_data, file_prefix); From c06653c3ab1ec3241b66e708750813a380378fad Mon Sep 17 00:00:00 2001 From: m-holger Date: Thu, 15 Feb 2024 18:02:01 +0000 Subject: [PATCH 10/13] Refactor QPDF::writeJSON --- libqpdf/QPDF_json.cc | 153 +++++++++++++++---------------------------- 1 file changed, 52 insertions(+), 101 deletions(-) diff --git a/libqpdf/QPDF_json.cc b/libqpdf/QPDF_json.cc index 5fc02ad5..b06b70c9 100644 --- a/libqpdf/QPDF_json.cc +++ b/libqpdf/QPDF_json.cc @@ -848,48 +848,6 @@ writeJSONStreamFile( fclose(f); } -void -writeJSONStream( - int version, - Pipeline* p, - bool& first, - std::string const& key, - 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) -{ - if (first) { - *p << "\n \"" << key << "\": {\n \"stream\": "; - first = false; - } else { - *p << ",\n \"" << key << "\": {\n \"stream\": "; - } - JSON::Writer jw{p, 4}; - if (json_stream_data == qpdf_sj_file) { - writeJSONStreamFile(version, jw, stream, id, decode_level, file_prefix); - } else { - stream.writeStreamJSON(version, jw, json_stream_data, decode_level, nullptr, ""); - } - *p << "\n }"; -} - -void -writeJSONObject( - int version, Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle& obj) -{ - if (first) { - *p << "\n \"" << key << "\": {\n \"value\": "; - first = false; - } else { - *p << ",\n \"" << key << "\": {\n \"value\": "; - } - auto w = JSON::Writer(p, 4); - obj.writeJSON(version, w, true); - *p << "\n }"; -} - void QPDF::writeJSON( int version, @@ -914,82 +872,75 @@ QPDF::writeJSON( std::string const& file_prefix, std::set 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; + jw << "{"; + } else if (!first_key) { + jw << ","; } - 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); + 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()) { auto const og = obj.getObjGen(); std::string key = "obj:" + og.unparse(' ') + " R"; if (all_objects || wanted_objects.count(key)) { - if (auto* stream = obj.getObjectPtr()->as()) { - writeJSONStream( - version, - p, - first_qpdf_inner, - key, - *stream, - og.getObj(), - 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()) { + 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); + if (!first) { + jw << "\n },"; + } + jw << "\n \"trailer\": {\n \"value\": "; + getTrailer().writeJSON(version, jw, true); + first = false; } - JSON::writeDictionaryClose(p, first_qpdf_inner, depth_qpdf); - JSON::writeArrayClose(p, first_qpdf, depth_top); + 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; } From f0bc2f11ef4f096b1338dd5fc91e3c4d88b3b9e0 Mon Sep 17 00:00:00 2001 From: m-holger Date: Fri, 16 Feb 2024 14:09:28 +0000 Subject: [PATCH 11/13] Expose QPDFObjectHandle::writeJSON --- include/qpdf/QPDFObjectHandle.hh | 7 +++++++ libqpdf/QPDFObjectHandle.cc | 7 +++++++ qpdf/qtest/qpdf-json.test | 7 +++++-- qpdf/test_driver.cc | 18 +++++++++++++++++- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh index a965f468..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 diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index 4fda24f5..9b9849a3 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -1638,6 +1638,13 @@ QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_ } } +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/qpdf/qtest/qpdf-json.test b/qpdf/qtest/qpdf-json.test index defabbe7..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 += 6; +$n_tests += 7; $td->runtest("handle binary names", {$td->COMMAND => "qpdf --json-output weird-tokens.pdf a.json"}, @@ -379,6 +379,9 @@ $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/test_driver.cc b/qpdf/test_driver.cc index 28d8062c..f421f86a 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -3380,6 +3380,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) { @@ -3481,7 +3497,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()) { From 4f54508f7f3b9d9d97b1163156a89bfad582a5ea Mon Sep 17 00:00:00 2001 From: m-holger Date: Fri, 16 Feb 2024 15:50:30 +0000 Subject: [PATCH 12/13] Refactor QPDFJob::doJSONObjects --- include/qpdf/QPDFJob.hh | 1 - libqpdf/QPDFJob.cc | 37 +++++++++++-------------------------- 2 files changed, 11 insertions(+), 27 deletions(-) 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/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc index a699e38d..a85ff0a2 100644 --- a/libqpdf/QPDFJob.cc +++ b/libqpdf/QPDFJob.cc @@ -954,23 +954,6 @@ QPDFJob::getWantedJSONObjects() 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 QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf) { @@ -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"; } } } From 413aba5bf2ef239d3abf024de3c819e153171035 Mon Sep 17 00:00:00 2001 From: m-holger Date: Sat, 17 Feb 2024 14:58:48 +0000 Subject: [PATCH 13/13] Add comment to QPDF_Name::writeJSON --- libqpdf/QPDF_Name.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libqpdf/QPDF_Name.cc b/libqpdf/QPDF_Name.cc index bcc9498b..0e7f441a 100644 --- a/libqpdf/QPDF_Name.cc +++ b/libqpdf/QPDF_Name.cc @@ -60,7 +60,7 @@ QPDF_Name::analyzeJSONEncoding(const std::string& name) std::basic_string_view view{ reinterpret_cast(name.data()), name.size()}; - int tail = 0; // Number of continuation characters expected. + 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; @@ -106,6 +106,8 @@ QPDF_Name::analyzeJSONEncoding(const std::string& name) 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 {