diff options
-rw-r--r-- | .github/workflows/main.yml | 5 | ||||
-rwxr-xr-x | build-scripts/build-mac | 7 | ||||
-rw-r--r-- | include/qpdf/JSON.hh | 28 | ||||
-rw-r--r-- | include/qpdf/QIntC.hh | 132 | ||||
-rw-r--r-- | include/qpdf/QUtil.hh | 6 | ||||
-rw-r--r-- | libqpdf/JSON.cc | 130 | ||||
-rw-r--r-- | libqpdf/QPDFTokenizer.cc | 8 | ||||
-rw-r--r-- | libqpdf/QPDF_Name.cc | 5 | ||||
-rw-r--r-- | manual/linearization.rst | 2 |
9 files changed, 203 insertions, 120 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aec50b2c..d64b7996 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -90,11 +90,6 @@ jobs: needs: Prebuild steps: - uses: actions/checkout@v3 - - name: 'Download external libs' - uses: actions/download-artifact@v3 - with: - name: external-libs - path: . - name: 'Mac build and test' run: build-scripts/build-mac AppImage: diff --git a/build-scripts/build-mac b/build-scripts/build-mac index 094de58f..89e2adf4 100755 --- a/build-scripts/build-mac +++ b/build-scripts/build-mac @@ -1,13 +1,6 @@ #!/bin/bash set -ex cd $(dirname $0)/.. -unzip qpdf-external-libs-src.zip -tar xzf external-libs-src/jpegsrc* -cd jpeg-* -./configure -make -k -sudo make install -cd .. cmake -S . -B build -DCI_MODE=1 -DCMAKE_BUILD_TYPE=Release cmake --build build --verbose -j$(sysctl -n hw.ncpu) -- -k (cd build; ctest --verbose) diff --git a/include/qpdf/JSON.hh b/include/qpdf/JSON.hh index 4b829017..2906d85a 100644 --- a/include/qpdf/JSON.hh +++ b/include/qpdf/JSON.hh @@ -340,13 +340,33 @@ class JSON static void writeClose(Pipeline* p, bool first, size_t depth, char const* delimeter); + enum value_type_e { + vt_none, + vt_dictionary, + vt_array, + vt_string, + vt_number, + vt_bool, + vt_null, + vt_blob, + }; + struct JSON_value { + JSON_value(value_type_e type_code) : + type_code(type_code) + { + } virtual ~JSON_value() = default; virtual void write(Pipeline*, size_t depth) const = 0; + const value_type_e type_code{vt_none}; }; struct JSON_dictionary: public JSON_value { + JSON_dictionary() : + JSON_value(vt_dictionary) + { + } virtual ~JSON_dictionary() = default; virtual void write(Pipeline*, size_t depth) const; std::map<std::string, std::shared_ptr<JSON_value>> members; @@ -354,6 +374,10 @@ class JSON }; struct JSON_array: public JSON_value { + JSON_array() : + JSON_value(vt_array) + { + } virtual ~JSON_array() = default; virtual void write(Pipeline*, size_t depth) const; std::vector<std::shared_ptr<JSON_value>> elements; @@ -384,6 +408,10 @@ class JSON }; struct JSON_null: public JSON_value { + JSON_null() : + JSON_value(vt_null) + { + } virtual ~JSON_null() = default; virtual void write(Pipeline*, size_t depth) const; }; diff --git a/include/qpdf/QIntC.hh b/include/qpdf/QIntC.hh index dadc7582..b896a026 100644 --- a/include/qpdf/QIntC.hh +++ b/include/qpdf/QIntC.hh @@ -63,48 +63,60 @@ namespace QIntC // QIntC = qpdf Integer Conversion class IntConverter<From, To, false, false> { public: - static To + inline static To convert(From const& i) { // From and To are both unsigned. if (i > std::numeric_limits<To>::max()) { - std::ostringstream msg; - msg.imbue(std::locale::classic()); - msg << "integer out of range converting " << i << " from a " - << sizeof(From) << "-byte unsigned type to a " << sizeof(To) - << "-byte unsigned type"; - throw std::range_error(msg.str()); + error(i); } return static_cast<To>(i); } + + static void + error(From i) + { + std::ostringstream msg; + msg.imbue(std::locale::classic()); + msg << "integer out of range converting " << i << " from a " + << sizeof(From) << "-byte unsigned type to a " << sizeof(To) + << "-byte unsigned type"; + throw std::range_error(msg.str()); + } }; template <typename From, typename To> class IntConverter<From, To, true, true> { public: - static To + inline static To convert(From const& i) { // From and To are both signed. if ((i < std::numeric_limits<To>::min()) || (i > std::numeric_limits<To>::max())) { - std::ostringstream msg; - msg.imbue(std::locale::classic()); - msg << "integer out of range converting " << i << " from a " - << sizeof(From) << "-byte signed type to a " << sizeof(To) - << "-byte signed type"; - throw std::range_error(msg.str()); + error(i); } return static_cast<To>(i); } + + static void + error(From i) + { + std::ostringstream msg; + msg.imbue(std::locale::classic()); + msg << "integer out of range converting " << i << " from a " + << sizeof(From) << "-byte signed type to a " << sizeof(To) + << "-byte signed type"; + throw std::range_error(msg.str()); + } }; template <typename From, typename To> class IntConverter<From, To, true, false> { public: - static To + inline static To convert(From const& i) { // From is signed, and To is unsigned. If i > 0, it's safe to @@ -112,22 +124,28 @@ namespace QIntC // QIntC = qpdf Integer Conversion // compare with To's max. auto ii = static_cast<typename to_u<From>::type>(i); if ((i < 0) || (ii > std::numeric_limits<To>::max())) { - std::ostringstream msg; - msg.imbue(std::locale::classic()); - msg << "integer out of range converting " << i << " from a " - << sizeof(From) << "-byte signed type to a " << sizeof(To) - << "-byte unsigned type"; - throw std::range_error(msg.str()); + error(i); } return static_cast<To>(i); } + + static void + error(From i) + { + std::ostringstream msg; + msg.imbue(std::locale::classic()); + msg << "integer out of range converting " << i << " from a " + << sizeof(From) << "-byte signed type to a " << sizeof(To) + << "-byte unsigned type"; + throw std::range_error(msg.str()); + } }; template <typename From, typename To> class IntConverter<From, To, false, true> { public: - static To + inline static To convert(From const& i) { // From is unsigned, and to is signed. Convert To's max to the @@ -135,98 +153,104 @@ namespace QIntC // QIntC = qpdf Integer Conversion auto maxval = static_cast<typename to_u<To>::type>( std::numeric_limits<To>::max()); if (i > maxval) { - std::ostringstream msg; - msg.imbue(std::locale::classic()); - msg << "integer out of range converting " << i << " from a " - << sizeof(From) << "-byte unsigned type to a " << sizeof(To) - << "-byte signed type"; - throw std::range_error(msg.str()); + error(i); } return static_cast<To>(i); } + + static void + error(From i) + { + std::ostringstream msg; + msg.imbue(std::locale::classic()); + msg << "integer out of range converting " << i << " from a " + << sizeof(From) << "-byte unsigned type to a " << sizeof(To) + << "-byte signed type"; + throw std::range_error(msg.str()); + } }; // Specific converters. The return type of each function must match // the second template parameter to IntConverter. template <typename T> - char + inline char to_char(T const& i) { return IntConverter<T, char>::convert(i); } template <typename T> - unsigned char + inline unsigned char to_uchar(T const& i) { return IntConverter<T, unsigned char>::convert(i); } template <typename T> - short + inline short to_short(T const& i) { return IntConverter<T, short>::convert(i); } template <typename T> - unsigned short + inline unsigned short to_ushort(T const& i) { return IntConverter<T, unsigned short>::convert(i); } template <typename T> - int + inline int to_int(T const& i) { return IntConverter<T, int>::convert(i); } template <typename T> - unsigned int + inline unsigned int to_uint(T const& i) { return IntConverter<T, unsigned int>::convert(i); } template <typename T> - size_t + inline size_t to_size(T const& i) { return IntConverter<T, size_t>::convert(i); } template <typename T> - qpdf_offset_t + inline qpdf_offset_t to_offset(T const& i) { return IntConverter<T, qpdf_offset_t>::convert(i); } template <typename T> - long + inline long to_long(T const& i) { return IntConverter<T, long>::convert(i); } template <typename T> - unsigned long + inline unsigned long to_ulong(T const& i) { return IntConverter<T, unsigned long>::convert(i); } template <typename T> - long long + inline long long to_longlong(T const& i) { return IntConverter<T, long long>::convert(i); } template <typename T> - unsigned long long + inline unsigned long long to_ulonglong(T const& i) { return IntConverter<T, unsigned long long>::convert(i); @@ -234,12 +258,8 @@ namespace QIntC // QIntC = qpdf Integer Conversion template <typename T> void - range_check(T const& cur, T const& delta) + range_check_error(T const& cur, T const& delta) { - if ((delta > 0) != (cur > 0)) { - return; - } - if ((delta > 0) && ((std::numeric_limits<T>::max() - cur) < delta)) { std::ostringstream msg; msg.imbue(std::locale::classic()); @@ -257,13 +277,19 @@ namespace QIntC // QIntC = qpdf Integer Conversion } template <typename T> - void - range_check_substract(T const& cur, T const& delta) + inline void + range_check(T const& cur, T const& delta) { - if ((delta >= 0) == (cur >= 0)) { + if ((delta > 0) != (cur > 0)) { return; } + QIntC::range_check_error<T>(cur, delta); + } + template <typename T> + void + range_check_substract_error(T const& cur, T const& delta) + { if ((delta > 0) && ((std::numeric_limits<T>::min() + delta) > cur)) { std::ostringstream msg; msg.imbue(std::locale::classic()); @@ -279,6 +305,16 @@ namespace QIntC // QIntC = qpdf Integer Conversion throw std::range_error(msg.str()); } } + + template <typename T> + inline void + range_check_substract(T const& cur, T const& delta) + { + if ((delta >= 0) == (cur >= 0)) { + return; + } + QIntC::range_check_substract_error<T>(cur, delta); + } }; // namespace QIntC #endif // QINTC_HH diff --git a/include/qpdf/QUtil.hh b/include/qpdf/QUtil.hh index 27521a70..dd452026 100644 --- a/include/qpdf/QUtil.hh +++ b/include/qpdf/QUtil.hh @@ -545,13 +545,15 @@ namespace QUtil inline bool QUtil::is_hex_digit(char ch) { - return (ch && (strchr("0123456789abcdefABCDEF", ch) != nullptr)); + return ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f') || + ('A' <= ch && ch <= 'F'); } inline bool QUtil::is_space(char ch) { - return (ch && (strchr(" \f\n\r\t\v", ch) != nullptr)); + return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\f' || + ch == '\v'; } inline bool diff --git a/libqpdf/JSON.cc b/libqpdf/JSON.cc index 1dc09013..76db652b 100644 --- a/libqpdf/JSON.cc +++ b/libqpdf/JSON.cc @@ -125,6 +125,7 @@ JSON::JSON_array::write(Pipeline* p, size_t depth) const } JSON::JSON_string::JSON_string(std::string const& utf8) : + JSON_value(vt_string), utf8(utf8), encoded(encode_string(utf8)) { @@ -137,16 +138,19 @@ JSON::JSON_string::write(Pipeline* p, size_t) const } JSON::JSON_number::JSON_number(long long value) : + JSON_value(vt_number), encoded(std::to_string(value)) { } JSON::JSON_number::JSON_number(double value) : + JSON_value(vt_number), encoded(QUtil::double_to_string(value, 6)) { } JSON::JSON_number::JSON_number(std::string const& value) : + JSON_value(vt_number), encoded(value) { } @@ -158,6 +162,7 @@ JSON::JSON_number::write(Pipeline* p, size_t) const } JSON::JSON_bool::JSON_bool(bool val) : + JSON_value(vt_bool), value(val) { } @@ -175,6 +180,7 @@ JSON::JSON_null::write(Pipeline* p, size_t) const } JSON::JSON_blob::JSON_blob(std::function<void(Pipeline*)> fn) : + JSON_value(vt_blob), fn(fn) { } @@ -212,41 +218,61 @@ JSON::unparse() const std::string JSON::encode_string(std::string const& str) { - std::string result; - size_t len = str.length(); - for (size_t i = 0; i < len; ++i) { - unsigned char ch = static_cast<unsigned char>(str.at(i)); - switch (ch) { - case '\\': - result += "\\\\"; - break; - case '\"': - result += "\\\""; - break; - case '\b': - result += "\\b"; - break; - case '\f': - result += "\\f"; - break; - case '\n': - result += "\\n"; - break; - case '\r': - result += "\\r"; - break; - case '\t': - result += "\\t"; - break; - default: - if (ch < 32) { - result += "\\u" + QUtil::int_to_string_base(ch, 16, 4); - } else { - result.append(1, static_cast<char>(ch)); + static auto constexpr hexchars = "0123456789abcdef"; + + auto begin = str.cbegin(); + auto end = str.cend(); + auto iter = begin; + while (iter != end) { + auto c = static_cast<unsigned char>(*iter); + if ((c > 34 && c != '\\') || c == ' ' || c == 33) { + // Optimistically check that no char in str requires escaping. + // Hopefully we can just return the input str. + ++iter; + } else { + // We found a char that requires escaping. Initialize result to the + // chars scanned so far, append/replace the rest of str one char at + // a time, and return the result. + std::string result{begin, iter}; + + for (; iter != end; ++iter) { + auto ch = static_cast<unsigned char>(*iter); + if ((ch > 34 && ch != '\\') || ch == ' ' || ch == 33) { + // Check for most common case first. + result += *iter; + } else { + switch (ch) { + case '\\': + result += "\\\\"; + break; + case '\"': + result += "\\\""; + break; + case '\b': + result += "\\b"; + break; + case '\f': + result += "\\f"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + default: + result += ch < 16 ? "\\u000" : "\\u001"; + result += hexchars[ch % 16]; + } + } } + return result; } } - return result; + return str; } JSON @@ -348,56 +374,52 @@ JSON::makeBlob(std::function<void(Pipeline*)> fn) bool JSON::isArray() const { - return nullptr != dynamic_cast<JSON_array const*>(this->m->value.get()); + return m->value->type_code == vt_array; } bool JSON::isDictionary() const { - return nullptr != - dynamic_cast<JSON_dictionary const*>(this->m->value.get()); + return m->value->type_code == vt_dictionary; } bool JSON::getString(std::string& utf8) const { - auto v = dynamic_cast<JSON_string const*>(this->m->value.get()); - if (v == nullptr) { - return false; + if (m->value->type_code == vt_string) { + auto v = dynamic_cast<JSON_string const*>(this->m->value.get()); + utf8 = v->utf8; + return true; } - utf8 = v->utf8; - return true; + return false; } bool JSON::getNumber(std::string& value) const { - auto v = dynamic_cast<JSON_number const*>(this->m->value.get()); - if (v == nullptr) { - return false; + if (m->value->type_code == vt_number) { + auto v = dynamic_cast<JSON_number const*>(this->m->value.get()); + value = v->encoded; + return true; } - value = v->encoded; - return true; + return false; } bool JSON::getBool(bool& value) const { - auto v = dynamic_cast<JSON_bool const*>(this->m->value.get()); - if (v == nullptr) { - return false; + if (m->value->type_code == vt_bool) { + auto v = dynamic_cast<JSON_bool const*>(this->m->value.get()); + value = v->value; + return true; } - value = v->value; - return true; + return false; } bool JSON::isNull() const { - if (dynamic_cast<JSON_null const*>(this->m->value.get())) { - return true; - } - return false; + return m->value->type_code == vt_null; } bool diff --git a/libqpdf/QPDFTokenizer.cc b/libqpdf/QPDFTokenizer.cc index fe36d768..990d5b65 100644 --- a/libqpdf/QPDFTokenizer.cc +++ b/libqpdf/QPDFTokenizer.cc @@ -14,10 +14,14 @@ #include <stdlib.h> #include <string.h> -static bool +static inline bool is_delimiter(char ch) { - return (strchr(" \t\n\v\f\r()<>[]{}/%", ch) != nullptr); + return ( + ch == ' ' || ch == '\n' || ch == '/' || ch == '(' || ch == ')' || + ch == '{' || ch == '}' || ch == '<' || ch == '>' || ch == '[' || + ch == ']' || ch == '%' || ch == '\t' || ch == '\r' || ch == '\v' || + ch == '\f' || ch == 0); } namespace diff --git a/libqpdf/QPDF_Name.cc b/libqpdf/QPDF_Name.cc index 1587bcf4..3241ec1c 100644 --- a/libqpdf/QPDF_Name.cc +++ b/libqpdf/QPDF_Name.cc @@ -37,7 +37,10 @@ QPDF_Name::normalizeName(std::string const& name) // QPDFTokenizer embeds a null character to encode an // invalid #. result += "#"; - } else if (strchr("#()<>[]{}/%", ch) || (ch < 33) || (ch > 126)) { + } else if ( + ch < 33 || ch == '/' || ch == '(' || ch == ')' || ch == '{' || + ch == '}' || ch == '<' || ch == '>' || ch == '[' || ch == ']' || + ch == '%' || ch > 126) { result += QUtil::hex_encode_char(ch); } else { result += ch; diff --git a/manual/linearization.rst b/manual/linearization.rst index 89362bc2..9b0ec50d 100644 --- a/manual/linearization.rst +++ b/manual/linearization.rst @@ -4,7 +4,7 @@ Linearization ============= This chapter describes how ``QPDF`` and ``QPDFWriter`` implement -creation and processing of linearized PDFS. +creation and processing of linearized PDFs. .. _linearization-strategy: |