diff --git a/include/qpdf/Constants.h b/include/qpdf/Constants.h index d009e3e..fe19448 100644 --- a/include/qpdf/Constants.h +++ b/include/qpdf/Constants.h @@ -126,6 +126,7 @@ enum qpdf_object_type_e { /* Object types internal to qpdf */ ot_unresolved, ot_destroyed, + ot_reference, }; /* Write Parameters. See QPDFWriter.hh for details. */ diff --git a/include/qpdf/ObjectHandle.hh b/include/qpdf/ObjectHandle.hh new file mode 100644 index 0000000..6655322 --- /dev/null +++ b/include/qpdf/ObjectHandle.hh @@ -0,0 +1,84 @@ +// Copyright (c) 2005-2021 Jay Berkenbilt +// Copyright (c) 2022-2025 Jay Berkenbilt and Manfred Holger +// +// This file is part of qpdf. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under the License +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied. See the License for the specific language governing permissions and limitations under +// the License. +// +// Versions of qpdf prior to version 7 were released under the terms of version 2.0 of the Artistic +// License. At your option, you may continue to consider qpdf to be licensed under those terms. +// Please see the manual for additional information. + +#ifndef OBJECTHANDLE_HH +#define OBJECTHANDLE_HH + +#include +#include +#include +#include + +#include +#include + +class QPDF; +class QPDF_Dictionary; +class QPDFObject; +class QPDFObjectHandle; + +namespace qpdf +{ + class Array; + class BaseDictionary; + class Dictionary; + class Stream; + + enum typed : std::uint8_t { strict = 0, any_flag = 1, optional = 2, any = 3, error = 4 }; + + // Basehandle is only used as a base-class for QPDFObjectHandle like classes. Currently the only + // methods exposed in public API are operators to convert derived objects to QPDFObjectHandle, + // QPDFObjGen and bool. + class BaseHandle + { + public: + explicit inline operator bool() const; + inline operator QPDFObjectHandle() const; + operator QPDFObjGen() const; + + // The rest of the header file is for qpdf internal use only. + + inline QPDFObjGen id_gen() const; + inline bool indirect() const; + inline bool null() const; + inline QPDF* qpdf() const; + inline qpdf_object_type_e raw_type_code() const; + inline qpdf_object_type_e type_code() const; + + protected: + BaseHandle() = default; + BaseHandle(std::shared_ptr const& obj) : + obj(obj) {}; + BaseHandle(std::shared_ptr&& obj) : + obj(std::move(obj)) {}; + BaseHandle(BaseHandle const&) = default; + BaseHandle& operator=(BaseHandle const&) = default; + BaseHandle(BaseHandle&&) = default; + BaseHandle& operator=(BaseHandle&&) = default; + ~BaseHandle() = default; + + template + T* as() const; + + std::shared_ptr obj; + }; + +} // namespace qpdf + +#endif // QPDFOBJECTHANDLE_HH diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh index 0e79967..528a679 100644 --- a/include/qpdf/QPDF.hh +++ b/include/qpdf/QPDF.hh @@ -798,9 +798,10 @@ class QPDF { friend class QPDFObject; friend class QPDF_Unresolved; + friend class qpdf::BaseHandle; private: - static QPDFObject* + static std::shared_ptr const& resolved(QPDF* qpdf, QPDFObjGen og) { return qpdf->resolve(og); @@ -854,6 +855,7 @@ class QPDF class Pipe { friend class QPDF_Stream; + friend class qpdf::Stream; private: static bool @@ -1071,7 +1073,7 @@ class QPDF QPDFObjGen exp_og, QPDFObjGen& og, bool skip_cache_if_in_xref); - QPDFObject* resolve(QPDFObjGen og); + std::shared_ptr const& resolve(QPDFObjGen og); void resolveObjectsInStream(int obj_stream_number); void stopOnError(std::string const& message); QPDFObjGen nextObjGen(); @@ -1086,7 +1088,8 @@ class QPDF QPDFObjGen og, std::shared_ptr const& object, qpdf_offset_t end_before_space, - qpdf_offset_t end_after_space); + qpdf_offset_t end_after_space, + bool destroy = true); static QPDFExc damagedPDF( InputSource& input, std::string const& object, diff --git a/include/qpdf/QPDFObjGen.hh b/include/qpdf/QPDFObjGen.hh index 3553ff3..5c38665 100644 --- a/include/qpdf/QPDFObjGen.hh +++ b/include/qpdf/QPDFObjGen.hh @@ -130,12 +130,6 @@ class QPDFObjGen } QPDF_DLL - bool add(QPDFObjectHandle const& oh); - - QPDF_DLL - bool add(QPDFObjectHelper const& oh); - - QPDF_DLL void erase(QPDFObjGen og) { @@ -143,12 +137,6 @@ class QPDFObjGen std::set::erase(og); } } - - QPDF_DLL - void erase(QPDFObjectHandle const& oh); - - QPDF_DLL - void erase(QPDFObjectHelper const& oh); }; private: diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh index bcc0e55..d3ea1f0 100644 --- a/include/qpdf/QPDFObjectHandle.hh +++ b/include/qpdf/QPDFObjectHandle.hh @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -55,13 +56,14 @@ class QPDF_Reserved; class QPDF_Stream; class QPDF_String; class QPDFObject; +class QPDFObjectHandle; class QPDFTokenizer; class QPDFExc; class Pl_QPDFTokenizer; class QPDFMatrix; class QPDFParser; -class QPDFObjectHandle +class QPDFObjectHandle final: public qpdf::BaseHandle { friend class QPDFParser; @@ -290,25 +292,19 @@ class QPDFObjectHandle QPDFObjectHandle(QPDFObjectHandle const&) = default; QPDF_DLL QPDFObjectHandle& operator=(QPDFObjectHandle const&) = default; - QPDF_DLL QPDFObjectHandle(QPDFObjectHandle&&) = default; QPDF_DLL QPDFObjectHandle& operator=(QPDFObjectHandle&&) = default; - // Return true if the QPDFObjectHandle is initialized. This allows object handles to be used in - // if statements with initializer. - QPDF_DLL - explicit inline operator bool() const noexcept; - - [[deprecated("use operator bool()")]] QPDF_DLL inline bool isInitialized() const noexcept; + [[deprecated("use operator bool()")]] QPDF_DLL inline bool isInitialized() const; // This method returns true if the QPDFObjectHandle objects point to exactly the same underlying // object, meaning that changes to one are reflected in the other, or "if you paint one, the // other one changes color." This does not perform a structural comparison of the contents of // the objects. QPDF_DLL - bool isSameObjectAs(QPDFObjectHandle const&) const noexcept; + bool isSameObjectAs(QPDFObjectHandle const&) const; // Return type code and type name of underlying object. These are useful for doing rapid type // tests (like switch statements) or for testing and debugging. @@ -1258,8 +1254,7 @@ class QPDFObjectHandle // Provide access to specific classes for recursive disconnected(). class DisconnectAccess { - friend class QPDF_Dictionary; - friend class QPDF_Stream; + friend class QPDFObject; private: static void @@ -1329,7 +1324,11 @@ class QPDFObjectHandle // The following methods do not form part of the public API and are for internal use only. QPDFObjectHandle(std::shared_ptr const& obj) : - obj(obj) + qpdf::BaseHandle(obj) + { + } + QPDFObjectHandle(std::shared_ptr&& obj) : + qpdf::BaseHandle(std::move(obj)) { } std::shared_ptr @@ -1355,21 +1354,11 @@ class QPDFObjectHandle void writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect = false) const; - private: - QPDF_Array* asArray() const; - QPDF_Bool* asBool() const; - QPDF_Dictionary* asDictionary() const; - QPDF_InlineImage* asInlineImage() const; - QPDF_Integer* asInteger() const; - QPDF_Name* asName() const; - QPDF_Null* asNull() const; - QPDF_Operator* asOperator() const; - QPDF_Real* asReal() const; - QPDF_Reserved* asReserved() const; - QPDF_Stream* asStream() const; - QPDF_Stream* asStreamWithAssert() const; - QPDF_String* asString() const; + inline qpdf::Array as_array(qpdf::typed options = qpdf::typed::any) const; + inline qpdf::Dictionary as_dictionary(qpdf::typed options = qpdf::typed::any) const; + inline qpdf::Stream as_stream(qpdf::typed options = qpdf::typed::strict) const; + private: void typeWarning(char const* expected_type, std::string const& warning) const; void objectWarning(std::string const& warning) const; void assertType(char const* type_name, bool istype) const; @@ -1386,10 +1375,6 @@ class QPDFObjectHandle arrayOrStreamToStreamArray(std::string const& description, std::string& all_description); static void warn(QPDF*, QPDFExc const&); void checkOwnership(QPDFObjectHandle const&) const; - - // Moving members of QPDFObjectHandle into a smart pointer incurs a substantial performance - // penalty since QPDFObjectHandle objects are copied around so frequently. - std::shared_ptr obj; }; #ifndef QPDF_NO_QPDF_STRING @@ -1606,6 +1591,22 @@ class QPDFObjectHandle::QPDFArrayItems QPDFObjectHandle oh; }; +namespace qpdf +{ + inline BaseHandle:: + operator bool() const + { + return static_cast(obj); + } + + inline BaseHandle:: + operator QPDFObjectHandle() const + { + return {obj}; + } + +} // namespace qpdf + inline int QPDFObjectHandle::getObjectID() const { @@ -1625,15 +1626,9 @@ QPDFObjectHandle::isIndirect() const } inline bool -QPDFObjectHandle::isInitialized() const noexcept +QPDFObjectHandle::isInitialized() const { return obj != nullptr; } -inline QPDFObjectHandle:: -operator bool() const noexcept -{ - return static_cast(obj); -} - #endif // QPDFOBJECTHANDLE_HH diff --git a/include/qpdf/QPDFObjectHandle_future.hh b/include/qpdf/QPDFObjectHandle_future.hh deleted file mode 100644 index e69de29..0000000 --- a/include/qpdf/QPDFObjectHandle_future.hh +++ /dev/null diff --git a/include/qpdf/QPDFObjectHelper.hh b/include/qpdf/QPDFObjectHelper.hh index ebd2e80..648b0f7 100644 --- a/include/qpdf/QPDFObjectHelper.hh +++ b/include/qpdf/QPDFObjectHelper.hh @@ -31,13 +31,12 @@ // underlying QPDF objects unless there is a specific comment in a specific helper method that says // otherwise. The pattern of using helper objects was introduced to allow creation of higher level // helper functions without polluting the public interface of QPDFObjectHandle. - -class QPDF_DLL_CLASS QPDFObjectHelper +class QPDF_DLL_CLASS QPDFObjectHelper: public qpdf::BaseHandle { public: QPDF_DLL QPDFObjectHelper(QPDFObjectHandle oh) : - oh(oh) + qpdf::BaseHandle(oh.getObj()) { } QPDF_DLL @@ -46,17 +45,29 @@ class QPDF_DLL_CLASS QPDFObjectHelper QPDFObjectHandle getObjectHandle() { - return this->oh; + return {obj}; } QPDF_DLL QPDFObjectHandle const getObjectHandle() const { - return this->oh; + return {obj}; } protected: - QPDFObjectHandle oh; + QPDF_DLL_PRIVATE + QPDFObjectHandle + oh() + { + return {obj}; + } + QPDF_DLL_PRIVATE + QPDFObjectHandle const + oh() const + { + return {obj}; + } + QPDFObjectHandle oh_; }; #endif // QPDFOBJECTHELPER_HH diff --git a/libqpdf/CMakeLists.txt b/libqpdf/CMakeLists.txt index 91344a5..ce60238 100644 --- a/libqpdf/CMakeLists.txt +++ b/libqpdf/CMakeLists.txt @@ -76,7 +76,6 @@ set(libqpdf_SOURCES QPDFObject.cc QPDFObjectHandle.cc QPDFObjectHelper.cc - QPDFObjGen.cc QPDFOutlineDocumentHelper.cc QPDFOutlineObjectHelper.cc QPDFPageDocumentHelper.cc @@ -87,23 +86,12 @@ set(libqpdf_SOURCES QPDFSystemError.cc QPDFTokenizer.cc QPDFUsage.cc - QPDFValue.cc QPDFWriter.cc QPDFXRefEntry.cc QPDF_Array.cc - QPDF_Bool.cc - QPDF_Destroyed.cc QPDF_Dictionary.cc - QPDF_InlineImage.cc - QPDF_Integer.cc - QPDF_Name.cc - QPDF_Null.cc - QPDF_Operator.cc - QPDF_Real.cc - QPDF_Reserved.cc QPDF_Stream.cc QPDF_String.cc - QPDF_Unresolved.cc QPDF_encryption.cc QPDF_json.cc QPDF_linearization.cc diff --git a/libqpdf/ContentNormalizer.cc b/libqpdf/ContentNormalizer.cc index bca8ad6..a092f02 100644 --- a/libqpdf/ContentNormalizer.cc +++ b/libqpdf/ContentNormalizer.cc @@ -1,8 +1,10 @@ #include -#include +#include #include +using namespace qpdf; + ContentNormalizer::ContentNormalizer() : any_bad_tokens(false), last_token_was_bad(false) @@ -55,7 +57,7 @@ ContentNormalizer::handleToken(QPDFTokenizer::Token const& token) break; case QPDFTokenizer::tt_name: - write(QPDF_Name::normalizeName(token.getValue())); + write(Name::normalize(token.getValue())); break; default: diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index 60303d1..08e877e 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -17,14 +17,9 @@ #include #include #include +#include #include #include -#include -#include -#include -#include -#include -#include #include #include @@ -298,7 +293,7 @@ void QPDF::registerStreamFilter( std::string const& filter_name, std::function()> factory) { - QPDF_Stream::registerStreamFilter(filter_name, factory); + qpdf::Stream::registerStreamFilter(filter_name, factory); } void @@ -1565,7 +1560,7 @@ QPDF::readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset) throw; } } - object = {QPDF_Stream::create(this, og, object, stream_offset, length)}; + object = QPDFObjectHandle(qpdf::Stream(*this, og, object, stream_offset, length)); } void @@ -1872,11 +1867,11 @@ QPDF::readObjectAtOffset( return oh; } -QPDFObject* +std::shared_ptr const& QPDF::resolve(QPDFObjGen og) { if (!isUnresolved(og)) { - return m->obj_cache[og].object.get(); + return m->obj_cache[og].object; } if (m->resolving.count(og)) { @@ -1884,8 +1879,8 @@ QPDF::resolve(QPDFObjGen og) // has to be resolved during object parsing, such as stream length. QTC::TC("qpdf", "QPDF recursion loop in resolve"); warn(damagedPDF("", "loop detected resolving object " + og.unparse(' '))); - updateCache(og, QPDF_Null::create(), -1, -1); - return m->obj_cache[og].object.get(); + updateCache(og, QPDFObject::create(), -1, -1); + return m->obj_cache[og].object; } ResolveRecorder rr(this, og); @@ -1921,12 +1916,12 @@ QPDF::resolve(QPDFObjGen og) if (isUnresolved(og)) { // PDF spec says unknown objects resolve to the null object. QTC::TC("qpdf", "QPDF resolve failure to null"); - updateCache(og, QPDF_Null::create(), -1, -1); + updateCache(og, QPDFObject::create(), -1, -1); } - auto result(m->obj_cache[og].object); + auto& result(m->obj_cache[og].object); result->setDefaultDescription(this, og); - return result.get(); + return result; } void @@ -2034,12 +2029,13 @@ QPDF::updateCache( QPDFObjGen og, std::shared_ptr const& object, qpdf_offset_t end_before_space, - qpdf_offset_t end_after_space) + qpdf_offset_t end_after_space, + bool destroy) { object->setObjGen(this, og); if (isCached(og)) { auto& cache = m->obj_cache[og]; - cache.object->assign(object); + object->move_to(cache.object, destroy); cache.end_before_space = end_before_space; cache.end_after_space = end_after_space; } else { @@ -2089,20 +2085,20 @@ QPDF::makeIndirectObject(QPDFObjectHandle oh) QPDFObjectHandle QPDF::newReserved() { - return makeIndirectFromQPDFObject(QPDF_Reserved::create()); + return makeIndirectFromQPDFObject(QPDFObject::create()); } QPDFObjectHandle QPDF::newIndirectNull() { - return makeIndirectFromQPDFObject(QPDF_Null::create()); + return makeIndirectFromQPDFObject(QPDFObject::create()); } QPDFObjectHandle QPDF::newStream() { - return makeIndirectFromQPDFObject( - QPDF_Stream::create(this, nextObjGen(), QPDFObjectHandle::newDictionary(), 0, 0)); + return makeIndirectObject( + qpdf::Stream(*this, nextObjGen(), QPDFObjectHandle::newDictionary(), 0, 0)); } QPDFObjectHandle @@ -2130,12 +2126,13 @@ QPDF::getObjectForParser(int id, int gen, bool parse_pdf) return iter->second.object; } if (m->xref_table.count(og) || !m->parsed) { - return m->obj_cache.insert({og, QPDF_Unresolved::create(this, og)}).first->second.object; + return m->obj_cache.insert({og, QPDFObject::create(this, og)}) + .first->second.object; } if (parse_pdf) { - return QPDF_Null::create(); + return QPDFObject::create(); } - return m->obj_cache.insert({og, QPDF_Null::create(this, og)}).first->second.object; + return m->obj_cache.insert({og, QPDFObject::create(this, og)}).first->second.object; } std::shared_ptr @@ -2145,8 +2142,9 @@ QPDF::getObjectForJSON(int id, int gen) auto [it, inserted] = m->obj_cache.try_emplace(og); auto& obj = it->second.object; if (inserted) { - obj = (m->parsed && !m->xref_table.count(og)) ? QPDF_Null::create(this, og) - : QPDF_Unresolved::create(this, og); + obj = (m->parsed && !m->xref_table.count(og)) + ? QPDFObject::create(this, og) + : QPDFObject::create(this, og); } return obj; } @@ -2157,9 +2155,10 @@ QPDF::getObject(QPDFObjGen og) if (auto it = m->obj_cache.find(og); it != m->obj_cache.end()) { return {it->second.object}; } else if (m->parsed && !m->xref_table.count(og)) { - return QPDF_Null::create(); + return QPDFObject::create(); } else { - auto result = m->obj_cache.try_emplace(og, QPDF_Unresolved::create(this, og), -1, -1); + auto result = + m->obj_cache.try_emplace(og, QPDFObject::create(this, og), -1, -1); return {result.first->second.object}; } } @@ -2195,7 +2194,7 @@ QPDF::replaceObject(QPDFObjGen og, QPDFObjectHandle oh) QTC::TC("qpdf", "QPDF replaceObject called with indirect object"); throw std::logic_error("QPDF::replaceObject called with indirect object handle"); } - updateCache(og, oh.getObj(), -1, -1); + updateCache(og, oh.getObj(), -1, -1, false); } void @@ -2204,7 +2203,7 @@ QPDF::removeObject(QPDFObjGen og) m->xref_table.erase(og); if (auto cached = m->obj_cache.find(og); cached != m->obj_cache.end()) { // Take care of any object handles that may be floating around. - cached->second.object->assign(QPDF_Null::create()); + cached->second.object->assign_null(); cached->second.object->setObjGen(nullptr, QPDFObjGen()); m->obj_cache.erase(cached); } @@ -2347,14 +2346,15 @@ QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top) if (foreign_tc == ::ot_array) { QTC::TC("qpdf", "QPDF reserve array"); - int n = foreign.getArrayNItems(); - for (int i = 0; i < n; ++i) { - reserveObjects(foreign.getArrayItem(i), obj_copier, false); + for (auto const& item: foreign.as_array()) { + reserveObjects(item, obj_copier, false); } } else if (foreign_tc == ::ot_dictionary) { QTC::TC("qpdf", "QPDF reserve dictionary"); - for (auto const& key: foreign.getKeys()) { - reserveObjects(foreign.getKey(key), obj_copier, false); + for (auto const& item: foreign.as_dictionary()) { + if (!item.second.null()) { + reserveObjects(item.second, obj_copier, false); + } } } else if (foreign_tc == ::ot_stream) { QTC::TC("qpdf", "QPDF reserve stream"); @@ -2383,30 +2383,26 @@ QPDF::replaceForeignIndirectObjects(QPDFObjectHandle foreign, ObjCopier& obj_cop } else if (foreign_tc == ::ot_array) { QTC::TC("qpdf", "QPDF replace array"); result = QPDFObjectHandle::newArray(); - int n = foreign.getArrayNItems(); - for (int i = 0; i < n; ++i) { - result.appendItem( - // line-break - replaceForeignIndirectObjects(foreign.getArrayItem(i), obj_copier, false)); + for (auto const& item: foreign.as_array()) { + result.appendItem(replaceForeignIndirectObjects(item, obj_copier, false)); } } else if (foreign_tc == ::ot_dictionary) { QTC::TC("qpdf", "QPDF replace dictionary"); result = QPDFObjectHandle::newDictionary(); - std::set keys = foreign.getKeys(); - for (auto const& iter: keys) { - result.replaceKey( - iter, replaceForeignIndirectObjects(foreign.getKey(iter), obj_copier, false)); + for (auto const& [key, value]: foreign.as_dictionary()) { + if (!value.null()) { + result.replaceKey(key, replaceForeignIndirectObjects(value, obj_copier, false)); + } } } else if (foreign_tc == ::ot_stream) { QTC::TC("qpdf", "QPDF replace stream"); result = obj_copier.object_map[foreign.getObjGen()]; - result.assertStream(); QPDFObjectHandle dict = result.getDict(); QPDFObjectHandle old_dict = foreign.getDict(); - std::set keys = old_dict.getKeys(); - for (auto const& iter: keys) { - dict.replaceKey( - iter, replaceForeignIndirectObjects(old_dict.getKey(iter), obj_copier, false)); + for (auto const& [key, value]: old_dict.as_dictionary()) { + if (!value.null()) { + dict.replaceKey(key, replaceForeignIndirectObjects(value, obj_copier, false)); + } } copyStreamData(result, foreign); } else { @@ -2442,13 +2438,11 @@ QPDF::copyStreamData(QPDFObjectHandle result, QPDFObjectHandle foreign) QPDF& foreign_stream_qpdf = foreign.getQPDF("unable to retrieve owning qpdf from foreign stream"); - auto stream = foreign.getObjectPtr()->as(); - if (stream == nullptr) { - throw std::logic_error( - "unable to retrieve underlying" - " stream object from foreign stream"); + auto stream = foreign.as_stream(); + if (!stream) { + throw std::logic_error("unable to retrieve underlying stream object from foreign stream"); } - std::shared_ptr stream_buffer = stream->getStreamDataBuffer(); + std::shared_ptr stream_buffer = stream.getStreamDataBuffer(); if ((foreign_stream_qpdf.m->immediate_copy_from) && (stream_buffer == nullptr)) { // Pull the stream data into a buffer before attempting the copy operation. Do it on the // source stream so that if the source stream is copied multiple times, we don't have to @@ -2458,10 +2452,10 @@ QPDF::copyStreamData(QPDFObjectHandle result, QPDFObjectHandle foreign) foreign.getRawStreamData(), old_dict.getKey("/Filter"), old_dict.getKey("/DecodeParms")); - stream_buffer = stream->getStreamDataBuffer(); + stream_buffer = stream.getStreamDataBuffer(); } std::shared_ptr stream_provider = - stream->getStreamDataProvider(); + stream.getStreamDataProvider(); if (stream_buffer.get()) { QTC::TC("qpdf", "QPDF copy foreign stream with buffer"); result.replaceStreamData( @@ -2476,9 +2470,9 @@ QPDF::copyStreamData(QPDFObjectHandle result, QPDFObjectHandle foreign) auto foreign_stream_data = std::make_shared( foreign_stream_qpdf.m->encp, foreign_stream_qpdf.m->file, - foreign.getObjGen(), - stream->getParsedOffset(), - stream->getLength(), + foreign, + foreign.getParsedOffset(), + stream.getLength(), dict); m->copied_stream_data_provider->registerForeignStream(local_og, foreign_stream_data); result.replaceStreamData( @@ -2692,30 +2686,32 @@ QPDF::getCompressibleObjGens() } } if (obj.isStream()) { - QPDFObjectHandle dict = obj.getDict(); - std::set keys = dict.getKeys(); - for (auto iter = keys.rbegin(); iter != keys.rend(); ++iter) { - std::string const& key = *iter; - QPDFObjectHandle value = dict.getKey(key); - if (key == "/Length") { - // omit stream lengths - if (value.isIndirect()) { - QTC::TC("qpdf", "QPDF exclude indirect length"); + auto dict = obj.getDict().as_dictionary(); + auto end = dict.crend(); + for (auto iter = dict.crbegin(); iter != end; ++iter) { + std::string const& key = iter->first; + QPDFObjectHandle const& value = iter->second; + if (!value.null()) { + if (key == "/Length") { + // omit stream lengths + if (value.isIndirect()) { + QTC::TC("qpdf", "QPDF exclude indirect length"); + } + } else { + queue.emplace_back(value); } - } else { - queue.push_back(value); } } } else if (obj.isDictionary()) { - std::set keys = obj.getKeys(); - for (auto iter = keys.rbegin(); iter != keys.rend(); ++iter) { - queue.push_back(obj.getKey(*iter)); - } - } else if (obj.isArray()) { - int n = obj.getArrayNItems(); - for (int i = 1; i <= n; ++i) { - queue.push_back(obj.getArrayItem(n - i)); + auto dict = obj.as_dictionary(); + auto end = dict.crend(); + for (auto iter = dict.crbegin(); iter != end; ++iter) { + if (!iter->second.null()) { + queue.emplace_back(iter->second); + } } + } else if (auto items = obj.as_array()) { + queue.insert(queue.end(), items.crbegin(), items.crend()); } } diff --git a/libqpdf/QPDFAcroFormDocumentHelper.cc b/libqpdf/QPDFAcroFormDocumentHelper.cc index 7fe779a..2857870 100644 --- a/libqpdf/QPDFAcroFormDocumentHelper.cc +++ b/libqpdf/QPDFAcroFormDocumentHelper.cc @@ -1,11 +1,14 @@ #include #include +#include #include #include #include #include +using namespace qpdf; + QPDFAcroFormDocumentHelper::Members::Members() : cache_valid(false) { @@ -238,26 +241,25 @@ QPDFAcroFormDocumentHelper::analyze() return; } m->cache_valid = true; - QPDFObjectHandle acroform = this->qpdf.getRoot().getKey("/AcroForm"); + QPDFObjectHandle acroform = qpdf.getRoot().getKey("/AcroForm"); if (!(acroform.isDictionary() && acroform.hasKey("/Fields"))) { return; } QPDFObjectHandle fields = acroform.getKey("/Fields"); - if (!fields.isArray()) { + if (auto fa = fields.as_array(strict)) { + // Traverse /AcroForm to find annotations and map them bidirectionally to fields. + + QPDFObjGen::set visited; + QPDFObjectHandle null(QPDFObjectHandle::newNull()); + for (auto const& field: fa) { + traverseField(field, null, 0, visited); + } + } else { QTC::TC("qpdf", "QPDFAcroFormDocumentHelper fields not array"); acroform.warnIfPossible("/Fields key of /AcroForm dictionary is not an array; ignoring"); fields = QPDFObjectHandle::newArray(); } - // Traverse /AcroForm to find annotations and map them bidirectionally to fields. - - QPDFObjGen::set visited; - int nfields = fields.getArrayNItems(); - QPDFObjectHandle null(QPDFObjectHandle::newNull()); - for (int i = 0; i < nfields; ++i) { - traverseField(fields.getArrayItem(i), null, 0, visited); - } - // All Widget annotations should have been encountered by traversing /AcroForm, but in case any // weren't, find them by walking through pages, and treat any widget annotation that is not // associated with a field as its own field. This just ensures that requesting the field for any @@ -324,12 +326,10 @@ QPDFAcroFormDocumentHelper::traverseField( bool is_annotation = false; bool is_field = (0 == depth); - QPDFObjectHandle kids = field.getKey("/Kids"); - if (kids.isArray()) { + if (auto a = field.getKey("/Kids").as_array(strict)) { is_field = true; - int nkids = kids.getArrayNItems(); - for (int k = 0; k < nkids; ++k) { - traverseField(kids.getArrayItem(k), field, 1 + depth, visited); + for (auto const& item: a) { + traverseField(item, field, 1 + depth, visited); } } else { if (field.hasKey("/Parent")) { @@ -975,17 +975,14 @@ QPDFAcroFormDocumentHelper::transformAnnotations( auto replace_stream = [](auto& dict, auto& key, auto& old) { return dict.replaceKeyAndGetNew(key, old.copyStream()); }; - if (apdict.isDictionary()) { - for (auto& ap: apdict.ditems()) { - if (ap.second.isStream()) { - streams.push_back(replace_stream(apdict, ap.first, ap.second)); - } else if (ap.second.isDictionary()) { - for (auto& ap2: ap.second.ditems()) { - if (ap2.second.isStream()) { - streams.push_back( - // line-break - replace_stream(ap.second, ap2.first, ap2.second)); - } + + for (auto& [key1, value1]: apdict.as_dictionary()) { + if (value1.isStream()) { + streams.emplace_back(replace_stream(apdict, key1, value1)); + } else { + for (auto& [key2, value2]: value1.as_dictionary()) { + if (value2.isStream()) { + streams.emplace_back(replace_stream(value1, key2, value2)); } } } diff --git a/libqpdf/QPDFAnnotationObjectHelper.cc b/libqpdf/QPDFAnnotationObjectHelper.cc index 1878c0e..573273c 100644 --- a/libqpdf/QPDFAnnotationObjectHelper.cc +++ b/libqpdf/QPDFAnnotationObjectHelper.cc @@ -13,27 +13,27 @@ QPDFAnnotationObjectHelper::QPDFAnnotationObjectHelper(QPDFObjectHandle oh) : std::string QPDFAnnotationObjectHelper::getSubtype() { - return this->oh.getKey("/Subtype").getName(); + return oh().getKey("/Subtype").getName(); } QPDFObjectHandle::Rectangle QPDFAnnotationObjectHelper::getRect() { - return this->oh.getKey("/Rect").getArrayAsRectangle(); + return oh().getKey("/Rect").getArrayAsRectangle(); } QPDFObjectHandle QPDFAnnotationObjectHelper::getAppearanceDictionary() { - return this->oh.getKey("/AP"); + return oh().getKey("/AP"); } std::string QPDFAnnotationObjectHelper::getAppearanceState() { - if (this->oh.getKey("/AS").isName()) { + if (oh().getKey("/AS").isName()) { QTC::TC("qpdf", "QPDFAnnotationObjectHelper AS present"); - return this->oh.getKey("/AS").getName(); + return oh().getKey("/AS").getName(); } QTC::TC("qpdf", "QPDFAnnotationObjectHelper AS absent"); return ""; @@ -42,7 +42,7 @@ QPDFAnnotationObjectHelper::getAppearanceState() int QPDFAnnotationObjectHelper::getFlags() { - QPDFObjectHandle flags_obj = this->oh.getKey("/F"); + QPDFObjectHandle flags_obj = oh().getKey("/F"); return flags_obj.isInteger() ? flags_obj.getIntValueAsInt() : 0; } @@ -143,7 +143,7 @@ QPDFAnnotationObjectHelper::getPageContentForAppearance( // 3. Apply the rotation to A as computed above to get the final appearance matrix. - QPDFObjectHandle rect_obj = this->oh.getKey("/Rect"); + QPDFObjectHandle rect_obj = oh().getKey("/Rect"); QPDFObjectHandle as = getAppearanceStream("/N").getDict(); QPDFObjectHandle bbox_obj = as.getKey("/BBox"); QPDFObjectHandle matrix_obj = as.getKey("/Matrix"); diff --git a/libqpdf/QPDFEFStreamObjectHelper.cc b/libqpdf/QPDFEFStreamObjectHelper.cc index b21c898..8728617 100644 --- a/libqpdf/QPDFEFStreamObjectHelper.cc +++ b/libqpdf/QPDFEFStreamObjectHelper.cc @@ -16,7 +16,7 @@ QPDFEFStreamObjectHelper::QPDFEFStreamObjectHelper(QPDFObjectHandle oh) : QPDFObjectHandle QPDFEFStreamObjectHelper::getParam(std::string const& pkey) { - auto params = this->oh.getDict().getKey("/Params"); + auto params = oh().getDict().getKey("/Params"); if (params.isDictionary()) { return params.getKey(pkey); } @@ -26,10 +26,9 @@ QPDFEFStreamObjectHelper::getParam(std::string const& pkey) void QPDFEFStreamObjectHelper::setParam(std::string const& pkey, QPDFObjectHandle const& pval) { - auto params = this->oh.getDict().getKey("/Params"); + auto params = oh().getDict().getKey("/Params"); if (!params.isDictionary()) { - params = - this->oh.getDict().replaceKeyAndGetNew("/Params", QPDFObjectHandle::newDictionary()); + params = oh().getDict().replaceKeyAndGetNew("/Params", QPDFObjectHandle::newDictionary()); } params.replaceKey(pkey, pval); } @@ -67,7 +66,7 @@ QPDFEFStreamObjectHelper::getSize() std::string QPDFEFStreamObjectHelper::getSubtype() { - auto val = this->oh.getDict().getKey("/Subtype"); + auto val = oh().getDict().getKey("/Subtype"); if (val.isName()) { auto n = val.getName(); if (n.length() > 1) { @@ -124,7 +123,7 @@ QPDFEFStreamObjectHelper::setModDate(std::string const& date) QPDFEFStreamObjectHelper& QPDFEFStreamObjectHelper::setSubtype(std::string const& subtype) { - this->oh.getDict().replaceKey("/Subtype", QPDFObjectHandle::newName("/" + subtype)); + oh().getDict().replaceKey("/Subtype", QPDFObjectHandle::newName("/" + subtype)); return *this; } diff --git a/libqpdf/QPDFFileSpecObjectHelper.cc b/libqpdf/QPDFFileSpecObjectHelper.cc index eada461..8fac3ba 100644 --- a/libqpdf/QPDFFileSpecObjectHelper.cc +++ b/libqpdf/QPDFFileSpecObjectHelper.cc @@ -25,7 +25,7 @@ std::string QPDFFileSpecObjectHelper::getDescription() { std::string result; - auto desc = this->oh.getKey("/Desc"); + auto desc = oh().getKey("/Desc"); if (desc.isString()) { result = desc.getUTF8Value(); } @@ -36,7 +36,7 @@ std::string QPDFFileSpecObjectHelper::getFilename() { for (auto const& i: name_keys) { - auto k = this->oh.getKey(i); + auto k = oh().getKey(i); if (k.isString()) { return k.getUTF8Value(); } @@ -49,7 +49,7 @@ QPDFFileSpecObjectHelper::getFilenames() { std::map result; for (auto const& i: name_keys) { - auto k = this->oh.getKey(i); + auto k = oh().getKey(i); if (k.isString()) { result[i] = k.getUTF8Value(); } @@ -60,7 +60,7 @@ QPDFFileSpecObjectHelper::getFilenames() QPDFObjectHandle QPDFFileSpecObjectHelper::getEmbeddedFileStream(std::string const& key) { - auto ef = this->oh.getKey("/EF"); + auto ef = oh().getKey("/EF"); if (!ef.isDictionary()) { return QPDFObjectHandle::newNull(); } @@ -79,7 +79,7 @@ QPDFFileSpecObjectHelper::getEmbeddedFileStream(std::string const& key) QPDFObjectHandle QPDFFileSpecObjectHelper::getEmbeddedFileStreams() { - return this->oh.getKey("/EF"); + return oh().getKey("/EF"); } QPDFFileSpecObjectHelper @@ -110,7 +110,7 @@ QPDFFileSpecObjectHelper::createFileSpec( QPDFFileSpecObjectHelper& QPDFFileSpecObjectHelper::setDescription(std::string const& desc) { - this->oh.replaceKey("/Desc", QPDFObjectHandle::newUnicodeString(desc)); + oh().replaceKey("/Desc", QPDFObjectHandle::newUnicodeString(desc)); return *this; } @@ -119,13 +119,13 @@ QPDFFileSpecObjectHelper::setFilename( std::string const& unicode_name, std::string const& compat_name) { auto uf = QPDFObjectHandle::newUnicodeString(unicode_name); - this->oh.replaceKey("/UF", uf); + oh().replaceKey("/UF", uf); if (compat_name.empty()) { QTC::TC("qpdf", "QPDFFileSpecObjectHelper empty compat_name"); - this->oh.replaceKey("/F", uf); + oh().replaceKey("/F", uf); } else { QTC::TC("qpdf", "QPDFFileSpecObjectHelper non-empty compat_name"); - this->oh.replaceKey("/F", QPDFObjectHandle::newString(compat_name)); + oh().replaceKey("/F", QPDFObjectHandle::newString(compat_name)); } return *this; } diff --git a/libqpdf/QPDFFormFieldObjectHelper.cc b/libqpdf/QPDFFormFieldObjectHelper.cc index 8e5b2d8..b333202 100644 --- a/libqpdf/QPDFFormFieldObjectHelper.cc +++ b/libqpdf/QPDFFormFieldObjectHelper.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -23,19 +24,19 @@ QPDFFormFieldObjectHelper::QPDFFormFieldObjectHelper() : bool QPDFFormFieldObjectHelper::isNull() { - return this->oh.isNull(); + return oh().isNull(); } QPDFFormFieldObjectHelper QPDFFormFieldObjectHelper::getParent() { - return this->oh.getKey("/Parent"); // may be null + return oh().getKey("/Parent"); // may be null } QPDFFormFieldObjectHelper QPDFFormFieldObjectHelper::getTopLevelField(bool* is_different) { - auto top_field = this->oh; + auto top_field = oh(); QPDFObjGen::set seen; while (seen.add(top_field) && !top_field.getKeyIfDict("/Parent").isNull()) { top_field = top_field.getKey("/Parent"); @@ -51,7 +52,7 @@ QPDFFormFieldObjectHelper::getFieldFromAcroForm(std::string const& name) { QPDFObjectHandle result = QPDFObjectHandle::newNull(); // Fields are supposed to be indirect, so this should work. - QPDF* q = this->oh.getOwningQPDF(); + QPDF* q = oh().getOwningQPDF(); if (!q) { return result; } @@ -65,7 +66,7 @@ QPDFFormFieldObjectHelper::getFieldFromAcroForm(std::string const& name) QPDFObjectHandle QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& name) { - QPDFObjectHandle node = this->oh; + QPDFObjectHandle node = oh(); if (!node.isDictionary()) { return QPDFObjectHandle::newNull(); } @@ -116,7 +117,7 @@ std::string QPDFFormFieldObjectHelper::getFullyQualifiedName() { std::string result; - QPDFObjectHandle node = this->oh; + QPDFObjectHandle node = oh(); QPDFObjGen::set seen; while (!node.isNull() && seen.add(node)) { if (node.getKey("/T").isString()) { @@ -135,8 +136,8 @@ std::string QPDFFormFieldObjectHelper::getPartialName() { std::string result; - if (this->oh.getKey("/T").isString()) { - result = this->oh.getKey("/T").getUTF8Value(); + if (oh().getKey("/T").isString()) { + result = oh().getKey("/T").getUTF8Value(); } return result; } @@ -144,9 +145,9 @@ QPDFFormFieldObjectHelper::getPartialName() std::string QPDFFormFieldObjectHelper::getAlternativeName() { - if (this->oh.getKey("/TU").isString()) { + if (oh().getKey("/TU").isString()) { QTC::TC("qpdf", "QPDFFormFieldObjectHelper TU present"); - return this->oh.getKey("/TU").getUTF8Value(); + return oh().getKey("/TU").getUTF8Value(); } QTC::TC("qpdf", "QPDFFormFieldObjectHelper TU absent"); return getFullyQualifiedName(); @@ -155,9 +156,9 @@ QPDFFormFieldObjectHelper::getAlternativeName() std::string QPDFFormFieldObjectHelper::getMappingName() { - if (this->oh.getKey("/TM").isString()) { + if (oh().getKey("/TM").isString()) { QTC::TC("qpdf", "QPDFFormFieldObjectHelper TM present"); - return this->oh.getKey("/TM").getUTF8Value(); + return oh().getKey("/TM").getUTF8Value(); } QTC::TC("qpdf", "QPDFFormFieldObjectHelper TM absent"); return getAlternativeName(); @@ -271,14 +272,9 @@ QPDFFormFieldObjectHelper::getChoices() if (!isChoice()) { return result; } - QPDFObjectHandle opt = getInheritableFieldValue("/Opt"); - if (opt.isArray()) { - int n = opt.getArrayNItems(); - for (int i = 0; i < n; ++i) { - QPDFObjectHandle item = opt.getArrayItem(i); - if (item.isString()) { - result.push_back(item.getUTF8Value()); - } + for (auto const& item: getInheritableFieldValue("/Opt").as_array()) { + if (item.isString()) { + result.emplace_back(item.getUTF8Value()); } } return result; @@ -287,13 +283,13 @@ QPDFFormFieldObjectHelper::getChoices() void QPDFFormFieldObjectHelper::setFieldAttribute(std::string const& key, QPDFObjectHandle value) { - this->oh.replaceKey(key, value); + oh().replaceKey(key, value); } void QPDFFormFieldObjectHelper::setFieldAttribute(std::string const& key, std::string const& utf8_value) { - this->oh.replaceKey(key, QPDFObjectHandle::newUnicodeString(utf8_value)); + oh().replaceKey(key, QPDFObjectHandle::newUnicodeString(utf8_value)); } void @@ -310,18 +306,18 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances) setCheckBoxValue((name != "/Off")); } if (!okay) { - this->oh.warnIfPossible( + oh().warnIfPossible( "ignoring attempt to set a checkbox field to a value whose type is not name"); } } else if (isRadioButton()) { if (value.isName()) { setRadioButtonValue(value); } else { - this->oh.warnIfPossible( + oh().warnIfPossible( "ignoring attempt to set a radio button field to an object that is not a name"); } } else if (isPushbutton()) { - this->oh.warnIfPossible("ignoring attempt set the value of a pushbutton field"); + oh().warnIfPossible("ignoring attempt set the value of a pushbutton field"); } return; } @@ -331,7 +327,7 @@ QPDFFormFieldObjectHelper::setV(QPDFObjectHandle value, bool need_appearances) setFieldAttribute("/V", value); } if (need_appearances) { - QPDF& qpdf = this->oh.getQPDF( + QPDF& qpdf = oh().getQPDF( "QPDFFormFieldObjectHelper::setV called with need_appearances = " "true on an object that is not associated with an owning QPDF"); QPDFAcroFormDocumentHelper(qpdf).setNeedAppearances(true); @@ -355,7 +351,7 @@ QPDFFormFieldObjectHelper::setRadioButtonValue(QPDFObjectHandle name) // its /AP (i.e. its normal appearance stream dictionary), set /AS to name; otherwise, if /Off // is a member, set /AS to /Off. // Note that we never turn on /NeedAppearances when setting a radio button field. - QPDFObjectHandle parent = this->oh.getKey("/Parent"); + QPDFObjectHandle parent = oh().getKey("/Parent"); if (parent.isDictionary() && parent.getKey("/Parent").isNull()) { QPDFFormFieldObjectHelper ph(parent); if (ph.isRadioButton()) { @@ -366,32 +362,23 @@ QPDFFormFieldObjectHelper::setRadioButtonValue(QPDFObjectHandle name) } } - QPDFObjectHandle kids = this->oh.getKey("/Kids"); + QPDFObjectHandle kids = oh().getKey("/Kids"); if (!(isRadioButton() && parent.isNull() && kids.isArray())) { - this->oh.warnIfPossible( - "don't know how to set the value" - " of this field as a radio button"); + oh().warnIfPossible("don't know how to set the value of this field as a radio button"); return; } setFieldAttribute("/V", name); - int nkids = kids.getArrayNItems(); - for (int i = 0; i < nkids; ++i) { - QPDFObjectHandle kid = kids.getArrayItem(i); + for (auto const& kid: kids.as_array()) { QPDFObjectHandle AP = kid.getKey("/AP"); QPDFObjectHandle annot; - if (AP.isNull()) { + if (AP.null()) { // The widget may be below. If there is more than one, just find the first one. - QPDFObjectHandle grandkids = kid.getKey("/Kids"); - if (grandkids.isArray()) { - int ngrandkids = grandkids.getArrayNItems(); - for (int j = 0; j < ngrandkids; ++j) { - QPDFObjectHandle grandkid = grandkids.getArrayItem(j); - AP = grandkid.getKey("/AP"); - if (!AP.isNull()) { - QTC::TC("qpdf", "QPDFFormFieldObjectHelper radio button grandkid"); - annot = grandkid; - break; - } + for (auto const& grandkid: kid.getKey("/Kids").as_array()) { + AP = grandkid.getKey("/AP"); + if (!AP.null()) { + QTC::TC("qpdf", "QPDFFormFieldObjectHelper radio button grandkid"); + annot = grandkid; + break; } } } else { @@ -399,7 +386,7 @@ QPDFFormFieldObjectHelper::setRadioButtonValue(QPDFObjectHandle name) } if (!annot) { QTC::TC("qpdf", "QPDFObjectHandle broken radio button"); - this->oh.warnIfPossible("unable to set the value of this radio button"); + oh().warnIfPossible("unable to set the value of this radio button"); continue; } if (AP.isDictionary() && AP.getKey("/N").isDictionary() && @@ -416,39 +403,32 @@ QPDFFormFieldObjectHelper::setRadioButtonValue(QPDFObjectHandle name) void QPDFFormFieldObjectHelper::setCheckBoxValue(bool value) { - QPDFObjectHandle AP = this->oh.getKey("/AP"); + QPDFObjectHandle AP = oh().getKey("/AP"); QPDFObjectHandle annot; - if (AP.isNull()) { + if (AP.null()) { // The widget may be below. If there is more than one, just // find the first one. - QPDFObjectHandle kids = this->oh.getKey("/Kids"); - if (kids.isArray()) { - int nkids = kids.getArrayNItems(); - for (int i = 0; i < nkids; ++i) { - QPDFObjectHandle kid = kids.getArrayItem(i); - AP = kid.getKey("/AP"); - if (!AP.isNull()) { - QTC::TC("qpdf", "QPDFFormFieldObjectHelper checkbox kid widget"); - annot = kid; - break; - } + QPDFObjectHandle kids = oh().getKey("/Kids"); + for (auto const& kid: oh().getKey("/Kids").as_array(qpdf::strict)) { + AP = kid.getKey("/AP"); + if (!AP.null()) { + QTC::TC("qpdf", "QPDFFormFieldObjectHelper checkbox kid widget"); + annot = kid; + break; } } } else { - annot = this->oh; + annot = oh(); } std::string on_value; if (value) { // Set the "on" value to the first value in the appearance stream's normal state dictionary // that isn't /Off. If not found, fall back to /Yes. if (AP.isDictionary()) { - auto N = AP.getKey("/N"); - if (N.isDictionary()) { - for (auto const& iter: N.ditems()) { - if (iter.first != "/Off") { - on_value = iter.first; - break; - } + for (auto const& item: AP.getKey("/N").as_dictionary()) { + if (item.first != "/Off") { + on_value = item.first; + break; } } } @@ -462,7 +442,7 @@ QPDFFormFieldObjectHelper::setCheckBoxValue(bool value) setFieldAttribute("/V", name); if (!annot) { QTC::TC("qpdf", "QPDFObjectHandle broken checkbox"); - this->oh.warnIfPossible("unable to set the value of this checkbox"); + oh().warnIfPossible("unable to set the value of this checkbox"); return; } QTC::TC("qpdf", "QPDFFormFieldObjectHelper set checkbox AS"); @@ -775,7 +755,7 @@ QPDFFormFieldObjectHelper::generateTextAppearance(QPDFAnnotationObjectHelper& ao "<< /Resources << /ProcSet [ /PDF /Text ] >>" " /Type /XObject /Subtype /Form >>"); dict.replaceKey("/BBox", QPDFObjectHandle::newFromRectangle(bbox)); - AS = QPDFObjectHandle::newStream(this->oh.getOwningQPDF(), "/Tx BMC\nEMC\n"); + AS = QPDFObjectHandle::newStream(oh().getOwningQPDF(), "/Tx BMC\nEMC\n"); AS.replaceDict(dict); QPDFObjectHandle AP = aoh.getAppearanceDictionary(); if (AP.isNull()) { diff --git a/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc index 8ea06dc..0aa5609 100644 --- a/libqpdf/QPDFJob.cc +++ b/libqpdf/QPDFJob.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -916,10 +917,13 @@ QPDFJob::doListAttachments(QPDF& pdf) v << " " << i2.first << " -> " << i2.second << "\n"; } v << " all data streams:\n"; - for (auto const& i2: efoh->getEmbeddedFileStreams().ditems()) { - auto efs = QPDFEFStreamObjectHelper(i2.second); - v << " " << i2.first << " -> " - << efs.getObjectHandle().getObjGen().unparse(',') << "\n"; + for (auto const& [key2, value2]: efoh->getEmbeddedFileStreams().as_dictionary()) { + if (value2.null()) { + continue; + } + auto efs = QPDFEFStreamObjectHelper(value2); + v << " " << key2 << " -> " << efs.getObjectHandle().getObjGen().unparse(',') + << "\n"; v << " creation date: " << efs.getCreationDate() << "\n" << " modification date: " << efs.getModDate() << "\n" << " mime type: " << efs.getSubtype() << "\n" @@ -1338,9 +1342,12 @@ QPDFJob::doJSONAttachments(Pipeline* p, bool& first, QPDF& pdf) j_names.addDictionaryMember(i2.first, JSON::makeString(i2.second)); } auto j_streams = j_details.addDictionaryMember("streams", JSON::makeDictionary()); - for (auto const& i2: fsoh->getEmbeddedFileStreams().ditems()) { - auto efs = QPDFEFStreamObjectHelper(i2.second); - auto j_stream = j_streams.addDictionaryMember(i2.first, JSON::makeDictionary()); + for (auto const& [key2, value2]: fsoh->getEmbeddedFileStreams().as_dictionary()) { + if (value2.null()) { + continue; + } + auto efs = QPDFEFStreamObjectHelper(value2); + auto j_stream = j_streams.addDictionaryMember(key2, JSON::makeDictionary()); j_stream.addDictionaryMember( "creationdate", null_or_string(to_iso8601(efs.getCreationDate()))); j_stream.addDictionaryMember( @@ -2347,12 +2354,10 @@ QPDFJob::shouldRemoveUnreferencedResources(QPDF& pdf) return true; } } - if (xobject.isDictionary()) { - for (auto const& k: xobject.getKeys()) { - QPDFObjectHandle xobj = xobject.getKey(k); - if (xobj.isFormXObject()) { - queue.push_back(xobj); - } + + for (auto const& xobj: xobject.as_dictionary()) { + if (xobj.second.isFormXObject()) { + queue.emplace_back(xobj.second); } } } diff --git a/libqpdf/QPDFObjGen.cc b/libqpdf/QPDFObjGen.cc deleted file mode 100644 index 5242f4f..0000000 --- a/libqpdf/QPDFObjGen.cc +++ /dev/null @@ -1,53 +0,0 @@ -#include - -#include -#include -#include - -#include - -bool -QPDFObjGen::set::add(QPDFObjectHandle const& oh) -{ - if (auto* ptr = oh.getObjectPtr()) { - return add(ptr->getObjGen()); - } else { - throw std::logic_error( - "attempt to retrieve QPDFObjGen from uninitialized QPDFObjectHandle"); - return false; - } -} - -bool -QPDFObjGen::set::add(QPDFObjectHelper const& helper) -{ - if (auto* ptr = helper.getObjectHandle().getObjectPtr()) { - return add(ptr->getObjGen()); - } else { - throw std::logic_error( - "attempt to retrieve QPDFObjGen from uninitialized QPDFObjectHandle"); - return false; - } -} - -void -QPDFObjGen::set::erase(QPDFObjectHandle const& oh) -{ - if (auto* ptr = oh.getObjectPtr()) { - erase(ptr->getObjGen()); - } else { - throw std::logic_error( - "attempt to retrieve QPDFObjGen from uninitialized QPDFObjectHandle"); - } -} - -void -QPDFObjGen::set::erase(QPDFObjectHelper const& helper) -{ - if (auto* ptr = helper.getObjectHandle().getObjectPtr()) { - erase(ptr->getObjGen()); - } else { - throw std::logic_error( - "attempt to retrieve QPDFObjGen from uninitialized QPDFObjectHandle"); - } -} diff --git a/libqpdf/QPDFObject.cc b/libqpdf/QPDFObject.cc index 7e3cc21..a5cb986 100644 --- a/libqpdf/QPDFObject.cc +++ b/libqpdf/QPDFObject.cc @@ -1,10 +1,52 @@ #include -#include -#include - -void -QPDFObject::destroy() +std::string +QPDFObject::getDescription() { - value = QPDF_Destroyed::getInstance(); + if (object_description) { + switch (object_description->index()) { + case 0: + { + // Simple template string + auto description = std::get<0>(*object_description); + + if (auto pos = description.find("$OG"); pos != std::string::npos) { + description.replace(pos, 3, og.unparse(' ')); + } + if (auto pos = description.find("$PO"); pos != std::string::npos) { + qpdf_offset_t shift = (getTypeCode() == ::ot_dictionary) ? 2 + : (getTypeCode() == ::ot_array) ? 1 + : 0; + + description.replace(pos, 3, std::to_string(parsed_offset + shift)); + } + return description; + } + case 1: + { + // QPDF::JSONReactor generated description + auto j_descr = std::get<1>(*object_description); + return ( + *j_descr.input + (j_descr.object.empty() ? "" : ", " + j_descr.object) + + " at offset " + std::to_string(parsed_offset)); + } + case 2: + { + // Child object description + auto j_descr = std::get<2>(*object_description); + std::string result; + if (auto p = j_descr.parent.lock()) { + result = p->getDescription(); + } + result += j_descr.static_descr; + if (auto pos = result.find("$VD"); pos != std::string::npos) { + result.replace(pos, 3, j_descr.var_descr); + } + return result; + } + } + } else if (og.isIndirect()) { + return "object " + og.unparse(' '); + } + return {}; } diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index 9dad223..ffe2509 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -11,19 +11,6 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include @@ -38,6 +25,13 @@ #include using namespace std::literals; +using namespace qpdf; + +BaseHandle:: +operator QPDFObjGen() const +{ + return obj ? obj->getObjGen() : QPDFObjGen(); +} namespace { @@ -221,8 +215,441 @@ LastChar::getLastChar() return this->last_char; } +std::pair +Name::analyzeJSONEncoding(const std::string& name) +{ + 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& it: name) { + auto c = static_cast(it); + 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}; +} + +std::string +Name::normalize(std::string const& name) +{ + if (name.empty()) { + return name; + } + std::string result; + result += name.at(0); + for (size_t i = 1; i < name.length(); ++i) { + char ch = name.at(i); + // Don't use locale/ctype here; follow PDF spec guidelines. + if (ch == '\0') { + // QPDFTokenizer embeds a null character to encode an invalid #. + result += "#"; + } else if ( + ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' || + ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) { + result += QUtil::hex_encode_char(ch); + } else { + result += ch; + } + } + return result; +} + +std::shared_ptr +QPDFObject::copy(bool shallow) +{ + switch (getResolvedTypeCode()) { + case ::ot_uninitialized: + throw std::logic_error("QPDFObjectHandle: attempting to copy an uninitialized object"); + return {}; // does not return + case ::ot_reserved: + return create(); + case ::ot_null: + return create(); + case ::ot_boolean: + return create(std::get(value).val); + case ::ot_integer: + return create(std::get(value).val); + case ::ot_real: + return create(std::get(value).val); + case ::ot_string: + return create(std::get(value).val); + case ::ot_name: + return create(std::get(value).name); + case ::ot_array: + { + auto const& a = std::get(value); + if (shallow) { + return QPDFObject::create(a); + } else { + QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1); + if (a.sp) { + QPDF_Array result; + result.sp = std::make_unique(); + result.sp->size = a.sp->size; + for (auto const& [idx, oh]: a.sp->elements) { + result.sp->elements[idx] = oh.indirect() ? oh : oh.getObj()->copy(); + } + return QPDFObject::create(std::move(result)); + } else { + std::vector result; + result.reserve(a.elements.size()); + for (auto const& element: a.elements) { + result.emplace_back( + element ? (element.indirect() ? element : element.getObj()->copy()) + : element); + } + return QPDFObject::create(std::move(result), false); + } + } + } + case ::ot_dictionary: + { + auto const& d = std::get(value); + if (shallow) { + return QPDFObject::create(d.items); + } else { + std::map new_items; + for (auto const& [key, val]: d.items) { + new_items[key] = val.indirect() ? val : val.getObj()->copy(); + } + return QPDFObject::create(new_items); + } + } + case ::ot_stream: + QTC::TC("qpdf", "QPDF_Stream ERR shallow copy stream"); + throw std::runtime_error("stream objects cannot be cloned"); + return {}; // does not return + case ::ot_operator: + return create(std::get(value).val); + case ::ot_inlineimage: + return create(std::get(value).val); + case ::ot_unresolved: + throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object"); + return {}; // does not return + case ::ot_destroyed: + throw std::logic_error("attempted to shallow copy QPDFObjectHandle from destroyed QPDF"); + return {}; // does not return + case ::ot_reference: + return qpdf->getObject(og).getObj(); + } + return {}; // unreachable +} + +std::string +QPDFObject::unparse() +{ + switch (getResolvedTypeCode()) { + case ::ot_uninitialized: + throw std::logic_error("QPDFObjectHandle: attempting to unparse an uninitialized object"); + return ""; // does not return + case ::ot_reserved: + throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object"); + return ""; // does not return + case ::ot_null: + return "null"; + case ::ot_boolean: + return std::get(value).val ? "true" : "false"; + case ::ot_integer: + return std::to_string(std::get(value).val); + case ::ot_real: + return std::get(value).val; + case ::ot_string: + return std::get(value).unparse(false); + case ::ot_name: + return Name::normalize(std::get(value).name); + case ::ot_array: + { + auto const& a = std::get(value); + std::string result = "[ "; + if (a.sp) { + int next = 0; + for (auto& item: a.sp->elements) { + int key = item.first; + for (int j = next; j < key; ++j) { + result += "null "; + } + auto item_og = item.second.id_gen(); + result += item_og.isIndirect() ? item_og.unparse(' ') + " R " + : item.second.getObj()->unparse() + " "; + next = ++key; + } + for (int j = next; j < a.sp->size; ++j) { + result += "null "; + } + } else { + for (auto const& item: a.elements) { + auto item_og = item.id_gen(); + result += item_og.isIndirect() ? item_og.unparse(' ') + " R " + : item.getObj()->unparse() + " "; + } + } + result += "]"; + return result; + } + case ::ot_dictionary: + { + auto const& items = std::get(value).items; + std::string result = "<< "; + for (auto& iter: items) { + if (!iter.second.null()) { + result += Name::normalize(iter.first) + " " + iter.second.unparse() + " "; + } + } + result += ">>"; + return result; + } + case ::ot_stream: + return og.unparse(' ') + " R"; + case ::ot_operator: + return std::get(value).val; + case ::ot_inlineimage: + return std::get(value).val; + case ::ot_unresolved: + throw std::logic_error("QPDFObjectHandle: attempting to unparse a unresolved object"); + return ""; // does not return + case ::ot_destroyed: + throw std::logic_error("attempted to unparse a QPDFObjectHandle from a destroyed QPDF"); + return ""; // does not return + case ::ot_reference: + return og.unparse(' ') + " R"; + } + return {}; // unreachable +} + +void +QPDFObject::write_json(int json_version, JSON::Writer& p) +{ + switch (getResolvedTypeCode()) { + case ::ot_uninitialized: + throw std::logic_error( + "QPDFObjectHandle: attempting to get JSON from a uninitialized object"); + break; // unreachable + case ::ot_null: + case ::ot_operator: + case ::ot_inlineimage: + p << "null"; + break; + case ::ot_boolean: + p << std::get(value).val; + break; + case ::ot_integer: + p << std::to_string(std::get(value).val); + break; + case ::ot_real: + { + auto const& val = std::get(value).val; + if (val.length() == 0) { + // Can't really happen... + p << "0"; + } else if (val.at(0) == '.') { + p << "0" << val; + } else if (val.length() >= 2 && val.at(0) == '-' && val.at(1) == '.') { + p << "-0." << val.substr(2); + } else { + p << val; + } + if (val.back() == '.') { + p << "0"; + } + } + break; + case ::ot_string: + std::get(value).writeJSON(json_version, p); + break; + case ::ot_name: + { + auto const& n = std::get(value); + // 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(Name::normalize(n.name)) << "\""; + } else { + if (auto res = Name::analyzeJSONEncoding(n.name); res.first) { + if (res.second) { + p << "\"" << n.name << "\""; + } else { + p << "\"" << JSON::Writer::encode_string(n.name) << "\""; + } + } else { + p << "\"n:" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\""; + } + } + } + break; + case ::ot_array: + { + auto const& a = std::get(value); + p.writeStart('['); + if (a.sp) { + int next = 0; + for (auto& item: a.sp->elements) { + int key = item.first; + for (int j = next; j < key; ++j) { + p.writeNext() << "null"; + } + p.writeNext(); + auto item_og = item.second.getObj()->getObjGen(); + if (item_og.isIndirect()) { + p << "\"" << item_og.unparse(' ') << " R\""; + } else { + item.second.getObj()->write_json(json_version, p); + } + next = ++key; + } + for (int j = next; j < a.sp->size; ++j) { + p.writeNext() << "null"; + } + } else { + for (auto const& item: a.elements) { + p.writeNext(); + auto item_og = item.getObj()->getObjGen(); + if (item_og.isIndirect()) { + p << "\"" << item_og.unparse(' ') << " R\""; + } else { + item.getObj()->write_json(json_version, p); + } + } + } + p.writeEnd(']'); + } + break; + case ::ot_dictionary: + { + auto const& d = std::get(value); + p.writeStart('{'); + for (auto& iter: d.items) { + if (!iter.second.null()) { + p.writeNext(); + if (json_version == 1) { + p << "\"" << JSON::Writer::encode_string(Name::normalize(iter.first)) + << "\": "; + } else if (auto res = Name::analyzeJSONEncoding(iter.first); res.first) { + if (res.second) { + p << "\"" << iter.first << "\": "; + } else { + p << "\"" << JSON::Writer::encode_string(iter.first) << "\": "; + } + } else { + p << "\"n:" << JSON::Writer::encode_string(Name::normalize(iter.first)) + << "\": "; + } + iter.second.writeJSON(json_version, p); + } + } + p.writeEnd('}'); + } + break; + case ::ot_stream: + std::get(value).m->stream_dict.writeJSON(json_version, p); + break; + case ::ot_reference: + p << "\"" << getObjGen().unparse(' ') << " R\""; + break; + default: + throw std::logic_error("attempted to write an unsuitable object as JSON"); + } +} + +void +QPDFObject::disconnect() +{ + // Disconnect an object from its owning QPDF. This is called by QPDF's destructor. + + switch (getTypeCode()) { + case ::ot_array: + { + auto& a = std::get(value); + if (a.sp) { + for (auto& item: a.sp->elements) { + auto& obj = item.second; + if (!obj.indirect()) { + obj.getObj()->disconnect(); + } + } + } else { + for (auto& obj: a.elements) { + if (!obj.indirect()) { + obj.getObj()->disconnect(); + } + } + } + } + break; + case ::ot_dictionary: + for (auto& iter: std::get(value).items) { + QPDFObjectHandle::DisconnectAccess::disconnect(iter.second); + } + break; + case ::ot_stream: + { + auto& s = std::get(value); + s.m->stream_provider = nullptr; + QPDFObjectHandle::DisconnectAccess::disconnect(s.m->stream_dict); + } + break; + default: + break; + } + qpdf = nullptr; + og = QPDFObjGen(); +} +std::string +QPDFObject::getStringValue() const +{ + switch (getResolvedTypeCode()) { + case ::ot_real: + return std::get(value).val; + case ::ot_string: + return std::get(value).val; + case ::ot_name: + return std::get(value).name; + case ::ot_operator: + return std::get(value).val; + case ::ot_inlineimage: + return std::get(value).val; + case ::ot_reference: + return std::get(value).obj->getStringValue(); + default: + throw std::logic_error("Internal error in QPDFObject::getStringValue"); + } + return ""; // unreachable +} + bool -QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const noexcept +QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const { return this->obj == rhs.obj; } @@ -246,7 +673,7 @@ QPDFObjectHandle::getTypeCode() const char const* QPDFObjectHandle::getTypeName() const { - static constexpr std::array tn{ + static constexpr std::array tn{ "uninitialized", "reserved", "null", @@ -261,100 +688,21 @@ QPDFObjectHandle::getTypeName() const "operator", "inline-image", "unresolved", - "destroyed"}; + "destroyed", + "reference"}; return obj ? tn[getTypeCode()] : "uninitialized"; } -QPDF_Array* -QPDFObjectHandle::asArray() const -{ - return obj ? obj->as() : nullptr; -} - -QPDF_Bool* -QPDFObjectHandle::asBool() const -{ - return obj ? obj->as() : nullptr; -} - -QPDF_Dictionary* -QPDFObjectHandle::asDictionary() const -{ - return obj ? obj->as() : nullptr; -} - -QPDF_InlineImage* -QPDFObjectHandle::asInlineImage() const -{ - return obj ? obj->as() : nullptr; -} - -QPDF_Integer* -QPDFObjectHandle::asInteger() const -{ - return obj ? obj->as() : nullptr; -} - -QPDF_Name* -QPDFObjectHandle::asName() const -{ - return obj ? obj->as() : nullptr; -} - -QPDF_Null* -QPDFObjectHandle::asNull() const -{ - return obj ? obj->as() : nullptr; -} - -QPDF_Operator* -QPDFObjectHandle::asOperator() const -{ - return obj ? obj->as() : nullptr; -} - -QPDF_Real* -QPDFObjectHandle::asReal() const -{ - return obj ? obj->as() : nullptr; -} - -QPDF_Reserved* -QPDFObjectHandle::asReserved() const -{ - return obj ? obj->as() : nullptr; -} - -QPDF_Stream* -QPDFObjectHandle::asStream() const -{ - return obj ? obj->as() : nullptr; -} - -QPDF_Stream* -QPDFObjectHandle::asStreamWithAssert() const -{ - auto stream = asStream(); - assertType("stream", stream); - return stream; -} - -QPDF_String* -QPDFObjectHandle::asString() const -{ - return obj ? obj->as() : nullptr; -} - bool QPDFObjectHandle::isDestroyed() const { - return obj && obj->getResolvedTypeCode() == ::ot_destroyed; + return type_code() == ::ot_destroyed; } bool QPDFObjectHandle::isBool() const { - return obj && obj->getResolvedTypeCode() == ::ot_boolean; + return type_code() == ::ot_boolean; } bool @@ -362,25 +710,25 @@ QPDFObjectHandle::isDirectNull() const { // Don't call dereference() -- this is a const method, and we know // objid == 0, so there's nothing to resolve. - return (obj && getObjectID() == 0 && obj->getTypeCode() == ::ot_null); + return !indirect() && raw_type_code() == ::ot_null; } bool QPDFObjectHandle::isNull() const { - return obj && obj->getResolvedTypeCode() == ::ot_null; + return type_code() == ::ot_null; } bool QPDFObjectHandle::isInteger() const { - return obj && obj->getResolvedTypeCode() == ::ot_integer; + return type_code() == ::ot_integer; } bool QPDFObjectHandle::isReal() const { - return obj && obj->getResolvedTypeCode() == ::ot_real; + return type_code() == ::ot_real; } bool @@ -416,49 +764,49 @@ QPDFObjectHandle::getValueAsNumber(double& value) const bool QPDFObjectHandle::isName() const { - return obj && obj->getResolvedTypeCode() == ::ot_name; + return type_code() == ::ot_name; } bool QPDFObjectHandle::isString() const { - return obj && obj->getResolvedTypeCode() == ::ot_string; + return type_code() == ::ot_string; } bool QPDFObjectHandle::isOperator() const { - return obj && obj->getResolvedTypeCode() == ::ot_operator; + return type_code() == ::ot_operator; } bool QPDFObjectHandle::isInlineImage() const { - return obj && obj->getResolvedTypeCode() == ::ot_inlineimage; + return type_code() == ::ot_inlineimage; } bool QPDFObjectHandle::isArray() const { - return obj && obj->getResolvedTypeCode() == ::ot_array; + return type_code() == ::ot_array; } bool QPDFObjectHandle::isDictionary() const { - return obj && obj->getResolvedTypeCode() == ::ot_dictionary; + return type_code() == ::ot_dictionary; } bool QPDFObjectHandle::isStream() const { - return obj && obj->getResolvedTypeCode() == ::ot_stream; + return type_code() == ::ot_stream; } bool QPDFObjectHandle::isReserved() const { - return obj && obj->getResolvedTypeCode() == ::ot_reserved; + return type_code() == ::ot_reserved; } bool @@ -491,9 +839,8 @@ QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& sub bool QPDFObjectHandle::getBoolValue() const { - auto boolean = asBool(); - if (boolean) { - return boolean->getVal(); + if (auto boolean = as()) { + return boolean->val; } else { typeWarning("boolean", "returning false"); QTC::TC("qpdf", "QPDFObjectHandle boolean returning false"); @@ -504,12 +851,11 @@ QPDFObjectHandle::getBoolValue() const bool QPDFObjectHandle::getValueAsBool(bool& value) const { - auto boolean = asBool(); - if (boolean == nullptr) { - return false; + if (auto boolean = as()) { + value = boolean->val; + return true; } - value = boolean->getVal(); - return true; + return false; } // Integer accessors @@ -517,9 +863,8 @@ QPDFObjectHandle::getValueAsBool(bool& value) const long long QPDFObjectHandle::getIntValue() const { - auto integer = asInteger(); - if (integer) { - return integer->getVal(); + if (auto integer = as()) { + return integer->val; } else { typeWarning("integer", "returning 0"); QTC::TC("qpdf", "QPDFObjectHandle integer returning 0"); @@ -530,12 +875,11 @@ QPDFObjectHandle::getIntValue() const bool QPDFObjectHandle::getValueAsInt(long long& value) const { - auto integer = asInteger(); - if (integer == nullptr) { - return false; + if (auto integer = as()) { + value = integer->val; + return true; } - value = integer->getVal(); - return true; + return false; } int @@ -692,8 +1036,7 @@ QPDFObjectHandle::getValueAsString(std::string& value) const std::string QPDFObjectHandle::getUTF8Value() const { - auto str = asString(); - if (str) { + if (auto str = as()) { return str->getUTF8Val(); } else { typeWarning("string", "returning empty string"); @@ -705,12 +1048,11 @@ QPDFObjectHandle::getUTF8Value() const bool QPDFObjectHandle::getValueAsUTF8(std::string& value) const { - auto str = asString(); - if (str == nullptr) { - return false; + if (auto str = as()) { + value = str->getUTF8Val(); + return true; } - value = str->getUTF8Val(); - return true; + return false; } // Operator and Inline Image accessors @@ -759,7 +1101,7 @@ QPDFObjectHandle::getValueAsInlineImage(std::string& value) const return true; } -// Array accessors +// Array accessors and mutators are in QPDF_Array.cc QPDFObjectHandle::QPDFArrayItems QPDFObjectHandle::aitems() @@ -767,207 +1109,7 @@ QPDFObjectHandle::aitems() return *this; } -int -QPDFObjectHandle::getArrayNItems() const -{ - if (auto array = asArray()) { - return array->size(); - } else { - typeWarning("array", "treating as empty"); - QTC::TC("qpdf", "QPDFObjectHandle array treating as empty"); - return 0; - } -} - -QPDFObjectHandle -QPDFObjectHandle::getArrayItem(int n) const -{ - if (auto array = asArray()) { - auto result = array->at(n); - if (result.first) { - return result.second; - } else { - objectWarning("returning null for out of bounds array access"); - QTC::TC("qpdf", "QPDFObjectHandle array bounds"); - } - } else { - typeWarning("array", "returning null"); - QTC::TC("qpdf", "QPDFObjectHandle array null for non-array"); - } - static auto constexpr msg = " -> null returned from invalid array access"sv; - return QPDF_Null::create(obj, msg, ""); -} - -bool -QPDFObjectHandle::isRectangle() const -{ - if (auto array = asArray()) { - for (int i = 0; i < 4; ++i) { - if (auto item = array->at(i).second; !item.isNumber()) { - return false; - } - } - return array->size() == 4; - } - return false; -} - -bool -QPDFObjectHandle::isMatrix() const -{ - if (auto array = asArray()) { - for (int i = 0; i < 6; ++i) { - if (auto item = array->at(i).second; !item.isNumber()) { - return false; - } - } - return array->size() == 6; - } - return false; -} - -QPDFObjectHandle::Rectangle -QPDFObjectHandle::getArrayAsRectangle() const -{ - if (auto array = asArray()) { - if (array->size() != 4) { - return {}; - } - double items[4]; - for (int i = 0; i < 4; ++i) { - if (auto item = array->at(i).second; !item.getValueAsNumber(items[i])) { - return {}; - } - } - return { - std::min(items[0], items[2]), - std::min(items[1], items[3]), - std::max(items[0], items[2]), - std::max(items[1], items[3])}; - } - return {}; -} - -QPDFObjectHandle::Matrix -QPDFObjectHandle::getArrayAsMatrix() const -{ - if (auto array = asArray()) { - if (array->size() != 6) { - return {}; - } - double items[6]; - for (int i = 0; i < 6; ++i) { - if (auto item = array->at(i).second; !item.getValueAsNumber(items[i])) { - return {}; - } - } - return {items[0], items[1], items[2], items[3], items[4], items[5]}; - } - return {}; -} - -std::vector -QPDFObjectHandle::getArrayAsVector() const -{ - auto array = asArray(); - if (array) { - return array->getAsVector(); - } else { - typeWarning("array", "treating as empty"); - QTC::TC("qpdf", "QPDFObjectHandle array treating as empty vector"); - } - return {}; -} - -// Array mutators - -void -QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const& item) -{ - if (auto array = asArray()) { - if (!array->setAt(n, item)) { - objectWarning("ignoring attempt to set out of bounds array item"); - QTC::TC("qpdf", "QPDFObjectHandle set array bounds"); - } - } else { - typeWarning("array", "ignoring attempt to set item"); - QTC::TC("qpdf", "QPDFObjectHandle array ignoring set item"); - } -} -void -QPDFObjectHandle::setArrayFromVector(std::vector const& items) -{ - if (auto array = asArray()) { - array->setFromVector(items); - } else { - typeWarning("array", "ignoring attempt to replace items"); - QTC::TC("qpdf", "QPDFObjectHandle array ignoring replace items"); - } -} - -void -QPDFObjectHandle::insertItem(int at, QPDFObjectHandle const& item) -{ - if (auto array = asArray()) { - if (!array->insert(at, item)) { - objectWarning("ignoring attempt to insert out of bounds array item"); - QTC::TC("qpdf", "QPDFObjectHandle insert array bounds"); - } - } else { - typeWarning("array", "ignoring attempt to insert item"); - QTC::TC("qpdf", "QPDFObjectHandle array ignoring insert item"); - } -} - -QPDFObjectHandle -QPDFObjectHandle::insertItemAndGetNew(int at, QPDFObjectHandle const& item) -{ - insertItem(at, item); - return item; -} - -void -QPDFObjectHandle::appendItem(QPDFObjectHandle const& item) -{ - if (auto array = asArray()) { - array->push_back(item); - } else { - typeWarning("array", "ignoring attempt to append item"); - QTC::TC("qpdf", "QPDFObjectHandle array ignoring append item"); - } -} - -QPDFObjectHandle -QPDFObjectHandle::appendItemAndGetNew(QPDFObjectHandle const& item) -{ - appendItem(item); - return item; -} - -void -QPDFObjectHandle::eraseItem(int at) -{ - if (auto array = asArray()) { - if (!array->erase(at)) { - objectWarning("ignoring attempt to erase out of bounds array item"); - QTC::TC("qpdf", "QPDFObjectHandle erase array bounds"); - } - } else { - typeWarning("array", "ignoring attempt to erase item"); - QTC::TC("qpdf", "QPDFObjectHandle array ignoring erase item"); - } -} - -QPDFObjectHandle -QPDFObjectHandle::eraseItemAndGetOld(int at) -{ - auto array = asArray(); - auto result = (array && at < array->size() && at >= 0) ? array->at(at).second : newNull(); - eraseItem(at); - return result; -} - -// Dictionary accessors +// Dictionary accessors are in QPDF_Dictionary.cc QPDFObjectHandle::QPDFDictItems QPDFObjectHandle::ditems() @@ -975,66 +1117,6 @@ QPDFObjectHandle::ditems() return {*this}; } -bool -QPDFObjectHandle::hasKey(std::string const& key) const -{ - auto dict = asDictionary(); - if (dict) { - return dict->hasKey(key); - } else { - typeWarning("dictionary", "returning false for a key containment request"); - QTC::TC("qpdf", "QPDFObjectHandle dictionary false for hasKey"); - return false; - } -} - -QPDFObjectHandle -QPDFObjectHandle::getKey(std::string const& key) const -{ - if (auto dict = asDictionary()) { - return dict->getKey(key); - } else { - typeWarning("dictionary", "returning null for attempted key retrieval"); - QTC::TC("qpdf", "QPDFObjectHandle dictionary null for getKey"); - static auto constexpr msg = " -> null returned from getting key $VD from non-Dictionary"sv; - return QPDF_Null::create(obj, msg, ""); - } -} - -QPDFObjectHandle -QPDFObjectHandle::getKeyIfDict(std::string const& key) const -{ - return isNull() ? newNull() : getKey(key); -} - -std::set -QPDFObjectHandle::getKeys() const -{ - std::set result; - auto dict = asDictionary(); - if (dict) { - result = dict->getKeys(); - } else { - typeWarning("dictionary", "treating as empty"); - QTC::TC("qpdf", "QPDFObjectHandle dictionary empty set for getKeys"); - } - return result; -} - -std::map -QPDFObjectHandle::getDictAsMap() const -{ - std::map result; - auto dict = asDictionary(); - if (dict) { - result = dict->getAsMap(); - } else { - typeWarning("dictionary", "treating as empty"); - QTC::TC("qpdf", "QPDFObjectHandle dictionary empty map for asMap"); - } - return result; -} - // Array and Name accessors bool @@ -1055,19 +1137,10 @@ QPDFObjectHandle::isOrHasName(std::string const& value) const void QPDFObjectHandle::makeResourcesIndirect(QPDF& owning_qpdf) { - if (!isDictionary()) { - return; - } - for (auto const& i1: ditems()) { - QPDFObjectHandle sub = i1.second; - if (!sub.isDictionary()) { - continue; - } - for (auto const& i2: sub.ditems()) { - std::string const& key = i2.first; - QPDFObjectHandle val = i2.second; - if (!val.isIndirect()) { - sub.replaceKey(key, owning_qpdf.makeIndirectObject(val)); + for (auto const& i1: as_dictionary()) { + for (auto& i2: i1.second.as_dictionary()) { + if (!i2.second.null() && !i2.second.isIndirect()) { + i2.second = owning_qpdf.makeIndirectObject(i2.second); } } } @@ -1084,18 +1157,17 @@ QPDFObjectHandle::mergeResources( auto make_og_to_name = [](QPDFObjectHandle& dict, std::map& og_to_name) { - for (auto const& i: dict.ditems()) { - if (i.second.isIndirect()) { - og_to_name[i.second.getObjGen()] = i.first; + for (auto const& [key, value]: dict.as_dictionary()) { + if (!value.null() && value.isIndirect()) { + og_to_name.insert_or_assign(value.getObjGen(), key); } } }; // This algorithm is described in comments in QPDFObjectHandle.hh // above the declaration of mergeResources. - for (auto const& o_top: other.ditems()) { - std::string const& rtype = o_top.first; - QPDFObjectHandle other_val = o_top.second; + for (auto const& [rtype, value1]: other.as_dictionary()) { + auto other_val = value1; if (hasKey(rtype)) { QPDFObjectHandle this_val = getKey(rtype); if (this_val.isDictionary() && other_val.isDictionary()) { @@ -1110,9 +1182,8 @@ QPDFObjectHandle::mergeResources( std::set rnames; int min_suffix = 1; bool initialized_maps = false; - for (auto const& ov_iter: other_val.ditems()) { - std::string const& key = ov_iter.first; - QPDFObjectHandle rval = ov_iter.second; + for (auto const& [key, value2]: other_val.as_dictionary()) { + QPDFObjectHandle rval = value2; if (!this_val.hasKey(key)) { if (!rval.isIndirect()) { QTC::TC("qpdf", "QPDFObjectHandle merge shallow copy"); @@ -1171,14 +1242,10 @@ QPDFObjectHandle::getResourceNames() const { // Return second-level dictionary keys std::set result; - if (!isDictionary()) { - return result; - } - for (auto const& key: getKeys()) { - QPDFObjectHandle val = getKey(key); - if (val.isDictionary()) { - for (auto const& val_key: val.getKeys()) { - result.insert(val_key); + for (auto const& item: as_dictionary(strict)) { + for (auto const& [key2, val2]: item.second.as_dictionary(strict)) { + if (!val2.null()) { + result.insert(key2); } } } @@ -1208,234 +1275,9 @@ QPDFObjectHandle::getUniqueResourceName( " QPDFObjectHandle::getUniqueResourceName"); } -// Dictionary mutators - -void -QPDFObjectHandle::replaceKey(std::string const& key, QPDFObjectHandle const& value) -{ - auto dict = asDictionary(); - if (dict) { - checkOwnership(value); - dict->replaceKey(key, value); - } else { - typeWarning("dictionary", "ignoring key replacement request"); - QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring replaceKey"); - } -} - -QPDFObjectHandle -QPDFObjectHandle::replaceKeyAndGetNew(std::string const& key, QPDFObjectHandle const& value) -{ - replaceKey(key, value); - return value; -} - -QPDFObjectHandle -QPDFObjectHandle::replaceKeyAndGetOld(std::string const& key, QPDFObjectHandle const& value) -{ - QPDFObjectHandle old = removeKeyAndGetOld(key); - replaceKey(key, value); - return old; -} - -void -QPDFObjectHandle::removeKey(std::string const& key) -{ - auto dict = asDictionary(); - if (dict) { - dict->removeKey(key); - } else { - typeWarning("dictionary", "ignoring key removal request"); - QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring removeKey"); - } -} - -QPDFObjectHandle -QPDFObjectHandle::removeKeyAndGetOld(std::string const& key) -{ - auto result = QPDFObjectHandle::newNull(); - auto dict = asDictionary(); - if (dict) { - result = dict->getKey(key); - } - removeKey(key); - return result; -} - -// Stream accessors - -QPDFObjectHandle -QPDFObjectHandle::getDict() const -{ - return asStreamWithAssert()->getDict(); -} - -void -QPDFObjectHandle::setFilterOnWrite(bool val) -{ - asStreamWithAssert()->setFilterOnWrite(val); -} - -bool -QPDFObjectHandle::getFilterOnWrite() -{ - return asStreamWithAssert()->getFilterOnWrite(); -} - -bool -QPDFObjectHandle::isDataModified() -{ - return asStreamWithAssert()->isDataModified(); -} - -void -QPDFObjectHandle::replaceDict(QPDFObjectHandle const& new_dict) -{ - asStreamWithAssert()->replaceDict(new_dict); -} - -std::shared_ptr -QPDFObjectHandle::getStreamData(qpdf_stream_decode_level_e level) -{ - return asStreamWithAssert()->getStreamData(level); -} - -std::shared_ptr -QPDFObjectHandle::getRawStreamData() -{ - return asStreamWithAssert()->getRawStreamData(); -} - -bool -QPDFObjectHandle::pipeStreamData( - Pipeline* p, - bool* filtering_attempted, - int encode_flags, - qpdf_stream_decode_level_e decode_level, - bool suppress_warnings, - bool will_retry) -{ - return asStreamWithAssert()->pipeStreamData( - p, filtering_attempted, encode_flags, decode_level, suppress_warnings, will_retry); -} - -bool -QPDFObjectHandle::pipeStreamData( - Pipeline* p, - int encode_flags, - qpdf_stream_decode_level_e decode_level, - bool suppress_warnings, - bool will_retry) -{ - bool filtering_attempted; - asStreamWithAssert()->pipeStreamData( - p, &filtering_attempted, encode_flags, decode_level, suppress_warnings, will_retry); - return filtering_attempted; -} - -bool -QPDFObjectHandle::pipeStreamData(Pipeline* p, bool filter, bool normalize, bool compress) -{ - int encode_flags = 0; - qpdf_stream_decode_level_e decode_level = qpdf_dl_none; - if (filter) { - decode_level = qpdf_dl_generalized; - if (normalize) { - encode_flags |= qpdf_ef_normalize; - } - if (compress) { - encode_flags |= qpdf_ef_compress; - } - } - return pipeStreamData(p, encode_flags, decode_level, false); -} - -void -QPDFObjectHandle::replaceStreamData( - std::shared_ptr data, - QPDFObjectHandle const& filter, - QPDFObjectHandle const& decode_parms) -{ - asStreamWithAssert()->replaceStreamData(data, filter, decode_parms); -} - -void -QPDFObjectHandle::replaceStreamData( - std::string const& data, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms) -{ - auto b = std::make_shared(data.length()); - unsigned char* bp = b->getBuffer(); - if (bp) { - memcpy(bp, data.c_str(), data.length()); - } - asStreamWithAssert()->replaceStreamData(b, filter, decode_parms); -} - -void -QPDFObjectHandle::replaceStreamData( - std::shared_ptr provider, - QPDFObjectHandle const& filter, - QPDFObjectHandle const& decode_parms) -{ - asStreamWithAssert()->replaceStreamData(provider, filter, decode_parms); -} - -namespace -{ - class FunctionProvider: public QPDFObjectHandle::StreamDataProvider - { - public: - FunctionProvider(std::function provider) : - StreamDataProvider(false), - p1(provider), - p2(nullptr) - { - } - FunctionProvider(std::function provider) : - StreamDataProvider(true), - p1(nullptr), - p2(provider) - { - } +// Dictionary mutators are in QPDF_Dictionary.cc - void - provideStreamData(QPDFObjGen const&, Pipeline* pipeline) override - { - p1(pipeline); - } - - bool - provideStreamData( - QPDFObjGen const&, Pipeline* pipeline, bool suppress_warnings, bool will_retry) override - { - return p2(pipeline, suppress_warnings, will_retry); - } - - private: - std::function p1; - std::function p2; - }; -} // namespace - -void -QPDFObjectHandle::replaceStreamData( - std::function provider, - QPDFObjectHandle const& filter, - QPDFObjectHandle const& decode_parms) -{ - auto sdp = std::shared_ptr(new FunctionProvider(provider)); - asStreamWithAssert()->replaceStreamData(sdp, filter, decode_parms); -} - -void -QPDFObjectHandle::replaceStreamData( - std::function provider, - QPDFObjectHandle const& filter, - QPDFObjectHandle const& decode_parms) -{ - auto sdp = std::shared_ptr(new FunctionProvider(provider)); - asStreamWithAssert()->replaceStreamData(sdp, filter, decode_parms); -} +// Stream accessors are in QPDF_Stream.cc std::map QPDFObjectHandle::getPageImages() @@ -1449,12 +1291,12 @@ QPDFObjectHandle::arrayOrStreamToStreamArray( { all_description = description; std::vector result; - if (auto array = asArray()) { - int n_items = array->size(); + if (auto array = as_array(strict)) { + int n_items = array.size(); for (int i = 0; i < n_items; ++i) { - QPDFObjectHandle item = array->at(i).second; + QPDFObjectHandle item = array.at(i).second; if (item.isStream()) { - result.push_back(item); + result.emplace_back(item); } else { QTC::TC("qpdf", "QPDFObjectHandle non-stream in stream array"); warn( @@ -1468,7 +1310,7 @@ QPDFObjectHandle::arrayOrStreamToStreamArray( } } } else if (isStream()) { - result.push_back(*this); + result.emplace_back(*this); } else if (!isNull()) { warn( getOwningQPDF(), @@ -1602,7 +1444,7 @@ QPDFObjectHandle::unparseResolved() const std::string QPDFObjectHandle::unparseBinary() const { - if (auto str = asString()) { + if (auto str = as()) { return str->unparse(true); } else { return unparse(); @@ -1633,7 +1475,7 @@ QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_ } else if (!obj) { throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle"); } else { - obj->writeJSON(json_version, p); + obj->write_json(json_version, p); } } @@ -1645,18 +1487,6 @@ QPDFObjectHandle::writeJSON( writeJSON(json_version, jw, dereference_indirect); } -JSON -QPDFObjectHandle::getStreamJSON( - int json_version, - qpdf_json_stream_data_e json_data, - qpdf_stream_decode_level_e decode_level, - Pipeline* p, - std::string const& data_filename) -{ - return asStreamWithAssert()->getStreamJSON( - json_version, json_data, decode_level, p, data_filename); -} - QPDFObjectHandle QPDFObjectHandle::wrapInArray() { @@ -1857,7 +1687,7 @@ QPDFObjectHandle::addContentTokenFilter(std::shared_ptr filter) void QPDFObjectHandle::addTokenFilter(std::shared_ptr filter) { - return asStreamWithAssert()->addTokenFilter(filter); + return as_stream(error).addTokenFilter(filter); } QPDFObjectHandle @@ -1882,43 +1712,43 @@ QPDFObjectHandle::getParsedOffset() const QPDFObjectHandle QPDFObjectHandle::newBool(bool value) { - return {QPDF_Bool::create(value)}; + return {QPDFObject::create(value)}; } QPDFObjectHandle QPDFObjectHandle::newNull() { - return {QPDF_Null::create()}; + return {QPDFObject::create()}; } QPDFObjectHandle QPDFObjectHandle::newInteger(long long value) { - return {QPDF_Integer::create(value)}; + return {QPDFObject::create(value)}; } QPDFObjectHandle QPDFObjectHandle::newReal(std::string const& value) { - return {QPDF_Real::create(value)}; + return {QPDFObject::create(value)}; } QPDFObjectHandle QPDFObjectHandle::newReal(double value, int decimal_places, bool trim_trailing_zeroes) { - return {QPDF_Real::create(value, decimal_places, trim_trailing_zeroes)}; + return {QPDFObject::create(value, decimal_places, trim_trailing_zeroes)}; } QPDFObjectHandle QPDFObjectHandle::newName(std::string const& name) { - return {QPDF_Name::create(name)}; + return {QPDFObject::create(name)}; } QPDFObjectHandle QPDFObjectHandle::newString(std::string const& str) { - return {QPDF_String::create(str)}; + return {QPDFObject::create(str)}; } QPDFObjectHandle @@ -1930,13 +1760,13 @@ QPDFObjectHandle::newUnicodeString(std::string const& utf8_str) QPDFObjectHandle QPDFObjectHandle::newOperator(std::string const& value) { - return {QPDF_Operator::create(value)}; + return {QPDFObject::create(value)}; } QPDFObjectHandle QPDFObjectHandle::newInlineImage(std::string const& value) { - return {QPDF_InlineImage::create(value)}; + return {QPDFObject::create(value)}; } QPDFObjectHandle @@ -1948,7 +1778,7 @@ QPDFObjectHandle::newArray() QPDFObjectHandle QPDFObjectHandle::newArray(std::vector const& items) { - return {QPDF_Array::create(items)}; + return {QPDFObject::create(items)}; } QPDFObjectHandle @@ -2008,7 +1838,7 @@ QPDFObjectHandle::newDictionary() QPDFObjectHandle QPDFObjectHandle::newDictionary(std::map const& items) { - return {QPDF_Dictionary::create(items)}; + return {QPDFObject::create(items)}; } QPDFObjectHandle @@ -2054,7 +1884,7 @@ void QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf, std::string const& object_description) { if (obj) { - auto descr = std::make_shared(object_description); + auto descr = std::make_shared(object_description); obj->setDescription(owning_qpdf, descr); } } @@ -2096,23 +1926,22 @@ QPDFObjectHandle::makeDirect(QPDFObjGen::set& visited, bool stop_at_streams) if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) { this->obj = obj->copy(true); - } else if (isArray()) { + } else if (auto a = as_array(strict)) { std::vector items; - auto array = asArray(); - int n = array->size(); - for (int i = 0; i < n; ++i) { - items.push_back(array->at(i).second); + for (auto const& item: a) { + items.emplace_back(item); items.back().makeDirect(visited, stop_at_streams); } - this->obj = QPDF_Array::create(items); + this->obj = QPDFObject::create(items); } else if (isDictionary()) { std::map items; - auto dict = asDictionary(); - for (auto const& key: getKeys()) { - items[key] = dict->getKey(key); - items[key].makeDirect(visited, stop_at_streams); + for (auto const& [key, value]: as_dictionary(strict)) { + if (!value.null()) { + items.insert({key, value}); + items[key].makeDirect(visited, stop_at_streams); + } } - this->obj = QPDF_Dictionary::create(items); + this->obj = QPDFObject::create(items); } else if (isStream()) { QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1); if (!stop_at_streams) { @@ -2349,19 +2178,6 @@ QPDFObjectHandle::isImage(bool exclude_imagemask) const } void -QPDFObjectHandle::checkOwnership(QPDFObjectHandle const& item) const -{ - auto qpdf = getOwningQPDF(); - auto item_qpdf = item.getOwningQPDF(); - if ((qpdf != nullptr) && (item_qpdf != nullptr) && (qpdf != item_qpdf)) { - QTC::TC("qpdf", "QPDFObjectHandle check ownership"); - throw std::logic_error( - "Attempting to add an object from a different QPDF. Use " - "QPDF::copyForeignObject to add objects from another file."); - } -} - -void QPDFObjectHandle::assertPageObject() const { if (!isPageObject()) { diff --git a/libqpdf/QPDFOutlineObjectHelper.cc b/libqpdf/QPDFOutlineObjectHelper.cc index 4da38b8..8d561f8 100644 --- a/libqpdf/QPDFOutlineObjectHelper.cc +++ b/libqpdf/QPDFOutlineObjectHelper.cc @@ -9,8 +9,8 @@ QPDFOutlineObjectHelper::Members::Members(QPDFOutlineDocumentHelper& dh) : } QPDFOutlineObjectHelper::QPDFOutlineObjectHelper( - QPDFObjectHandle oh, QPDFOutlineDocumentHelper& dh, int depth) : - QPDFObjectHelper(oh), + QPDFObjectHandle a_oh, QPDFOutlineDocumentHelper& dh, int depth) : + QPDFObjectHelper(a_oh), m(new Members(dh)) { if (depth > 50) { @@ -18,13 +18,13 @@ QPDFOutlineObjectHelper::QPDFOutlineObjectHelper( // to 1. return; } - if (QPDFOutlineDocumentHelper::Accessor::checkSeen(m->dh, this->oh.getObjGen())) { + if (QPDFOutlineDocumentHelper::Accessor::checkSeen(m->dh, a_oh.getObjGen())) { QTC::TC("qpdf", "QPDFOutlineObjectHelper loop"); return; } QPDFObjGen::set children; - QPDFObjectHandle cur = oh.getKey("/First"); + QPDFObjectHandle cur = a_oh.getKey("/First"); while (!cur.isNull() && cur.isIndirect() && children.add(cur)) { QPDFOutlineObjectHelper new_ooh(cur, dh, 1 + depth); new_ooh.m->parent = std::make_shared(*this); @@ -50,11 +50,11 @@ QPDFOutlineObjectHelper::getDest() { QPDFObjectHandle dest; QPDFObjectHandle A; - if (this->oh.hasKey("/Dest")) { + if (oh().hasKey("/Dest")) { QTC::TC("qpdf", "QPDFOutlineObjectHelper direct dest"); - dest = this->oh.getKey("/Dest"); + dest = oh().getKey("/Dest"); } else if ( - (A = this->oh.getKey("/A")).isDictionary() && A.getKey("/S").isName() && + (A = oh().getKey("/A")).isDictionary() && A.getKey("/S").isName() && (A.getKey("/S").getName() == "/GoTo") && A.hasKey("/D")) { QTC::TC("qpdf", "QPDFOutlineObjectHelper action dest"); dest = A.getKey("/D"); @@ -85,8 +85,8 @@ int QPDFOutlineObjectHelper::getCount() { int count = 0; - if (this->oh.hasKey("/Count")) { - count = this->oh.getKey("/Count").getIntValueAsInt(); + if (oh().hasKey("/Count")) { + count = oh().getKey("/Count").getIntValueAsInt(); } return count; } @@ -95,8 +95,8 @@ std::string QPDFOutlineObjectHelper::getTitle() { std::string result; - if (this->oh.hasKey("/Title")) { - result = this->oh.getKey("/Title").getUTF8Value(); + if (oh().hasKey("/Title")) { + result = oh().getKey("/Title").getUTF8Value(); } return result; } diff --git a/libqpdf/QPDFPageObjectHelper.cc b/libqpdf/QPDFPageObjectHelper.cc index f474e1c..1e22778 100644 --- a/libqpdf/QPDFPageObjectHelper.cc +++ b/libqpdf/QPDFPageObjectHelper.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -72,9 +73,12 @@ InlineImageTracker::convertIIDict(QPDFObjectHandle odict) QPDFObjectHandle dict = QPDFObjectHandle::newDictionary(); dict.replaceKey("/Type", QPDFObjectHandle::newName("/XObject")); dict.replaceKey("/Subtype", QPDFObjectHandle::newName("/Image")); - std::set keys = odict.getKeys(); - for (auto key: keys) { - QPDFObjectHandle value = odict.getKey(key); + for (auto const& [k, v]: odict.as_dictionary()) { + if (v.null()) { + continue; + } + auto key = k; + auto value = v; if (key == "/BPC") { key = "/BitsPerComponent"; } else if (key == "/CS") { @@ -227,9 +231,9 @@ QPDFPageObjectHelper::getAttribute( std::function get_fallback, bool copy_if_fallback) { - const bool is_form_xobject = this->oh.isFormXObject(); + const bool is_form_xobject = oh().isFormXObject(); bool inherited = false; - auto dict = is_form_xobject ? oh.getDict() : oh; + auto dict = is_form_xobject ? oh().getDict() : oh(); auto result = dict.getKey(name); if (!is_form_xobject && result.isNull() && @@ -324,23 +328,24 @@ QPDFPageObjectHelper::forEachXObject( QTC::TC( "qpdf", "QPDFPageObjectHelper::forEachXObject", - recursive ? (this->oh.isFormXObject() ? 0 : 1) : (this->oh.isFormXObject() ? 2 : 3)); + recursive ? (oh().isFormXObject() ? 0 : 1) : (oh().isFormXObject() ? 2 : 3)); QPDFObjGen::set seen; std::list queue; - queue.push_back(*this); + queue.emplace_back(*this); while (!queue.empty()) { auto& ph = queue.front(); if (seen.add(ph)) { auto xobj_dict = ph.getAttribute("/Resources", false).getKeyIfDict("/XObject"); - if (xobj_dict.isDictionary()) { - for (auto const& key: xobj_dict.getKeys()) { - QPDFObjectHandle obj = xobj_dict.getKey(key); - if ((!selector) || selector(obj)) { - action(obj, xobj_dict, key); - } - if (recursive && obj.isFormXObject()) { - queue.emplace_back(obj); - } + for (auto const& [key, value]: xobj_dict.as_dictionary()) { + if (value.null()) { + continue; + } + auto obj = value; + if ((!selector) || selector(obj)) { + action(obj, xobj_dict, key); + } + if (recursive && obj.isFormXObject()) { + queue.emplace_back(obj); } } } @@ -402,28 +407,27 @@ QPDFPageObjectHelper::externalizeInlineImages(size_t min_size, bool shallow) // Calling mergeResources also ensures that /XObject becomes direct and is not shared with // other pages. resources.mergeResources("<< /XObject << >> >>"_qpdf); - InlineImageTracker iit(this->oh.getOwningQPDF(), min_size, resources); + InlineImageTracker iit(oh().getOwningQPDF(), min_size, resources); Pl_Buffer b("new page content"); bool filtered = false; try { filterContents(&iit, &b); filtered = true; } catch (std::exception& e) { - this->oh.warnIfPossible( + oh().warnIfPossible( std::string("Unable to filter content stream: ") + e.what() + - "; not attempting to externalize inline images" - " from this stream"); + "; not attempting to externalize inline images from this stream"); } if (filtered && iit.any_images) { - if (this->oh.isFormXObject()) { - this->oh.replaceStreamData( + if (oh().isFormXObject()) { + oh().replaceStreamData( b.getBufferSharedPointer(), QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull()); } else { - this->oh.replaceKey( + oh().replaceKey( "/Contents", - QPDFObjectHandle::newStream(&this->oh.getQPDF(), b.getBufferSharedPointer())); + QPDFObjectHandle::newStream(&oh().getQPDF(), b.getBufferSharedPointer())); } } } else { @@ -439,7 +443,7 @@ std::vector QPDFPageObjectHelper::getAnnotations(std::string const& only_subtype) { std::vector result; - QPDFObjectHandle annots = this->oh.getKey("/Annots"); + QPDFObjectHandle annots = oh().getKey("/Annots"); if (annots.isArray()) { int nannots = annots.getArrayNItems(); for (int i = 0; i < nannots; ++i) { @@ -455,25 +459,25 @@ QPDFPageObjectHelper::getAnnotations(std::string const& only_subtype) std::vector QPDFPageObjectHelper::getPageContents() { - return this->oh.getPageContents(); + return oh().getPageContents(); } void QPDFPageObjectHelper::addPageContents(QPDFObjectHandle contents, bool first) { - this->oh.addPageContents(contents, first); + oh().addPageContents(contents, first); } void QPDFPageObjectHelper::rotatePage(int angle, bool relative) { - this->oh.rotatePage(angle, relative); + oh().rotatePage(angle, relative); } void QPDFPageObjectHelper::coalesceContentStreams() { - this->oh.coalesceContentStreams(); + oh().coalesceContentStreams(); } void @@ -485,10 +489,10 @@ QPDFPageObjectHelper::parsePageContents(QPDFObjectHandle::ParserCallbacks* callb void QPDFPageObjectHelper::parseContents(QPDFObjectHandle::ParserCallbacks* callbacks) { - if (this->oh.isFormXObject()) { - this->oh.parseAsContents(callbacks); + if (oh().isFormXObject()) { + oh().parseAsContents(callbacks); } else { - this->oh.parsePageContents(callbacks); + oh().parsePageContents(callbacks); } } @@ -501,10 +505,10 @@ QPDFPageObjectHelper::filterPageContents(QPDFObjectHandle::TokenFilter* filter, void QPDFPageObjectHelper::filterContents(QPDFObjectHandle::TokenFilter* filter, Pipeline* next) { - if (this->oh.isFormXObject()) { - this->oh.filterAsContents(filter, next); + if (oh().isFormXObject()) { + oh().filterAsContents(filter, next); } else { - this->oh.filterPageContents(filter, next); + oh().filterPageContents(filter, next); } } @@ -517,10 +521,10 @@ QPDFPageObjectHelper::pipePageContents(Pipeline* p) void QPDFPageObjectHelper::pipeContents(Pipeline* p) { - if (this->oh.isFormXObject()) { - this->oh.pipeStreamData(p, 0, qpdf_dl_specialized); + if (oh().isFormXObject()) { + oh().pipeStreamData(p, 0, qpdf_dl_specialized); } else { - this->oh.pipePageContents(p); + oh().pipePageContents(p); } } @@ -528,10 +532,10 @@ void QPDFPageObjectHelper::addContentTokenFilter( std::shared_ptr token_filter) { - if (this->oh.isFormXObject()) { - this->oh.addTokenFilter(token_filter); + if (oh().isFormXObject()) { + oh().addTokenFilter(token_filter); } else { - this->oh.addContentTokenFilter(token_filter); + oh().addContentTokenFilter(token_filter); } } @@ -539,30 +543,28 @@ bool QPDFPageObjectHelper::removeUnreferencedResourcesHelper( QPDFPageObjectHelper ph, std::set& unresolved) { - bool is_page = (!ph.oh.isFormXObject()); + bool is_page = (!ph.oh().isFormXObject()); if (!is_page) { QTC::TC("qpdf", "QPDFPageObjectHelper filter form xobject"); } ResourceFinder rf; try { - auto q = ph.oh.getOwningQPDF(); + auto q = ph.oh().getOwningQPDF(); size_t before_nw = (q ? q->numWarnings() : 0); ph.parseContents(&rf); size_t after_nw = (q ? q->numWarnings() : 0); if (after_nw > before_nw) { - ph.oh.warnIfPossible( + ph.oh().warnIfPossible( "Bad token found while scanning content stream; " - "not attempting to remove unreferenced objects from" - " this object"); + "not attempting to remove unreferenced objects from this object"); return false; } } catch (std::exception& e) { QTC::TC("qpdf", "QPDFPageObjectHelper bad token finding names"); - ph.oh.warnIfPossible( + ph.oh().warnIfPossible( std::string("Unable to parse content stream: ") + e.what() + - "; not attempting to remove unreferenced objects" - " from this object"); + "; not attempting to remove unreferenced objects from this object"); return false; } @@ -646,7 +648,7 @@ QPDFPageObjectHelper::removeUnreferencedResources() any_failures = true; } }); - if (this->oh.isFormXObject() || (!any_failures)) { + if (oh().isFormXObject() || (!any_failures)) { removeUnreferencedResourcesHelper(*this, unresolved); } } @@ -654,9 +656,8 @@ QPDFPageObjectHelper::removeUnreferencedResources() QPDFPageObjectHelper QPDFPageObjectHelper::shallowCopyPage() { - QPDF& qpdf = - this->oh.getQPDF("QPDFPageObjectHelper::shallowCopyPage called with a direct object"); - QPDFObjectHandle new_page = this->oh.shallowCopy(); + QPDF& qpdf = oh().getQPDF("QPDFPageObjectHelper::shallowCopyPage called with a direct object"); + QPDFObjectHandle new_page = oh().shallowCopy(); return {qpdf.makeIndirectObject(new_page)}; } @@ -707,7 +708,7 @@ QPDFObjectHandle QPDFPageObjectHelper::getFormXObjectForPage(bool handle_transformations) { auto result = - this->oh.getQPDF("QPDFPageObjectHelper::getFormXObjectForPage called with a direct object") + oh().getQPDF("QPDFPageObjectHelper::getFormXObjectForPage called with a direct object") .newStream(); QPDFObjectHandle newdict = result.getDict(); newdict.replaceKey("/Type", QPDFObjectHandle::newName("/XObject")); @@ -716,13 +717,13 @@ QPDFPageObjectHelper::getFormXObjectForPage(bool handle_transformations) newdict.replaceKey("/Group", getAttribute("/Group", false).shallowCopy()); QPDFObjectHandle bbox = getTrimBox(false).shallowCopy(); if (!bbox.isRectangle()) { - this->oh.warnIfPossible( + oh().warnIfPossible( "bounding box is invalid; form" " XObject created from page will not work"); } newdict.replaceKey("/BBox", bbox); auto provider = - std::shared_ptr(new ContentProvider(this->oh)); + std::shared_ptr(new ContentProvider(oh())); result.replaceStreamData(provider, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull()); QPDFObjectHandle rotate_obj = getAttribute("/Rotate", false); QPDFObjectHandle scale_obj = getAttribute("/UserUnit", false); @@ -863,9 +864,8 @@ QPDFPageObjectHelper::placeFormXObject( void QPDFPageObjectHelper::flattenRotation(QPDFAcroFormDocumentHelper* afdh) { - QPDF& qpdf = - this->oh.getQPDF("QPDFPageObjectHelper::flattenRotation called with a direct object"); - auto rotate_oh = this->oh.getKey("/Rotate"); + QPDF& qpdf = oh().getQPDF("QPDFPageObjectHelper::flattenRotation called with a direct object"); + auto rotate_oh = oh().getKey("/Rotate"); int rotate = 0; if (rotate_oh.isInteger()) { rotate = rotate_oh.getIntValueAsInt(); @@ -873,7 +873,7 @@ QPDFPageObjectHelper::flattenRotation(QPDFAcroFormDocumentHelper* afdh) if (!((rotate == 90) || (rotate == 180) || (rotate == 270))) { return; } - auto mediabox = this->oh.getKey("/MediaBox"); + auto mediabox = oh().getKey("/MediaBox"); if (!mediabox.isRectangle()) { return; } @@ -887,7 +887,7 @@ QPDFPageObjectHelper::flattenRotation(QPDFAcroFormDocumentHelper* afdh) "/ArtBox", }; for (auto const& boxkey: boxes) { - auto box = this->oh.getKey(boxkey); + auto box = oh().getKey(boxkey); if (!box.isRectangle()) { continue; } @@ -930,7 +930,7 @@ QPDFPageObjectHelper::flattenRotation(QPDFAcroFormDocumentHelper* afdh) break; } - this->oh.replaceKey(boxkey, QPDFObjectHandle::newFromRectangle(new_rect)); + oh().replaceKey(boxkey, QPDFObjectHandle::newFromRectangle(new_rect)); } // When we rotate the page, pivot about the point 0, 0 and then translate so the page is visible @@ -962,16 +962,16 @@ QPDFPageObjectHelper::flattenRotation(QPDFAcroFormDocumentHelper* afdh) break; } std::string cm_str = std::string("q\n") + cm.unparse() + " cm\n"; - this->oh.addPageContents(QPDFObjectHandle::newStream(&qpdf, cm_str), true); - this->oh.addPageContents(qpdf.newStream("\nQ\n"), false); - this->oh.removeKey("/Rotate"); + oh().addPageContents(QPDFObjectHandle::newStream(&qpdf, cm_str), true); + oh().addPageContents(qpdf.newStream("\nQ\n"), false); + oh().removeKey("/Rotate"); QPDFObjectHandle rotate_obj = getAttribute("/Rotate", false); if (!rotate_obj.isNull()) { QTC::TC("qpdf", "QPDFPageObjectHelper flatten inherit rotate"); - this->oh.replaceKey("/Rotate", QPDFObjectHandle::newInteger(0)); + oh().replaceKey("/Rotate", QPDFObjectHandle::newInteger(0)); } - QPDFObjectHandle annots = this->oh.getKey("/Annots"); + QPDFObjectHandle annots = oh().getKey("/Annots"); if (annots.isArray()) { std::vector new_annots; std::vector new_fields; @@ -986,7 +986,7 @@ QPDFPageObjectHelper::flattenRotation(QPDFAcroFormDocumentHelper* afdh) for (auto const& f: new_fields) { afdh->addFormField(QPDFFormFieldObjectHelper(f)); } - this->oh.replaceKey("/Annots", QPDFObjectHandle::newArray(new_annots)); + oh().replaceKey("/Annots", QPDFObjectHandle::newArray(new_annots)); } } @@ -1005,7 +1005,7 @@ QPDFPageObjectHelper::copyAnnotations( QPDF& from_qpdf = from_page.getObjectHandle().getQPDF( "QPDFPageObjectHelper::copyAnnotations: from page is a direct object"); QPDF& this_qpdf = - this->oh.getQPDF("QPDFPageObjectHelper::copyAnnotations: this page is a direct object"); + oh().getQPDF("QPDFPageObjectHelper::copyAnnotations: this page is a direct object"); std::vector new_annots; std::vector new_fields; @@ -1032,9 +1032,9 @@ QPDFPageObjectHelper::copyAnnotations( afdh->transformAnnotations( old_annots, new_annots, new_fields, old_fields, cm, &from_qpdf, from_afdh); afdh->addAndRenameFormFields(new_fields); - auto annots = this->oh.getKey("/Annots"); + auto annots = oh().getKey("/Annots"); if (!annots.isArray()) { - annots = this->oh.replaceKeyAndGetNew("/Annots", QPDFObjectHandle::newArray()); + annots = oh().replaceKeyAndGetNew("/Annots", QPDFObjectHandle::newArray()); } for (auto const& annot: new_annots) { annots.appendItem(annot); diff --git a/libqpdf/QPDFParser.cc b/libqpdf/QPDFParser.cc index 4e9de63..8bb6fd4 100644 --- a/libqpdf/QPDFParser.cc +++ b/libqpdf/QPDFParser.cc @@ -4,18 +4,6 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include @@ -47,27 +35,27 @@ QPDFParser::parse(bool& empty, bool content_stream) } QTC::TC("qpdf", "QPDFParser eof in parse"); warn("unexpected EOF"); - return {QPDF_Null::create()}; + return {QPDFObject::create()}; case QPDFTokenizer::tt_bad: QTC::TC("qpdf", "QPDFParser bad token in parse"); - return {QPDF_Null::create()}; + return {QPDFObject::create()}; case QPDFTokenizer::tt_brace_open: case QPDFTokenizer::tt_brace_close: QTC::TC("qpdf", "QPDFParser bad brace"); warn("treating unexpected brace token as null"); - return {QPDF_Null::create()}; + return {QPDFObject::create()}; case QPDFTokenizer::tt_array_close: QTC::TC("qpdf", "QPDFParser bad array close"); warn("treating unexpected array close token as null"); - return {QPDF_Null::create()}; + return {QPDFObject::create()}; case QPDFTokenizer::tt_dict_close: QTC::TC("qpdf", "QPDFParser bad dictionary close"); warn("unexpected dictionary close token"); - return {QPDF_Null::create()}; + return {QPDFObject::create()}; case QPDFTokenizer::tt_array_open: case QPDFTokenizer::tt_dict_open: @@ -82,7 +70,7 @@ QPDFParser::parse(bool& empty, bool content_stream) return withDescription(tokenizer.getValue() == "true"); case QPDFTokenizer::tt_null: - return {QPDF_Null::create()}; + return {QPDFObject::create()}; case QPDFTokenizer::tt_integer: return withDescription(QUtil::string_to_ll(tokenizer.getValue().c_str())); @@ -103,7 +91,7 @@ QPDFParser::parse(bool& empty, bool content_stream) // not move the input source's offset. input.seek(input.getLastOffset(), SEEK_SET); empty = true; - return {QPDF_Null::create()}; + return {QPDFObject::create()}; } else { QTC::TC("qpdf", "QPDFParser treat word as string"); warn("unknown token while reading object; treating as string"); @@ -122,7 +110,7 @@ QPDFParser::parse(bool& empty, bool content_stream) default: warn("treating unknown token type as null while reading object"); - return {QPDF_Null::create()}; + return {QPDFObject::create()}; } } @@ -194,12 +182,12 @@ QPDFParser::parseRemainder(bool content_stream) } QTC::TC("qpdf", "QPDFParser eof in parseRemainder"); warn("unexpected EOF"); - return {QPDF_Null::create()}; + return {QPDFObject::create()}; case QPDFTokenizer::tt_bad: QTC::TC("qpdf", "QPDFParser bad token in parseRemainder"); if (tooManyBadTokens()) { - return {QPDF_Null::create()}; + return {QPDFObject::create()}; } addNull(); continue; @@ -209,7 +197,7 @@ QPDFParser::parseRemainder(bool content_stream) QTC::TC("qpdf", "QPDFParser bad brace in parseRemainder"); warn("treating unexpected brace token as null"); if (tooManyBadTokens()) { - return {QPDF_Null::create()}; + return {QPDFObject::create()}; } addNull(); continue; @@ -218,10 +206,12 @@ QPDFParser::parseRemainder(bool content_stream) if (bad_count && !max_bad_count) { // Trigger warning. (void)tooManyBadTokens(); - return {QPDF_Null::create()}; + return {QPDFObject::create()}; } if (frame->state == st_array) { - auto object = QPDF_Array::create(std::move(frame->olist), frame->null_count > 100); + auto object = frame->null_count > 100 + ? QPDFObject::create(std::move(frame->olist), true) + : QPDFObject::create(std::move(frame->olist)); setDescription(object, frame->offset - 1); // The `offset` points to the next of "[". Set the rewind offset to point to the // beginning of "[". This has been explicitly tested with whitespace surrounding the @@ -237,7 +227,7 @@ QPDFParser::parseRemainder(bool content_stream) QTC::TC("qpdf", "QPDFParser bad array close in parseRemainder"); warn("treating unexpected array close token as null"); if (tooManyBadTokens()) { - return {QPDF_Null::create()}; + return {QPDFObject::create()}; } addNull(); } @@ -247,7 +237,7 @@ QPDFParser::parseRemainder(bool content_stream) if (bad_count && !max_bad_count) { // Trigger warning. (void)tooManyBadTokens(); - return {QPDF_Null::create()}; + return {QPDFObject::create()}; } if (frame->state <= st_dictionary_value) { // Attempt to recover more or less gracefully from invalid dictionaries. @@ -258,7 +248,7 @@ QPDFParser::parseRemainder(bool content_stream) warn( frame->offset, "dictionary ended prematurely; using null as value for last key"); - dict[frame->key] = QPDF_Null::create(); + dict[frame->key] = QPDFObject::create(); } if (!frame->olist.empty()) { @@ -271,7 +261,7 @@ QPDFParser::parseRemainder(bool content_stream) dict["/Contents"] = QPDFObjectHandle::newString(frame->contents_string); dict["/Contents"].setParsedOffset(frame->contents_offset); } - auto object = QPDF_Dictionary::create(std::move(dict)); + auto object = QPDFObject::create(std::move(dict)); setDescription(object, frame->offset - 2); // The `offset` points to the next of "<<". Set the rewind offset to point to the // beginning of "<<". This has been explicitly tested with whitespace surrounding @@ -287,7 +277,7 @@ QPDFParser::parseRemainder(bool content_stream) QTC::TC("qpdf", "QPDFParser bad dictionary close in parseRemainder"); warn("unexpected dictionary close token"); if (tooManyBadTokens()) { - return {QPDF_Null::create()}; + return {QPDFObject::create()}; } addNull(); } @@ -298,7 +288,7 @@ QPDFParser::parseRemainder(bool content_stream) if (stack.size() > 499) { QTC::TC("qpdf", "QPDFParser too deep"); warn("ignoring excessively deeply nested data structure"); - return {QPDF_Null::create()}; + return {QPDFObject::create()}; } else { b_contents = false; stack.emplace_back( @@ -350,7 +340,7 @@ QPDFParser::parseRemainder(bool content_stream) QTC::TC("qpdf", "QPDFParser treat word as string in parseRemainder"); warn("unknown token while reading object; treating as string"); if (tooManyBadTokens()) { - return {QPDF_Null::create()}; + return {QPDFObject::create()}; } addScalar(tokenizer.getValue()); } @@ -377,7 +367,7 @@ QPDFParser::parseRemainder(bool content_stream) default: warn("treating unknown token type as null while reading object"); if (tooManyBadTokens()) { - return {QPDF_Null::create()}; + return {QPDFObject::create()}; } addNull(); } @@ -402,7 +392,7 @@ QPDFParser::add(std::shared_ptr&& obj) void QPDFParser::addNull() { - const static ObjectPtr null_obj = QPDF_Null::create(); + const static ObjectPtr null_obj = QPDFObject::create(); if (frame->state != st_dictionary_value) { // If state is st_dictionary_key then there is a missing key. Push onto olist for @@ -420,7 +410,7 @@ QPDFParser::addNull() void QPDFParser::addInt(int count) { - auto obj = QPDF_Integer::create(int_buffer[count % 2]); + auto obj = QPDFObject::create(int_buffer[count % 2]); obj->setDescription(context, description, last_offset_buffer[count % 2]); add(std::move(obj)); } @@ -435,7 +425,7 @@ QPDFParser::addScalar(Args&&... args) max_bad_count = 0; return; } - auto obj = T::create(args...); + auto obj = QPDFObject::create(std::forward(args)...); obj->setDescription(context, description, input.getLastOffset()); add(std::move(obj)); } @@ -444,7 +434,7 @@ template QPDFObjectHandle QPDFParser::withDescription(Args&&... args) { - auto obj = T::create(args...); + auto obj = QPDFObject::create(std::forward(args)...); obj->setDescription(context, description, start); return {obj}; } @@ -462,8 +452,8 @@ QPDFParser::fixMissingKeys() { std::set names; for (auto& obj: frame->olist) { - if (obj->getTypeCode() == ::ot_name) { - names.insert(obj->getStringValue()); + if (obj.getObj()->getTypeCode() == ::ot_name) { + names.insert(obj.getObj()->getStringValue()); } } int next_fake_key = 1; diff --git a/libqpdf/QPDFValue.cc b/libqpdf/QPDFValue.cc deleted file mode 100644 index 3441223..0000000 --- a/libqpdf/QPDFValue.cc +++ /dev/null @@ -1,62 +0,0 @@ -#include - -#include - -std::shared_ptr -QPDFValue::do_create(QPDFValue* object) -{ - std::shared_ptr obj(new QPDFObject()); - obj->value = std::shared_ptr(object); - return obj; -} - -std::string -QPDFValue::getDescription() -{ - if (object_description) { - switch (object_description->index()) { - case 0: - { - // Simple template string - auto description = std::get<0>(*object_description); - - if (auto pos = description.find("$OG"); pos != std::string::npos) { - description.replace(pos, 3, og.unparse(' ')); - } - if (auto pos = description.find("$PO"); pos != std::string::npos) { - qpdf_offset_t shift = (type_code == ::ot_dictionary) ? 2 - : (type_code == ::ot_array) ? 1 - : 0; - - description.replace(pos, 3, std::to_string(parsed_offset + shift)); - } - return description; - } - case 1: - { - // QPDF::JSONReactor generated description - auto j_descr = std::get<1>(*object_description); - return ( - *j_descr.input + (j_descr.object.empty() ? "" : ", " + j_descr.object) + - " at offset " + std::to_string(parsed_offset)); - } - case 2: - { - // Child object description - auto j_descr = std::get<2>(*object_description); - std::string result; - if (auto p = j_descr.parent.lock()) { - result = p->getDescription(); - } - result += j_descr.static_descr; - if (auto pos = result.find("$VD"); pos != std::string::npos) { - result.replace(pos, 3, j_descr.var_descr); - } - return result; - } - } - } else if (og.isIndirect()) { - return "object " + og.unparse(' '); - } - return {}; -} diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index d0d9159..c9b90cc 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -15,9 +15,8 @@ #include #include #include -#include -#include -#include +#include +#include #include #include #include @@ -27,6 +26,7 @@ #include using namespace std::literals; +using namespace qpdf; QPDFWriter::ProgressReporter::~ProgressReporter() // NOLINT (modernize-use-equals-default) { @@ -1129,12 +1129,12 @@ QPDFWriter::enqueueObject(QPDFObjectHandle object) return; } else if (!m->linearized) { if (object.isArray()) { - for (auto& item: object.getArrayAsVector()) { + for (auto& item: object.as_array()) { enqueueObject(item); } - } else if (object.isDictionary()) { - for (auto& item: object.getDictAsMap()) { - if (!item.second.isNull()) { + } else if (auto d = object.as_dictionary()) { + for (auto const& item: d) { + if (!item.second.null()) { enqueueObject(item.second); } } @@ -1173,10 +1173,13 @@ QPDFWriter::writeTrailer( writeString(" /Size "); writeString(std::to_string(size)); } else { - for (auto const& key: trailer.getKeys()) { + for (auto const& [key, value]: trailer.as_dictionary()) { + if (value.null()) { + continue; + } writeStringQDF(" "); writeStringNoQDF(" "); - writeString(QPDF_Name::normalizeName(key)); + writeString(Name::normalize(key)); writeString(" "); if (key == "/Size") { writeString(std::to_string(size)); @@ -1187,7 +1190,7 @@ QPDFWriter::writeTrailer( writePad(QIntC::to_size(pos - m->pipeline->getCount() + 21)); } } else { - unparseChild(trailer.getKey(key), 1, 0); + unparseChild(value, 1, 0); } writeStringQDF("\n"); } @@ -1347,7 +1350,7 @@ QPDFWriter::unparseObject( // [ in the /H key of the linearization parameter dictionary. We'll do this unconditionally // for all arrays because it looks nicer and doesn't make the files that much bigger. writeString("["); - for (auto const& item: object.getArrayAsVector()) { + for (auto const& item: object.as_array()) { writeString(indent); writeStringQDF(" "); unparseChild(item, level + 1, child_flags); @@ -1498,20 +1501,18 @@ QPDFWriter::unparseObject( writeString("<<"); - for (auto& item: object.getDictAsMap()) { - if (!item.second.isNull()) { - auto const& key = item.first; + for (auto const& [key, value]: object.as_dictionary()) { + if (!value.null()) { writeString(indent); writeStringQDF(" "); - writeString(QPDF_Name::normalizeName(key)); + writeString(Name::normalize(key)); writeString(" "); if (key == "/Contents" && object.isDictionaryOfType("/Sig") && object.hasKey("/ByteRange")) { QTC::TC("qpdf", "QPDFWriter no encryption sig contents"); - unparseChild( - item.second, level + 1, child_flags | f_hex_string | f_no_encryption); + unparseChild(value, level + 1, child_flags | f_hex_string | f_no_encryption); } else { - unparseChild(item.second, level + 1, child_flags); + unparseChild(value, level + 1, child_flags); } } } @@ -1891,12 +1892,10 @@ QPDFWriter::generateID() } seed += " QPDF "; if (trailer.hasKey("/Info")) { - QPDFObjectHandle info = trailer.getKey("/Info"); - for (auto const& key: info.getKeys()) { - QPDFObjectHandle obj = info.getKey(key); - if (obj.isString()) { + for (auto const& item: trailer.getKey("/Info").as_dictionary()) { + if (item.second.isString()) { seed += " "; - seed += obj.getStringValue(); + seed += item.second.getStringValue(); } } } @@ -1922,8 +1921,7 @@ QPDFWriter::generateID() void QPDFWriter::initializeSpecialStreams() { - // Mark all page content streams in case we are filtering or - // normalizing. + // Mark all page content streams in case we are filtering or normalizing. std::vector pages = m->pdf.getAllPages(); int num = 0; for (auto& page: pages) { @@ -2939,8 +2937,10 @@ QPDFWriter::enqueueObjectsStandard() // Next place any other objects referenced from the trailer dictionary into the queue, handling // direct objects recursively. Root is already there, so enqueuing it a second time is a no-op. - for (auto const& key: trailer.getKeys()) { - enqueueObject(trailer.getKey(key)); + for (auto& item: trailer.as_dictionary()) { + if (!item.second.null()) { + enqueueObject(item.second); + } } } @@ -2962,9 +2962,11 @@ QPDFWriter::enqueueObjectsPCLm() // enqueue all the strips for each page QPDFObjectHandle strips = page.getKey("/Resources").getKey("/XObject"); - for (auto const& image: strips.getKeys()) { - enqueueObject(strips.getKey(image)); - enqueueObject(QPDFObjectHandle::newStream(&m->pdf, image_transform_content)); + for (auto& image: strips.as_dictionary()) { + if (!image.second.null()) { + enqueueObject(image.second); + enqueueObject(QPDFObjectHandle::newStream(&m->pdf, image_transform_content)); + } } } diff --git a/libqpdf/QPDF_Array.cc b/libqpdf/QPDF_Array.cc index 4ce15ad..2c4104c 100644 --- a/libqpdf/QPDF_Array.cc +++ b/libqpdf/QPDF_Array.cc @@ -1,54 +1,31 @@ -#include +#include -#include -#include -#include #include +using namespace std::literals; +using namespace qpdf; + static const QPDFObjectHandle null_oh = QPDFObjectHandle::newNull(); inline void -QPDF_Array::checkOwnership(QPDFObjectHandle const& item) const -{ - if (auto obj = item.getObjectPtr()) { - if (qpdf) { - if (auto item_qpdf = obj->getQPDF()) { - if (qpdf != item_qpdf) { - throw std::logic_error( - "Attempting to add an object from a different QPDF. Use " - "QPDF::copyForeignObject to add objects from another file."); - } - } - } - } else { +Array::checkOwnership(QPDFObjectHandle const& item) const +{ + if (!item) { throw std::logic_error("Attempting to add an uninitialized object to a QPDF_Array."); } + if (qpdf() && item.qpdf() && qpdf() != item.qpdf()) { + throw std::logic_error( + "Attempting to add an object from a different QPDF. Use " + "QPDF::copyForeignObject to add objects from another file."); + } } -QPDF_Array::QPDF_Array() : - QPDFValue(::ot_array) -{ -} - -QPDF_Array::QPDF_Array(QPDF_Array const& other) : - QPDFValue(::ot_array), - sp(other.sp ? std::make_unique(*other.sp) : nullptr) -{ -} - -QPDF_Array::QPDF_Array(std::vector const& v) : - QPDFValue(::ot_array) -{ - setFromVector(v); -} - -QPDF_Array::QPDF_Array(std::vector>&& v, bool sparse) : - QPDFValue(::ot_array) +QPDF_Array::QPDF_Array(std::vector&& v, bool sparse) { if (sparse) { sp = std::make_unique(); - for (auto&& item: v) { - if (item->getTypeCode() != ::ot_null || item->getObjGen().isIndirect()) { + for (auto& item: v) { + if (item.raw_type_code() != ::ot_null || item.indirect()) { sp->elements[sp->size] = std::move(item); } ++sp->size; @@ -58,191 +35,184 @@ QPDF_Array::QPDF_Array(std::vector>&& v, bool sparse } } -std::shared_ptr -QPDF_Array::create(std::vector const& items) +QPDF_Array* +Array::array() const { - return do_create(new QPDF_Array(items)); + if (auto a = as()) { + return a; + } + + throw std::runtime_error("Expected an array but found a non-array object"); + return nullptr; // unreachable } -std::shared_ptr -QPDF_Array::create(std::vector>&& items, bool sparse) +Array::iterator +Array::begin() { - return do_create(new QPDF_Array(std::move(items), sparse)); + if (auto a = as()) { + if (!a->sp) { + return a->elements.begin(); + } + if (!sp_elements) { + sp_elements = std::make_unique>(getAsVector()); + } + return sp_elements->begin(); + } + return {}; } -std::shared_ptr -QPDF_Array::copy(bool shallow) +Array::iterator +Array::end() { - if (shallow) { - return do_create(new QPDF_Array(*this)); - } else { - QTC::TC("qpdf", "QPDF_Array copy", sp ? 0 : 1); - if (sp) { - auto* result = new QPDF_Array(); - result->sp = std::make_unique(); - result->sp->size = sp->size; - for (auto const& element: sp->elements) { - auto const& obj = element.second; - result->sp->elements[element.first] = - obj->getObjGen().isIndirect() ? obj : obj->copy(); - } - return do_create(result); - } else { - std::vector> result; - result.reserve(elements.size()); - for (auto const& element: elements) { - result.push_back( - element ? (element->getObjGen().isIndirect() ? element : element->copy()) - : element); - } - return create(std::move(result), false); + if (auto a = as()) { + if (!a->sp) { + return a->elements.end(); } + if (!sp_elements) { + sp_elements = std::make_unique>(getAsVector()); + } + return sp_elements->end(); } + return {}; } -void -QPDF_Array::disconnect() +Array::const_iterator +Array::cbegin() { - if (sp) { - for (auto& item: sp->elements) { - auto& obj = item.second; - if (!obj->getObjGen().isIndirect()) { - obj->disconnect(); - } + if (auto a = as()) { + if (!a->sp) { + return a->elements.cbegin(); } - } else { - for (auto& obj: elements) { - if (!obj->getObjGen().isIndirect()) { - obj->disconnect(); - } + if (!sp_elements) { + sp_elements = std::make_unique>(getAsVector()); } + return sp_elements->cbegin(); } + return {}; } -std::string -QPDF_Array::unparse() +Array::const_iterator +Array::cend() { - std::string result = "[ "; - if (sp) { - int next = 0; - for (auto& item: sp->elements) { - int key = item.first; - for (int j = next; j < key; ++j) { - result += "null "; - } - auto og = item.second->resolved_object()->getObjGen(); - result += og.isIndirect() ? og.unparse(' ') + " R " : item.second->unparse() + " "; - next = ++key; + if (auto a = as()) { + if (!a->sp) { + return a->elements.cend(); } - for (int j = next; j < sp->size; ++j) { - result += "null "; - } - } else { - for (auto const& item: elements) { - auto og = item->resolved_object()->getObjGen(); - result += og.isIndirect() ? og.unparse(' ') + " R " : item->unparse() + " "; + if (!sp_elements) { + sp_elements = std::make_unique>(getAsVector()); } + return sp_elements->cend(); } - result += "]"; - return result; + return {}; } -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; +Array::const_reverse_iterator +Array::crbegin() +{ + if (auto a = as()) { + if (!a->sp) { + return a->elements.crbegin(); } - for (int j = next; j < sp->size; ++j) { - p.writeNext() << "null"; + if (!sp_elements) { + sp_elements = std::make_unique>(getAsVector()); } - } 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); - } + return sp_elements->crbegin(); + } + return {}; +} + +Array::const_reverse_iterator +Array::crend() +{ + if (auto a = as()) { + if (!a->sp) { + return a->elements.crend(); } + if (!sp_elements) { + sp_elements = std::make_unique>(getAsVector()); + } + return sp_elements->crend(); } - p.writeEnd(']'); + return {}; +} + +QPDFObjectHandle +Array::null() const +{ + return null_oh; +} + +int +Array::size() const +{ + auto a = array(); + return a->sp ? a->sp->size : int(a->elements.size()); } std::pair -QPDF_Array::at(int n) const noexcept +Array::at(int n) const { + auto a = array(); if (n < 0 || n >= size()) { return {false, {}}; - } else if (sp) { - auto const& iter = sp->elements.find(n); - return {true, iter == sp->elements.end() ? null_oh : (*iter).second}; - } else { - return {true, elements[size_t(n)]}; } + if (!a->sp) { + return {true, a->elements[size_t(n)]}; + } + auto const& iter = a->sp->elements.find(n); + return {true, iter == a->sp->elements.end() ? null() : iter->second}; } std::vector -QPDF_Array::getAsVector() const +Array::getAsVector() const { - if (sp) { + auto a = array(); + if (a->sp) { std::vector v; v.reserve(size_t(size())); - for (auto const& item: sp->elements) { + for (auto const& item: a->sp->elements) { v.resize(size_t(item.first), null_oh); v.emplace_back(item.second); } v.resize(size_t(size()), null_oh); return v; } else { - return {elements.cbegin(), elements.cend()}; + return a->elements; } } bool -QPDF_Array::setAt(int at, QPDFObjectHandle const& oh) +Array::setAt(int at, QPDFObjectHandle const& oh) { if (at < 0 || at >= size()) { return false; } + auto a = array(); checkOwnership(oh); - if (sp) { - sp->elements[at] = oh.getObj(); + if (a->sp) { + a->sp->elements[at] = oh; } else { - elements[size_t(at)] = oh.getObj(); + a->elements[size_t(at)] = oh; } return true; } void -QPDF_Array::setFromVector(std::vector const& v) +Array::setFromVector(std::vector const& v) { - elements.resize(0); - elements.reserve(v.size()); + auto a = array(); + a->elements.resize(0); + a->elements.reserve(v.size()); for (auto const& item: v) { checkOwnership(item); - elements.push_back(item.getObj()); + a->elements.emplace_back(item); } } bool -QPDF_Array::insert(int at, QPDFObjectHandle const& item) +Array::insert(int at, QPDFObjectHandle const& item) { + auto a = array(); int sz = size(); if (at < 0 || at > sz) { // As special case, also allow insert beyond the end @@ -251,61 +221,257 @@ QPDF_Array::insert(int at, QPDFObjectHandle const& item) push_back(item); } else { checkOwnership(item); - if (sp) { - auto iter = sp->elements.crbegin(); - while (iter != sp->elements.crend()) { + if (a->sp) { + auto iter = a->sp->elements.crbegin(); + while (iter != a->sp->elements.crend()) { auto key = (iter++)->first; if (key >= at) { - auto nh = sp->elements.extract(key); + auto nh = a->sp->elements.extract(key); ++nh.key(); - sp->elements.insert(std::move(nh)); + a->sp->elements.insert(std::move(nh)); } else { break; } } - sp->elements[at] = item.getObj(); - ++sp->size; + a->sp->elements[at] = item.getObj(); + ++a->sp->size; } else { - elements.insert(elements.cbegin() + at, item.getObj()); + a->elements.insert(a->elements.cbegin() + at, item.getObj()); } } return true; } void -QPDF_Array::push_back(QPDFObjectHandle const& item) +Array::push_back(QPDFObjectHandle const& item) { + auto a = array(); checkOwnership(item); - if (sp) { - sp->elements[(sp->size)++] = item.getObj(); + if (a->sp) { + a->sp->elements[(a->sp->size)++] = item; } else { - elements.push_back(item.getObj()); + a->elements.emplace_back(item); } } bool -QPDF_Array::erase(int at) +Array::erase(int at) { + auto a = array(); if (at < 0 || at >= size()) { return false; } - if (sp) { - auto end = sp->elements.end(); - if (auto iter = sp->elements.lower_bound(at); iter != end) { + if (a->sp) { + auto end = a->sp->elements.end(); + if (auto iter = a->sp->elements.lower_bound(at); iter != end) { if (iter->first == at) { iter++; - sp->elements.erase(at); + a->sp->elements.erase(at); } while (iter != end) { - auto nh = sp->elements.extract(iter++); + auto nh = a->sp->elements.extract(iter++); --nh.key(); - sp->elements.insert(std::move(nh)); + a->sp->elements.insert(std::move(nh)); } } - --(sp->size); + --(a->sp->size); } else { - elements.erase(elements.cbegin() + at); + a->elements.erase(a->elements.cbegin() + at); } return true; } + +int +QPDFObjectHandle::getArrayNItems() const +{ + if (auto array = as_array(strict)) { + return array.size(); + } + typeWarning("array", "treating as empty"); + QTC::TC("qpdf", "QPDFObjectHandle array treating as empty"); + return 0; +} + +QPDFObjectHandle +QPDFObjectHandle::getArrayItem(int n) const +{ + if (auto array = as_array(strict)) { + if (auto const [success, oh] = array.at(n); success) { + return oh; + } else { + objectWarning("returning null for out of bounds array access"); + QTC::TC("qpdf", "QPDFObjectHandle array bounds"); + } + } else { + typeWarning("array", "returning null"); + QTC::TC("qpdf", "QPDFObjectHandle array null for non-array"); + } + static auto constexpr msg = " -> null returned from invalid array access"sv; + return QPDF_Null::create(obj, msg, ""); +} + +bool +QPDFObjectHandle::isRectangle() const +{ + if (auto array = as_array(strict)) { + for (int i = 0; i < 4; ++i) { + if (auto item = array.at(i).second; !item.isNumber()) { + return false; + } + } + return array.size() == 4; + } + return false; +} + +bool +QPDFObjectHandle::isMatrix() const +{ + if (auto array = as_array(strict)) { + for (int i = 0; i < 6; ++i) { + if (auto item = array.at(i).second; !item.isNumber()) { + return false; + } + } + return array.size() == 6; + } + return false; +} + +QPDFObjectHandle::Rectangle +QPDFObjectHandle::getArrayAsRectangle() const +{ + if (auto array = as_array(strict)) { + if (array.size() != 4) { + return {}; + } + double items[4]; + for (int i = 0; i < 4; ++i) { + if (auto item = array.at(i).second; !item.getValueAsNumber(items[i])) { + return {}; + } + } + return { + std::min(items[0], items[2]), + std::min(items[1], items[3]), + std::max(items[0], items[2]), + std::max(items[1], items[3])}; + } + return {}; +} + +QPDFObjectHandle::Matrix +QPDFObjectHandle::getArrayAsMatrix() const +{ + if (auto array = as_array(strict)) { + if (array.size() != 6) { + return {}; + } + double items[6]; + for (int i = 0; i < 6; ++i) { + if (auto item = array.at(i).second; !item.getValueAsNumber(items[i])) { + return {}; + } + } + return {items[0], items[1], items[2], items[3], items[4], items[5]}; + } + return {}; +} + +std::vector +QPDFObjectHandle::getArrayAsVector() const +{ + if (auto array = as_array(strict)) { + return array.getAsVector(); + } + typeWarning("array", "treating as empty"); + QTC::TC("qpdf", "QPDFObjectHandle array treating as empty vector"); + return {}; +} + +void +QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const& item) +{ + if (auto array = as_array(strict)) { + if (!array.setAt(n, item)) { + objectWarning("ignoring attempt to set out of bounds array item"); + QTC::TC("qpdf", "QPDFObjectHandle set array bounds"); + } + } else { + typeWarning("array", "ignoring attempt to set item"); + QTC::TC("qpdf", "QPDFObjectHandle array ignoring set item"); + } +} +void +QPDFObjectHandle::setArrayFromVector(std::vector const& items) +{ + if (auto array = as_array(strict)) { + array.setFromVector(items); + } else { + typeWarning("array", "ignoring attempt to replace items"); + QTC::TC("qpdf", "QPDFObjectHandle array ignoring replace items"); + } +} + +void +QPDFObjectHandle::insertItem(int at, QPDFObjectHandle const& item) +{ + if (auto array = as_array(strict)) { + if (!array.insert(at, item)) { + objectWarning("ignoring attempt to insert out of bounds array item"); + QTC::TC("qpdf", "QPDFObjectHandle insert array bounds"); + } + } else { + typeWarning("array", "ignoring attempt to insert item"); + QTC::TC("qpdf", "QPDFObjectHandle array ignoring insert item"); + } +} + +QPDFObjectHandle +QPDFObjectHandle::insertItemAndGetNew(int at, QPDFObjectHandle const& item) +{ + insertItem(at, item); + return item; +} + +void +QPDFObjectHandle::appendItem(QPDFObjectHandle const& item) +{ + if (auto array = as_array(strict)) { + array.push_back(item); + } else { + typeWarning("array", "ignoring attempt to append item"); + QTC::TC("qpdf", "QPDFObjectHandle array ignoring append item"); + } +} + +QPDFObjectHandle +QPDFObjectHandle::appendItemAndGetNew(QPDFObjectHandle const& item) +{ + appendItem(item); + return item; +} + +void +QPDFObjectHandle::eraseItem(int at) +{ + if (auto array = as_array(strict)) { + if (!array.erase(at)) { + objectWarning("ignoring attempt to erase out of bounds array item"); + QTC::TC("qpdf", "QPDFObjectHandle erase array bounds"); + } + } else { + typeWarning("array", "ignoring attempt to erase item"); + QTC::TC("qpdf", "QPDFObjectHandle array ignoring erase item"); + } +} + +QPDFObjectHandle +QPDFObjectHandle::eraseItemAndGetOld(int at) +{ + auto array = as_array(strict); + auto result = (array && at < array.size() && at >= 0) ? array.at(at).second : newNull(); + eraseItem(at); + return result; +} diff --git a/libqpdf/QPDF_Bool.cc b/libqpdf/QPDF_Bool.cc deleted file mode 100644 index edb47a7..0000000 --- a/libqpdf/QPDF_Bool.cc +++ /dev/null @@ -1,39 +0,0 @@ -#include - -#include - -QPDF_Bool::QPDF_Bool(bool val) : - QPDFValue(::ot_boolean), - val(val) -{ -} - -std::shared_ptr -QPDF_Bool::create(bool value) -{ - return do_create(new QPDF_Bool(value)); -} - -std::shared_ptr -QPDF_Bool::copy(bool shallow) -{ - return create(val); -} - -std::string -QPDF_Bool::unparse() -{ - return (val ? "true" : "false"); -} - -void -QPDF_Bool::writeJSON(int json_version, JSON::Writer& p) -{ - p << val; -} - -bool -QPDF_Bool::getVal() const -{ - return this->val; -} diff --git a/libqpdf/QPDF_Destroyed.cc b/libqpdf/QPDF_Destroyed.cc deleted file mode 100644 index 34b2a9c..0000000 --- a/libqpdf/QPDF_Destroyed.cc +++ /dev/null @@ -1,35 +0,0 @@ -#include - -#include - -QPDF_Destroyed::QPDF_Destroyed() : - QPDFValue(::ot_destroyed) -{ -} - -std::shared_ptr -QPDF_Destroyed::getInstance() -{ - static std::shared_ptr instance(new QPDF_Destroyed()); - return instance; -} - -std::shared_ptr -QPDF_Destroyed::copy(bool shallow) -{ - throw std::logic_error("attempted to shallow copy QPDFObjectHandle from destroyed QPDF"); - return nullptr; -} - -std::string -QPDF_Destroyed::unparse() -{ - throw std::logic_error("attempted to unparse a QPDFObjectHandle from a destroyed QPDF"); - return ""; -} - -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 9567e3c..e28d30a 100644 --- a/libqpdf/QPDF_Dictionary.cc +++ b/libqpdf/QPDF_Dictionary.cc @@ -1,154 +1,194 @@ -#include +#include -#include #include -#include -#include -#include +#include using namespace std::literals; +using namespace qpdf; -QPDF_Dictionary::QPDF_Dictionary(std::map const& items) : - QPDFValue(::ot_dictionary), - items(items) +QPDF_Dictionary* +BaseDictionary::dict() const { + if (auto d = as()) { + return d; + } + throw std::runtime_error("Expected a dictionary but found a non-dictionary object"); + return nullptr; // unreachable } -QPDF_Dictionary::QPDF_Dictionary(std::map&& items) : - QPDFValue(::ot_dictionary), - items(items) +bool +BaseDictionary::hasKey(std::string const& key) const { + auto d = dict(); + return d->items.count(key) > 0 && !d->items[key].isNull(); } -std::shared_ptr -QPDF_Dictionary::create(std::map const& items) +QPDFObjectHandle +BaseDictionary::getKey(std::string const& key) const { - return do_create(new QPDF_Dictionary(items)); -} + auto d = dict(); -std::shared_ptr -QPDF_Dictionary::create(std::map&& items) -{ - return do_create(new QPDF_Dictionary(items)); + // PDF spec says fetching a non-existent key from a dictionary returns the null object. + auto item = d->items.find(key); + if (item != d->items.end()) { + // May be a null object + return item->second; + } + static auto constexpr msg = " -> dictionary key $VD"sv; + return QPDF_Null::create(obj, msg, key); } -std::shared_ptr -QPDF_Dictionary::copy(bool shallow) +std::set +BaseDictionary::getKeys() { - if (shallow) { - return create(items); - } else { - std::map new_items; - for (auto const& item: this->items) { - auto value = item.second; - new_items[item.first] = value.isIndirect() ? value : value.shallowCopy(); + std::set result; + for (auto& iter: dict()->items) { + if (!iter.second.isNull()) { + result.insert(iter.first); } - return create(new_items); } + return result; +} + +std::map const& +BaseDictionary::getAsMap() const +{ + return dict()->items; } void -QPDF_Dictionary::disconnect() +BaseDictionary::removeKey(std::string const& key) { - for (auto& iter: this->items) { - QPDFObjectHandle::DisconnectAccess::disconnect(iter.second); - } + // no-op if key does not exist + dict()->items.erase(key); } -std::string -QPDF_Dictionary::unparse() +void +BaseDictionary::replaceKey(std::string const& key, QPDFObjectHandle value) { - std::string result = "<< "; - for (auto& iter: this->items) { - if (!iter.second.isNull()) { - result += QPDF_Name::normalizeName(iter.first) + " " + iter.second.unparse() + " "; - } + auto d = dict(); + if (value.isNull() && !value.isIndirect()) { + // The PDF spec doesn't distinguish between keys with null values and missing keys. + // Allow indirect nulls which are equivalent to a dangling reference, which is + // permitted by the spec. + d->items.erase(key); + } else { + // add or replace value + d->items[key] = value; } - result += ">>"; - return result; } void -QPDF_Dictionary::writeJSON(int json_version, JSON::Writer& p) +QPDFObjectHandle::checkOwnership(QPDFObjectHandle const& item) const { - 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 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 { - p << "\"n:" << JSON::Writer::encode_string(QPDF_Name::normalizeName(iter.first)) - << "\": "; - } - iter.second.writeJSON(json_version, p); - } + auto qpdf = getOwningQPDF(); + auto item_qpdf = item.getOwningQPDF(); + if (qpdf && item_qpdf && qpdf != item_qpdf) { + QTC::TC("qpdf", "QPDFObjectHandle check ownership"); + throw std::logic_error( + "Attempting to add an object from a different QPDF. Use " + "QPDF::copyForeignObject to add objects from another file."); } - p.writeEnd('}'); } bool -QPDF_Dictionary::hasKey(std::string const& key) +QPDFObjectHandle::hasKey(std::string const& key) const { - return ((this->items.count(key) > 0) && (!this->items[key].isNull())); + auto dict = as_dictionary(strict); + if (dict) { + return dict.hasKey(key); + } else { + typeWarning("dictionary", "returning false for a key containment request"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary false for hasKey"); + return false; + } } QPDFObjectHandle -QPDF_Dictionary::getKey(std::string const& key) +QPDFObjectHandle::getKey(std::string const& key) const { - // PDF spec says fetching a non-existent key from a dictionary returns the null object. - auto item = this->items.find(key); - if (item != this->items.end()) { - // May be a null object - return item->second; - } else { - static auto constexpr msg = " -> dictionary key $VD"sv; - return QPDF_Null::create(shared_from_this(), msg, key); + if (auto dict = as_dictionary(strict)) { + return dict.getKey(key); } + typeWarning("dictionary", "returning null for attempted key retrieval"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary null for getKey"); + static auto constexpr msg = " -> null returned from getting key $VD from non-Dictionary"sv; + return QPDF_Null::create(obj, msg, ""); +} + +QPDFObjectHandle +QPDFObjectHandle::getKeyIfDict(std::string const& key) const +{ + return isNull() ? newNull() : getKey(key); } std::set -QPDF_Dictionary::getKeys() +QPDFObjectHandle::getKeys() const { - std::set result; - for (auto& iter: this->items) { - if (!iter.second.isNull()) { - result.insert(iter.first); - } + if (auto dict = as_dictionary(strict)) { + return dict.getKeys(); } - return result; + typeWarning("dictionary", "treating as empty"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary empty set for getKeys"); + return {}; } -std::map const& -QPDF_Dictionary::getAsMap() const +std::map +QPDFObjectHandle::getDictAsMap() const { - return this->items; + if (auto dict = as_dictionary(strict)) { + return dict.getAsMap(); + } + typeWarning("dictionary", "treating as empty"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary empty map for asMap"); + return {}; } void -QPDF_Dictionary::replaceKey(std::string const& key, QPDFObjectHandle value) +QPDFObjectHandle::replaceKey(std::string const& key, QPDFObjectHandle const& value) { - if (value.isNull() && !value.isIndirect()) { - // The PDF spec doesn't distinguish between keys with null values and missing keys. Allow - // indirect nulls which are equivalent to a dangling reference, which is permitted by the - // spec. - removeKey(key); - } else { - // add or replace value - this->items[key] = value; + if (auto dict = as_dictionary(strict)) { + checkOwnership(value); + dict.replaceKey(key, value); + return; } + typeWarning("dictionary", "ignoring key replacement request"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring replaceKey"); +} + +QPDFObjectHandle +QPDFObjectHandle::replaceKeyAndGetNew(std::string const& key, QPDFObjectHandle const& value) +{ + replaceKey(key, value); + return value; +} + +QPDFObjectHandle +QPDFObjectHandle::replaceKeyAndGetOld(std::string const& key, QPDFObjectHandle const& value) +{ + QPDFObjectHandle old = removeKeyAndGetOld(key); + replaceKey(key, value); + return old; } void -QPDF_Dictionary::removeKey(std::string const& key) +QPDFObjectHandle::removeKey(std::string const& key) { - // no-op if key does not exist - this->items.erase(key); + if (auto dict = as_dictionary(strict)) { + dict.removeKey(key); + return; + } + typeWarning("dictionary", "ignoring key removal request"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring removeKey"); +} + +QPDFObjectHandle +QPDFObjectHandle::removeKeyAndGetOld(std::string const& key) +{ + auto result = QPDFObjectHandle::newNull(); + if (auto dict = as_dictionary(strict)) { + result = dict.getKey(key); + } + removeKey(key); + return result; } diff --git a/libqpdf/QPDF_InlineImage.cc b/libqpdf/QPDF_InlineImage.cc deleted file mode 100644 index 3b8c12d..0000000 --- a/libqpdf/QPDF_InlineImage.cc +++ /dev/null @@ -1,33 +0,0 @@ -#include - -#include - -QPDF_InlineImage::QPDF_InlineImage(std::string const& val) : - QPDFValue(::ot_inlineimage), - val(val) -{ -} - -std::shared_ptr -QPDF_InlineImage::create(std::string const& val) -{ - return do_create(new QPDF_InlineImage(val)); -} - -std::shared_ptr -QPDF_InlineImage::copy(bool shallow) -{ - return create(val); -} - -std::string -QPDF_InlineImage::unparse() -{ - return this->val; -} - -void -QPDF_InlineImage::writeJSON(int json_version, JSON::Writer& p) -{ - p << "null"; -} diff --git a/libqpdf/QPDF_Integer.cc b/libqpdf/QPDF_Integer.cc deleted file mode 100644 index 5327edb..0000000 --- a/libqpdf/QPDF_Integer.cc +++ /dev/null @@ -1,40 +0,0 @@ -#include - -#include -#include - -QPDF_Integer::QPDF_Integer(long long val) : - QPDFValue(::ot_integer), - val(val) -{ -} - -std::shared_ptr -QPDF_Integer::create(long long value) -{ - return do_create(new QPDF_Integer(value)); -} - -std::shared_ptr -QPDF_Integer::copy(bool shallow) -{ - return create(val); -} - -std::string -QPDF_Integer::unparse() -{ - return std::to_string(this->val); -} - -void -QPDF_Integer::writeJSON(int json_version, JSON::Writer& p) -{ - p << std::to_string(this->val); -} - -long long -QPDF_Integer::getVal() const -{ - return this->val; -} diff --git a/libqpdf/QPDF_Name.cc b/libqpdf/QPDF_Name.cc index b66a8a2..e69de29 100644 --- a/libqpdf/QPDF_Name.cc +++ b/libqpdf/QPDF_Name.cc @@ -1,120 +0,0 @@ -#include - -#include -#include - -QPDF_Name::QPDF_Name(std::string const& name) : - QPDFValue(::ot_name), - name(name) -{ -} - -std::shared_ptr -QPDF_Name::create(std::string const& name) -{ - return do_create(new QPDF_Name(name)); -} - -std::shared_ptr -QPDF_Name::copy(bool shallow) -{ - return create(name); -} - -std::string -QPDF_Name::normalizeName(std::string const& name) -{ - if (name.empty()) { - return name; - } - std::string result; - result += name.at(0); - for (size_t i = 1; i < name.length(); ++i) { - char ch = name.at(i); - // Don't use locale/ctype here; follow PDF spec guidelines. - if (ch == '\0') { - // QPDFTokenizer embeds a null character to encode an invalid #. - result += "#"; - } else if ( - ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' || - ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) { - result += QUtil::hex_encode_char(ch); - } else { - result += ch; - } - } - return result; -} - -std::string -QPDF_Name::unparse() -{ - return normalizeName(this->name); -} - -std::pair -QPDF_Name::analyzeJSONEncoding(const std::string& name) -{ - 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& it: name) { - auto c = static_cast(it); - 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) { - p << "\"" << JSON::Writer::encode_string(normalizeName(name)) << "\""; - } else { - if (auto res = analyzeJSONEncoding(name); res.first) { - if (res.second) { - p << "\"" << name << "\""; - } else { - p << "\"" << JSON::Writer::encode_string(name) << "\""; - } - } else { - p << "\"n:" << JSON::Writer::encode_string(normalizeName(name)) << "\""; - } - } -} diff --git a/libqpdf/QPDF_Null.cc b/libqpdf/QPDF_Null.cc deleted file mode 100644 index 4fd36ab..0000000 --- a/libqpdf/QPDF_Null.cc +++ /dev/null @@ -1,51 +0,0 @@ -#include - -#include -#include - -QPDF_Null::QPDF_Null(QPDF* qpdf, QPDFObjGen og) : - QPDFValue(::ot_null, qpdf, og) -{ -} - -std::shared_ptr -QPDF_Null::create(QPDF* qpdf, QPDFObjGen og) -{ - return do_create(new QPDF_Null(qpdf, og)); -} - -std::shared_ptr -QPDF_Null::create( - std::shared_ptr parent, std::string_view const& static_descr, std::string var_descr) -{ - auto n = do_create(new QPDF_Null()); - n->setChildDescription(parent, static_descr, var_descr); - return n; -} - -std::shared_ptr -QPDF_Null::create( - std::shared_ptr parent, std::string_view const& static_descr, std::string var_descr) -{ - auto n = do_create(new QPDF_Null()); - n->setChildDescription(parent, static_descr, var_descr); - return n; -} - -std::shared_ptr -QPDF_Null::copy(bool shallow) -{ - return create(); -} - -std::string -QPDF_Null::unparse() -{ - return "null"; -} - -void -QPDF_Null::writeJSON(int json_version, JSON::Writer& p) -{ - p << "null"; -} diff --git a/libqpdf/QPDF_Operator.cc b/libqpdf/QPDF_Operator.cc deleted file mode 100644 index f0de6d3..0000000 --- a/libqpdf/QPDF_Operator.cc +++ /dev/null @@ -1,33 +0,0 @@ -#include - -#include - -QPDF_Operator::QPDF_Operator(std::string const& val) : - QPDFValue(::ot_operator), - val(val) -{ -} - -std::shared_ptr -QPDF_Operator::create(std::string const& val) -{ - return do_create(new QPDF_Operator(val)); -} - -std::shared_ptr -QPDF_Operator::copy(bool shallow) -{ - return create(val); -} - -std::string -QPDF_Operator::unparse() -{ - return val; -} - -void -QPDF_Operator::writeJSON(int json_version, JSON::Writer& p) -{ - p << "null"; -} diff --git a/libqpdf/QPDF_Real.cc b/libqpdf/QPDF_Real.cc deleted file mode 100644 index df1da62..0000000 --- a/libqpdf/QPDF_Real.cc +++ /dev/null @@ -1,58 +0,0 @@ -#include - -#include -#include - -QPDF_Real::QPDF_Real(std::string const& val) : - QPDFValue(::ot_real), - val(val) -{ -} - -QPDF_Real::QPDF_Real(double value, int decimal_places, bool trim_trailing_zeroes) : - QPDFValue(::ot_real), - val(QUtil::double_to_string(value, decimal_places, trim_trailing_zeroes)) -{ -} - -std::shared_ptr -QPDF_Real::create(std::string const& val) -{ - return do_create(new QPDF_Real(val)); -} - -std::shared_ptr -QPDF_Real::create(double value, int decimal_places, bool trim_trailing_zeroes) -{ - return do_create(new QPDF_Real(value, decimal_places, trim_trailing_zeroes)); -} - -std::shared_ptr -QPDF_Real::copy(bool shallow) -{ - return create(val); -} - -std::string -QPDF_Real::unparse() -{ - return this->val; -} - -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; - } - if (val.back() == '.') { - p << "0"; - } -} diff --git a/libqpdf/QPDF_Reserved.cc b/libqpdf/QPDF_Reserved.cc deleted file mode 100644 index 242567f..0000000 --- a/libqpdf/QPDF_Reserved.cc +++ /dev/null @@ -1,33 +0,0 @@ -#include - -#include - -QPDF_Reserved::QPDF_Reserved() : - QPDFValue(::ot_reserved) -{ -} - -std::shared_ptr -QPDF_Reserved::create() -{ - return do_create(new QPDF_Reserved()); -} - -std::shared_ptr -QPDF_Reserved::copy(bool shallow) -{ - return create(); -} - -std::string -QPDF_Reserved::unparse() -{ - throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object"); - return ""; -} - -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 3e1defa..c846b5a 100644 --- a/libqpdf/QPDF_Stream.cc +++ b/libqpdf/QPDF_Stream.cc @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -22,6 +22,9 @@ #include +using namespace std::literals; +using namespace qpdf; + namespace { class SF_Crypt: public QPDFStreamFilter @@ -60,16 +63,24 @@ namespace class StreamBlobProvider { public: - StreamBlobProvider(QPDF_Stream* stream, qpdf_stream_decode_level_e decode_level); - void operator()(Pipeline*); + StreamBlobProvider(Stream stream, qpdf_stream_decode_level_e decode_level) : + stream(stream), + decode_level(decode_level) + { + } + void + operator()(Pipeline* p) + { + stream.pipeStreamData(p, nullptr, 0, decode_level, false, false); + } private: - QPDF_Stream* stream; + Stream stream; qpdf_stream_decode_level_e decode_level; }; } // namespace -std::map QPDF_Stream::filter_abbreviations = { +std::map Stream::filter_abbreviations = { // The PDF specification provides these filter abbreviations for use in inline images, but // according to table H.1 in the pre-ISO versions of the PDF specification, Adobe Reader also // accepts them for stream filters. @@ -82,8 +93,8 @@ std::map QPDF_Stream::filter_abbreviations = { {"/DCT", "/DCTDecode"}, }; -std::map()>> - QPDF_Stream::filter_factories = { +std::map()>> Stream::filter_factories = + { {"/Crypt", []() { return std::make_shared(); }}, {"/FlateDecode", SF_FlateLzwDecode::flate_factory}, {"/LZWDecode", SF_FlateLzwDecode::lzw_factory}, @@ -93,90 +104,25 @@ std::map()>> {"/ASCIIHexDecode", SF_ASCIIHexDecode::factory}, }; -StreamBlobProvider::StreamBlobProvider( - QPDF_Stream* stream, qpdf_stream_decode_level_e decode_level) : - stream(stream), - decode_level(decode_level) +Stream::Stream( + QPDF& qpdf, QPDFObjGen og, QPDFObjectHandle stream_dict, qpdf_offset_t offset, size_t length) : + BaseHandle(QPDFObject::create(&qpdf, og, std::move(stream_dict), length)) { + auto descr = std::make_shared( + qpdf.getFilename() + ", stream object " + og.unparse(' ')); + obj->setDescription(&qpdf, descr, offset); + setDictDescription(); } void -StreamBlobProvider::operator()(Pipeline* p) -{ - this->stream->pipeStreamData(p, nullptr, 0, decode_level, false, false); -} - -QPDF_Stream::QPDF_Stream( - QPDF* qpdf, QPDFObjGen og, QPDFObjectHandle stream_dict, qpdf_offset_t offset, size_t length) : - QPDFValue(::ot_stream, qpdf, og), - filter_on_write(true), - stream_dict(stream_dict), - length(length) -{ - if (!stream_dict.isDictionary()) { - throw std::logic_error( - "stream object instantiated with non-dictionary object for dictionary"); - } - auto descr = std::make_shared( - qpdf->getFilename() + ", stream object " + og.unparse(' ')); - setDescription(qpdf, descr, offset); -} - -std::shared_ptr -QPDF_Stream::create( - QPDF* qpdf, QPDFObjGen og, QPDFObjectHandle stream_dict, qpdf_offset_t offset, size_t length) -{ - return do_create(new QPDF_Stream(qpdf, og, stream_dict, offset, length)); -} - -std::shared_ptr -QPDF_Stream::copy(bool shallow) -{ - QTC::TC("qpdf", "QPDF_Stream ERR shallow copy stream"); - throw std::runtime_error("stream objects cannot be cloned"); -} - -void -QPDF_Stream::registerStreamFilter( +Stream::registerStreamFilter( std::string const& filter_name, std::function()> factory) { filter_factories[filter_name] = factory; } -void -QPDF_Stream::setFilterOnWrite(bool val) -{ - this->filter_on_write = val; -} - -bool -QPDF_Stream::getFilterOnWrite() const -{ - return this->filter_on_write; -} - -void -QPDF_Stream::disconnect() -{ - this->stream_provider = nullptr; - QPDFObjectHandle::DisconnectAccess::disconnect(this->stream_dict); -} - -std::string -QPDF_Stream::unparse() -{ - // Unparse stream objects as indirect references - return og.unparse(' ') + " R"; -} - -void -QPDF_Stream::writeJSON(int json_version, JSON::Writer& jw) -{ - stream_dict.writeJSON(json_version, jw); -} - JSON -QPDF_Stream::getStreamJSON( +Stream::getStreamJSON( int json_version, qpdf_json_stream_data_e json_data, qpdf_stream_decode_level_e decode_level, @@ -190,13 +136,13 @@ QPDF_Stream::getStreamJSON( pb.finish(); auto result = JSON::parse(pb.getString()); if (json_data == qpdf_sj_inline) { - result.addDictionaryMember("data", JSON::makeBlob(StreamBlobProvider(this, decode_level))); + result.addDictionaryMember("data", JSON::makeBlob(StreamBlobProvider(*this, decode_level))); } return result; } qpdf_stream_decode_level_e -QPDF_Stream::writeStreamJSON( +Stream::writeStreamJSON( int json_version, JSON::Writer& jw, qpdf_json_stream_data_e json_data, @@ -205,6 +151,7 @@ QPDF_Stream::writeStreamJSON( std::string const& data_filename, bool no_data_key) { + auto s = stream(); switch (json_data) { case qpdf_sj_none: case qpdf_sj_inline: @@ -232,7 +179,7 @@ QPDF_Stream::writeStreamJSON( if (json_data == qpdf_sj_none) { jw.writeNext(); jw << R"("dict": )"; - stream_dict.writeJSON(json_version, jw); + s->stream_dict.writeJSON(json_version, jw); jw.writeEnd('}'); return decode_level; } @@ -264,7 +211,7 @@ QPDF_Stream::writeStreamJSON( 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(); + auto dict = s->stream_dict.unsafeShallowCopy(); dict.removeKey("/Length"); if (filter && filtered) { dict.removeKey("/Filter"); @@ -290,53 +237,17 @@ QPDF_Stream::writeStreamJSON( } void -QPDF_Stream::setDescription( - QPDF* qpdf, std::shared_ptr& description, qpdf_offset_t offset) -{ - this->QPDFValue::setDescription(qpdf, description, offset); - setDictDescription(); -} - -void -QPDF_Stream::setDictDescription() +qpdf::Stream::setDictDescription() { - if (!this->stream_dict.hasObjectDescription()) { - this->stream_dict.setObjectDescription(qpdf, getDescription() + " -> stream dictionary"); + auto s = stream(); + if (!s->stream_dict.hasObjectDescription()) { + s->stream_dict.setObjectDescription( + obj->getQPDF(), obj->getDescription() + " -> stream dictionary"); } } -QPDFObjectHandle -QPDF_Stream::getDict() const -{ - return this->stream_dict; -} - -bool -QPDF_Stream::isDataModified() const -{ - return (!this->token_filters.empty()); -} - -size_t -QPDF_Stream::getLength() const -{ - return this->length; -} - std::shared_ptr -QPDF_Stream::getStreamDataBuffer() const -{ - return this->stream_data; -} - -std::shared_ptr -QPDF_Stream::getStreamDataProvider() const -{ - return this->stream_provider; -} - -std::shared_ptr -QPDF_Stream::getStreamData(qpdf_stream_decode_level_e decode_level) +Stream::getStreamData(qpdf_stream_decode_level_e decode_level) { Pl_Buffer buf("stream data buffer"); bool filtered; @@ -344,9 +255,9 @@ QPDF_Stream::getStreamData(qpdf_stream_decode_level_e decode_level) if (!filtered) { throw QPDFExc( qpdf_e_unsupported, - qpdf->getFilename(), + obj->getQPDF()->getFilename(), "", - this->parsed_offset, + obj->getParsedOffset(), "getStreamData called on unfilterable stream"); } QTC::TC("qpdf", "QPDF_Stream getStreamData"); @@ -354,15 +265,15 @@ QPDF_Stream::getStreamData(qpdf_stream_decode_level_e decode_level) } std::shared_ptr -QPDF_Stream::getRawStreamData() +Stream::getRawStreamData() { Pl_Buffer buf("stream data buffer"); if (!pipeStreamData(&buf, nullptr, 0, qpdf_dl_none, false, false)) { throw QPDFExc( qpdf_e_unsupported, - qpdf->getFilename(), + obj->getQPDF()->getFilename(), "", - this->parsed_offset, + obj->getParsedOffset(), "error getting raw stream data"); } QTC::TC("qpdf", "QPDF_Stream getRawStreamData"); @@ -370,14 +281,15 @@ QPDF_Stream::getRawStreamData() } bool -QPDF_Stream::filterable( +Stream::filterable( std::vector>& filters, bool& specialized_compression, bool& lossy_compression) { + auto s = stream(); // Check filters - QPDFObjectHandle filter_obj = this->stream_dict.getKey("/Filter"); + QPDFObjectHandle filter_obj = s->stream_dict.getKey("/Filter"); bool filters_okay = true; std::vector filter_names; @@ -432,7 +344,7 @@ QPDF_Stream::filterable( // See if we can support any decode parameters that are specified. - QPDFObjectHandle decode_obj = this->stream_dict.getKey("/DecodeParms"); + QPDFObjectHandle decode_obj = s->stream_dict.getKey("/DecodeParms"); std::vector decode_parms; if (decode_obj.isArray() && (decode_obj.getArrayNItems() == 0)) { decode_obj = QPDFObjectHandle::newNull(); @@ -479,7 +391,7 @@ QPDF_Stream::filterable( } bool -QPDF_Stream::pipeStreamData( +Stream::pipeStreamData( Pipeline* pipeline, bool* filterp, int encode_flags, @@ -487,6 +399,7 @@ QPDF_Stream::pipeStreamData( bool suppress_warnings, bool will_retry) { + auto s = stream(); std::vector> filters; bool specialized_compression = false; bool lossy_compression = false; @@ -543,7 +456,7 @@ QPDF_Stream::pipeStreamData( pipeline = new_pipeline.get(); } - for (auto iter = this->token_filters.rbegin(); iter != this->token_filters.rend(); ++iter) { + for (auto iter = s->token_filters.rbegin(); iter != s->token_filters.rend(); ++iter) { new_pipeline = std::make_shared("token filter", (*iter).get(), pipeline); to_delete.push_back(new_pipeline); @@ -562,25 +475,25 @@ QPDF_Stream::pipeStreamData( } } - if (this->stream_data.get()) { + if (s->stream_data.get()) { QTC::TC("qpdf", "QPDF_Stream pipe replaced stream data"); - pipeline->write(this->stream_data->getBuffer(), this->stream_data->getSize()); + pipeline->write(s->stream_data->getBuffer(), s->stream_data->getSize()); pipeline->finish(); - } else if (this->stream_provider.get()) { + } else if (s->stream_provider.get()) { Pl_Count count("stream provider count", pipeline); - if (this->stream_provider->supportsRetry()) { - if (!this->stream_provider->provideStreamData( - og, &count, suppress_warnings, will_retry)) { + if (s->stream_provider->supportsRetry()) { + if (!s->stream_provider->provideStreamData( + obj->getObjGen(), &count, suppress_warnings, will_retry)) { filter = false; success = false; } } else { - this->stream_provider->provideStreamData(og, &count); + s->stream_provider->provideStreamData(obj->getObjGen(), &count); } qpdf_offset_t actual_length = count.getCount(); qpdf_offset_t desired_length = 0; - if (success && this->stream_dict.hasKey("/Length")) { - desired_length = this->stream_dict.getKey("/Length").getIntValue(); + if (success && s->stream_dict.hasKey("/Length")) { + desired_length = s->stream_dict.getKey("/Length").getIntValue(); if (actual_length == desired_length) { QTC::TC("qpdf", "QPDF_Stream pipe use stream provider"); } else { @@ -588,25 +501,25 @@ QPDF_Stream::pipeStreamData( // This would be caused by programmer error on the part of a library user, not by // invalid input data. throw std::runtime_error( - "stream data provider for " + og.unparse(' ') + " provided " + + "stream data provider for " + obj->getObjGen().unparse(' ') + " provided " + std::to_string(actual_length) + " bytes instead of expected " + std::to_string(desired_length) + " bytes"); } } else if (success) { QTC::TC("qpdf", "QPDF_Stream provider length not provided"); - this->stream_dict.replaceKey("/Length", QPDFObjectHandle::newInteger(actual_length)); + s->stream_dict.replaceKey("/Length", QPDFObjectHandle::newInteger(actual_length)); } - } else if (this->parsed_offset == 0) { + } else if (obj->getParsedOffset() == 0) { QTC::TC("qpdf", "QPDF_Stream pipe no stream data"); throw std::logic_error("pipeStreamData called for stream with no data"); } else { QTC::TC("qpdf", "QPDF_Stream pipe original stream data"); if (!QPDF::Pipe::pipeStreamData( - this->qpdf, - og, - this->parsed_offset, - this->length, - this->stream_dict, + obj->getQPDF(), + obj->getObjGen(), + obj->getParsedOffset(), + s->length, + s->stream_dict, pipeline, suppress_warnings, will_retry)) { @@ -634,60 +547,235 @@ QPDF_Stream::pipeStreamData( } void -QPDF_Stream::replaceStreamData( +Stream::replaceStreamData( std::shared_ptr data, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms) { - this->stream_data = data; - this->stream_provider = nullptr; + auto s = stream(); + s->stream_data = data; + s->stream_provider = nullptr; replaceFilterData(filter, decode_parms, data->getSize()); } void -QPDF_Stream::replaceStreamData( +Stream::replaceStreamData( std::shared_ptr provider, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms) { - this->stream_provider = provider; - this->stream_data = nullptr; + auto s = stream(); + s->stream_provider = provider; + s->stream_data = nullptr; replaceFilterData(filter, decode_parms, 0); } void -QPDF_Stream::addTokenFilter(std::shared_ptr token_filter) -{ - this->token_filters.push_back(token_filter); -} - -void -QPDF_Stream::replaceFilterData( +Stream::replaceFilterData( QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms, size_t length) { + auto s = stream(); if (filter) { - stream_dict.replaceKey("/Filter", filter); + s->stream_dict.replaceKey("/Filter", filter); } if (decode_parms) { - stream_dict.replaceKey("/DecodeParms", decode_parms); + s->stream_dict.replaceKey("/DecodeParms", decode_parms); } if (length == 0) { QTC::TC("qpdf", "QPDF_Stream unknown stream length"); - stream_dict.removeKey("/Length"); + s->stream_dict.removeKey("/Length"); } else { - stream_dict.replaceKey("/Length", QPDFObjectHandle::newInteger(QIntC::to_longlong(length))); + s->stream_dict.replaceKey( + "/Length", QPDFObjectHandle::newInteger(QIntC::to_longlong(length))); } } void -QPDF_Stream::replaceDict(QPDFObjectHandle const& new_dict) +Stream::warn(std::string const& message) { - this->stream_dict = new_dict; - setDictDescription(); + obj->getQPDF()->warn(qpdf_e_damaged_pdf, "", obj->getParsedOffset(), message); +} + +QPDFObjectHandle +QPDFObjectHandle::getDict() const +{ + return as_stream(error).getDict(); +} + +void +QPDFObjectHandle::setFilterOnWrite(bool val) +{ + as_stream(error).setFilterOnWrite(val); +} + +bool +QPDFObjectHandle::getFilterOnWrite() +{ + return as_stream(error).getFilterOnWrite(); +} + +bool +QPDFObjectHandle::isDataModified() +{ + return as_stream(error).isDataModified(); } void -QPDF_Stream::warn(std::string const& message) +QPDFObjectHandle::replaceDict(QPDFObjectHandle const& new_dict) +{ + as_stream(error).replaceDict(new_dict); +} + +std::shared_ptr +QPDFObjectHandle::getStreamData(qpdf_stream_decode_level_e level) +{ + return as_stream(error).getStreamData(level); +} + +std::shared_ptr +QPDFObjectHandle::getRawStreamData() +{ + return as_stream(error).getRawStreamData(); +} + +bool +QPDFObjectHandle::pipeStreamData( + Pipeline* p, + bool* filtering_attempted, + int encode_flags, + qpdf_stream_decode_level_e decode_level, + bool suppress_warnings, + bool will_retry) +{ + return as_stream(error).pipeStreamData( + p, filtering_attempted, encode_flags, decode_level, suppress_warnings, will_retry); +} + +bool +QPDFObjectHandle::pipeStreamData( + Pipeline* p, + int encode_flags, + qpdf_stream_decode_level_e decode_level, + bool suppress_warnings, + bool will_retry) +{ + bool filtering_attempted; + as_stream(error).pipeStreamData( + p, &filtering_attempted, encode_flags, decode_level, suppress_warnings, will_retry); + return filtering_attempted; +} + +bool +QPDFObjectHandle::pipeStreamData(Pipeline* p, bool filter, bool normalize, bool compress) +{ + int encode_flags = 0; + qpdf_stream_decode_level_e decode_level = qpdf_dl_none; + if (filter) { + decode_level = qpdf_dl_generalized; + if (normalize) { + encode_flags |= qpdf_ef_normalize; + } + if (compress) { + encode_flags |= qpdf_ef_compress; + } + } + return pipeStreamData(p, encode_flags, decode_level, false); +} + +void +QPDFObjectHandle::replaceStreamData( + std::shared_ptr data, + QPDFObjectHandle const& filter, + QPDFObjectHandle const& decode_parms) +{ + as_stream(error).replaceStreamData(data, filter, decode_parms); +} + +void +QPDFObjectHandle::replaceStreamData( + std::string const& data, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms) +{ + auto b = std::make_shared(data.length()); + unsigned char* bp = b->getBuffer(); + if (bp) { + memcpy(bp, data.c_str(), data.length()); + } + as_stream(error).replaceStreamData(b, filter, decode_parms); +} + +void +QPDFObjectHandle::replaceStreamData( + std::shared_ptr provider, + QPDFObjectHandle const& filter, + QPDFObjectHandle const& decode_parms) +{ + as_stream(error).replaceStreamData(provider, filter, decode_parms); +} + +namespace +{ + class FunctionProvider: public QPDFObjectHandle::StreamDataProvider + { + public: + FunctionProvider(std::function provider) : + StreamDataProvider(false), + p1(provider), + p2(nullptr) + { + } + FunctionProvider(std::function provider) : + StreamDataProvider(true), + p1(nullptr), + p2(provider) + { + } + + void + provideStreamData(QPDFObjGen const&, Pipeline* pipeline) override + { + p1(pipeline); + } + + bool + provideStreamData( + QPDFObjGen const&, Pipeline* pipeline, bool suppress_warnings, bool will_retry) override + { + return p2(pipeline, suppress_warnings, will_retry); + } + + private: + std::function p1; + std::function p2; + }; +} // namespace + +void +QPDFObjectHandle::replaceStreamData( + std::function provider, + QPDFObjectHandle const& filter, + QPDFObjectHandle const& decode_parms) +{ + auto sdp = std::shared_ptr(new FunctionProvider(provider)); + as_stream(error).replaceStreamData(sdp, filter, decode_parms); +} + +void +QPDFObjectHandle::replaceStreamData( + std::function provider, + QPDFObjectHandle const& filter, + QPDFObjectHandle const& decode_parms) +{ + auto sdp = std::shared_ptr(new FunctionProvider(provider)); + as_stream(error).replaceStreamData(sdp, filter, decode_parms); +} + +JSON +QPDFObjectHandle::getStreamJSON( + int json_version, + qpdf_json_stream_data_e json_data, + qpdf_stream_decode_level_e decode_level, + Pipeline* p, + std::string const& data_filename) { - this->qpdf->warn(qpdf_e_damaged_pdf, "", this->parsed_offset, message); + return as_stream(error).getStreamJSON(json_version, json_data, decode_level, p, data_filename); } diff --git a/libqpdf/QPDF_String.cc b/libqpdf/QPDF_String.cc index 9f12a3d..187106f 100644 --- a/libqpdf/QPDF_String.cc +++ b/libqpdf/QPDF_String.cc @@ -1,6 +1,6 @@ -#include +#include -#include +#include #include // DO NOT USE ctype -- it is locale dependent for some things, and it's not worth the risk of @@ -12,18 +12,6 @@ is_iso_latin1_printable(char ch) return (((ch >= 32) && (ch <= 126)) || (static_cast(ch) >= 160)); } -QPDF_String::QPDF_String(std::string const& val) : - QPDFValue(::ot_string), - val(val) -{ -} - -std::shared_ptr -QPDF_String::create(std::string const& val) -{ - return do_create(new QPDF_String(val)); -} - std::shared_ptr QPDF_String::create_utf16(std::string const& utf8_val) { @@ -31,19 +19,7 @@ QPDF_String::create_utf16(std::string const& utf8_val) if (!QUtil::utf8_to_pdf_doc(utf8_val, result, '?')) { result = QUtil::utf8_to_utf16(utf8_val); } - return do_create(new QPDF_String(result)); -} - -std::shared_ptr -QPDF_String::copy(bool shallow) -{ - return create(val); -} - -std::string -QPDF_String::unparse() -{ - return unparse(false); + return QPDFObject::create(result); } void diff --git a/libqpdf/QPDF_Unresolved.cc b/libqpdf/QPDF_Unresolved.cc deleted file mode 100644 index ae0c6a6..0000000 --- a/libqpdf/QPDF_Unresolved.cc +++ /dev/null @@ -1,39 +0,0 @@ -#include - -#include -#include - -QPDF_Unresolved::QPDF_Unresolved(QPDF* qpdf, QPDFObjGen og) : - QPDFValue(::ot_unresolved, qpdf, og) -{ -} - -std::shared_ptr -QPDF_Unresolved::create(QPDF* qpdf, QPDFObjGen og) -{ - return do_create(new QPDF_Unresolved(qpdf, og)); -} - -std::shared_ptr -QPDF_Unresolved::copy(bool shallow) -{ - return QPDF::Resolver::resolved(qpdf, og)->copy(shallow); -} - -std::string -QPDF_Unresolved::unparse() -{ - return QPDF::Resolver::resolved(qpdf, og)->unparse(); -} - -void -QPDF_Unresolved::writeJSON(int json_version, JSON::Writer& p) -{ - QPDF::Resolver::resolved(qpdf, og)->writeJSON(json_version, p); -} - -std::string -QPDF_Unresolved::getStringValue() const -{ - return QPDF::Resolver::resolved(qpdf, og)->getStringValue(); -} diff --git a/libqpdf/QPDF_json.cc b/libqpdf/QPDF_json.cc index f1a9f25..288f265 100644 --- a/libqpdf/QPDF_json.cc +++ b/libqpdf/QPDF_json.cc @@ -5,10 +5,8 @@ #include #include #include +#include #include -#include -#include -#include #include #include #include @@ -238,8 +236,8 @@ class QPDF::JSONReactor: public JSON::Reactor is(is), must_be_complete(must_be_complete), descr( - std::make_shared( - QPDFValue::JSON_Descr(std::make_shared(is->getName()), ""))) + std::make_shared( + QPDFObject::JSON_Descr(std::make_shared(is->getName()), ""))) { } ~JSONReactor() override = default; @@ -286,7 +284,7 @@ class QPDF::JSONReactor: public JSON::Reactor QPDF& pdf; std::shared_ptr is; bool must_be_complete{true}; - std::shared_ptr descr; + std::shared_ptr descr; bool errors{false}; bool saw_qpdf{false}; bool saw_qpdf_meta{false}; @@ -576,8 +574,8 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value) } else { this_stream_needs_data = true; replaceObject( - QPDF_Stream::create( - &pdf, tos.object.getObjGen(), QPDFObjectHandle::newDictionary(), 0, 0), + qpdf::Stream( + pdf, tos.object.getObjGen(), QPDFObjectHandle::newDictionary(), 0, 0), value); } next_obj = tos.object; @@ -706,10 +704,10 @@ QPDF::JSONReactor::arrayItem(JSON const& value) void QPDF::JSONReactor::setObjectDescription(QPDFObjectHandle& oh, JSON const& value) { - auto j_descr = std::get(*descr); + auto j_descr = std::get(*descr); if (j_descr.object != cur_object) { - descr = std::make_shared( - QPDFValue::JSON_Descr(j_descr.input, cur_object)); + descr = std::make_shared( + QPDFObject::JSON_Descr(j_descr.input, cur_object)); } oh.getObjectPtr()->setDescription(&pdf, descr, value.getStart()); @@ -821,7 +819,7 @@ void writeJSONStreamFile( int version, JSON::Writer& jw, - QPDF_Stream& stream, + qpdf::Stream& stream, int id, qpdf_stream_decode_level_e decode_level, std::string const& file_prefix) @@ -894,13 +892,13 @@ QPDF::writeJSON( } else { jw << "\n },\n \"" << key; } - if (auto* stream = obj.getObjectPtr()->as()) { + if (auto stream = obj.as_stream()) { jw << "\": {\n \"stream\": "; if (json_stream_data == qpdf_sj_file) { writeJSONStreamFile( - version, jw, *stream, og.getObj(), decode_level, file_prefix); + version, jw, stream, og.getObj(), decode_level, file_prefix); } else { - stream->writeStreamJSON( + stream.writeStreamJSON( version, jw, json_stream_data, decode_level, nullptr, ""); } } else { diff --git a/libqpdf/QPDF_optimization.cc b/libqpdf/QPDF_optimization.cc index d8c5129..d19c2c6 100644 --- a/libqpdf/QPDF_optimization.cc +++ b/libqpdf/QPDF_optimization.cc @@ -5,9 +5,8 @@ #include #include +#include #include -#include -#include #include QPDF::ObjUser::ObjUser() : @@ -115,24 +114,25 @@ QPDF::optimize_internal( } // Traverse document-level items - for (auto const& key: m->trailer.getKeys()) { + for (auto const& [key, value]: m->trailer.as_dictionary()) { if (key == "/Root") { // handled separately } else { - updateObjectMaps( - ObjUser(ObjUser::ou_trailer_key, key), - m->trailer.getKey(key), - skip_stream_parameters); + if (!value.null()) { + updateObjectMaps( + ObjUser(ObjUser::ou_trailer_key, key), value, skip_stream_parameters); + } } } - for (auto const& key: root.getKeys()) { + for (auto const& [key, value]: root.as_dictionary()) { // Technically, /I keys from /Thread dictionaries are supposed to be handled separately, but // we are going to disregard that specification for now. There is loads of evidence that // pdlin and Acrobat both disregard things like this from time to time, so this is almost // certain not to cause any problems. - updateObjectMaps( - ObjUser(ObjUser::ou_root_key, key), root.getKey(key), skip_stream_parameters); + if (!value.null()) { + updateObjectMaps(ObjUser(ObjUser::ou_root_key, key), value, skip_stream_parameters); + } } ObjUser root_ou = ObjUser(ObjUser::ou_root); @@ -319,9 +319,8 @@ QPDF::updateObjectMaps( } if (cur.oh.isArray()) { - int n = cur.oh.getArrayNItems(); - for (int i = 0; i < n; ++i) { - pending.emplace_back(cur.ou, cur.oh.getArrayItem(i), false); + for (auto const& item: cur.oh.as_array()) { + pending.emplace_back(cur.ou, item, false); } } else if (cur.oh.isDictionary() || cur.oh.isStream()) { QPDFObjectHandle dict = cur.oh; @@ -334,7 +333,11 @@ QPDF::updateObjectMaps( } } - for (auto const& key: dict.getKeys()) { + for (auto& [key, value]: dict.as_dictionary()) { + if (value.null()) { + continue; + } + if (is_page_node && (key == "/Thumb")) { // Traverse page thumbnail dictionaries as a special case. There can only ever // be one /Thumb key on a page, and we see at most one page node per call. @@ -347,7 +350,7 @@ QPDF::updateObjectMaps( ((ssp >= 2) && ((key == "/Filter") || (key == "/DecodeParms")))) { // Don't traverse into stream parameters that we are not going to write. } else { - pending.emplace_back(cur.ou, dict.getKey(key), false); + pending.emplace_back(cur.ou, value, false); } } } diff --git a/libqpdf/QPDF_pages.cc b/libqpdf/QPDF_pages.cc index aa90f7f..ece5c32 100644 --- a/libqpdf/QPDF_pages.cc +++ b/libqpdf/QPDF_pages.cc @@ -1,6 +1,7 @@ #include #include +#include #include #include @@ -108,9 +109,9 @@ QPDF::getAllPagesInternal( QTC::TC("qpdf", "QPDF inherit mediabox", media_box ? 0 : 1); } auto kids = cur_node.getKey("/Kids"); - int n = kids.getArrayNItems(); - for (int i = 0; i < n; ++i) { - auto kid = kids.getArrayItem(i); + int i = -1; + for (auto& kid: kids.as_array()) { + ++i; if (!kid.isDictionary()) { kid.warnIfPossible("Pages tree includes non-dictionary object; ignoring"); m->invalid_page_found = true; @@ -133,7 +134,6 @@ QPDF::getAllPagesInternal( cur_node.warnIfPossible( "kid " + std::to_string(i) + " (from 0) is direct; converting to indirect"); kid = makeIndirectObject(kid); - kids.setArrayItem(i, kid); } else if (!seen.add(kid)) { // Make a copy of the page. This does the same as shallowCopyPage in // QPDFPageObjectHelper. @@ -144,7 +144,6 @@ QPDF::getAllPagesInternal( " creating a new page object as a copy"); kid = makeIndirectObject(QPDFObjectHandle(kid).shallowCopy()); seen.add(kid); - kids.setArrayItem(i, kid); } if (!kid.isDictionaryOfType("/Page")) { kid.warnIfPossible("/Type key should be /Page but is not; overriding"); diff --git a/libqpdf/qpdf/QPDFObjectHandle_private.hh b/libqpdf/qpdf/QPDFObjectHandle_private.hh new file mode 100644 index 0000000..61ce62a --- /dev/null +++ b/libqpdf/qpdf/QPDFObjectHandle_private.hh @@ -0,0 +1,458 @@ +#ifndef OBJECTHANDLE_PRIVATE_HH +#define OBJECTHANDLE_PRIVATE_HH + +#include + +#include +#include + +namespace qpdf +{ + class Array final: public BaseHandle + { + public: + explicit Array(std::shared_ptr const& obj) : + BaseHandle(obj) + { + } + + explicit Array(std::shared_ptr&& obj) : + BaseHandle(std::move(obj)) + { + } + + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + using const_reverse_iterator = std::vector::const_reverse_iterator; + + iterator begin(); + + iterator end(); + + const_iterator cbegin(); + + const_iterator cend(); + + const_reverse_iterator crbegin(); + + const_reverse_iterator crend(); + + int size() const; + std::pair at(int n) const; + bool setAt(int at, QPDFObjectHandle const& oh); + bool insert(int at, QPDFObjectHandle const& item); + void push_back(QPDFObjectHandle const& item); + bool erase(int at); + + std::vector getAsVector() const; + void setFromVector(std::vector const& items); + + private: + QPDF_Array* array() const; + void checkOwnership(QPDFObjectHandle const& item) const; + QPDFObjectHandle null() const; + + std::unique_ptr> sp_elements{}; + }; + + // BaseDictionary is only used as a base class. It does not contain any methods exposed in the + // public API. + class BaseDictionary: public BaseHandle + { + public: + using iterator = std::map::iterator; + using const_iterator = std::map::const_iterator; + using reverse_iterator = std::map::reverse_iterator; + using const_reverse_iterator = + std::map::const_reverse_iterator; + + iterator + begin() + { + if (auto d = as()) { + return d->items.begin(); + } + return {}; + } + + iterator + end() + { + if (auto d = as()) { + return d->items.end(); + } + return {}; + } + + const_iterator + cbegin() + { + if (auto d = as()) { + return d->items.cbegin(); + } + return {}; + } + + const_iterator + cend() + { + if (auto d = as()) { + return d->items.cend(); + } + return {}; + } + + reverse_iterator + rbegin() + { + if (auto d = as()) { + return d->items.rbegin(); + } + return {}; + } + + reverse_iterator + rend() + { + if (auto d = as()) { + return d->items.rend(); + } + return {}; + } + + const_reverse_iterator + crbegin() + { + if (auto d = as()) { + return d->items.crbegin(); + } + return {}; + } + + const_reverse_iterator + crend() + { + if (auto d = as()) { + return d->items.crend(); + } + return {}; + } + + // The following methods are not part of the public API. + bool hasKey(std::string const& key) const; + QPDFObjectHandle getKey(std::string const& key) const; + std::set getKeys(); + std::map const& getAsMap() const; + void removeKey(std::string const& key); + void replaceKey(std::string const& key, QPDFObjectHandle value); + + protected: + BaseDictionary() = default; + BaseDictionary(std::shared_ptr const& obj) : + BaseHandle(obj) {}; + BaseDictionary(std::shared_ptr&& obj) : + BaseHandle(std::move(obj)) {}; + BaseDictionary(BaseDictionary const&) = default; + BaseDictionary& operator=(BaseDictionary const&) = default; + BaseDictionary(BaseDictionary&&) = default; + BaseDictionary& operator=(BaseDictionary&&) = default; + ~BaseDictionary() = default; + + QPDF_Dictionary* dict() const; + }; + + class Dictionary final: public BaseDictionary + { + public: + explicit Dictionary(std::shared_ptr const& obj) : + BaseDictionary(obj) + { + } + + explicit Dictionary(std::shared_ptr&& obj) : + BaseDictionary(std::move(obj)) + { + } + }; + + class Name final: public BaseHandle + { + public: + // Put # into strings with characters unsuitable for name token + static std::string normalize(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); + }; + + class Stream final: public BaseHandle + { + public: + explicit Stream(std::shared_ptr const& obj) : + BaseHandle(obj) + { + } + + explicit Stream(std::shared_ptr&& obj) : + BaseHandle(std::move(obj)) + { + } + + Stream( + QPDF& qpdf, + QPDFObjGen og, + QPDFObjectHandle stream_dict, + qpdf_offset_t offset, + size_t length); + + QPDFObjectHandle + getDict() const + { + return stream()->stream_dict; + } + bool + isDataModified() const + { + return !stream()->token_filters.empty(); + } + void + setFilterOnWrite(bool val) + { + stream()->filter_on_write = val; + } + bool + getFilterOnWrite() const + { + return stream()->filter_on_write; + } + + // Methods to help QPDF copy foreign streams + size_t + getLength() const + { + return stream()->length; + } + std::shared_ptr + getStreamDataBuffer() const + { + return stream()->stream_data; + } + std::shared_ptr + getStreamDataProvider() const + { + return stream()->stream_provider; + } + + // See comments in QPDFObjectHandle.hh for these methods. + bool pipeStreamData( + Pipeline* p, + bool* tried_filtering, + int encode_flags, + qpdf_stream_decode_level_e decode_level, + bool suppress_warnings, + bool will_retry); + std::shared_ptr getStreamData(qpdf_stream_decode_level_e level); + std::shared_ptr getRawStreamData(); + void replaceStreamData( + std::shared_ptr data, + QPDFObjectHandle const& filter, + QPDFObjectHandle const& decode_parms); + void replaceStreamData( + std::shared_ptr provider, + QPDFObjectHandle const& filter, + QPDFObjectHandle const& decode_parms); + void + addTokenFilter(std::shared_ptr token_filter) + { + stream()->token_filters.emplace_back(token_filter); + } + JSON getStreamJSON( + int json_version, + qpdf_json_stream_data_e json_data, + 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) + { + auto s = stream(); + s->stream_dict = new_dict; + setDictDescription(); + } + + void setDictDescription(); + + static void registerStreamFilter( + std::string const& filter_name, + std::function()> factory); + + private: + QPDF_Stream::Members* + stream() const + { + if (auto s = as()) { + if (auto ptr = s->m.get()) { + return ptr; + } + throw std::logic_error("QPDF_Stream: unexpected nullptr"); + } + throw std::runtime_error("operation for stream attempted on non-stream object"); + return nullptr; // unreachable + } + bool filterable( + std::vector>& filters, + bool& specialized_compression, + bool& lossy_compression); + void replaceFilterData( + QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms, size_t length); + + void warn(std::string const& message); + + static std::map filter_abbreviations; + static std::map()>> + filter_factories; + }; + + template + T* + BaseHandle::as() const + { + if (!obj) { + return nullptr; + } + if (std::holds_alternative(obj->value)) { + return &std::get(obj->value); + } + if (std::holds_alternative(obj->value)) { + return BaseHandle(QPDF::Resolver::resolved(obj->qpdf, obj->og)).as(); + } + if (std::holds_alternative(obj->value)) { + // see comment in QPDF_Reference. + return BaseHandle(std::get(obj->value).obj).as(); + } + return nullptr; + } + + inline QPDFObjGen + BaseHandle::id_gen() const + { + return obj ? obj->og : QPDFObjGen(); + } + + inline bool + BaseHandle::indirect() const + { + return obj ? obj->og.isIndirect() : false; + } + + inline bool + BaseHandle::null() const + { + return !obj || obj->getResolvedTypeCode() == ::ot_null; + } + + inline QPDF* + BaseHandle::qpdf() const + { + return obj ? obj->qpdf : nullptr; + } + + inline qpdf_object_type_e + BaseHandle::raw_type_code() const + { + return obj ? static_cast(obj->value.index()) : ::ot_uninitialized; + } + + inline qpdf_object_type_e + BaseHandle::type_code() const + { + if (!obj) { + return ::ot_uninitialized; + } + if (raw_type_code() == ::ot_unresolved) { + return QPDF::Resolver::resolved(obj->qpdf, obj->og)->getTypeCode(); + } + if (raw_type_code() == ::ot_reference) { + return std::get(obj->value).obj->getResolvedTypeCode(); + } + return raw_type_code(); + } + +} // namespace qpdf + +inline QPDF_Dictionary::QPDF_Dictionary(std::map&& items) : + items(std::move(items)) +{ +} + +inline std::shared_ptr +QPDF_Null::create( + std::shared_ptr parent, std::string_view const& static_descr, std::string var_descr) +{ + auto n = QPDFObject::create(); + n->setChildDescription(parent->getQPDF(), parent, static_descr, var_descr); + return n; +} + +inline QPDF_Real::QPDF_Real(double value, int decimal_places, bool trim_trailing_zeroes) : + val(QUtil::double_to_string(value, decimal_places, trim_trailing_zeroes)) +{ +} + +template +inline std::shared_ptr +QPDFObject::create(Args&&... args) +{ + return std::make_shared(std::forward(T(std::forward(args)...))); +} + +inline qpdf::Array +QPDFObjectHandle::as_array(qpdf::typed options) const +{ + if (options & qpdf::error) { + assertType("array", false); + } + if (options & qpdf::any_flag || type_code() == ::ot_array || + (options & qpdf::optional && type_code() == ::ot_null)) { + return qpdf::Array(obj); + } + return qpdf::Array(std::shared_ptr()); +} + +inline qpdf::Dictionary +QPDFObjectHandle::as_dictionary(qpdf::typed options) const +{ + if (options & qpdf::any_flag || type_code() == ::ot_dictionary || + (options & qpdf::optional && type_code() == ::ot_null)) { + return qpdf::Dictionary(obj); + } + if (options & qpdf::error) { + assertType("dictionary", false); + } + return qpdf::Dictionary(std::shared_ptr()); +} + +inline qpdf::Stream +QPDFObjectHandle::as_stream(qpdf::typed options) const +{ + if (options & qpdf::any_flag || type_code() == ::ot_stream || + (options & qpdf::optional && type_code() == ::ot_null)) { + return qpdf::Stream(obj); + } + if (options & qpdf::error) { + assertType("stream", false); + } + return qpdf::Stream(std::shared_ptr()); +} + +#endif // OBJECTHANDLE_PRIVATE_HH diff --git a/libqpdf/qpdf/QPDFObject_private.hh b/libqpdf/qpdf/QPDFObject_private.hh index d3f3821..25db481 100644 --- a/libqpdf/qpdf/QPDFObject_private.hh +++ b/libqpdf/qpdf/QPDFObject_private.hh @@ -6,182 +6,486 @@ #include #include +#include #include -#include +#include #include +#include +#include #include #include +#include +#include -class QPDF; +class QPDFObject; class QPDFObjectHandle; -class QPDFObject +namespace qpdf { - friend class QPDFValue; + class Array; + class BaseDictionary; + class Dictionary; + class Stream; +} // namespace qpdf - public: - QPDFObject() = default; +class QPDF_Array final +{ + private: + struct Sparse + { + int size{0}; + std::map elements; + }; - std::shared_ptr - copy(bool shallow = false) + public: + QPDF_Array() = default; + QPDF_Array(QPDF_Array const& other) : + sp(other.sp ? std::make_unique(*other.sp) : nullptr) { - return value->copy(shallow); } - std::string - unparse() + + QPDF_Array(QPDF_Array&&) = default; + QPDF_Array& operator=(QPDF_Array&&) = default; + + private: + friend class QPDFObject; + friend class qpdf::Array; + QPDF_Array(std::vector const& items) : + elements(items) { - return value->unparse(); } - void - writeJSON(int json_version, JSON::Writer& p) + QPDF_Array(std::vector&& items, bool sparse); + + QPDF_Array(std::vector&& items) : + elements(std::move(items)) { - return value->writeJSON(json_version, p); } - std::string - getStringValue() const + + int + size() const { - return value->getStringValue(); + return sp ? sp->size : int(elements.size()); } - // Return a unique type code for the resolved object - qpdf_object_type_e - getResolvedTypeCode() const + + std::unique_ptr sp; + std::vector elements; +}; + +class QPDF_Bool final +{ + friend class QPDFObject; + friend class QPDFObjectHandle; + + explicit QPDF_Bool(bool val) : + val(val) { - auto tc = value->type_code; - return tc == ::ot_unresolved - ? QPDF::Resolver::resolved(value->qpdf, value->og)->value->type_code - : tc; } - // Return a unique type code for the object - qpdf_object_type_e - getTypeCode() const noexcept + bool val; +}; + +class QPDF_Destroyed final +{ +}; + +class QPDF_Dictionary final +{ + friend class QPDFObject; + friend class qpdf::BaseDictionary; + + QPDF_Dictionary(std::map const& items) : + items(items) { - return value->type_code; } + inline QPDF_Dictionary(std::map&& items); - QPDF* - getQPDF() const + std::map items; +}; + +class QPDF_InlineImage final +{ + friend class QPDFObject; + + explicit QPDF_InlineImage(std::string val) : + val(std::move(val)) { - return value->qpdf; } - QPDFObjGen - getObjGen() const + std::string val; +}; + +class QPDF_Integer final +{ + friend class QPDFObject; + friend class QPDFObjectHandle; + + QPDF_Integer(long long val) : + val(val) { - return value->og; } - void - setDescription( - QPDF* qpdf, std::shared_ptr& description, qpdf_offset_t offset = -1) + long long val; +}; + +class QPDF_Name final +{ + friend class QPDFObject; + + explicit QPDF_Name(std::string name) : + name(std::move(name)) { - return value->setDescription(qpdf, description, offset); } - void - setChildDescription( + std::string name; +}; + +class QPDF_Null final +{ + friend class QPDFObject; + + public: + static inline std::shared_ptr create( std::shared_ptr parent, std::string_view const& static_descr, - std::string var_descr) + std::string var_descr); +}; + +class QPDF_Operator final +{ + friend class QPDFObject; + + QPDF_Operator(std::string val) : + val(std::move(val)) { - auto qpdf = parent ? parent->value->qpdf : nullptr; - value->setChildDescription(qpdf, parent->value, static_descr, var_descr); } - void - setChildDescription( - std::shared_ptr parent, - std::string_view const& static_descr, - std::string var_descr) + + std::string val; +}; + +class QPDF_Real final +{ + friend class QPDFObject; + + QPDF_Real(std::string val) : + val(std::move(val)) { - auto qpdf = parent ? parent->qpdf : nullptr; - value->setChildDescription(qpdf, parent, static_descr, var_descr); } - bool - getDescription(QPDF*& qpdf, std::string& description) + inline QPDF_Real(double value, int decimal_places, bool trim_trailing_zeroes); + // Store reals as strings to avoid roundoff errors. + std::string val; +}; + +class QPDF_Reference +{ + // This is a minimal implementation to support QPDF::replaceObject. Once we support parsing of + // objects that are an indirect reference we will need to support multiple levels of + // indirection, including the possibility of circular references. + friend class QPDFObject; + friend class qpdf::BaseHandle; + + QPDF_Reference(std::shared_ptr obj) : + obj(std::move(obj)) { - qpdf = value->qpdf; - description = value->getDescription(); - return qpdf != nullptr; } - bool - hasDescription() + + std::shared_ptr obj; +}; + +class QPDF_Reserved final +{ +}; + +class QPDF_Stream final +{ + class Members { - return value->hasDescription(); + friend class QPDF_Stream; + friend class QPDFObject; + friend class qpdf::Stream; + + public: + Members(QPDFObjectHandle stream_dict, size_t length) : + stream_dict(std::move(stream_dict)), + length(length) + { + } + + private: + bool filter_on_write{true}; + QPDFObjectHandle stream_dict; + size_t length{0}; + std::shared_ptr stream_data; + std::shared_ptr stream_provider; + std::vector> token_filters; + }; + + friend class QPDFObject; + friend class qpdf::Stream; + + QPDF_Stream(QPDFObjectHandle stream_dict, size_t length) : + m(std::make_unique(stream_dict, length)) + { + if (!stream_dict.isDictionary()) { + throw std::logic_error( + "stream object instantiated with non-dictionary object for dictionary"); + } } - void - setParsedOffset(qpdf_offset_t offset) + + std::unique_ptr m; +}; + +// QPDF_Strings may included embedded null characters. +class QPDF_String final +{ + friend class QPDFObject; + friend class QPDFWriter; + + public: + static std::shared_ptr create_utf16(std::string const& utf8_val); + std::string unparse(bool force_binary = false); + void writeJSON(int json_version, JSON::Writer& p); + std::string getUTF8Val() const; + + private: + QPDF_String(std::string val) : + val(std::move(val)) { - value->setParsedOffset(offset); } - qpdf_offset_t - getParsedOffset() + bool useHexString() const; + std::string val; +}; + +class QPDF_Unresolved final +{ +}; + +class QPDFObject +{ + public: + template + QPDFObject(T&& value) : + value(std::forward(value)) { - return value->getParsedOffset(); } - void - assign(std::shared_ptr o) + + template + QPDFObject(QPDF* qpdf, QPDFObjGen og, T&& value) : + value(std::forward(value)), + qpdf(qpdf), + og(og) { - value = o->value; } - void - swapWith(std::shared_ptr o) + + template + inline static std::shared_ptr create(Args&&... args); + + template + inline static std::shared_ptr + create(QPDF* qpdf, QPDFObjGen og, Args&&... args) { - auto v = value; - value = o->value; - o->value = v; - auto og = value->og; - value->og = o->value->og; - o->value->og = og; + return std::make_shared( + qpdf, og, std::forward(T(std::forward(args)...))); } + std::shared_ptr copy(bool shallow = false); + std::string unparse(); + void write_json(int json_version, JSON::Writer& p); + void disconnect(); + std::string getStringValue() const; + + // Return a unique type code for the resolved object + qpdf_object_type_e + getResolvedTypeCode() const + { + if (getTypeCode() == ::ot_unresolved) { + return QPDF::Resolver::resolved(qpdf, og)->getTypeCode(); + } + if (getTypeCode() == ::ot_reference) { + return std::get(value).obj->getResolvedTypeCode(); + } + return getTypeCode(); + } + // Return a unique type code for the object + qpdf_object_type_e + getTypeCode() const + { + return static_cast(value.index()); + } void - setDefaultDescription(QPDF* qpdf, QPDFObjGen og) + assign_null() { - // Intended for use by the QPDF class - value->setDefaultDescription(qpdf, og); + value = QPDF_Null(); + qpdf = nullptr; + og = QPDFObjGen(); + object_description = nullptr; + parsed_offset = -1; } void - setObjGen(QPDF* qpdf, QPDFObjGen og) + move_to(std::shared_ptr& o, bool destroy) { - value->qpdf = qpdf; - value->og = og; + o->value = std::move(value); + o->qpdf = qpdf; + o->og = og; + o->object_description = object_description; + o->parsed_offset = parsed_offset; + if (!destroy) { + value = QPDF_Reference(o); + } } void - disconnect() + swapWith(std::shared_ptr o) + { + std::swap(value, o->value); + std::swap(qpdf, o->qpdf); + std::swap(object_description, o->object_description); + std::swap(parsed_offset, o->parsed_offset); + } + + void + setObjGen(QPDF* a_qpdf, QPDFObjGen a_og) { - // Disconnect an object from its owning QPDF. This is called by QPDF's destructor. - value->disconnect(); - value->qpdf = nullptr; - value->og = QPDFObjGen(); + qpdf = a_qpdf; + og = a_og; } // Mark an object as destroyed. Used by QPDF's destructor for its indirect objects. - void destroy(); + void + destroy() + { + value = QPDF_Destroyed(); + } bool isUnresolved() const { - return value->type_code == ::ot_unresolved; + return getTypeCode() == ::ot_unresolved; } const QPDFObject* resolved_object() const { - return isUnresolved() ? QPDF::Resolver::resolved(value->qpdf, value->og) : this; + return isUnresolved() ? QPDF::Resolver::resolved(qpdf, og).get() : this; } - template - T* - as() const - { - if (auto result = dynamic_cast(value.get())) { - return result; - } else { - return isUnresolved() - ? dynamic_cast(QPDF::Resolver::resolved(value->qpdf, value->og)->value.get()) - : nullptr; + struct JSON_Descr + { + JSON_Descr(std::shared_ptr input, std::string const& object) : + input(input), + object(object) + { } + + std::shared_ptr input; + std::string object; + }; + + struct ChildDescr + { + ChildDescr( + std::shared_ptr parent, + std::string_view const& static_descr, + std::string var_descr) : + parent(parent), + static_descr(static_descr), + var_descr(var_descr) + { + } + + std::weak_ptr parent; + std::string_view const& static_descr; + std::string var_descr; + }; + + using Description = std::variant; + + void + setDescription( + QPDF* qpdf_p, std::shared_ptr& description, qpdf_offset_t offset = -1) + { + qpdf = qpdf_p; + object_description = description; + setParsedOffset(offset); + } + void + setDefaultDescription(QPDF* a_qpdf, QPDFObjGen const& a_og) + { + qpdf = a_qpdf; + og = a_og; + } + void + setChildDescription( + QPDF* a_qpdf, + std::shared_ptr parent, + std::string_view const& static_descr, + std::string var_descr) + { + object_description = + std::make_shared(ChildDescr(parent, static_descr, var_descr)); + qpdf = a_qpdf; + } + std::string getDescription(); + bool + hasDescription() + { + return object_description || og.isIndirect(); + } + void + setParsedOffset(qpdf_offset_t offset) + { + if (parsed_offset < 0) { + parsed_offset = offset; + } + } + bool + getDescription(QPDF*& a_qpdf, std::string& description) + { + a_qpdf = qpdf; + description = getDescription(); + return qpdf != nullptr; + } + qpdf_offset_t + getParsedOffset() + { + return parsed_offset; + } + QPDF* + getQPDF() + { + return qpdf; + } + QPDFObjGen + getObjGen() + { + return og; } private: + friend class QPDF_Stream; + friend class qpdf::BaseHandle; + + typedef std::variant< + std::monostate, + QPDF_Reserved, + QPDF_Null, + QPDF_Bool, + QPDF_Integer, + QPDF_Real, + QPDF_String, + QPDF_Name, + QPDF_Array, + QPDF_Dictionary, + QPDF_Stream, + QPDF_Operator, + QPDF_InlineImage, + QPDF_Unresolved, + QPDF_Destroyed, + QPDF_Reference> + Value; + Value value; + QPDFObject(QPDFObject const&) = delete; QPDFObject& operator=(QPDFObject const&) = delete; - std::shared_ptr value; + + std::shared_ptr object_description; + + QPDF* qpdf{nullptr}; + QPDFObjGen og{}; + qpdf_offset_t parsed_offset{-1}; }; #endif // QPDFOBJECT_HH diff --git a/libqpdf/qpdf/QPDFParser.hh b/libqpdf/qpdf/QPDFParser.hh index 8a3dadd..545a7c9 100644 --- a/libqpdf/qpdf/QPDFParser.hh +++ b/libqpdf/qpdf/QPDFParser.hh @@ -1,8 +1,8 @@ #ifndef QPDFPARSER_HH #define QPDFPARSER_HH -#include -#include +#include +#include #include #include @@ -24,7 +24,7 @@ class QPDFParser decrypter(decrypter), context(context), description( - std::make_shared( + std::make_shared( std::string(input.getName() + ", " + object_description + " at offset $PO"))), parse_pdf(parse_pdf) { @@ -46,7 +46,7 @@ class QPDFParser { } - std::vector> olist; + std::vector olist; std::map dict; parser_state_e state; std::string key; @@ -78,7 +78,7 @@ class QPDFParser QPDFTokenizer& tokenizer; QPDFObjectHandle::StringDecrypter* decrypter; QPDF* context; - std::shared_ptr description; + std::shared_ptr description; bool parse_pdf; std::vector stack; diff --git a/libqpdf/qpdf/QPDFValue.hh b/libqpdf/qpdf/QPDFValue.hh deleted file mode 100644 index b247093..0000000 --- a/libqpdf/qpdf/QPDFValue.hh +++ /dev/null @@ -1,151 +0,0 @@ -#ifndef QPDFVALUE_HH -#define QPDFVALUE_HH - -#include -#include -#include -#include -#include - -#include -#include -#include - -class QPDF; -class QPDFObjectHandle; -class QPDFObject; - -class QPDFValue: public std::enable_shared_from_this -{ - friend class QPDFObject; - - public: - virtual ~QPDFValue() = default; - - virtual std::shared_ptr copy(bool shallow = false) = 0; - virtual std::string unparse() = 0; - virtual void writeJSON(int json_version, JSON::Writer& p) = 0; - - struct JSON_Descr - { - JSON_Descr(std::shared_ptr input, std::string const& object) : - input(input), - object(object) - { - } - - std::shared_ptr input; - std::string object; - }; - - struct ChildDescr - { - ChildDescr( - std::shared_ptr parent, - std::string_view const& static_descr, - std::string var_descr) : - parent(parent), - static_descr(static_descr), - var_descr(var_descr) - { - } - - std::weak_ptr parent; - std::string_view const& static_descr; - std::string var_descr; - }; - - using Description = std::variant; - - virtual void - setDescription(QPDF* qpdf_p, std::shared_ptr& description, qpdf_offset_t offset) - { - qpdf = qpdf_p; - object_description = description; - setParsedOffset(offset); - } - void - setDefaultDescription(QPDF* a_qpdf, QPDFObjGen a_og) - { - qpdf = a_qpdf; - og = a_og; - } - void - setChildDescription( - QPDF* a_qpdf, - std::shared_ptr parent, - std::string_view const& static_descr, - std::string var_descr) - { - object_description = - std::make_shared(ChildDescr(parent, static_descr, var_descr)); - qpdf = a_qpdf; - } - std::string getDescription(); - bool - hasDescription() - { - return object_description || og.isIndirect(); - } - void - setParsedOffset(qpdf_offset_t offset) - { - if (parsed_offset < 0) { - parsed_offset = offset; - } - } - qpdf_offset_t - getParsedOffset() - { - return parsed_offset; - } - QPDF* - getQPDF() - { - return qpdf; - } - QPDFObjGen - getObjGen() - { - return og; - } - virtual void - disconnect() - { - } - virtual std::string - getStringValue() const - { - return ""; - } - - protected: - QPDFValue() = default; - - QPDFValue(qpdf_object_type_e type_code) : - type_code(type_code) - { - } - QPDFValue(qpdf_object_type_e type_code, QPDF* qpdf, QPDFObjGen og) : - type_code(type_code), - qpdf(qpdf), - og(og) - { - } - - static std::shared_ptr do_create(QPDFValue*); - - private: - QPDFValue(QPDFValue const&) = delete; - QPDFValue& operator=(QPDFValue const&) = delete; - std::shared_ptr object_description; - - const qpdf_object_type_e type_code{::ot_uninitialized}; - - protected: - QPDF* qpdf{nullptr}; - QPDFObjGen og{}; - qpdf_offset_t parsed_offset{-1}; -}; - -#endif // QPDFVALUE_HH diff --git a/libqpdf/qpdf/QPDF_Array.hh b/libqpdf/qpdf/QPDF_Array.hh deleted file mode 100644 index 645e591..0000000 --- a/libqpdf/qpdf/QPDF_Array.hh +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef QPDF_ARRAY_HH -#define QPDF_ARRAY_HH - -#include - -#include -#include - -class QPDF_Array: public QPDFValue -{ - private: - struct Sparse - { - int size{0}; - std::map> elements; - }; - - public: - ~QPDF_Array() override = default; - static std::shared_ptr create(std::vector const& items); - static std::shared_ptr - create(std::vector>&& items, bool sparse); - std::shared_ptr copy(bool shallow = false) override; - std::string unparse() override; - void writeJSON(int json_version, JSON::Writer& p) override; - void disconnect() override; - - int - size() const noexcept - { - return sp ? sp->size : int(elements.size()); - } - std::pair at(int n) const noexcept; - bool setAt(int n, QPDFObjectHandle const& oh); - std::vector getAsVector() const; - void setFromVector(std::vector const& items); - bool insert(int at, QPDFObjectHandle const& item); - void push_back(QPDFObjectHandle const& item); - bool erase(int at); - - private: - QPDF_Array(); - QPDF_Array(QPDF_Array const&); - QPDF_Array(std::vector const& items); - QPDF_Array(std::vector>&& items, bool sparse); - - void checkOwnership(QPDFObjectHandle const& item) const; - - std::unique_ptr sp; - std::vector> elements; -}; - -#endif // QPDF_ARRAY_HH diff --git a/libqpdf/qpdf/QPDF_Bool.hh b/libqpdf/qpdf/QPDF_Bool.hh deleted file mode 100644 index 1692bdc..0000000 --- a/libqpdf/qpdf/QPDF_Bool.hh +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef QPDF_BOOL_HH -#define QPDF_BOOL_HH - -#include - -class QPDF_Bool: public QPDFValue -{ - public: - ~QPDF_Bool() override = default; - static std::shared_ptr create(bool val); - std::shared_ptr copy(bool shallow = false) override; - std::string unparse() override; - void writeJSON(int json_version, JSON::Writer& p) override; - - bool getVal() const; - - private: - QPDF_Bool(bool val); - bool val; -}; - -#endif // QPDF_BOOL_HH diff --git a/libqpdf/qpdf/QPDF_Destroyed.hh b/libqpdf/qpdf/QPDF_Destroyed.hh deleted file mode 100644 index 9259a2d..0000000 --- a/libqpdf/qpdf/QPDF_Destroyed.hh +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef QPDF_DESTROYED_HH -#define QPDF_DESTROYED_HH - -#include - -class QPDF_Destroyed: public QPDFValue -{ - public: - ~QPDF_Destroyed() override = default; - std::shared_ptr copy(bool shallow = false) override; - std::string unparse() override; - void writeJSON(int json_version, JSON::Writer& p) override; - static std::shared_ptr getInstance(); - - private: - QPDF_Destroyed(); -}; - -#endif // QPDF_DESTROYED_HH diff --git a/libqpdf/qpdf/QPDF_Dictionary.hh b/libqpdf/qpdf/QPDF_Dictionary.hh deleted file mode 100644 index 8713a45..0000000 --- a/libqpdf/qpdf/QPDF_Dictionary.hh +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef QPDF_DICTIONARY_HH -#define QPDF_DICTIONARY_HH - -#include - -#include -#include - -#include - -class QPDF_Dictionary: public QPDFValue -{ - public: - ~QPDF_Dictionary() override = default; - static std::shared_ptr create(std::map const& items); - static std::shared_ptr create(std::map&& items); - std::shared_ptr copy(bool shallow = false) override; - std::string unparse() 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 - // null for the value of a non-existent key. This is as per the PDF spec. - bool hasKey(std::string const&); - QPDFObjectHandle getKey(std::string const&); - std::set getKeys(); - std::map const& getAsMap() const; - - // If value is null, remove key; otherwise, replace the value of key, adding it if it does not - // exist. - void replaceKey(std::string const& key, QPDFObjectHandle value); - // Remove key, doing nothing if key does not exist - void removeKey(std::string const& key); - - private: - QPDF_Dictionary(std::map const& items); - QPDF_Dictionary(std::map&& items); - std::map items; -}; - -#endif // QPDF_DICTIONARY_HH diff --git a/libqpdf/qpdf/QPDF_InlineImage.hh b/libqpdf/qpdf/QPDF_InlineImage.hh deleted file mode 100644 index c06662d..0000000 --- a/libqpdf/qpdf/QPDF_InlineImage.hh +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef QPDF_INLINEIMAGE_HH -#define QPDF_INLINEIMAGE_HH - -#include - -class QPDF_InlineImage: public QPDFValue -{ - public: - ~QPDF_InlineImage() override = default; - static std::shared_ptr create(std::string const& val); - std::shared_ptr copy(bool shallow = false) override; - std::string unparse() override; - void writeJSON(int json_version, JSON::Writer& p) override; - std::string - getStringValue() const override - { - return val; - } - - private: - QPDF_InlineImage(std::string const& val); - std::string val; -}; - -#endif // QPDF_INLINEIMAGE_HH diff --git a/libqpdf/qpdf/QPDF_Integer.hh b/libqpdf/qpdf/QPDF_Integer.hh deleted file mode 100644 index ae7f789..0000000 --- a/libqpdf/qpdf/QPDF_Integer.hh +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef QPDF_INTEGER_HH -#define QPDF_INTEGER_HH - -#include - -class QPDF_Integer: public QPDFValue -{ - public: - ~QPDF_Integer() override = default; - static std::shared_ptr create(long long value); - std::shared_ptr copy(bool shallow = false) override; - std::string unparse() override; - void writeJSON(int json_version, JSON::Writer& p) override; - long long getVal() const; - - private: - QPDF_Integer(long long val); - long long val; -}; - -#endif // QPDF_INTEGER_HH diff --git a/libqpdf/qpdf/QPDF_Name.hh b/libqpdf/qpdf/QPDF_Name.hh index b5d3c31..e69de29 100644 --- a/libqpdf/qpdf/QPDF_Name.hh +++ b/libqpdf/qpdf/QPDF_Name.hh @@ -1,33 +0,0 @@ -#ifndef QPDF_NAME_HH -#define QPDF_NAME_HH - -#include - -class QPDF_Name: public QPDFValue -{ - public: - ~QPDF_Name() override = default; - static std::shared_ptr create(std::string const& name); - std::shared_ptr copy(bool shallow = false) override; - std::string unparse() 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 analyzeJSONEncoding(std::string const& name); - std::string - getStringValue() const override - { - return name; - } - - private: - QPDF_Name(std::string const& name); - std::string name; -}; - -#endif // QPDF_NAME_HH diff --git a/libqpdf/qpdf/QPDF_Null.hh b/libqpdf/qpdf/QPDF_Null.hh deleted file mode 100644 index 231ea66..0000000 --- a/libqpdf/qpdf/QPDF_Null.hh +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef QPDF_NULL_HH -#define QPDF_NULL_HH - -#include - -class QPDF_Null: public QPDFValue -{ - public: - ~QPDF_Null() override = default; - static std::shared_ptr create(QPDF* qpdf = nullptr, QPDFObjGen og = QPDFObjGen()); - static std::shared_ptr create( - std::shared_ptr parent, - std::string_view const& static_descr, - std::string var_descr); - static std::shared_ptr create( - std::shared_ptr parent, - std::string_view const& static_descr, - std::string var_descr); - std::shared_ptr copy(bool shallow = false) override; - std::string unparse() override; - void writeJSON(int json_version, JSON::Writer& p) override; - - private: - QPDF_Null(QPDF* qpdf = nullptr, QPDFObjGen og = QPDFObjGen()); -}; - -#endif // QPDF_NULL_HH diff --git a/libqpdf/qpdf/QPDF_Operator.hh b/libqpdf/qpdf/QPDF_Operator.hh deleted file mode 100644 index b9b040d..0000000 --- a/libqpdf/qpdf/QPDF_Operator.hh +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef QPDF_OPERATOR_HH -#define QPDF_OPERATOR_HH - -#include - -class QPDF_Operator: public QPDFValue -{ - public: - ~QPDF_Operator() override = default; - static std::shared_ptr create(std::string const& val); - std::shared_ptr copy(bool shallow = false) override; - std::string unparse() override; - void writeJSON(int json_version, JSON::Writer& p) override; - std::string - getStringValue() const override - { - return val; - } - - private: - QPDF_Operator(std::string const& val); - std::string val; -}; - -#endif // QPDF_OPERATOR_HH diff --git a/libqpdf/qpdf/QPDF_Real.hh b/libqpdf/qpdf/QPDF_Real.hh deleted file mode 100644 index aa9baa5..0000000 --- a/libqpdf/qpdf/QPDF_Real.hh +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef QPDF_REAL_HH -#define QPDF_REAL_HH - -#include - -class QPDF_Real: public QPDFValue -{ - public: - ~QPDF_Real() override = default; - static std::shared_ptr create(std::string const& val); - static std::shared_ptr - create(double value, int decimal_places, bool trim_trailing_zeroes); - std::shared_ptr copy(bool shallow = false) override; - std::string unparse() override; - void writeJSON(int json_version, JSON::Writer& p) override; - std::string - getStringValue() const override - { - return val; - } - - private: - QPDF_Real(std::string const& val); - QPDF_Real(double value, int decimal_places, bool trim_trailing_zeroes); - // Store reals as strings to avoid roundoff errors. - std::string val; -}; - -#endif // QPDF_REAL_HH diff --git a/libqpdf/qpdf/QPDF_Reserved.hh b/libqpdf/qpdf/QPDF_Reserved.hh deleted file mode 100644 index 801987e..0000000 --- a/libqpdf/qpdf/QPDF_Reserved.hh +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef QPDF_RESERVED_HH -#define QPDF_RESERVED_HH - -#include - -class QPDF_Reserved: public QPDFValue -{ - public: - ~QPDF_Reserved() override = default; - static std::shared_ptr create(); - std::shared_ptr copy(bool shallow = false) override; - std::string unparse() override; - void writeJSON(int json_version, JSON::Writer& p) override; - - private: - QPDF_Reserved(); -}; - -#endif // QPDF_RESERVED_HH diff --git a/libqpdf/qpdf/QPDF_Stream.hh b/libqpdf/qpdf/QPDF_Stream.hh deleted file mode 100644 index 0acdf71..0000000 --- a/libqpdf/qpdf/QPDF_Stream.hh +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef QPDF_STREAM_HH -#define QPDF_STREAM_HH - -#include - -#include -#include -#include - -#include -#include - -class Pipeline; -class QPDF; - -class QPDF_Stream final: public QPDFValue -{ - public: - ~QPDF_Stream() final = default; - static std::shared_ptr - create(QPDF*, QPDFObjGen og, QPDFObjectHandle stream_dict, qpdf_offset_t offset, size_t length); - std::shared_ptr copy(bool shallow = false) final; - std::string unparse() final; - void writeJSON(int json_version, JSON::Writer& p) final; - void setDescription( - QPDF*, std::shared_ptr& description, qpdf_offset_t offset) final; - void disconnect() final; - QPDFObjectHandle getDict() const; - bool isDataModified() const; - void setFilterOnWrite(bool); - bool getFilterOnWrite() const; - - // Methods to help QPDF copy foreign streams - size_t getLength() const; - std::shared_ptr getStreamDataBuffer() const; - std::shared_ptr getStreamDataProvider() const; - - // See comments in QPDFObjectHandle.hh for these methods. - bool pipeStreamData( - Pipeline*, - bool* tried_filtering, - int encode_flags, - qpdf_stream_decode_level_e decode_level, - bool suppress_warnings, - bool will_retry); - std::shared_ptr getStreamData(qpdf_stream_decode_level_e); - std::shared_ptr getRawStreamData(); - void replaceStreamData( - std::shared_ptr data, - QPDFObjectHandle const& filter, - QPDFObjectHandle const& decode_parms); - void replaceStreamData( - std::shared_ptr provider, - QPDFObjectHandle const& filter, - QPDFObjectHandle const& decode_parms); - void addTokenFilter(std::shared_ptr token_filter); - JSON getStreamJSON( - int json_version, - qpdf_json_stream_data_e json_data, - 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); - - static void registerStreamFilter( - std::string const& filter_name, std::function()> factory); - - private: - QPDF_Stream( - QPDF*, QPDFObjGen og, QPDFObjectHandle stream_dict, qpdf_offset_t offset, size_t length); - static std::map filter_abbreviations; - static std::map()>> - filter_factories; - - void replaceFilterData( - QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms, size_t length); - bool filterable( - std::vector>& filters, - bool& specialized_compression, - bool& lossy_compression); - void warn(std::string const& message); - void setDictDescription(); - - bool filter_on_write; - QPDFObjectHandle stream_dict; - size_t length; - std::shared_ptr stream_data; - std::shared_ptr stream_provider; - std::vector> token_filters; -}; - -#endif // QPDF_STREAM_HH diff --git a/libqpdf/qpdf/QPDF_String.hh b/libqpdf/qpdf/QPDF_String.hh deleted file mode 100644 index 967b2d3..0000000 --- a/libqpdf/qpdf/QPDF_String.hh +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef QPDF_STRING_HH -#define QPDF_STRING_HH - -#include - -// QPDF_Strings may included embedded null characters. - -class QPDF_String: public QPDFValue -{ - friend class QPDFWriter; - - public: - ~QPDF_String() override = default; - static std::shared_ptr create(std::string const& val); - static std::shared_ptr create_utf16(std::string const& utf8_val); - std::shared_ptr copy(bool shallow = false) override; - std::string unparse() override; - std::string unparse(bool force_binary); - void writeJSON(int json_version, JSON::Writer& p) override; - std::string getUTF8Val() const; - std::string - getStringValue() const override - { - return val; - } - - private: - QPDF_String(std::string const& val); - bool useHexString() const; - std::string val; -}; - -#endif // QPDF_STRING_HH diff --git a/libqpdf/qpdf/QPDF_Unresolved.hh b/libqpdf/qpdf/QPDF_Unresolved.hh deleted file mode 100644 index bcd5a63..0000000 --- a/libqpdf/qpdf/QPDF_Unresolved.hh +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef QPDF_UNRESOLVED_HH -#define QPDF_UNRESOLVED_HH - -#include - -class QPDF_Unresolved: public QPDFValue -{ - public: - ~QPDF_Unresolved() override = default; - static std::shared_ptr create(QPDF* qpdf, QPDFObjGen og); - std::shared_ptr copy(bool shallow = false) override; - std::string unparse() override; - void writeJSON(int json_version, JSON::Writer& p) override; - std::string getStringValue() const override; - - private: - QPDF_Unresolved(QPDF* qpdf, QPDFObjGen og); -}; - -#endif // QPDF_UNRESOLVED_HH diff --git a/libtests/sparse_array.cc b/libtests/sparse_array.cc index 2182d5a..a930ee7 100644 --- a/libtests/sparse_array.cc +++ b/libtests/sparse_array.cc @@ -1,17 +1,16 @@ #include #include -#include +#include #include -#include #include int main() { - auto obj = QPDF_Array::create({}, true); - QPDF_Array& a = *obj->as(); + auto obj = QPDFObject::create(std::vector(), true); + auto a = qpdf::Array(obj); assert(a.size() == 0); @@ -88,16 +87,17 @@ main() QPDF pdf; pdf.emptyPDF(); - obj = QPDF_Array::create({10, "null"_qpdf.getObj()}, true); - QPDF_Array& b = *obj->as(); + obj = QPDFObject::create( + std::vector{10, "null"_qpdf.getObj()}, true); + auto b = qpdf::Array(obj); b.setAt(5, pdf.newIndirectNull()); b.setAt(7, "[0 1 2 3]"_qpdf); assert(b.at(3).second.isNull()); assert(b.at(8).second.isNull()); assert(b.at(5).second.isIndirect()); - assert(b.unparse() == "[ null null null null null 3 0 R null [ 0 1 2 3 ] null null ]"); - auto c = b.copy(true); - auto d = b.copy(false); + assert(obj->unparse() == "[ null null null null null 3 0 R null [ 0 1 2 3 ] null null ]"); + auto c = obj->copy(true); + auto d = obj->copy(false); b.at(7).second.setArrayItem(2, "42"_qpdf); assert(c->unparse() == "[ null null null null null 3 0 R null [ 0 1 42 3 ] null null ]"); assert(d->unparse() == "[ null null null null null 3 0 R null [ 0 1 2 3 ] null null ]"); diff --git a/qpdf/sizes.cc b/qpdf/sizes.cc index 6512187..6075712 100644 --- a/qpdf/sizes.cc +++ b/qpdf/sizes.cc @@ -109,7 +109,6 @@ main() print_size(QPDFNumberTreeObjectHelper); print_size(QPDFNumberTreeObjectHelper::iterator); print_size(QPDFObjGen); - print_size(QPDFObjGen::set); print_size(QPDFObjectHandle); print_size(QPDFObjectHandle::ParserCallbacks); print_size(QPDFObjectHandle::QPDFArrayItems); @@ -118,6 +117,7 @@ main() print_size(QPDFObjectHandle::QPDFDictItems::iterator); print_size(QPDFObjectHandle::StreamDataProvider); print_size(QPDFObjectHandle::TokenFilter); + print_size(QPDFObjectHelper); print_size(QPDFOutlineDocumentHelper); print_size(QPDFOutlineObjectHelper); print_size(QPDFPageDocumentHelper);