diff options
author | Jay Berkenbilt <ejb@ql.org> | 2024-02-17 20:15:48 +0100 |
---|---|---|
committer | Jay Berkenbilt <ejb@ql.org> | 2024-02-17 20:15:48 +0100 |
commit | e362bce8e86f4912eaa008bac06f9e2c19b72d3f (patch) | |
tree | 42fcd9eed699714f98ddee634763e4d670a2652b /libqpdf | |
parent | 5a29b7f9dd353c7baf2ca738bd2a56582a6525ea (diff) | |
parent | 413aba5bf2ef239d3abf024de3c819e153171035 (diff) | |
download | qpdf-e362bce8e86f4912eaa008bac06f9e2c19b72d3f.tar.zst |
Merge branch 'jw' from #1146 into work
Diffstat (limited to 'libqpdf')
35 files changed, 524 insertions, 299 deletions
diff --git a/libqpdf/JSON.cc b/libqpdf/JSON.cc index 27405df7..bc28f236 100644 --- a/libqpdf/JSON.cc +++ b/libqpdf/JSON.cc @@ -1,5 +1,7 @@ #include <qpdf/JSON.hh> +#include <qpdf/JSON_writer.hh> + #include <qpdf/BufferInputSource.hh> #include <qpdf/Pl_Base64.hh> #include <qpdf/Pl_Concatenate.hh> @@ -119,7 +121,7 @@ JSON::JSON_array::write(Pipeline* p, size_t depth) const JSON::JSON_string::JSON_string(std::string const& utf8) : JSON_value(vt_string), utf8(utf8), - encoded(encode_string(utf8)) + encoded(Writer::encode_string(utf8)) { } @@ -211,7 +213,7 @@ JSON::unparse() const } std::string -JSON::encode_string(std::string const& str) +JSON::Writer::encode_string(std::string const& str) { static auto constexpr hexchars = "0123456789abcdef"; @@ -279,7 +281,7 @@ JSON JSON::addDictionaryMember(std::string const& key, JSON const& val) { if (auto* obj = m ? dynamic_cast<JSON_dictionary*>(m->value.get()) : nullptr) { - return obj->members[encode_string(key)] = val.m ? val : makeNull(); + return obj->members[Writer::encode_string(key)] = val.m ? val : makeNull(); } else { throw std::runtime_error("JSON::addDictionaryMember called on non-dictionary"); } diff --git a/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc index a699e38d..a85ff0a2 100644 --- a/libqpdf/QPDFJob.cc +++ b/libqpdf/QPDFJob.cc @@ -955,23 +955,6 @@ QPDFJob::getWantedJSONObjects() } void -QPDFJob::doJSONObject(Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle& obj) -{ - if (m->json_version == 1) { - JSON::writeDictionaryItem(p, first, key, obj.getJSON(1, true), 2); - } else { - auto j = JSON::makeDictionary(); - if (obj.isStream()) { - j.addDictionaryMember("stream", JSON::makeDictionary()) - .addDictionaryMember("dict", obj.getDict().getJSON(m->json_version, true)); - } else { - j.addDictionaryMember("value", obj.getJSON(m->json_version, true)); - } - JSON::writeDictionaryItem(p, first, key, j, 2); - } -} - -void QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf) { if (m->json_version == 1) { @@ -982,16 +965,17 @@ QPDFJob::doJSONObjects(Pipeline* p, bool& first, QPDF& pdf) auto wanted_og = getWantedJSONObjects(); for (auto& obj: pdf.getAllObjects()) { std::string key = obj.unparse(); - if (m->json_version > 1) { - key = "obj:" + key; - } + if (all_objects || wanted_og.count(obj.getObjGen())) { - doJSONObject(p, first_object, key, obj); + JSON::writeDictionaryKey(p, first_object, obj.unparse(), 2); + obj.writeJSON(1, p, true, 2); + first_object = false; } } if (all_objects || m->json_objects.count("trailer")) { - auto trailer = pdf.getTrailer(); - doJSONObject(p, first_object, "trailer", trailer); + JSON::writeDictionaryKey(p, first_object, "trailer", 2); + pdf.getTrailer().writeJSON(1, p, true, 2); + first_object = false; } JSON::writeDictionaryClose(p, first_object, 1); } else { @@ -3097,9 +3081,10 @@ QPDFJob::writeOutfile(QPDF& pdf) try { QUtil::remove_file(backup.c_str()); } catch (QPDFSystemError& e) { - *m->log->getError() << m->message_prefix << ": unable to delete original file (" - << e.what() << ");" << " original file left in " << backup - << ", but the input was successfully replaced\n"; + *m->log->getError() + << m->message_prefix << ": unable to delete original file (" << e.what() << ");" + << " original file left in " << backup + << ", but the input was successfully replaced\n"; } } } diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index d543f98e..9b9849a3 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -3,6 +3,7 @@ #include <qpdf/BufferInputSource.hh> #include <qpdf/Pl_Buffer.hh> #include <qpdf/Pl_QPDFTokenizer.hh> +#include <qpdf/JSON_writer.hh> #include <qpdf/QPDF.hh> #include <qpdf/QPDFExc.hh> #include <qpdf/QPDFLogger.hh> @@ -1617,10 +1618,33 @@ QPDFObjectHandle::getJSON(int json_version, bool dereference_indirect) } else if (!dereference()) { throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle"); } else { - return obj->getJSON(json_version); + Pl_Buffer p{"json"}; + JSON::Writer jw{&p, 0}; + writeJSON(json_version, jw, dereference_indirect); + p.finish(); + return JSON::parse(p.getString()); } } +void +QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect) +{ + if (!dereference_indirect && isIndirect()) { + p << "\"" << getObjGen().unparse(' ') << " R\""; + } else if (!dereference()) { + throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle"); + } else { + obj->writeJSON(json_version, p); + } +} + +void +QPDFObjectHandle::writeJSON(int json_version, Pipeline* p, bool dereference_indirect, size_t depth) +{ + JSON::Writer jw{p, depth}; + writeJSON(json_version, jw, dereference_indirect); +} + JSON QPDFObjectHandle::getStreamJSON( int json_version, diff --git a/libqpdf/QPDF_Array.cc b/libqpdf/QPDF_Array.cc index 789acc35..ab2a4c8c 100644 --- a/libqpdf/QPDF_Array.cc +++ b/libqpdf/QPDF_Array.cc @@ -1,5 +1,6 @@ #include <qpdf/QPDF_Array.hh> +#include <qpdf/JSON_writer.hh> #include <qpdf/QPDFObjectHandle.hh> #include <qpdf/QPDFObject_private.hh> #include <qpdf/QTC.hh> @@ -148,36 +149,41 @@ QPDF_Array::unparse() return result; } -JSON -QPDF_Array::getJSON(int json_version) +void +QPDF_Array::writeJSON(int json_version, JSON::Writer& p) { - static const JSON j_null = JSON::makeNull(); - JSON j_array = JSON::makeArray(); + p.writeStart('['); if (sp) { int next = 0; for (auto& item: sp->elements) { int key = item.first; for (int j = next; j < key; ++j) { - j_array.addArrayElement(j_null); + p.writeNext() << "null"; } + p.writeNext(); auto og = item.second->getObjGen(); - j_array.addArrayElement( - og.isIndirect() ? JSON::makeString(og.unparse(' ') + " R") - : item.second->getJSON(json_version)); + if (og.isIndirect()) { + p << "\"" << og.unparse(' ') << " R\""; + } else { + item.second->writeJSON(json_version, p); + } next = ++key; } for (int j = next; j < sp->size; ++j) { - j_array.addArrayElement(j_null); + p.writeNext() << "null"; } } else { for (auto const& item: elements) { + p.writeNext(); auto og = item->getObjGen(); - j_array.addArrayElement( - og.isIndirect() ? JSON::makeString(og.unparse(' ') + " R") - : item->getJSON(json_version)); + if (og.isIndirect()) { + p << "\"" << og.unparse(' ') << " R\""; + } else { + item->writeJSON(json_version, p); + } } } - return j_array; + p.writeEnd(']'); } QPDFObjectHandle diff --git a/libqpdf/QPDF_Bool.cc b/libqpdf/QPDF_Bool.cc index 05c52f22..97f47881 100644 --- a/libqpdf/QPDF_Bool.cc +++ b/libqpdf/QPDF_Bool.cc @@ -1,5 +1,7 @@ #include <qpdf/QPDF_Bool.hh> +#include <qpdf/JSON_writer.hh> + QPDF_Bool::QPDF_Bool(bool val) : QPDFValue(::ot_boolean, "boolean"), val(val) @@ -24,10 +26,10 @@ QPDF_Bool::unparse() return (val ? "true" : "false"); } -JSON -QPDF_Bool::getJSON(int json_version) +void +QPDF_Bool::writeJSON(int json_version, JSON::Writer& p) { - return JSON::makeBool(this->val); + p << val; } bool diff --git a/libqpdf/QPDF_Destroyed.cc b/libqpdf/QPDF_Destroyed.cc index 4e34b508..5e04566a 100644 --- a/libqpdf/QPDF_Destroyed.cc +++ b/libqpdf/QPDF_Destroyed.cc @@ -28,9 +28,8 @@ QPDF_Destroyed::unparse() return ""; } -JSON -QPDF_Destroyed::getJSON(int json_version) +void +QPDF_Destroyed::writeJSON(int json_version, JSON::Writer& p) { throw std::logic_error("attempted to get JSON from a QPDFObjectHandle from a destroyed QPDF"); - return JSON::makeNull(); -} +}
\ No newline at end of file diff --git a/libqpdf/QPDF_Dictionary.cc b/libqpdf/QPDF_Dictionary.cc index f7e32fc9..9332b1d3 100644 --- a/libqpdf/QPDF_Dictionary.cc +++ b/libqpdf/QPDF_Dictionary.cc @@ -1,5 +1,6 @@ #include <qpdf/QPDF_Dictionary.hh> +#include <qpdf/JSON_writer.hh> #include <qpdf/QPDFObject_private.hh> #include <qpdf/QPDF_Name.hh> #include <qpdf/QPDF_Null.hh> @@ -67,28 +68,30 @@ QPDF_Dictionary::unparse() return result; } -JSON -QPDF_Dictionary::getJSON(int json_version) +void +QPDF_Dictionary::writeJSON(int json_version, JSON::Writer& p) { - JSON j = JSON::makeDictionary(); + p.writeStart('{'); for (auto& iter: this->items) { if (!iter.second.isNull()) { + p.writeNext(); if (json_version == 1) { - j.addDictionaryMember( - QPDF_Name::normalizeName(iter.first), iter.second.getJSON(json_version)); + p << "\"" << JSON::Writer::encode_string(QPDF_Name::normalizeName(iter.first)) + << "\": "; + } else if (auto res = QPDF_Name::analyzeJSONEncoding(iter.first); res.first) { + if (res.second) { + p << "\"" << iter.first << "\": "; + } else { + p << "\"" << JSON::Writer::encode_string(iter.first) << "\": "; + } } else { - bool has_8bit_chars; - bool is_valid_utf8; - bool is_utf16; - QUtil::analyze_encoding(iter.first, has_8bit_chars, is_valid_utf8, is_utf16); - std::string key = !has_8bit_chars || is_valid_utf8 - ? iter.first - : "n:" + QPDF_Name::normalizeName(iter.first); - j.addDictionaryMember(key, iter.second.getJSON(json_version)); + p << "\"n:" << JSON::Writer::encode_string(QPDF_Name::normalizeName(iter.first)) + << "\": "; } + iter.second.writeJSON(json_version, p); } } - return j; + p.writeEnd('}'); } bool diff --git a/libqpdf/QPDF_InlineImage.cc b/libqpdf/QPDF_InlineImage.cc index 18f2fed6..2d62071d 100644 --- a/libqpdf/QPDF_InlineImage.cc +++ b/libqpdf/QPDF_InlineImage.cc @@ -1,5 +1,7 @@ #include <qpdf/QPDF_InlineImage.hh> +#include <qpdf/JSON_writer.hh> + QPDF_InlineImage::QPDF_InlineImage(std::string const& val) : QPDFValue(::ot_inlineimage, "inline-image"), val(val) @@ -24,8 +26,8 @@ QPDF_InlineImage::unparse() return this->val; } -JSON -QPDF_InlineImage::getJSON(int json_version) +void +QPDF_InlineImage::writeJSON(int json_version, JSON::Writer& p) { - return JSON::makeNull(); + p << "null"; } diff --git a/libqpdf/QPDF_Integer.cc b/libqpdf/QPDF_Integer.cc index 716a11e0..aa0437a1 100644 --- a/libqpdf/QPDF_Integer.cc +++ b/libqpdf/QPDF_Integer.cc @@ -1,5 +1,6 @@ #include <qpdf/QPDF_Integer.hh> +#include <qpdf/JSON_writer.hh> #include <qpdf/QUtil.hh> QPDF_Integer::QPDF_Integer(long long val) : @@ -26,10 +27,10 @@ QPDF_Integer::unparse() return std::to_string(this->val); } -JSON -QPDF_Integer::getJSON(int json_version) +void +QPDF_Integer::writeJSON(int json_version, JSON::Writer& p) { - return JSON::makeInt(this->val); + p << std::to_string(this->val); } long long diff --git a/libqpdf/QPDF_Name.cc b/libqpdf/QPDF_Name.cc index 5fde9c65..0e7f441a 100644 --- a/libqpdf/QPDF_Name.cc +++ b/libqpdf/QPDF_Name.cc @@ -1,7 +1,10 @@ #include <qpdf/QPDF_Name.hh> +#include <qpdf/JSON_writer.hh> #include <qpdf/QUtil.hh> +#include <string_view> + QPDF_Name::QPDF_Name(std::string const& name) : QPDFValue(::ot_name, "name"), name(name) @@ -51,20 +54,71 @@ QPDF_Name::unparse() return normalizeName(this->name); } -JSON -QPDF_Name::getJSON(int json_version) +std::pair<bool, bool> +QPDF_Name::analyzeJSONEncoding(const std::string& name) +{ + std::basic_string_view<unsigned char> view{ + reinterpret_cast<const unsigned char*>(name.data()), name.size()}; + + int tail = 0; // Number of continuation characters expected. + bool tail2 = false; // Potential overlong 3 octet utf-8. + bool tail3 = false; // potential overlong 4 octet + bool needs_escaping = false; + for (auto const& c: view) { + if (tail) { + if ((c & 0xc0) != 0x80) { + return {false, false}; + } + if (tail2) { + if ((c & 0xe0) == 0x80) { + return {false, false}; + } + tail2 = false; + } else if (tail3) { + if ((c & 0xf0) == 0x80) { + return {false, false}; + } + tail3 = false; + } + tail--; + } else if (c < 0x80) { + if (!needs_escaping) { + needs_escaping = !((c > 34 && c != '\\') || c == ' ' || c == 33); + } + } else if ((c & 0xe0) == 0xc0) { + if ((c & 0xfe) == 0xc0) { + return {false, false}; + } + tail = 1; + } else if ((c & 0xf0) == 0xe0) { + tail2 = (c == 0xe0); + tail = 2; + } else if ((c & 0xf8) == 0xf0) { + tail3 = (c == 0xf0); + tail = 3; + } else { + return {false, false}; + } + } + return {tail == 0, !needs_escaping}; +} + +void +QPDF_Name::writeJSON(int json_version, JSON::Writer& p) { + // For performance reasons this code is duplicated in QPDF_Dictionary::writeJSON. When updating + // this method make sure QPDF_Dictionary is also update. if (json_version == 1) { - return JSON::makeString(normalizeName(this->name)); + p << "\"" << JSON::Writer::encode_string(normalizeName(name)) << "\""; } else { - bool has_8bit_chars; - bool is_valid_utf8; - bool is_utf16; - QUtil::analyze_encoding(this->name, has_8bit_chars, is_valid_utf8, is_utf16); - if (!has_8bit_chars || is_valid_utf8) { - return JSON::makeString(this->name); + if (auto res = analyzeJSONEncoding(name); res.first) { + if (res.second) { + p << "\"" << name << "\""; + } else { + p << "\"" << JSON::Writer::encode_string(name) << "\""; + } } else { - return JSON::makeString("n:" + normalizeName(this->name)); + p << "\"n:" << JSON::Writer::encode_string(normalizeName(name)) << "\""; } } } diff --git a/libqpdf/QPDF_Null.cc b/libqpdf/QPDF_Null.cc index fdabdfa7..fa4b6cab 100644 --- a/libqpdf/QPDF_Null.cc +++ b/libqpdf/QPDF_Null.cc @@ -1,5 +1,6 @@ #include <qpdf/QPDF_Null.hh> +#include <qpdf/JSON_writer.hh> #include <qpdf/QPDFObject_private.hh> QPDF_Null::QPDF_Null() : @@ -43,9 +44,8 @@ QPDF_Null::unparse() return "null"; } -JSON -QPDF_Null::getJSON(int json_version) +void +QPDF_Null::writeJSON(int json_version, JSON::Writer& p) { - // If this is updated, QPDF_Array::getJSON must also be updated. - return JSON::makeNull(); + p << "null"; } diff --git a/libqpdf/QPDF_Operator.cc b/libqpdf/QPDF_Operator.cc index d1b2969d..c35a8391 100644 --- a/libqpdf/QPDF_Operator.cc +++ b/libqpdf/QPDF_Operator.cc @@ -1,5 +1,7 @@ #include <qpdf/QPDF_Operator.hh> +#include <qpdf/JSON_writer.hh> + QPDF_Operator::QPDF_Operator(std::string const& val) : QPDFValue(::ot_operator, "operator"), val(val) @@ -24,8 +26,8 @@ QPDF_Operator::unparse() return val; } -JSON -QPDF_Operator::getJSON(int json_version) +void +QPDF_Operator::writeJSON(int json_version, JSON::Writer& p) { - return JSON::makeNull(); + p << "null"; } diff --git a/libqpdf/QPDF_Real.cc b/libqpdf/QPDF_Real.cc index 1d954dcd..f4304397 100644 --- a/libqpdf/QPDF_Real.cc +++ b/libqpdf/QPDF_Real.cc @@ -1,5 +1,6 @@ #include <qpdf/QPDF_Real.hh> +#include <qpdf/JSON_writer.hh> #include <qpdf/QUtil.hh> QPDF_Real::QPDF_Real(std::string const& val) : @@ -38,21 +39,17 @@ QPDF_Real::unparse() return this->val; } -JSON -QPDF_Real::getJSON(int json_version) +void +QPDF_Real::writeJSON(int json_version, JSON::Writer& p) { - // While PDF allows .x or -.x, JSON does not. Rather than converting from string to double and - // back, just handle this as a special case for JSON. - std::string result; if (this->val.length() == 0) { // Can't really happen... - result = "0"; + p << "0"; } else if (this->val.at(0) == '.') { - result = "0" + this->val; - } else if ((this->val.length() >= 2) && (this->val.at(0) == '-') && (this->val.at(1) == '.')) { - result = "-0." + this->val.substr(2); + p << "0" << this->val; + } else if (this->val.length() >= 2 && this->val.at(0) == '-' && this->val.at(1) == '.') { + p << "-0." << this->val.substr(2); } else { - result = this->val; + p << this->val; } - return JSON::makeNumber(result); } diff --git a/libqpdf/QPDF_Reserved.cc b/libqpdf/QPDF_Reserved.cc index 845d6ebc..da112acc 100644 --- a/libqpdf/QPDF_Reserved.cc +++ b/libqpdf/QPDF_Reserved.cc @@ -26,9 +26,8 @@ QPDF_Reserved::unparse() return ""; } -JSON -QPDF_Reserved::getJSON(int json_version) +void +QPDF_Reserved::writeJSON(int json_version, JSON::Writer& p) { throw std::logic_error("QPDFObjectHandle: attempting to get JSON from a reserved object"); - return JSON::makeNull(); } diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc index a43d91ff..6cfcdd46 100644 --- a/libqpdf/QPDF_Stream.cc +++ b/libqpdf/QPDF_Stream.cc @@ -1,6 +1,7 @@ #include <qpdf/QPDF_Stream.hh> #include <qpdf/ContentNormalizer.hh> +#include <qpdf/JSON_writer.hh> #include <qpdf/Pipeline.hh> #include <qpdf/Pl_Base64.hh> #include <qpdf/Pl_Buffer.hh> @@ -176,13 +177,10 @@ QPDF_Stream::unparse() return og.unparse(' ') + " R"; } -JSON -QPDF_Stream::getJSON(int json_version) +void +QPDF_Stream::writeJSON(int json_version, JSON::Writer& jw) { - if (json_version == 1) { - return this->stream_dict.getJSON(json_version); - } - return getStreamJSON(json_version, qpdf_sj_none, qpdf_dl_none, nullptr, ""); + stream_dict.writeJSON(json_version, jw); } JSON @@ -193,77 +191,108 @@ QPDF_Stream::getStreamJSON( Pipeline* p, std::string const& data_filename) { + Pl_Buffer pb{"streamjson"}; + JSON::Writer jw{&pb, 0}; + decode_level = + writeStreamJSON(json_version, jw, json_data, decode_level, p, data_filename, true); + pb.finish(); + auto result = JSON::parse(pb.getString()); + if (json_data == qpdf_sj_inline) { + result.addDictionaryMember("data", JSON::makeBlob(StreamBlobProvider(this, decode_level))); + } + return result; +} + +qpdf_stream_decode_level_e +QPDF_Stream::writeStreamJSON( + int json_version, + JSON::Writer& jw, + qpdf_json_stream_data_e json_data, + qpdf_stream_decode_level_e decode_level, + Pipeline* p, + std::string const& data_filename, + bool no_data_key) +{ switch (json_data) { case qpdf_sj_none: case qpdf_sj_inline: if (p != nullptr) { - throw std::logic_error("QPDF_Stream::getStreamJSON: pipeline should only be supplied " + throw std::logic_error("QPDF_Stream::writeStreamJSON: pipeline should only be supplied " "when json_data is file"); } break; case qpdf_sj_file: if (p == nullptr) { throw std::logic_error( - "QPDF_Stream::getStreamJSON: pipeline must be supplied when json_data is file"); + "QPDF_Stream::writeStreamJSON: pipeline must be supplied when json_data is file"); } if (data_filename.empty()) { - throw std::logic_error("QPDF_Stream::getStreamJSON: data_filename must be supplied " + throw std::logic_error("QPDF_Stream::writeStreamJSON: data_filename must be supplied " "when json_data is file"); } break; } - auto dict = this->stream_dict; - JSON result = JSON::makeDictionary(); - if (json_data != qpdf_sj_none) { - Pl_Discard discard; - Pl_Buffer buf_pl{"stream data"}; - // buf_pl contains valid data and is ready for retrieval of the data. - bool buf_pl_ready = false; - bool filtered = false; - bool filter = (decode_level != qpdf_dl_none); - for (int attempt = 1; attempt <= 2; ++attempt) { - Pipeline* data_pipeline = &discard; - if (json_data == qpdf_sj_file) { - // We need to capture the data to write - data_pipeline = &buf_pl; - } - bool succeeded = - pipeStreamData(data_pipeline, &filtered, 0, decode_level, false, (attempt == 1)); - if (!succeeded || (filter && !filtered)) { - // Try again - filter = false; - decode_level = qpdf_dl_none; - buf_pl.getString(); // reset buf_pl - } else { - if (json_data == qpdf_sj_file) { - buf_pl_ready = true; - } - break; - } - } - // We can use unsafeShallowCopy because we are only touching top-level keys. - dict = this->stream_dict.unsafeShallowCopy(); - dict.removeKey("/Length"); - if (filter && filtered) { - dict.removeKey("/Filter"); - dict.removeKey("/DecodeParms"); - } - if (json_data == qpdf_sj_file) { - result.addDictionaryMember("datafile", JSON::makeString(data_filename)); - if (!buf_pl_ready) { - throw std::logic_error("QPDF_Stream: failed to get stream data in json file mode"); - } - p->writeString(buf_pl.getString()); - } else if (json_data == qpdf_sj_inline) { - result.addDictionaryMember( - "data", JSON::makeBlob(StreamBlobProvider(this, decode_level))); + jw.writeStart('{'); + + if (json_data == qpdf_sj_none) { + jw.writeNext(); + jw << R"("dict": )"; + stream_dict.writeJSON(json_version, jw); + jw.writeEnd('}'); + return decode_level; + } + + Pl_Discard discard; + Pl_Buffer buf_pl{"stream data"}; + Pipeline* data_pipeline = &buf_pl; + if (no_data_key && json_data == qpdf_sj_inline) { + data_pipeline = &discard; + } + // pipeStreamData produced valid data. + bool buf_pl_ready = false; + bool filtered = false; + bool filter = (decode_level != qpdf_dl_none); + for (int attempt = 1; attempt <= 2; ++attempt) { + bool succeeded = + pipeStreamData(data_pipeline, &filtered, 0, decode_level, false, (attempt == 1)); + if (!succeeded || (filter && !filtered)) { + // Try again + filter = false; + decode_level = qpdf_dl_none; + buf_pl.getString(); // reset buf_pl } else { - throw std::logic_error("QPDF_Stream: unexpected value of json_data"); + buf_pl_ready = true; + break; } } - result.addDictionaryMember("dict", dict.getJSON(json_version)); - return result; + if (!buf_pl_ready) { + throw std::logic_error("QPDF_Stream: failed to get stream data"); + } + // We can use unsafeShallowCopy because we are only touching top-level keys. + auto dict = stream_dict.unsafeShallowCopy(); + dict.removeKey("/Length"); + if (filter && filtered) { + dict.removeKey("/Filter"); + dict.removeKey("/DecodeParms"); + } + if (json_data == qpdf_sj_file) { + jw.writeNext() << R"("datafile": ")" << JSON::Writer::encode_string(data_filename) << "\""; + p->writeString(buf_pl.getString()); + } else if (json_data == qpdf_sj_inline) { + if (!no_data_key) { + jw.writeNext() << R"("data": ")"; + jw.writeBase64(buf_pl.getString()) << "\""; + } + } else { + throw std::logic_error("QPDF_Stream::writeStreamJSON : unexpected value of json_data"); + } + + jw.writeNext() << R"("dict": )"; + dict.writeJSON(json_version, jw); + jw.writeEnd('}'); + + return decode_level; } void diff --git a/libqpdf/QPDF_String.cc b/libqpdf/QPDF_String.cc index 3886b399..f425b313 100644 --- a/libqpdf/QPDF_String.cc +++ b/libqpdf/QPDF_String.cc @@ -1,5 +1,6 @@ #include <qpdf/QPDF_String.hh> +#include <qpdf/JSON_writer.hh> #include <qpdf/QUtil.hh> // DO NOT USE ctype -- it is locale dependent for some things, and it's not worth the risk of @@ -45,33 +46,28 @@ QPDF_String::unparse() return unparse(false); } -JSON -QPDF_String::getJSON(int json_version) +void +QPDF_String::writeJSON(int json_version, JSON::Writer& p) { + auto candidate = getUTF8Val(); if (json_version == 1) { - return JSON::makeString(getUTF8Val()); - } - // See if we can unambiguously represent as Unicode. - bool is_unicode = false; - std::string result; - std::string candidate = getUTF8Val(); - if (QUtil::is_utf16(this->val) || QUtil::is_explicit_utf8(this->val)) { - is_unicode = true; - result = candidate; - } else if (!useHexString()) { - std::string test; - if (QUtil::utf8_to_pdf_doc(candidate, test, '?') && (test == this->val)) { - // This is a PDF-doc string that can be losslessly encoded as Unicode. - is_unicode = true; - result = candidate; - } - } - if (is_unicode) { - result = "u:" + result; + + p << "\"" << JSON::Writer::encode_string(candidate) << "\""; } else { - result = "b:" + QUtil::hex_encode(this->val); + // See if we can unambiguously represent as Unicode. + if (QUtil::is_utf16(this->val) || QUtil::is_explicit_utf8(this->val)) { + p << "\"u:" << JSON::Writer::encode_string(candidate) <<"\""; + return; + } else if (!useHexString()) { + std::string test; + if (QUtil::utf8_to_pdf_doc(candidate, test, '?') && (test == this->val)) { + // This is a PDF-doc string that can be losslessly encoded as Unicode. + p << "\"u:" << JSON::Writer::encode_string(candidate) <<"\""; + return; + } + } + p << "\"b:" << QUtil::hex_encode(val) <<"\""; } - return JSON::makeString(result); } bool diff --git a/libqpdf/QPDF_Unresolved.cc b/libqpdf/QPDF_Unresolved.cc index fbf5e15f..dc14c2f2 100644 --- a/libqpdf/QPDF_Unresolved.cc +++ b/libqpdf/QPDF_Unresolved.cc @@ -27,9 +27,8 @@ QPDF_Unresolved::unparse() return ""; } -JSON -QPDF_Unresolved::getJSON(int json_version) +void +QPDF_Unresolved::writeJSON(int json_version, JSON::Writer& p) { throw std::logic_error("attempted to get JSON from an unresolved QPDFObjectHandle"); - return JSON::makeNull(); } diff --git a/libqpdf/QPDF_json.cc b/libqpdf/QPDF_json.cc index 8326e6a5..b06b70c9 100644 --- a/libqpdf/QPDF_json.cc +++ b/libqpdf/QPDF_json.cc @@ -1,12 +1,14 @@ #include <qpdf/QPDF.hh> #include <qpdf/FileInputSource.hh> +#include <qpdf/JSON_writer.hh> #include <qpdf/Pl_Base64.hh> #include <qpdf/Pl_StdioFile.hh> #include <qpdf/QIntC.hh> #include <qpdf/QPDFObject_private.hh> #include <qpdf/QPDFValue.hh> #include <qpdf/QPDF_Null.hh> +#include <qpdf/QPDF_Stream.hh> #include <qpdf/QTC.hh> #include <qpdf/QUtil.hh> #include <algorithm> @@ -442,7 +444,9 @@ void QPDF::JSONReactor::replaceObject(QPDFObjectHandle&& replacement, JSON const& value) { if (replacement.isIndirect()) { - error(replacement.getParsedOffset(), "the value of an object may not be an indirect object reference"); + error( + replacement.getParsedOffset(), + "the value of an object may not be an indirect object reference"); return; } auto& tos = stack.back(); @@ -828,45 +832,20 @@ QPDF::importJSON(std::shared_ptr<InputSource> is, bool must_be_complete) } void -QPDF::writeJSONStream( +writeJSONStreamFile( int version, - Pipeline* p, - bool& first, - std::string const& key, - QPDFObjectHandle& obj, + JSON::Writer& jw, + QPDF_Stream& stream, + int id, qpdf_stream_decode_level_e decode_level, - qpdf_json_stream_data_e json_stream_data, std::string const& file_prefix) { - Pipeline* stream_p = nullptr; - FILE* f = nullptr; - std::shared_ptr<Pl_StdioFile> f_pl; - std::string filename; - if (json_stream_data == qpdf_sj_file) { - filename = file_prefix + "-" + std::to_string(obj.getObjectID()); - f = QUtil::safe_fopen(filename.c_str(), "wb"); - f_pl = std::make_shared<Pl_StdioFile>("stream data", f); - stream_p = f_pl.get(); - } - auto j = JSON::makeDictionary(); - j.addDictionaryMember( - "stream", obj.getStreamJSON(version, json_stream_data, decode_level, stream_p, filename)); - - JSON::writeDictionaryItem(p, first, key, j, 3); - if (f) { - f_pl->finish(); - f_pl = nullptr; - fclose(f); - } -} - -void -QPDF::writeJSONObject( - int version, Pipeline* p, bool& first, std::string const& key, QPDFObjectHandle& obj) -{ - auto j = JSON::makeDictionary(); - j.addDictionaryMember("value", obj.getJSON(version, true)); - JSON::writeDictionaryItem(p, first, key, j, 3); + auto filename = file_prefix + "-" + std::to_string(id); + auto* f = QUtil::safe_fopen(filename.c_str(), "wb"); + Pl_StdioFile f_pl{"stream data", f}; + stream.writeStreamJSON(version, jw, qpdf_sj_file, decode_level, &f_pl, filename); + f_pl.finish(); + fclose(f); } void @@ -893,80 +872,75 @@ QPDF::writeJSON( std::string const& file_prefix, std::set<std::string> wanted_objects) { - int const depth_outer = 1; - int const depth_top = 1; - int const depth_qpdf = 2; - int const depth_qpdf_inner = 3; - if (version != 2) { throw std::runtime_error("QPDF::writeJSON: only version 2 is supported"); } - bool first = true; + JSON::Writer jw{p, 4}; if (complete) { - JSON::writeDictionaryOpen(p, first, depth_outer); - } else { - first = first_key; - } - JSON::writeDictionaryKey(p, first, "qpdf", depth_top); - bool first_qpdf = true; - JSON::writeArrayOpen(p, first_qpdf, depth_top); - JSON::writeNext(p, first_qpdf, depth_qpdf); - bool first_qpdf_inner = true; - JSON::writeDictionaryOpen(p, first_qpdf_inner, depth_qpdf); - JSON::writeDictionaryItem( - p, first_qpdf_inner, "jsonversion", JSON::makeInt(version), depth_qpdf_inner); - JSON::writeDictionaryItem( - p, first_qpdf_inner, "pdfversion", JSON::makeString(getPDFVersion()), depth_qpdf_inner); - JSON::writeDictionaryItem( - p, - first_qpdf_inner, - "pushedinheritedpageresources", - JSON::makeBool(everPushedInheritedAttributesToPages()), - depth_qpdf_inner); - JSON::writeDictionaryItem( - p, - first_qpdf_inner, - "calledgetallpages", - JSON::makeBool(everCalledGetAllPages()), - depth_qpdf_inner); - JSON::writeDictionaryItem( - p, - first_qpdf_inner, - "maxobjectid", - JSON::makeInt(QIntC::to_longlong(getObjectCount())), - depth_qpdf_inner); - JSON::writeDictionaryClose(p, first_qpdf_inner, depth_qpdf); - JSON::writeNext(p, first_qpdf, depth_qpdf); - JSON::writeDictionaryOpen(p, first_qpdf_inner, depth_qpdf); + jw << "{"; + } else if (!first_key) { + jw << ","; + } + first_key = false; + + /* clang-format off */ + jw << "\n" + " \"qpdf\": [\n" + " {\n" + " \"jsonversion\": " << std::to_string(version) << ",\n" + " \"pdfversion\": \"" << getPDFVersion() << "\",\n" + " \"pushedinheritedpageresources\": " << (everPushedInheritedAttributesToPages() ? "true" : "false") << ",\n" + " \"calledgetallpages\": " << (everCalledGetAllPages() ? "true" : "false") << ",\n" + " \"maxobjectid\": " << std::to_string(getObjectCount()) << "\n" + " },\n" + " {"; + /* clang-format on */ + bool all_objects = wanted_objects.empty(); + bool first = true; for (auto& obj: getAllObjects()) { - std::string key = "obj:" + obj.unparse(); + auto const og = obj.getObjGen(); + std::string key = "obj:" + og.unparse(' ') + " R"; if (all_objects || wanted_objects.count(key)) { - if (obj.isStream()) { - writeJSONStream( - version, - p, - first_qpdf_inner, - key, - obj, - decode_level, - json_stream_data, - file_prefix); + if (first) { + jw << "\n \"" << key; + first = false; } else { - writeJSONObject(version, p, first_qpdf_inner, key, obj); + jw << "\n },\n \"" << key; + } + if (auto* stream = obj.getObjectPtr()->as<QPDF_Stream>()) { + jw << "\": {\n \"stream\": "; + if (json_stream_data == qpdf_sj_file) { + writeJSONStreamFile( + version, jw, *stream, og.getObj(), decode_level, file_prefix); + } else { + stream->writeStreamJSON( + version, jw, json_stream_data, decode_level, nullptr, ""); + } + } else { + jw << "\": {\n \"value\": "; + obj.writeJSON(version, jw, true); } } } if (all_objects || wanted_objects.count("trailer")) { - auto trailer = getTrailer(); - writeJSONObject(version, p, first_qpdf_inner, "trailer", trailer); - } - JSON::writeDictionaryClose(p, first_qpdf_inner, depth_qpdf); - JSON::writeArrayClose(p, first_qpdf, depth_top); + if (!first) { + jw << "\n },"; + } + jw << "\n \"trailer\": {\n \"value\": "; + getTrailer().writeJSON(version, jw, true); + first = false; + } + if (!first) { + jw << "\n }"; + } + /* clang-format off */ + jw << "\n" + " }\n" + " ]"; + /* clang-format on */ if (complete) { - JSON::writeDictionaryClose(p, first, 0); - *p << "\n"; + jw << "\n}\n"; p->finish(); } - first_key = false; } diff --git a/libqpdf/qpdf/JSON_writer.hh b/libqpdf/qpdf/JSON_writer.hh new file mode 100644 index 00000000..3f770c58 --- /dev/null +++ b/libqpdf/qpdf/JSON_writer.hh @@ -0,0 +1,137 @@ +#ifndef JSON_WRITER_HH +#define JSON_WRITER_HH + +#include <qpdf/JSON.hh> +#include <qpdf/Pipeline.hh> +#include <qpdf/Pl_Base64.hh> +#include <qpdf/Pl_Concatenate.hh> + +#include <string_view> + +// Writer is a small utility class to aid writing JSON to a pipeline. Methods are designed to allow +// chaining of calls. +// +// Some uses of the class have a significant performance impact. The class is intended purely for +// internal use to allow it to be adapted as needed to maintain performance. +class JSON::Writer +{ + public: + Writer(Pipeline* p, size_t depth) : + p(p), + indent(2 * depth) + { + } + + Writer& + write(char const* data, size_t len) + { + p->write(reinterpret_cast<unsigned char const*>(data), len); + return *this; + } + + Writer& + writeBase64(std::string_view sv) + { + Pl_Concatenate cat{"writer concat", p}; + Pl_Base64 base{"writer base64", &cat, Pl_Base64::a_encode}; + base.write(reinterpret_cast<unsigned char const*>(sv.data()), sv.size()); + base.finish(); + return *this; + } + + Writer& + writeNext() + { + auto n = indent; + if (first) { + first = false; + write(&spaces[1], n % n_spaces + 1); + } else { + write(&spaces[0], n % n_spaces + 2); + } + while (n >= n_spaces) { + write(&spaces[2], n_spaces); + n -= n_spaces; + } + return *this; + } + + Writer& + writeStart(char const& c) + { + write(&c, 1); + first = true; + indent += 2; + return *this; + } + + Writer& + writeEnd(char const& c) + { + if (indent > 1) { + indent -= 2; + } + if (!first) { + first = true; + writeNext(); + } + first = false; + write(&c, 1); + return *this; + } + + Writer& + operator<<(std::string_view sv) + { + p->write(reinterpret_cast<unsigned char const*>(sv.data()), sv.size()); + return *this; + } + + Writer& + operator<<(char const* s) + { + *this << std::string_view{s}; + return *this; + } + + Writer& + operator<<(bool val) + { + *this << (val ? "true" : "false"); + return *this; + } + + Writer& + operator<<(int val) + { + *this << std::to_string(val); + return *this; + } + + Writer& + operator<<(size_t val) + { + *this << std::to_string(val); + return *this; + } + + Writer& + operator<<(JSON&& j) + { + j.write(p, indent / 2); + return *this; + } + + static std::string encode_string(std::string const& utf8); + + private: + Pipeline* p; + bool first{true}; + size_t indent; + + static constexpr std::string_view spaces = + ",\n "; + static constexpr auto n_spaces = spaces.size() - 2; +}; + +#endif // JSON_WRITER_HH diff --git a/libqpdf/qpdf/QPDFObject_private.hh b/libqpdf/qpdf/QPDFObject_private.hh index 5e87c215..1c3dadc9 100644 --- a/libqpdf/qpdf/QPDFObject_private.hh +++ b/libqpdf/qpdf/QPDFObject_private.hh @@ -33,10 +33,10 @@ class QPDFObject { return value->unparse(); } - JSON - getJSON(int json_version) + void + writeJSON(int json_version, JSON::Writer& p) { - return value->getJSON(json_version); + return value->writeJSON(json_version, p); } std::string getStringValue() const diff --git a/libqpdf/qpdf/QPDFValue.hh b/libqpdf/qpdf/QPDFValue.hh index db8fb923..28abf147 100644 --- a/libqpdf/qpdf/QPDFValue.hh +++ b/libqpdf/qpdf/QPDFValue.hh @@ -24,7 +24,7 @@ class QPDFValue: public std::enable_shared_from_this<QPDFValue> virtual std::shared_ptr<QPDFObject> copy(bool shallow = false) = 0; virtual std::string unparse() = 0; - virtual JSON getJSON(int json_version) = 0; + virtual void writeJSON(int json_version, JSON::Writer& p) = 0; struct JSON_Descr { diff --git a/libqpdf/qpdf/QPDF_Array.hh b/libqpdf/qpdf/QPDF_Array.hh index 281e1d48..88b2a3d3 100644 --- a/libqpdf/qpdf/QPDF_Array.hh +++ b/libqpdf/qpdf/QPDF_Array.hh @@ -22,7 +22,7 @@ class QPDF_Array: public QPDFValue create(std::vector<std::shared_ptr<QPDFObject>>&& items, bool sparse); std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::string unparse() override; - JSON getJSON(int json_version) override; + void writeJSON(int json_version, JSON::Writer& p) override; void disconnect() override; int diff --git a/libqpdf/qpdf/QPDF_Bool.hh b/libqpdf/qpdf/QPDF_Bool.hh index 0b4a71fd..1692bdc4 100644 --- a/libqpdf/qpdf/QPDF_Bool.hh +++ b/libqpdf/qpdf/QPDF_Bool.hh @@ -10,7 +10,8 @@ class QPDF_Bool: public QPDFValue static std::shared_ptr<QPDFObject> create(bool val); std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::string unparse() override; - JSON getJSON(int json_version) override; + void writeJSON(int json_version, JSON::Writer& p) override; + bool getVal() const; private: diff --git a/libqpdf/qpdf/QPDF_Destroyed.hh b/libqpdf/qpdf/QPDF_Destroyed.hh index 72e9130a..9259a2dc 100644 --- a/libqpdf/qpdf/QPDF_Destroyed.hh +++ b/libqpdf/qpdf/QPDF_Destroyed.hh @@ -9,7 +9,7 @@ class QPDF_Destroyed: public QPDFValue ~QPDF_Destroyed() override = default; std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::string unparse() override; - JSON getJSON(int json_version) override; + void writeJSON(int json_version, JSON::Writer& p) override; static std::shared_ptr<QPDFValue> getInstance(); private: diff --git a/libqpdf/qpdf/QPDF_Dictionary.hh b/libqpdf/qpdf/QPDF_Dictionary.hh index 0fb6636e..8713a450 100644 --- a/libqpdf/qpdf/QPDF_Dictionary.hh +++ b/libqpdf/qpdf/QPDF_Dictionary.hh @@ -16,7 +16,7 @@ class QPDF_Dictionary: public QPDFValue static std::shared_ptr<QPDFObject> create(std::map<std::string, QPDFObjectHandle>&& items); std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::string unparse() override; - JSON getJSON(int json_version) override; + void writeJSON(int json_version, JSON::Writer& p) override; void disconnect() override; // hasKey() and getKeys() treat keys with null values as if they aren't there. getKey() returns diff --git a/libqpdf/qpdf/QPDF_InlineImage.hh b/libqpdf/qpdf/QPDF_InlineImage.hh index bee12354..c06662d7 100644 --- a/libqpdf/qpdf/QPDF_InlineImage.hh +++ b/libqpdf/qpdf/QPDF_InlineImage.hh @@ -10,7 +10,7 @@ class QPDF_InlineImage: public QPDFValue static std::shared_ptr<QPDFObject> create(std::string const& val); std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::string unparse() override; - JSON getJSON(int json_version) override; + void writeJSON(int json_version, JSON::Writer& p) override; std::string getStringValue() const override { diff --git a/libqpdf/qpdf/QPDF_Integer.hh b/libqpdf/qpdf/QPDF_Integer.hh index 2c2cf2f9..ae7f7893 100644 --- a/libqpdf/qpdf/QPDF_Integer.hh +++ b/libqpdf/qpdf/QPDF_Integer.hh @@ -10,7 +10,7 @@ class QPDF_Integer: public QPDFValue static std::shared_ptr<QPDFObject> create(long long value); std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::string unparse() override; - JSON getJSON(int json_version) override; + void writeJSON(int json_version, JSON::Writer& p) override; long long getVal() const; private: diff --git a/libqpdf/qpdf/QPDF_Name.hh b/libqpdf/qpdf/QPDF_Name.hh index c14d8659..b5d3c318 100644 --- a/libqpdf/qpdf/QPDF_Name.hh +++ b/libqpdf/qpdf/QPDF_Name.hh @@ -10,10 +10,15 @@ class QPDF_Name: public QPDFValue static std::shared_ptr<QPDFObject> create(std::string const& name); std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::string unparse() override; - JSON getJSON(int json_version) override; + void writeJSON(int json_version, JSON::Writer& p) override; // Put # into strings with characters unsuitable for name token static std::string normalizeName(std::string const& name); + + // Check whether name is valid utf-8 and whether it contains characters that require escaping. + // Return {false, false} if the name is not valid utf-8, otherwise return {true, true} if no + // characters require or {true, false} if escaping is required. + static std::pair<bool, bool> analyzeJSONEncoding(std::string const& name); std::string getStringValue() const override { diff --git a/libqpdf/qpdf/QPDF_Null.hh b/libqpdf/qpdf/QPDF_Null.hh index a59b7509..fc6e0b5f 100644 --- a/libqpdf/qpdf/QPDF_Null.hh +++ b/libqpdf/qpdf/QPDF_Null.hh @@ -18,7 +18,7 @@ class QPDF_Null: public QPDFValue std::string var_descr); std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::string unparse() override; - JSON getJSON(int json_version) override; + void writeJSON(int json_version, JSON::Writer& p) override; private: QPDF_Null(); diff --git a/libqpdf/qpdf/QPDF_Operator.hh b/libqpdf/qpdf/QPDF_Operator.hh index 77aa5a17..b9b040d6 100644 --- a/libqpdf/qpdf/QPDF_Operator.hh +++ b/libqpdf/qpdf/QPDF_Operator.hh @@ -10,7 +10,7 @@ class QPDF_Operator: public QPDFValue static std::shared_ptr<QPDFObject> create(std::string const& val); std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::string unparse() override; - JSON getJSON(int json_version) override; + void writeJSON(int json_version, JSON::Writer& p) override; std::string getStringValue() const override { diff --git a/libqpdf/qpdf/QPDF_Real.hh b/libqpdf/qpdf/QPDF_Real.hh index db5e0940..aa9baa5f 100644 --- a/libqpdf/qpdf/QPDF_Real.hh +++ b/libqpdf/qpdf/QPDF_Real.hh @@ -12,7 +12,7 @@ class QPDF_Real: public QPDFValue create(double value, int decimal_places, bool trim_trailing_zeroes); std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::string unparse() override; - JSON getJSON(int json_version) override; + void writeJSON(int json_version, JSON::Writer& p) override; std::string getStringValue() const override { diff --git a/libqpdf/qpdf/QPDF_Reserved.hh b/libqpdf/qpdf/QPDF_Reserved.hh index 9ba855b7..801987e4 100644 --- a/libqpdf/qpdf/QPDF_Reserved.hh +++ b/libqpdf/qpdf/QPDF_Reserved.hh @@ -10,7 +10,7 @@ class QPDF_Reserved: public QPDFValue static std::shared_ptr<QPDFObject> create(); std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::string unparse() override; - JSON getJSON(int json_version) override; + void writeJSON(int json_version, JSON::Writer& p) override; private: QPDF_Reserved(); diff --git a/libqpdf/qpdf/QPDF_Stream.hh b/libqpdf/qpdf/QPDF_Stream.hh index 8488e157..3fcbc428 100644 --- a/libqpdf/qpdf/QPDF_Stream.hh +++ b/libqpdf/qpdf/QPDF_Stream.hh @@ -25,7 +25,7 @@ class QPDF_Stream: public QPDFValue size_t length); std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::string unparse() override; - JSON getJSON(int json_version) override; + void writeJSON(int json_version, JSON::Writer& p) override; void setDescription( QPDF*, std::shared_ptr<QPDFValue::Description>& description, qpdf_offset_t offset) override; void disconnect() override; @@ -64,6 +64,14 @@ class QPDF_Stream: public QPDFValue qpdf_stream_decode_level_e decode_level, Pipeline* p, std::string const& data_filename); + qpdf_stream_decode_level_e writeStreamJSON( + int json_version, + JSON::Writer& jw, + qpdf_json_stream_data_e json_data, + qpdf_stream_decode_level_e decode_level, + Pipeline* p, + std::string const& data_filename, + bool no_data_key = false); void replaceDict(QPDFObjectHandle const& new_dict); diff --git a/libqpdf/qpdf/QPDF_String.hh b/libqpdf/qpdf/QPDF_String.hh index c34cafef..967b2d30 100644 --- a/libqpdf/qpdf/QPDF_String.hh +++ b/libqpdf/qpdf/QPDF_String.hh @@ -16,7 +16,7 @@ class QPDF_String: public QPDFValue std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::string unparse() override; std::string unparse(bool force_binary); - JSON getJSON(int json_version) override; + void writeJSON(int json_version, JSON::Writer& p) override; std::string getUTF8Val() const; std::string getStringValue() const override diff --git a/libqpdf/qpdf/QPDF_Unresolved.hh b/libqpdf/qpdf/QPDF_Unresolved.hh index 0a1fa9a5..b4c1d9e4 100644 --- a/libqpdf/qpdf/QPDF_Unresolved.hh +++ b/libqpdf/qpdf/QPDF_Unresolved.hh @@ -10,7 +10,7 @@ class QPDF_Unresolved: public QPDFValue static std::shared_ptr<QPDFObject> create(QPDF* qpdf, QPDFObjGen const& og); std::shared_ptr<QPDFObject> copy(bool shallow = false) override; std::string unparse() override; - JSON getJSON(int json_version) override; + void writeJSON(int json_version, JSON::Writer& p) override; private: QPDF_Unresolved(QPDF* qpdf, QPDFObjGen const& og); |