Commit ad3ecadf05200e8fd9fb2de6e4fc90a5ec6cb209
Committed by
GitHub
Merge pull request #1346 from jberkenbilt/zopfli
Add zopfli support
Showing
32 changed files
with
547 additions
and
60 deletions
.github/workflows/main.yml
| @@ -123,6 +123,13 @@ jobs: | @@ -123,6 +123,13 @@ jobs: | ||
| 123 | - uses: actions/checkout@v4 | 123 | - uses: actions/checkout@v4 |
| 124 | - name: 'Sanitizer Tests' | 124 | - name: 'Sanitizer Tests' |
| 125 | run: build-scripts/test-sanitizers | 125 | run: build-scripts/test-sanitizers |
| 126 | + Zopfli: | ||
| 127 | + runs-on: ubuntu-latest | ||
| 128 | + needs: Prebuild | ||
| 129 | + steps: | ||
| 130 | + - uses: actions/checkout@v4 | ||
| 131 | + - name: 'Zopfli Tests' | ||
| 132 | + run: build-scripts/test-zopfli | ||
| 126 | CodeCov: | 133 | CodeCov: |
| 127 | runs-on: ubuntu-latest | 134 | runs-on: ubuntu-latest |
| 128 | needs: Prebuild | 135 | needs: Prebuild |
CMakeLists.txt
| @@ -98,6 +98,8 @@ set(DEFAULT_CRYPTO CACHE STRING "") | @@ -98,6 +98,8 @@ set(DEFAULT_CRYPTO CACHE STRING "") | ||
| 98 | option(DEFAULT_CRYPTO | 98 | option(DEFAULT_CRYPTO |
| 99 | "Specify default crypto; otherwise chosen automatically" "") | 99 | "Specify default crypto; otherwise chosen automatically" "") |
| 100 | 100 | ||
| 101 | +option(ZOPFLI, "Use zopfli for zlib-compatible compression") | ||
| 102 | + | ||
| 101 | # INSTALL_MANUAL is not dependent on building docs. When creating some | 103 | # INSTALL_MANUAL is not dependent on building docs. When creating some |
| 102 | # distributions, we build the doc in one run, copy doc-dist in, and | 104 | # distributions, we build the doc in one run, copy doc-dist in, and |
| 103 | # install it elsewhere. | 105 | # install it elsewhere. |
ChangeLog
| 1 | 2025-02-02 Jay Berkenbilt <ejb@ql.org> | 1 | 2025-02-02 Jay Berkenbilt <ejb@ql.org> |
| 2 | 2 | ||
| 3 | + * Add support for the zopfli compression library. See manual for | ||
| 4 | + details. Fixes #1323. | ||
| 5 | + | ||
| 3 | * Have fix-qdf accept a second argument, interpreted as the output | 6 | * Have fix-qdf accept a second argument, interpreted as the output |
| 4 | file. Fixes #1330. | 7 | file. Fixes #1330. |
| 5 | 8 |
README-maintainer.md
| @@ -67,6 +67,10 @@ Note that, in early 2024, branch coverage information is not very accurate with | @@ -67,6 +67,10 @@ Note that, in early 2024, branch coverage information is not very accurate with | ||
| 67 | 67 | ||
| 68 | Memory checks: | 68 | Memory checks: |
| 69 | 69 | ||
| 70 | +Note: if clang++ fails to create output, it may be necessary to install a specific version of | ||
| 71 | +libstdc++-dev. For example, with clang++ version 20 on Ubuntu 24.04, `clang++ -v` indicates the | ||
| 72 | +selected GCC installation is 14, so it is necessary to install `libstdc++-14-dev`. | ||
| 73 | + | ||
| 70 | ``` | 74 | ``` |
| 71 | CFLAGS="-fsanitize=address -fsanitize=undefined" \ | 75 | CFLAGS="-fsanitize=address -fsanitize=undefined" \ |
| 72 | CXXFLAGS="-fsanitize=address -fsanitize=undefined" \ | 76 | CXXFLAGS="-fsanitize=address -fsanitize=undefined" \ |
| @@ -298,8 +302,8 @@ Building docs from pull requests is also enabled. | @@ -298,8 +302,8 @@ Building docs from pull requests is also enabled. | ||
| 298 | ## ZLIB COMPATIBILITY | 302 | ## ZLIB COMPATIBILITY |
| 299 | 303 | ||
| 300 | The qpdf test suite is designed to be independent of the output of any | 304 | The qpdf test suite is designed to be independent of the output of any |
| 301 | -particular version of zlib. There are several strategies to make this | ||
| 302 | -work: | 305 | +particular version of zlib. (See also `ZOPFLI` in README.md.) There |
| 306 | +are several strategies to make this work: | ||
| 303 | 307 | ||
| 304 | * `build-scripts/test-alt-zlib` runs in CI and runs the test suite | 308 | * `build-scripts/test-alt-zlib` runs in CI and runs the test suite |
| 305 | with a non-default zlib. Please refer to that code for an example of | 309 | with a non-default zlib. Please refer to that code for an example of |
README.md
| @@ -66,6 +66,16 @@ below. | @@ -66,6 +66,16 @@ below. | ||
| 66 | 66 | ||
| 67 | Detailed information appears in the [manual](https://qpdf.readthedocs.io/en/latest/installation.html). | 67 | Detailed information appears in the [manual](https://qpdf.readthedocs.io/en/latest/installation.html). |
| 68 | 68 | ||
| 69 | +## Zopfli | ||
| 70 | + | ||
| 71 | +If qpdf is built with [zopfli](https://github.com/google/zopfli) support and the `QPDF_ZOPFLI` environment variable is set to any value other than `disabled`, qpdf will use the zopfli compression library instead of zlib to generate flate-compressed streams. The zopfli algorithm is much slower (about 100x according to their website) than zlib but produces slightly smaller output, making it suitable for cases such as generation of archival PDFs where size is important regardless of speed. To build with zopfli support, you must have the zopfli library and header file installed. | ||
| 72 | + | ||
| 73 | +The environment variable `QPDF_ZOPFLI` can be set to the following values: | ||
| 74 | +* `disabled` (or unset): do not use zopfli | ||
| 75 | +* `force`: use zopfli; fail if zopfli is not compiled in | ||
| 76 | +* `silent`: use zopfli if available; otherwise silently fall back to zlib | ||
| 77 | +* any other value: use zopfli if available, and warn if not | ||
| 78 | + | ||
| 69 | # Licensing terms of embedded software | 79 | # Licensing terms of embedded software |
| 70 | 80 | ||
| 71 | qpdf makes use of zlib and jpeg libraries for its functionality. These packages can be downloaded separately from their | 81 | qpdf makes use of zlib and jpeg libraries for its functionality. These packages can be downloaded separately from their |
build-scripts/test-zopfli
0 โ 100755
| 1 | +#!/bin/bash | ||
| 2 | +set -eo pipefail | ||
| 3 | +sudo apt-get update | ||
| 4 | +sudo apt-get -y install \ | ||
| 5 | + build-essential cmake \ | ||
| 6 | + zlib1g-dev libjpeg-dev libgnutls28-dev libssl-dev \ | ||
| 7 | + libzopfli-dev | ||
| 8 | + | ||
| 9 | +cmake -S . -B build \ | ||
| 10 | + -DCI_MODE=1 -DBUILD_STATIC_LIBS=0 -DCMAKE_BUILD_TYPE=Release \ | ||
| 11 | + -DREQUIRE_CRYPTO_OPENSSL=1 -DREQUIRE_CRYPTO_GNUTLS=1 \ | ||
| 12 | + -DENABLE_QTC=1 -DZOPFLI=1 | ||
| 13 | +cmake --build build --verbose -j$(nproc) -- -k | ||
| 14 | + | ||
| 15 | +# Make sure we are using zopfli | ||
| 16 | +export QPDF_ZOPFLI=force | ||
| 17 | +zopfli="$(./build/zlib-flate/zlib-flate --_zopfli)" | ||
| 18 | +if [ "$zopfli" != "11" ]; then | ||
| 19 | + echo "zopfli is not working" | ||
| 20 | + exit 2 | ||
| 21 | +fi | ||
| 22 | + | ||
| 23 | +# If this fails, please see ZLIB COMPATIBILITY in README-maintainer.md. | ||
| 24 | +# The tests are very slow with this option. Just run essential tests. | ||
| 25 | +# If zlib-flate and qpdf tests all pass, we can be pretty sure it works. | ||
| 26 | +(cd build; ctest --verbose -R zlib-flate) | ||
| 27 | +(cd build; ctest --verbose -R qpdf) |
include/qpdf/Pl_Flate.hh
| @@ -24,8 +24,10 @@ | @@ -24,8 +24,10 @@ | ||
| 24 | #define PL_FLATE_HH | 24 | #define PL_FLATE_HH |
| 25 | 25 | ||
| 26 | #include <qpdf/Pipeline.hh> | 26 | #include <qpdf/Pipeline.hh> |
| 27 | +#include <qpdf/QPDFLogger.hh> | ||
| 27 | #include <functional> | 28 | #include <functional> |
| 28 | #include <memory> | 29 | #include <memory> |
| 30 | +#include <string> | ||
| 29 | 31 | ||
| 30 | class QPDF_DLL_CLASS Pl_Flate: public Pipeline | 32 | class QPDF_DLL_CLASS Pl_Flate: public Pipeline |
| 31 | { | 33 | { |
| @@ -65,6 +67,23 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline | @@ -65,6 +67,23 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline | ||
| 65 | QPDF_DLL | 67 | QPDF_DLL |
| 66 | void setWarnCallback(std::function<void(char const*, int)> callback); | 68 | void setWarnCallback(std::function<void(char const*, int)> callback); |
| 67 | 69 | ||
| 70 | + // Returns true if qpdf was built with zopfli support. | ||
| 71 | + QPDF_DLL | ||
| 72 | + static bool zopfli_supported(); | ||
| 73 | + | ||
| 74 | + // Returns true if zopfli is enabled. Zopfli is enabled if QPDF_ZOPFLI is set to a value other | ||
| 75 | + // than "disabled" and zopfli support is compiled in. | ||
| 76 | + QPDF_DLL | ||
| 77 | + static bool zopfli_enabled(); | ||
| 78 | + | ||
| 79 | + // If zopfli is supported, returns true. Otherwise, check the QPDF_ZOPFLI | ||
| 80 | + // environment variable as follows: | ||
| 81 | + // - "disabled" or "silent": return true | ||
| 82 | + // - "force": qpdf_exit_error, throw an exception | ||
| 83 | + // - Any other value: issue a warning, and return false | ||
| 84 | + QPDF_DLL | ||
| 85 | + static bool zopfli_check_env(QPDFLogger* logger = nullptr); | ||
| 86 | + | ||
| 68 | private: | 87 | private: |
| 69 | QPDF_DLL_PRIVATE | 88 | QPDF_DLL_PRIVATE |
| 70 | void handleData(unsigned char const* data, size_t len, int flush); | 89 | void handleData(unsigned char const* data, size_t len, int flush); |
| @@ -72,6 +91,8 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline | @@ -72,6 +91,8 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline | ||
| 72 | void checkError(char const* prefix, int error_code); | 91 | void checkError(char const* prefix, int error_code); |
| 73 | QPDF_DLL_PRIVATE | 92 | QPDF_DLL_PRIVATE |
| 74 | void warn(char const*, int error_code); | 93 | void warn(char const*, int error_code); |
| 94 | + QPDF_DLL_PRIVATE | ||
| 95 | + void finish_zopfli(); | ||
| 75 | 96 | ||
| 76 | QPDF_DLL_PRIVATE | 97 | QPDF_DLL_PRIVATE |
| 77 | static int compression_level; | 98 | static int compression_level; |
| @@ -95,6 +116,7 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline | @@ -95,6 +116,7 @@ class QPDF_DLL_CLASS Pl_Flate: public Pipeline | ||
| 95 | void* zdata; | 116 | void* zdata; |
| 96 | unsigned long long written{0}; | 117 | unsigned long long written{0}; |
| 97 | std::function<void(char const*, int)> callback; | 118 | std::function<void(char const*, int)> callback; |
| 119 | + std::unique_ptr<std::string> zopfli_buf; | ||
| 98 | }; | 120 | }; |
| 99 | 121 | ||
| 100 | std::shared_ptr<Members> m; | 122 | std::shared_ptr<Members> m; |
job.sums
| 1 | # Generated by generate_auto_job | 1 | # Generated by generate_auto_job |
| 2 | -CMakeLists.txt 4aaa3d5df1713d9e3b9c6778101c6af3efa2131a2f4c069095abee269d5eaccc | 2 | +CMakeLists.txt af74c05aea88512ef9a37a0708c2a03747a4ff23dc7919075c8d3b62b73aad79 |
| 3 | generate_auto_job f64733b79dcee5a0e3e8ccc6976448e8ddf0e8b6529987a66a7d3ab2ebc10a86 | 3 | generate_auto_job f64733b79dcee5a0e3e8ccc6976448e8ddf0e8b6529987a66a7d3ab2ebc10a86 |
| 4 | include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4 | 4 | include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4 |
| 5 | include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42 | 5 | include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42 |
| @@ -7,14 +7,14 @@ include/qpdf/auto_job_c_enc.hh 28446f3c32153a52afa239ea40503e6cc8ac2c026813526a3 | @@ -7,14 +7,14 @@ include/qpdf/auto_job_c_enc.hh 28446f3c32153a52afa239ea40503e6cc8ac2c026813526a3 | ||
| 7 | include/qpdf/auto_job_c_main.hh 84f463237235b2c095b747a4f5dd00f109ee596a1c207b944efb296c0c568cae | 7 | include/qpdf/auto_job_c_main.hh 84f463237235b2c095b747a4f5dd00f109ee596a1c207b944efb296c0c568cae |
| 8 | include/qpdf/auto_job_c_pages.hh 09ca15649cc94fdaf6d9bdae28a20723f2a66616bf15aa86d83df31051d82506 | 8 | include/qpdf/auto_job_c_pages.hh 09ca15649cc94fdaf6d9bdae28a20723f2a66616bf15aa86d83df31051d82506 |
| 9 | include/qpdf/auto_job_c_uo.hh 9c2f98a355858dd54d0bba444b73177a59c9e56833e02fa6406f429c07f39e62 | 9 | include/qpdf/auto_job_c_uo.hh 9c2f98a355858dd54d0bba444b73177a59c9e56833e02fa6406f429c07f39e62 |
| 10 | -job.yml 31935064eca625af7657b23f2f12c614d14751ec0b12702482b1768a04905d22 | ||
| 11 | -libqpdf/qpdf/auto_job_decl.hh 20d6affe1e260f5a1af4f1d82a820b933835440ff03020e877382da2e8dac6c6 | ||
| 12 | -libqpdf/qpdf/auto_job_help.hh 9628a4b3f57ed8ecda3c7b8761b4daa48eaf9da0a6a0fc68bf417c467bd737eb | ||
| 13 | -libqpdf/qpdf/auto_job_init.hh e2a6bb87870c5522a01b15461c9fe909e360f5c7fed06e41acf13a125bd1d03e | 10 | +job.yml 2c424c7be0c02545191969e849e1d8f7fdb4ab65bbf799b9a190e21343899751 |
| 11 | +libqpdf/qpdf/auto_job_decl.hh 34ba07d3891c3e5cdd8712f991e508a0652c9db314c5d5bcdf4421b76e6f6e01 | ||
| 12 | +libqpdf/qpdf/auto_job_help.hh a36476d0c823033b2af0e4170651e1fa31173887c310f2f208e9ed7e6e36a2ce | ||
| 13 | +libqpdf/qpdf/auto_job_init.hh f89e7f9950a185372732d2ff7f113161f275f45ee7937dd7fd37e38013bf22e7 | ||
| 14 | libqpdf/qpdf/auto_job_json_decl.hh 843892c8e8652a86b7eb573893ef24050b7f36fe313f7251874be5cd4cdbe3fd | 14 | libqpdf/qpdf/auto_job_json_decl.hh 843892c8e8652a86b7eb573893ef24050b7f36fe313f7251874be5cd4cdbe3fd |
| 15 | libqpdf/qpdf/auto_job_json_init.hh 344c2fb473f88fe829c93b1efe6c70a0e4796537b8eb35e421d955fff481ba7d | 15 | libqpdf/qpdf/auto_job_json_init.hh 344c2fb473f88fe829c93b1efe6c70a0e4796537b8eb35e421d955fff481ba7d |
| 16 | libqpdf/qpdf/auto_job_schema.hh 6d3eef5137b8828eaa301a1b3cf75cb7bb812aa6e2d8301de865b42d238d7a7c | 16 | libqpdf/qpdf/auto_job_schema.hh 6d3eef5137b8828eaa301a1b3cf75cb7bb812aa6e2d8301de865b42d238d7a7c |
| 17 | manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580 | 17 | manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580 |
| 18 | -manual/cli.rst 45629c81bb407e7a1d2302ff1a9ef87f706565904aa5c21e64adefb34eee575c | ||
| 19 | -manual/qpdf.1 e058bd97a2bbc1e39282c709fb3beb1c3f8d3e2372b2974e11a849fd0bfb3505 | 18 | +manual/cli.rst 67357688f9a52fafa9a4f231fe4ce74c3cd8977130da7501efe54439a1ee22d4 |
| 19 | +manual/qpdf.1 cf5fc00789744c619f2af285fd715e5f85ced53f0126f8df5f97e27f920a9a7a | ||
| 20 | manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b | 20 | manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b |
job.yml
libqpdf/CMakeLists.txt
| @@ -190,6 +190,18 @@ if(NOT EXTERNAL_LIBS) | @@ -190,6 +190,18 @@ if(NOT EXTERNAL_LIBS) | ||
| 190 | endif() | 190 | endif() |
| 191 | endif() | 191 | endif() |
| 192 | 192 | ||
| 193 | +if(ZOPFLI) | ||
| 194 | + find_path(ZOPFLI_H_PATH zopfli/zopfli.h) | ||
| 195 | + find_library(ZOPFLI_LIB_PATH NAMES zopfli) | ||
| 196 | + if(ZOPFLI_H_PATH AND ZOPFLI_LIB_PATH) | ||
| 197 | + list(APPEND dep_include_directories ${ZOPFLI_H_PATH}) | ||
| 198 | + list(APPEND dep_link_libraries ${ZOPFLI_LIB_PATH}) | ||
| 199 | + else() | ||
| 200 | + message(SEND_ERROR "zopfli not found") | ||
| 201 | + set(ANYTHING_MISSING 1) | ||
| 202 | + endif() | ||
| 203 | +endif() | ||
| 204 | + | ||
| 193 | # Update JPEG_INCLUDE in PARENT_SCOPE after we have finished setting it. | 205 | # Update JPEG_INCLUDE in PARENT_SCOPE after we have finished setting it. |
| 194 | set(JPEG_INCLUDE ${JPEG_INCLUDE} PARENT_SCOPE) | 206 | set(JPEG_INCLUDE ${JPEG_INCLUDE} PARENT_SCOPE) |
| 195 | 207 |
libqpdf/Pl_Flate.cc
| @@ -6,6 +6,11 @@ | @@ -6,6 +6,11 @@ | ||
| 6 | 6 | ||
| 7 | #include <qpdf/QIntC.hh> | 7 | #include <qpdf/QIntC.hh> |
| 8 | #include <qpdf/QUtil.hh> | 8 | #include <qpdf/QUtil.hh> |
| 9 | +#include <qpdf/qpdf-config.h> | ||
| 10 | + | ||
| 11 | +#ifdef ZOPFLI | ||
| 12 | +# include <zopfli/zopfli.h> | ||
| 13 | +#endif | ||
| 9 | 14 | ||
| 10 | namespace | 15 | namespace |
| 11 | { | 16 | { |
| @@ -39,6 +44,10 @@ Pl_Flate::Members::Members(size_t out_bufsize, action_e action) : | @@ -39,6 +44,10 @@ Pl_Flate::Members::Members(size_t out_bufsize, action_e action) : | ||
| 39 | zstream.avail_in = 0; | 44 | zstream.avail_in = 0; |
| 40 | zstream.next_out = this->outbuf.get(); | 45 | zstream.next_out = this->outbuf.get(); |
| 41 | zstream.avail_out = QIntC::to_uint(out_bufsize); | 46 | zstream.avail_out = QIntC::to_uint(out_bufsize); |
| 47 | + | ||
| 48 | + if (action == a_deflate && Pl_Flate::zopfli_enabled()) { | ||
| 49 | + zopfli_buf = std::make_unique<std::string>(); | ||
| 50 | + } | ||
| 42 | } | 51 | } |
| 43 | 52 | ||
| 44 | Pl_Flate::Members::~Members() | 53 | Pl_Flate::Members::~Members() |
| @@ -59,7 +68,7 @@ Pl_Flate::Members::~Members() | @@ -59,7 +68,7 @@ Pl_Flate::Members::~Members() | ||
| 59 | Pl_Flate::Pl_Flate( | 68 | Pl_Flate::Pl_Flate( |
| 60 | char const* identifier, Pipeline* next, action_e action, unsigned int out_bufsize_int) : | 69 | char const* identifier, Pipeline* next, action_e action, unsigned int out_bufsize_int) : |
| 61 | Pipeline(identifier, next), | 70 | Pipeline(identifier, next), |
| 62 | - m(new Members(QIntC::to_size(out_bufsize_int), action)) | 71 | + m(std::shared_ptr<Members>(new Members(QIntC::to_size(out_bufsize_int), action))) |
| 63 | { | 72 | { |
| 64 | if (!next) { | 73 | if (!next) { |
| 65 | throw std::logic_error("Attempt to create Pl_Flate with nullptr as next"); | 74 | throw std::logic_error("Attempt to create Pl_Flate with nullptr as next"); |
| @@ -98,6 +107,10 @@ Pl_Flate::write(unsigned char const* data, size_t len) | @@ -98,6 +107,10 @@ Pl_Flate::write(unsigned char const* data, size_t len) | ||
| 98 | throw std::logic_error( | 107 | throw std::logic_error( |
| 99 | this->identifier + ": Pl_Flate: write() called after finish() called"); | 108 | this->identifier + ": Pl_Flate: write() called after finish() called"); |
| 100 | } | 109 | } |
| 110 | + if (m->zopfli_buf) { | ||
| 111 | + m->zopfli_buf->append(reinterpret_cast<char const*>(data), len); | ||
| 112 | + return; | ||
| 113 | + } | ||
| 101 | 114 | ||
| 102 | // Write in chunks in case len is too big to fit in an int. Assume int is at least 32 bits. | 115 | // Write in chunks in case len is too big to fit in an int. Assume int is at least 32 bits. |
| 103 | static size_t const max_bytes = 1 << 30; | 116 | static size_t const max_bytes = 1 << 30; |
| @@ -211,7 +224,9 @@ Pl_Flate::finish() | @@ -211,7 +224,9 @@ Pl_Flate::finish() | ||
| 211 | throw std::runtime_error("PL_Flate memory limit exceeded"); | 224 | throw std::runtime_error("PL_Flate memory limit exceeded"); |
| 212 | } | 225 | } |
| 213 | try { | 226 | try { |
| 214 | - if (m->outbuf.get()) { | 227 | + if (m->zopfli_buf) { |
| 228 | + finish_zopfli(); | ||
| 229 | + } else if (m->outbuf.get()) { | ||
| 215 | if (m->initialized) { | 230 | if (m->initialized) { |
| 216 | z_stream& zstream = *(static_cast<z_stream*>(m->zdata)); | 231 | z_stream& zstream = *(static_cast<z_stream*>(m->zdata)); |
| 217 | unsigned char buf[1]; | 232 | unsigned char buf[1]; |
| @@ -291,3 +306,76 @@ Pl_Flate::checkError(char const* prefix, int error_code) | @@ -291,3 +306,76 @@ Pl_Flate::checkError(char const* prefix, int error_code) | ||
| 291 | throw std::runtime_error(msg); | 306 | throw std::runtime_error(msg); |
| 292 | } | 307 | } |
| 293 | } | 308 | } |
| 309 | + | ||
| 310 | +void | ||
| 311 | +Pl_Flate::finish_zopfli() | ||
| 312 | +{ | ||
| 313 | +#ifdef ZOPFLI | ||
| 314 | + if (!m->zopfli_buf) { | ||
| 315 | + return; | ||
| 316 | + } | ||
| 317 | + auto buf = std::move(*m->zopfli_buf.release()); | ||
| 318 | + ZopfliOptions z_opt; | ||
| 319 | + ZopfliInitOptions(&z_opt); | ||
| 320 | + unsigned char* out{nullptr}; | ||
| 321 | + size_t out_size{0}; | ||
| 322 | + ZopfliCompress( | ||
| 323 | + &z_opt, | ||
| 324 | + ZOPFLI_FORMAT_ZLIB, | ||
| 325 | + reinterpret_cast<unsigned char const*>(buf.c_str()), | ||
| 326 | + buf.size(), | ||
| 327 | + &out, | ||
| 328 | + &out_size); | ||
| 329 | + std::unique_ptr<unsigned char, decltype(&free)> p(out, &free); | ||
| 330 | + next()->write(out, out_size); | ||
| 331 | + // next()->finish is called by finish() | ||
| 332 | +#endif | ||
| 333 | +} | ||
| 334 | + | ||
| 335 | +bool | ||
| 336 | +Pl_Flate::zopfli_supported() | ||
| 337 | +{ | ||
| 338 | +#ifdef ZOPFLI | ||
| 339 | + return true; | ||
| 340 | +#else | ||
| 341 | + return false; | ||
| 342 | +#endif | ||
| 343 | +} | ||
| 344 | + | ||
| 345 | +bool | ||
| 346 | +Pl_Flate::zopfli_enabled() | ||
| 347 | +{ | ||
| 348 | + if (zopfli_supported()) { | ||
| 349 | + std::string value; | ||
| 350 | + static bool enabled = QUtil::get_env("QPDF_ZOPFLI", &value) && value != "disabled"; | ||
| 351 | + return enabled; | ||
| 352 | + } else { | ||
| 353 | + return false; | ||
| 354 | + } | ||
| 355 | +} | ||
| 356 | + | ||
| 357 | +bool | ||
| 358 | +Pl_Flate::zopfli_check_env(QPDFLogger* logger) | ||
| 359 | +{ | ||
| 360 | + if (Pl_Flate::zopfli_supported()) { | ||
| 361 | + return true; | ||
| 362 | + } | ||
| 363 | + std::string value; | ||
| 364 | + auto is_set = QUtil::get_env("QPDF_ZOPFLI", &value); | ||
| 365 | + if (!is_set || value == "disabled" || value == "silent") { | ||
| 366 | + return true; | ||
| 367 | + } | ||
| 368 | + if (!logger) { | ||
| 369 | + logger = QPDFLogger::defaultLogger().get(); | ||
| 370 | + } | ||
| 371 | + | ||
| 372 | + // This behavior is known in QPDFJob (for the --zopfli argument), Pl_Flate.hh, README.md, | ||
| 373 | + // and the manual. Do a case-insensitive search for zopfli if changing the behavior. | ||
| 374 | + if (value == "force") { | ||
| 375 | + throw std::runtime_error("QPDF_ZOPFLI=force, and zopfli support is not enabled"); | ||
| 376 | + } | ||
| 377 | + logger->warn("QPDF_ZOPFLI is set, but libqpdf was not built with zopfli support\n"); | ||
| 378 | + logger->warn( | ||
| 379 | + "Set QPDF_ZOPFLI=silent to suppress this warning and use zopfli when available.\n"); | ||
| 380 | + return false; | ||
| 381 | +} |
libqpdf/QPDFJob.cc
| @@ -498,6 +498,11 @@ QPDFJob::createQPDF() | @@ -498,6 +498,11 @@ QPDFJob::createQPDF() | ||
| 498 | void | 498 | void |
| 499 | QPDFJob::writeQPDF(QPDF& pdf) | 499 | QPDFJob::writeQPDF(QPDF& pdf) |
| 500 | { | 500 | { |
| 501 | + if (createsOutput()) { | ||
| 502 | + if (!Pl_Flate::zopfli_check_env(pdf.getLogger().get())) { | ||
| 503 | + m->warnings = true; | ||
| 504 | + } | ||
| 505 | + } | ||
| 501 | if (!createsOutput()) { | 506 | if (!createsOutput()) { |
| 502 | doInspection(pdf); | 507 | doInspection(pdf); |
| 503 | } else if (m->split_pages) { | 508 | } else if (m->split_pages) { |
libqpdf/QPDFJob_argv.cc
| @@ -6,6 +6,7 @@ | @@ -6,6 +6,7 @@ | ||
| 6 | #include <iostream> | 6 | #include <iostream> |
| 7 | #include <memory> | 7 | #include <memory> |
| 8 | 8 | ||
| 9 | +#include <qpdf/Pl_Flate.hh> | ||
| 9 | #include <qpdf/QPDFArgParser.hh> | 10 | #include <qpdf/QPDFArgParser.hh> |
| 10 | #include <qpdf/QPDFCryptoProvider.hh> | 11 | #include <qpdf/QPDFCryptoProvider.hh> |
| 11 | #include <qpdf/QPDFLogger.hh> | 12 | #include <qpdf/QPDFLogger.hh> |
| @@ -105,6 +106,27 @@ ArgParser::argVersion() | @@ -105,6 +106,27 @@ ArgParser::argVersion() | ||
| 105 | } | 106 | } |
| 106 | 107 | ||
| 107 | void | 108 | void |
| 109 | +ArgParser::argZopfli() | ||
| 110 | +{ | ||
| 111 | + auto logger = QPDFLogger::defaultLogger(); | ||
| 112 | + if (Pl_Flate::zopfli_supported()) { | ||
| 113 | + if (Pl_Flate::zopfli_enabled()) { | ||
| 114 | + logger->info("zopfli support is enabled, and zopfli is active\n"); | ||
| 115 | + } else { | ||
| 116 | + logger->info("zopfli support is enabled but not active\n"); | ||
| 117 | + logger->info("Set the environment variable QPDF_ZOPFLI to activate.\n"); | ||
| 118 | + logger->info("* QPDF_ZOPFLI=disabled or QPDF_ZOPFLI not set: don't use zopfli.\n"); | ||
| 119 | + logger->info("* QPDF_ZOPFLI=force: use zopfli, and fail if not available.\n"); | ||
| 120 | + logger->info("* QPDF_ZOPFLI=silent: use zopfli if available and silently fall back if not.\n"); | ||
| 121 | + logger->info("* QPDF_ZOPFLI= any other value: use zopfli if available, and warn if not.\n"); | ||
| 122 | + } | ||
| 123 | + } else { | ||
| 124 | + logger->error("zopfli support is not enabled\n"); | ||
| 125 | + std::exit(qpdf_exit_error); | ||
| 126 | + } | ||
| 127 | +} | ||
| 128 | + | ||
| 129 | +void | ||
| 108 | ArgParser::argCopyright() | 130 | ArgParser::argCopyright() |
| 109 | { | 131 | { |
| 110 | // clang-format off | 132 | // clang-format off |
libqpdf/qpdf/auto_job_decl.hh
| @@ -19,6 +19,7 @@ void argVersion(); | @@ -19,6 +19,7 @@ void argVersion(); | ||
| 19 | void argCopyright(); | 19 | void argCopyright(); |
| 20 | void argShowCrypto(); | 20 | void argShowCrypto(); |
| 21 | void argJobJsonHelp(); | 21 | void argJobJsonHelp(); |
| 22 | +void argZopfli(); | ||
| 22 | void argJsonHelp(std::string const&); | 23 | void argJsonHelp(std::string const&); |
| 23 | void argPositional(std::string const&); | 24 | void argPositional(std::string const&); |
| 24 | void argAddAttachment(); | 25 | void argAddAttachment(); |
libqpdf/qpdf/auto_job_help.hh
| @@ -73,6 +73,11 @@ default provider is shown first. | @@ -73,6 +73,11 @@ default provider is shown first. | ||
| 73 | ap.addOptionHelp("--job-json-help", "help", "show format of job JSON", R"(Describe the format of the QPDFJob JSON input used by | 73 | ap.addOptionHelp("--job-json-help", "help", "show format of job JSON", R"(Describe the format of the QPDFJob JSON input used by |
| 74 | --job-json-file. | 74 | --job-json-file. |
| 75 | )"); | 75 | )"); |
| 76 | +ap.addOptionHelp("--zopfli", "help", "indicate whether zopfli is enabled and active", R"(If zopfli support is compiled in, indicate whether it is active, | ||
| 77 | +and exit normally. Otherwise, indicate that it is not compiled | ||
| 78 | +in, and exit with an error code. If zopfli is compiled in, | ||
| 79 | +activate it by setting the ``QPDF_ZOPFLI`` environment variable. | ||
| 80 | +)"); | ||
| 76 | ap.addHelpTopic("general", "general options", R"(General options control qpdf's behavior in ways that are not | 81 | ap.addHelpTopic("general", "general options", R"(General options control qpdf's behavior in ways that are not |
| 77 | directly related to the operation it is performing. | 82 | directly related to the operation it is performing. |
| 78 | )"); | 83 | )"); |
| @@ -86,13 +91,13 @@ ap.addOptionHelp("--password-file", "general", "read password from a file", R"(- | @@ -86,13 +91,13 @@ ap.addOptionHelp("--password-file", "general", "read password from a file", R"(- | ||
| 86 | The first line of the specified file is used as the password. | 91 | The first line of the specified file is used as the password. |
| 87 | This is used in place of the --password option. | 92 | This is used in place of the --password option. |
| 88 | )"); | 93 | )"); |
| 94 | +} | ||
| 95 | +static void add_help_2(QPDFArgParser& ap) | ||
| 96 | +{ | ||
| 89 | ap.addOptionHelp("--verbose", "general", "print additional information", R"(Output additional information about various things qpdf is | 97 | ap.addOptionHelp("--verbose", "general", "print additional information", R"(Output additional information about various things qpdf is |
| 90 | doing, including information about files created and operations | 98 | doing, including information about files created and operations |
| 91 | performed. | 99 | performed. |
| 92 | )"); | 100 | )"); |
| 93 | -} | ||
| 94 | -static void add_help_2(QPDFArgParser& ap) | ||
| 95 | -{ | ||
| 96 | ap.addOptionHelp("--progress", "general", "show progress when writing", R"(Indicate progress when writing files. | 101 | ap.addOptionHelp("--progress", "general", "show progress when writing", R"(Indicate progress when writing files. |
| 97 | )"); | 102 | )"); |
| 98 | ap.addOptionHelp("--no-warn", "general", "suppress printing of warning messages", R"(Suppress printing of warning messages. If warnings were | 103 | ap.addOptionHelp("--no-warn", "general", "suppress printing of warning messages", R"(Suppress printing of warning messages. If warnings were |
| @@ -168,14 +173,14 @@ Copy encryption details from the specified file instead of | @@ -168,14 +173,14 @@ Copy encryption details from the specified file instead of | ||
| 168 | preserving the input file's encryption. Use --encryption-file-password | 173 | preserving the input file's encryption. Use --encryption-file-password |
| 169 | to specify the encryption file's password. | 174 | to specify the encryption file's password. |
| 170 | )"); | 175 | )"); |
| 176 | +} | ||
| 177 | +static void add_help_3(QPDFArgParser& ap) | ||
| 178 | +{ | ||
| 171 | ap.addOptionHelp("--encryption-file-password", "transformation", "supply password for --copy-encryption", R"(--encryption-file-password=password | 179 | ap.addOptionHelp("--encryption-file-password", "transformation", "supply password for --copy-encryption", R"(--encryption-file-password=password |
| 172 | 180 | ||
| 173 | If the file named in --copy-encryption requires a password, use | 181 | If the file named in --copy-encryption requires a password, use |
| 174 | this option to supply the password. | 182 | this option to supply the password. |
| 175 | )"); | 183 | )"); |
| 176 | -} | ||
| 177 | -static void add_help_3(QPDFArgParser& ap) | ||
| 178 | -{ | ||
| 179 | ap.addOptionHelp("--qdf", "transformation", "enable viewing PDF code in a text editor", R"(Create a PDF file suitable for viewing in a text editor and even | 184 | ap.addOptionHelp("--qdf", "transformation", "enable viewing PDF code in a text editor", R"(Create a PDF file suitable for viewing in a text editor and even |
| 180 | editing. This is for editing the PDF code, not the page contents. | 185 | editing. This is for editing the PDF code, not the page contents. |
| 181 | All streams that can be uncompressed are uncompressed, and | 186 | All streams that can be uncompressed are uncompressed, and |
| @@ -285,6 +290,9 @@ Force the output PDF file's PDF version header to be the specified | @@ -285,6 +290,9 @@ Force the output PDF file's PDF version header to be the specified | ||
| 285 | value, even if the file uses features that may not be available | 290 | value, even if the file uses features that may not be available |
| 286 | in that version. | 291 | in that version. |
| 287 | )"); | 292 | )"); |
| 293 | +} | ||
| 294 | +static void add_help_4(QPDFArgParser& ap) | ||
| 295 | +{ | ||
| 288 | ap.addHelpTopic("page-ranges", "page range syntax", R"(A full description of the page range syntax, with examples, can be | 296 | ap.addHelpTopic("page-ranges", "page range syntax", R"(A full description of the page range syntax, with examples, can be |
| 289 | found in the manual. In summary, a range is a comma-separated list | 297 | found in the manual. In summary, a range is a comma-separated list |
| 290 | of groups. A group is a number or a range of numbers separated by a | 298 | of groups. A group is a number or a range of numbers separated by a |
| @@ -305,9 +313,6 @@ resulting set of pages, where :odd starts with the first page and | @@ -305,9 +313,6 @@ resulting set of pages, where :odd starts with the first page and | ||
| 305 | :even starts with the second page. These are odd and even pages | 313 | :even starts with the second page. These are odd and even pages |
| 306 | from the resulting set, not based on the original page numbers. | 314 | from the resulting set, not based on the original page numbers. |
| 307 | )"); | 315 | )"); |
| 308 | -} | ||
| 309 | -static void add_help_4(QPDFArgParser& ap) | ||
| 310 | -{ | ||
| 311 | ap.addHelpTopic("modification", "change parts of the PDF", R"(Modification options make systematic changes to certain parts of | 316 | ap.addHelpTopic("modification", "change parts of the PDF", R"(Modification options make systematic changes to certain parts of |
| 312 | the PDF, causing the PDF to render differently from the original. | 317 | the PDF, causing the PDF to render differently from the original. |
| 313 | )"); | 318 | )"); |
| @@ -416,11 +421,11 @@ ap.addOptionHelp("--keep-inline-images", "modification", "exclude inline images | @@ -416,11 +421,11 @@ ap.addOptionHelp("--keep-inline-images", "modification", "exclude inline images | ||
| 416 | )"); | 421 | )"); |
| 417 | ap.addOptionHelp("--remove-info", "modification", "remove file information", R"(Exclude file information (except modification date) from the output file. | 422 | ap.addOptionHelp("--remove-info", "modification", "remove file information", R"(Exclude file information (except modification date) from the output file. |
| 418 | )"); | 423 | )"); |
| 419 | -ap.addOptionHelp("--remove-metadata", "modification", "remove metadata", R"(Exclude metadata from the output file. | ||
| 420 | -)"); | ||
| 421 | } | 424 | } |
| 422 | static void add_help_5(QPDFArgParser& ap) | 425 | static void add_help_5(QPDFArgParser& ap) |
| 423 | { | 426 | { |
| 427 | +ap.addOptionHelp("--remove-metadata", "modification", "remove metadata", R"(Exclude metadata from the output file. | ||
| 428 | +)"); | ||
| 424 | ap.addOptionHelp("--remove-page-labels", "modification", "remove explicit page numbers", R"(Exclude page labels (explicit page numbers) from the output file. | 429 | ap.addOptionHelp("--remove-page-labels", "modification", "remove explicit page numbers", R"(Exclude page labels (explicit page numbers) from the output file. |
| 425 | )"); | 430 | )"); |
| 426 | ap.addOptionHelp("--set-page-labels", "modification", "number pages for the entire document", R"(--set-page-labels label-spec ... -- | 431 | ap.addOptionHelp("--set-page-labels", "modification", "number pages for the entire document", R"(--set-page-labels label-spec ... -- |
| @@ -641,13 +646,13 @@ non-empty user passwords when using 256-bit encryption. | @@ -641,13 +646,13 @@ non-empty user passwords when using 256-bit encryption. | ||
| 641 | ap.addOptionHelp("--force-V4", "encryption", "force V=4 in encryption dictionary", R"(This option is for testing and is never needed in practice since | 646 | ap.addOptionHelp("--force-V4", "encryption", "force V=4 in encryption dictionary", R"(This option is for testing and is never needed in practice since |
| 642 | qpdf does this automatically when needed. | 647 | qpdf does this automatically when needed. |
| 643 | )"); | 648 | )"); |
| 649 | +} | ||
| 650 | +static void add_help_6(QPDFArgParser& ap) | ||
| 651 | +{ | ||
| 644 | ap.addOptionHelp("--force-R5", "encryption", "use unsupported R=5 encryption", R"(Use an undocumented, unsupported, deprecated encryption | 652 | ap.addOptionHelp("--force-R5", "encryption", "use unsupported R=5 encryption", R"(Use an undocumented, unsupported, deprecated encryption |
| 645 | algorithm that existed only in Acrobat version IX. This option | 653 | algorithm that existed only in Acrobat version IX. This option |
| 646 | should not be used except for compatibility testing. | 654 | should not be used except for compatibility testing. |
| 647 | )"); | 655 | )"); |
| 648 | -} | ||
| 649 | -static void add_help_6(QPDFArgParser& ap) | ||
| 650 | -{ | ||
| 651 | ap.addHelpTopic("page-selection", "select pages from one or more files", R"(Use the --pages option to select pages from multiple files. Usage: | 656 | ap.addHelpTopic("page-selection", "select pages from one or more files", R"(Use the --pages option to select pages from multiple files. Usage: |
| 652 | 657 | ||
| 653 | qpdf in.pdf --pages --file=input-file \ | 658 | qpdf in.pdf --pages --file=input-file \ |
| @@ -827,15 +832,15 @@ ap.addOptionHelp("--replace", "add-attachment", "replace attachment with same ke | @@ -827,15 +832,15 @@ ap.addOptionHelp("--replace", "add-attachment", "replace attachment with same ke | ||
| 827 | be replaced by the new attachment. Otherwise, qpdf gives an | 832 | be replaced by the new attachment. Otherwise, qpdf gives an |
| 828 | error if an attachment with that key is already present. | 833 | error if an attachment with that key is already present. |
| 829 | )"); | 834 | )"); |
| 835 | +} | ||
| 836 | +static void add_help_7(QPDFArgParser& ap) | ||
| 837 | +{ | ||
| 830 | ap.addHelpTopic("copy-attachments", "copy attachments from another file", R"(The options listed below appear between --copy-attachments-from and | 838 | ap.addHelpTopic("copy-attachments", "copy attachments from another file", R"(The options listed below appear between --copy-attachments-from and |
| 831 | its terminating "--". | 839 | its terminating "--". |
| 832 | 840 | ||
| 833 | To copy attachments from a password-protected file, use | 841 | To copy attachments from a password-protected file, use |
| 834 | the --password option after the file name. | 842 | the --password option after the file name. |
| 835 | )"); | 843 | )"); |
| 836 | -} | ||
| 837 | -static void add_help_7(QPDFArgParser& ap) | ||
| 838 | -{ | ||
| 839 | ap.addOptionHelp("--prefix", "copy-attachments", "key prefix for copying attachments", R"(--prefix=prefix | 844 | ap.addOptionHelp("--prefix", "copy-attachments", "key prefix for copying attachments", R"(--prefix=prefix |
| 840 | 845 | ||
| 841 | Prepend a prefix to each key; may be needed if there are | 846 | Prepend a prefix to each key; may be needed if there are |
| @@ -920,12 +925,12 @@ ap.addOptionHelp("--show-attachment", "inspection", "export an embedded file", R | @@ -920,12 +925,12 @@ ap.addOptionHelp("--show-attachment", "inspection", "export an embedded file", R | ||
| 920 | Write the contents of the specified attachment to standard | 925 | Write the contents of the specified attachment to standard |
| 921 | output as binary data. Get the key with --list-attachments. | 926 | output as binary data. Get the key with --list-attachments. |
| 922 | )"); | 927 | )"); |
| 923 | -ap.addHelpTopic("json", "JSON output for PDF information", R"(Show information about the PDF file in JSON format. Please see the | ||
| 924 | -JSON chapter in the qpdf manual for details. | ||
| 925 | -)"); | ||
| 926 | } | 928 | } |
| 927 | static void add_help_8(QPDFArgParser& ap) | 929 | static void add_help_8(QPDFArgParser& ap) |
| 928 | { | 930 | { |
| 931 | +ap.addHelpTopic("json", "JSON output for PDF information", R"(Show information about the PDF file in JSON format. Please see the | ||
| 932 | +JSON chapter in the qpdf manual for details. | ||
| 933 | +)"); | ||
| 929 | ap.addOptionHelp("--json", "json", "show file in JSON format", R"(--json[=version] | 934 | ap.addOptionHelp("--json", "json", "show file in JSON format", R"(--json[=version] |
| 930 | 935 | ||
| 931 | Generate a JSON representation of the file. This is described in | 936 | Generate a JSON representation of the file. This is described in |
libqpdf/qpdf/auto_job_init.hh
| @@ -32,6 +32,7 @@ this->ap.addBare("version", b(&ArgParser::argVersion)); | @@ -32,6 +32,7 @@ this->ap.addBare("version", b(&ArgParser::argVersion)); | ||
| 32 | this->ap.addBare("copyright", b(&ArgParser::argCopyright)); | 32 | this->ap.addBare("copyright", b(&ArgParser::argCopyright)); |
| 33 | this->ap.addBare("show-crypto", b(&ArgParser::argShowCrypto)); | 33 | this->ap.addBare("show-crypto", b(&ArgParser::argShowCrypto)); |
| 34 | this->ap.addBare("job-json-help", b(&ArgParser::argJobJsonHelp)); | 34 | this->ap.addBare("job-json-help", b(&ArgParser::argJobJsonHelp)); |
| 35 | +this->ap.addBare("zopfli", b(&ArgParser::argZopfli)); | ||
| 35 | this->ap.addChoices("json-help", p(&ArgParser::argJsonHelp), false, json_version_choices); | 36 | this->ap.addChoices("json-help", p(&ArgParser::argJsonHelp), false, json_version_choices); |
| 36 | this->ap.selectMainOptionTable(); | 37 | this->ap.selectMainOptionTable(); |
| 37 | this->ap.addPositional(p(&ArgParser::argPositional)); | 38 | this->ap.addPositional(p(&ArgParser::argPositional)); |
libqpdf/qpdf/qpdf-config.h.in
| @@ -6,6 +6,7 @@ | @@ -6,6 +6,7 @@ | ||
| 6 | #cmakedefine USE_CRYPTO_OPENSSL 1 | 6 | #cmakedefine USE_CRYPTO_OPENSSL 1 |
| 7 | #cmakedefine USE_INSECURE_RANDOM 1 | 7 | #cmakedefine USE_INSECURE_RANDOM 1 |
| 8 | #cmakedefine SKIP_OS_SECURE_RANDOM 1 | 8 | #cmakedefine SKIP_OS_SECURE_RANDOM 1 |
| 9 | +#cmakedefine ZOPFLI 1 | ||
| 9 | 10 | ||
| 10 | /* large file support -- may be needed for 32-bit systems */ | 11 | /* large file support -- may be needed for 32-bit systems */ |
| 11 | #cmakedefine _FILE_OFFSET_BITS ${_FILE_OFFSET_BITS} | 12 | #cmakedefine _FILE_OFFSET_BITS ${_FILE_OFFSET_BITS} |
manual/cli.rst
| @@ -354,6 +354,21 @@ Related Options | @@ -354,6 +354,21 @@ Related Options | ||
| 354 | :qpdf:ref:`--job-json-file`. For more information about QPDFJob, | 354 | :qpdf:ref:`--job-json-file`. For more information about QPDFJob, |
| 355 | see :ref:`qpdf-job`. | 355 | see :ref:`qpdf-job`. |
| 356 | 356 | ||
| 357 | +.. qpdf:option:: --zopfli | ||
| 358 | + | ||
| 359 | + .. help: indicate whether zopfli is enabled and active | ||
| 360 | + | ||
| 361 | + If zopfli support is compiled in, indicate whether it is active, | ||
| 362 | + and exit normally. Otherwise, indicate that it is not compiled | ||
| 363 | + in, and exit with an error code. If zopfli is compiled in, | ||
| 364 | + activate it by setting the ``QPDF_ZOPFLI`` environment variable. | ||
| 365 | + | ||
| 366 | + If zopfli support is compiled in, indicate whether it is active, | ||
| 367 | + and exit normally. Otherwise, indicate that it is not compiled in, | ||
| 368 | + and exit with an error code. If zopfli is compiled in, activate it | ||
| 369 | + by setting the ``QPDF_ZOPFLI`` environment variable. See | ||
| 370 | + :ref:`zopfli`. | ||
| 371 | + | ||
| 357 | .. _general-options: | 372 | .. _general-options: |
| 358 | 373 | ||
| 359 | General Options | 374 | General Options |
| @@ -936,7 +951,7 @@ Related Options | @@ -936,7 +951,7 @@ Related Options | ||
| 936 | 951 | ||
| 937 | As a special case, streams already compressed with ``/FlateDecode`` | 952 | As a special case, streams already compressed with ``/FlateDecode`` |
| 938 | are not uncompressed and recompressed. You can change this behavior | 953 | are not uncompressed and recompressed. You can change this behavior |
| 939 | - with :qpdf:ref:`--recompress-flate`. | 954 | + with :qpdf:ref:`--recompress-flate`. See also :ref:`small-files`. |
| 940 | 955 | ||
| 941 | .. qpdf:option:: --stream-data=parameter | 956 | .. qpdf:option:: --stream-data=parameter |
| 942 | 957 | ||
| @@ -986,7 +1001,8 @@ Related Options | @@ -986,7 +1001,8 @@ Related Options | ||
| 986 | tells :command:`qpdf` to uncompress and recompress streams | 1001 | tells :command:`qpdf` to uncompress and recompress streams |
| 987 | compressed with flate. This can be useful when combined with | 1002 | compressed with flate. This can be useful when combined with |
| 988 | :qpdf:ref:`--compression-level`. Using this option may make | 1003 | :qpdf:ref:`--compression-level`. Using this option may make |
| 989 | - :command:`qpdf` much slower when writing output files. | 1004 | + :command:`qpdf` much slower when writing output files. See also |
| 1005 | + :ref:`small-files`. | ||
| 990 | 1006 | ||
| 991 | .. qpdf:option:: --compression-level=level | 1007 | .. qpdf:option:: --compression-level=level |
| 992 | 1008 | ||
| @@ -1009,7 +1025,8 @@ Related Options | @@ -1009,7 +1025,8 @@ Related Options | ||
| 1009 | :qpdf:ref:`--recompress-flate`. If your goal is to shrink the size | 1025 | :qpdf:ref:`--recompress-flate`. If your goal is to shrink the size |
| 1010 | of PDF files, you should also use | 1026 | of PDF files, you should also use |
| 1011 | :samp:`--object-streams=generate`. If you omit this option, qpdf | 1027 | :samp:`--object-streams=generate`. If you omit this option, qpdf |
| 1012 | - defers to the compression library's default behavior. | 1028 | + defers to the compression library's default behavior. See also |
| 1029 | + :ref:`small-files`. | ||
| 1013 | 1030 | ||
| 1014 | .. qpdf:option:: --normalize-content=[y|n] | 1031 | .. qpdf:option:: --normalize-content=[y|n] |
| 1015 | 1032 | ||
| @@ -1734,7 +1751,7 @@ Related Options | @@ -1734,7 +1751,7 @@ Related Options | ||
| 1734 | and :qpdf:ref:`--oi-min-area` options. By default, inline images | 1751 | and :qpdf:ref:`--oi-min-area` options. By default, inline images |
| 1735 | are converted to regular images and optimized as well. Use | 1752 | are converted to regular images and optimized as well. Use |
| 1736 | :qpdf:ref:`--keep-inline-images` to prevent inline images from | 1753 | :qpdf:ref:`--keep-inline-images` to prevent inline images from |
| 1737 | - being included. | 1754 | + being included. See also :ref:`small-files`. |
| 1738 | 1755 | ||
| 1739 | .. qpdf:option:: --oi-min-width=width | 1756 | .. qpdf:option:: --oi-min-width=width |
| 1740 | 1757 | ||
| @@ -3941,3 +3958,86 @@ from password to encryption key entirely, allowing the raw | @@ -3941,3 +3958,86 @@ from password to encryption key entirely, allowing the raw | ||
| 3941 | encryption key to be specified directly. That behavior is useful for | 3958 | encryption key to be specified directly. That behavior is useful for |
| 3942 | forensic purposes or for brute-force recovery of files with unknown | 3959 | forensic purposes or for brute-force recovery of files with unknown |
| 3943 | passwords and has nothing to do with the document's actual passwords. | 3960 | passwords and has nothing to do with the document's actual passwords. |
| 3961 | + | ||
| 3962 | +.. _small-files: | ||
| 3963 | + | ||
| 3964 | +Optimizing File Size | ||
| 3965 | +-------------------- | ||
| 3966 | + | ||
| 3967 | +While qpdf's primary function is not to optimize the size of PDF | ||
| 3968 | +files, there are a number of things you can do to make files smaller. | ||
| 3969 | +Note that qpdf will not resample images or make optimizations that | ||
| 3970 | +modify content with the exception of possibly recompressing images | ||
| 3971 | +using DCT (JPEG) compression. | ||
| 3972 | + | ||
| 3973 | +The following options will give you the smallest files that qpdf can | ||
| 3974 | +generate: | ||
| 3975 | + | ||
| 3976 | +- ``--compress-streams=y``: make sure streams are compressed (see | ||
| 3977 | + :qpdf:ref:`--compress-streams`) | ||
| 3978 | + | ||
| 3979 | +- ``--decode-level=generalized``: apply any non-specialized filters | ||
| 3980 | + (see :qpdf:ref:`--decode-level`) | ||
| 3981 | + | ||
| 3982 | +- :qpdf:ref:`--recompress-flate`: uncompress and recompress streams | ||
| 3983 | + that are already compressed with zlib (flate) compression | ||
| 3984 | + | ||
| 3985 | +- ``--compression-level=9``: use the highest possible compression | ||
| 3986 | + level (see :ref:`zopfli` and :qpdf:ref:`--compression-level`) | ||
| 3987 | + | ||
| 3988 | +- :qpdf:ref:`--optimize-images`: replace non-JPEG images with JPEG if | ||
| 3989 | + doing so reduces their size. Not all types of images are supported, | ||
| 3990 | + but qpdf will only keep the result if it is supported and reduces | ||
| 3991 | + the size. Images are not resampled, but bear in mind that JPEG is | ||
| 3992 | + lossy, so images may have artifacts. These are not usually | ||
| 3993 | + noticeable to the casual observer. | ||
| 3994 | + | ||
| 3995 | +- ``--object-streams=generate``: generate object streams, which means | ||
| 3996 | + that more of the PDF file's structural content will be compressed | ||
| 3997 | + (see :qpdf:ref:`--object-streams`) | ||
| 3998 | + | ||
| 3999 | +.. _zopfli: | ||
| 4000 | + | ||
| 4001 | +Zopfli Compression Algorithm | ||
| 4002 | +---------------------------- | ||
| 4003 | + | ||
| 4004 | +If qpdf is built with `zopfli <https://github.com/google/zopfli>`__ | ||
| 4005 | +support (see :ref:`build-zopfli`), you can have qpdf use the Zopfli | ||
| 4006 | +Compression Algorithm in place of zlib. In this mode, qpdf is *much | ||
| 4007 | +slower*, but produces slightly smaller compressed output. (According | ||
| 4008 | +to their documentation, zopfli is about 100 times slower than zlib and | ||
| 4009 | +produces output that's about 5% better than the best compression | ||
| 4010 | +available with other libraries.) For this to be useful, you should run | ||
| 4011 | +qpdf with options to recompress compressed streams. See | ||
| 4012 | +:ref:`small-files` for tips. In order to use zopfli, in addition to | ||
| 4013 | +building with zopfli support, you must set the ``QPDF_ZOPFLI`` | ||
| 4014 | +environment variable to some value other than ``disabled``. Note that | ||
| 4015 | +:qpdf:ref:`--compression-level` has no effect when zopfli is in use, | ||
| 4016 | +since zopfli always optimizes for size over everything else. | ||
| 4017 | + | ||
| 4018 | +Here are the supported values for ``QPDF_ZOPFLI``: | ||
| 4019 | + | ||
| 4020 | +.. list-table:: ``QPDF_ZOPFLI`` values | ||
| 4021 | + :widths: 20 60 | ||
| 4022 | + :header-rows: 0 | ||
| 4023 | + | ||
| 4024 | + - - ``disabled`` or unset | ||
| 4025 | + - do not use zopfli even if available | ||
| 4026 | + | ||
| 4027 | + - - ``silent`` | ||
| 4028 | + - use zopfli if available; otherwise silently fall back to zlib | ||
| 4029 | + | ||
| 4030 | + - - ``force`` | ||
| 4031 | + - use zopfli if available; fail with an error if not available | ||
| 4032 | + | ||
| 4033 | + - - any other value | ||
| 4034 | + - use zopfli if available; otherwise issue a warning and fall | ||
| 4035 | + back to zlib | ||
| 4036 | + | ||
| 4037 | +Note that the warning and error behavior are managed in ``QPDFJob`` | ||
| 4038 | +and affect the ``qpdf`` executable. For code that directly uses the | ||
| 4039 | +qpdf library, the behavior is that zopfli is enabled with any value | ||
| 4040 | +other than ``disabled`` but silently falls back to zlib. If you want | ||
| 4041 | +your application to behave the same as the ``qpdf`` executable with | ||
| 4042 | +respect to zopfli, you can call ``Pl_Flate::zopfli_check_env()``. See | ||
| 4043 | +its documentation in the ``qpdf/Pl_Flate.hh`` header file. |
manual/installation.rst
| @@ -30,6 +30,9 @@ Basic Dependencies | @@ -30,6 +30,9 @@ Basic Dependencies | ||
| 30 | <https://openssl.org/>`__ to be able to use the openssl crypto | 30 | <https://openssl.org/>`__ to be able to use the openssl crypto |
| 31 | provider | 31 | provider |
| 32 | 32 | ||
| 33 | +- If the ``ZOPFLI`` build option is specified (off by default), the | ||
| 34 | + `zopfli <https://github.com/google/zopfli>`__ library. | ||
| 35 | + | ||
| 33 | The qpdf source tree includes a few automatically generated files. The | 36 | The qpdf source tree includes a few automatically generated files. The |
| 34 | code generator uses Python 3. Automatic code generation is off by | 37 | code generator uses Python 3. Automatic code generation is off by |
| 35 | default. For a discussion, refer to :ref:`build-options`. | 38 | default. For a discussion, refer to :ref:`build-options`. |
| @@ -291,6 +294,10 @@ QTEST_COLOR | @@ -291,6 +294,10 @@ QTEST_COLOR | ||
| 291 | Turn this on or off to control whether qtest uses color in its | 294 | Turn this on or off to control whether qtest uses color in its |
| 292 | output. | 295 | output. |
| 293 | 296 | ||
| 297 | +ZOPFLI | ||
| 298 | + Use the `zopfli <https://github.com/google/zopfli>`__ library for | ||
| 299 | + zlib-compatible compression. See :ref:`zopfli`. | ||
| 300 | + | ||
| 294 | Options for Working on qpdf | 301 | Options for Working on qpdf |
| 295 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ | 302 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 296 | 303 | ||
| @@ -648,6 +655,16 @@ Implementing the registration functions and internal storage of | @@ -648,6 +655,16 @@ Implementing the registration functions and internal storage of | ||
| 648 | registered providers was also easier using C++-11's functional | 655 | registered providers was also easier using C++-11's functional |
| 649 | interfaces, which was another reason to require C++-11 at this time. | 656 | interfaces, which was another reason to require C++-11 at this time. |
| 650 | 657 | ||
| 658 | +.. _build-zopfli: | ||
| 659 | + | ||
| 660 | +Building with zopfli support | ||
| 661 | +---------------------------- | ||
| 662 | + | ||
| 663 | +If you compile with ``-DZOPFLI-ON`` and have the `zopfli | ||
| 664 | +<https://github.com/google/zopfli>`__ development files available, | ||
| 665 | +qpdf will be built with zopfli support. See :ref:`zopfli` for | ||
| 666 | +information about using zopfli with qpdf. | ||
| 667 | + | ||
| 651 | .. _autoconf-to-cmake: | 668 | .. _autoconf-to-cmake: |
| 652 | 669 | ||
| 653 | Converting From autoconf to cmake | 670 | Converting From autoconf to cmake |
manual/packaging.rst
| @@ -44,6 +44,15 @@ particularly useful to packagers. | @@ -44,6 +44,15 @@ particularly useful to packagers. | ||
| 44 | 11, this was a recommendation for packagers but was not done | 44 | 11, this was a recommendation for packagers but was not done |
| 45 | automatically. | 45 | automatically. |
| 46 | 46 | ||
| 47 | +- Starting with qpdf 11.10, qpdf can be built with zopfli support (see | ||
| 48 | + :ref:`build-zopfli`). It is recommended not to build qpdf with zopfli | ||
| 49 | + for distributions since it adds zopfli as a dependency, and this | ||
| 50 | + library is less widely used that qpdf's other dependencies. Users | ||
| 51 | + who want that probably know they want it, and they can compile from | ||
| 52 | + source. Note that, per zopfli's own documentation, zopfli is about | ||
| 53 | + 100 times slower than zlib and produces compression output about 5% | ||
| 54 | + smaller. | ||
| 55 | + | ||
| 47 | .. _package-tests: | 56 | .. _package-tests: |
| 48 | 57 | ||
| 49 | Package Tests | 58 | Package Tests |
manual/qpdf.1
| @@ -115,6 +115,12 @@ default provider is shown first. | @@ -115,6 +115,12 @@ default provider is shown first. | ||
| 115 | .B --job-json-help \-\- show format of job JSON | 115 | .B --job-json-help \-\- show format of job JSON |
| 116 | Describe the format of the QPDFJob JSON input used by | 116 | Describe the format of the QPDFJob JSON input used by |
| 117 | --job-json-file. | 117 | --job-json-file. |
| 118 | +.TP | ||
| 119 | +.B --zopfli \-\- indicate whether zopfli is enabled and active | ||
| 120 | +If zopfli support is compiled in, indicate whether it is active, | ||
| 121 | +and exit normally. Otherwise, indicate that it is not compiled | ||
| 122 | +in, and exit with an error code. If zopfli is compiled in, | ||
| 123 | +activate it by setting the ``QPDF_ZOPFLI`` environment variable. | ||
| 118 | .SH GENERAL (general options) | 124 | .SH GENERAL (general options) |
| 119 | General options control qpdf's behavior in ways that are not | 125 | General options control qpdf's behavior in ways that are not |
| 120 | directly related to the operation it is performing. | 126 | directly related to the operation it is performing. |
manual/release-notes.rst
| @@ -46,6 +46,11 @@ Planned changes for future 12.x (subject to change): | @@ -46,6 +46,11 @@ Planned changes for future 12.x (subject to change): | ||
| 46 | environments in which writing a binary file to standard output | 46 | environments in which writing a binary file to standard output |
| 47 | doesn't work (such as PowerShell 5). | 47 | doesn't work (such as PowerShell 5). |
| 48 | 48 | ||
| 49 | + - Library Enhancements | ||
| 50 | + | ||
| 51 | + - qpdf can now be built with zopfli support. For details, see | ||
| 52 | + :ref:`zopfli`. | ||
| 53 | + | ||
| 49 | 11.9.1: June 7, 2024 | 54 | 11.9.1: June 7, 2024 |
| 50 | - Bug Fixes | 55 | - Bug Fixes |
| 51 | 56 |
qpdf/qtest/copy-foreign-objects.test
| @@ -51,8 +51,9 @@ $td->runtest("indirect filters", | @@ -51,8 +51,9 @@ $td->runtest("indirect filters", | ||
| 51 | foreach my $i (0, 1) | 51 | foreach my $i (0, 1) |
| 52 | { | 52 | { |
| 53 | $td->runtest("check output", | 53 | $td->runtest("check output", |
| 54 | - {$td->FILE => "auto-$i.pdf"}, | ||
| 55 | - {$td->FILE => "indirect-filter-out-$i.pdf"}); | 54 | + {$td->COMMAND => |
| 55 | + "qpdf-test-compare auto-$i.pdf indirect-filter-out-$i.pdf"}, | ||
| 56 | + {$td->FILE => "indirect-filter-out-$i.pdf", $td->EXIT_STATUS => 0}); | ||
| 56 | } | 57 | } |
| 57 | $td->runtest("issue 449", | 58 | $td->runtest("issue 449", |
| 58 | {$td->COMMAND => "test_driver 69 issue-449.pdf"}, | 59 | {$td->COMMAND => "test_driver 69 issue-449.pdf"}, |
qpdf/qtest/disable-filter-on-write.test
| @@ -21,8 +21,8 @@ $td->runtest("no filter on write", | @@ -21,8 +21,8 @@ $td->runtest("no filter on write", | ||
| 21 | {$td->STRING => "test 70 done\n", $td->EXIT_STATUS => 0}, | 21 | {$td->STRING => "test 70 done\n", $td->EXIT_STATUS => 0}, |
| 22 | $td->NORMALIZE_NEWLINES); | 22 | $td->NORMALIZE_NEWLINES); |
| 23 | $td->runtest("check output", | 23 | $td->runtest("check output", |
| 24 | - {$td->FILE => "a.pdf"}, | ||
| 25 | - {$td->FILE => "filter-on-write-out.pdf"}); | 24 | + {$td->COMMAND => "qpdf-test-compare a.pdf filter-on-write-out.pdf"}, |
| 25 | + {$td->FILE => "filter-on-write-out.pdf", $td->EXIT_STATUS => 0}); | ||
| 26 | 26 | ||
| 27 | cleanup(); | 27 | cleanup(); |
| 28 | $td->report($n_tests); | 28 | $td->report($n_tests); |
qpdf/qtest/qpdf/minimal-out.pdf
0 โ 100644
No preview for this file type
qpdf/qtest/qpdf/zopfli-warning.out
0 โ 100644
qpdf/qtest/qpdf_test_helpers.pm
| 1 | use File::Spec; | 1 | use File::Spec; |
| 2 | 2 | ||
| 3 | -my $devNull = File::Spec->devnull(); | 3 | +my $dev_null = File::Spec->devnull(); |
| 4 | 4 | ||
| 5 | my $compare_images = 0; | 5 | my $compare_images = 0; |
| 6 | if ((exists $ENV{'QPDF_TEST_COMPARE_IMAGES'}) && | 6 | if ((exists $ENV{'QPDF_TEST_COMPARE_IMAGES'}) && |
| @@ -95,7 +95,7 @@ sub compare_pdfs | @@ -95,7 +95,7 @@ sub compare_pdfs | ||
| 95 | $td->runtest("convert original file to image", | 95 | $td->runtest("convert original file to image", |
| 96 | {$td->COMMAND => | 96 | {$td->COMMAND => |
| 97 | "(cd tif1;" . | 97 | "(cd tif1;" . |
| 98 | - " gs 2>$devNull $x_gs_args" . | 98 | + " gs 2>$dev_null $x_gs_args" . |
| 99 | " -q -dNOPAUSE -sDEVICE=tiff24nc" . | 99 | " -q -dNOPAUSE -sDEVICE=tiff24nc" . |
| 100 | " -sOutputFile=a.tif - < ../$f1)"}, | 100 | " -sOutputFile=a.tif - < ../$f1)"}, |
| 101 | {$td->STRING => "", | 101 | {$td->STRING => "", |
| @@ -115,7 +115,7 @@ sub compare_pdfs | @@ -115,7 +115,7 @@ sub compare_pdfs | ||
| 115 | $td->runtest("convert new file to image", | 115 | $td->runtest("convert new file to image", |
| 116 | {$td->COMMAND => | 116 | {$td->COMMAND => |
| 117 | "(cd tif2;" . | 117 | "(cd tif2;" . |
| 118 | - " gs 2>$devNull $x_gs_args" . | 118 | + " gs 2>$dev_null $x_gs_args" . |
| 119 | " -q -dNOPAUSE -sDEVICE=tiff24nc" . | 119 | " -q -dNOPAUSE -sDEVICE=tiff24nc" . |
| 120 | " -sOutputFile=a.tif - < ../$f2)"}, | 120 | " -sOutputFile=a.tif - < ../$f2)"}, |
| 121 | {$td->STRING => "", | 121 | {$td->STRING => "", |
qpdf/qtest/split-pages.test
| @@ -172,8 +172,8 @@ $td->runtest("merge for compare", | @@ -172,8 +172,8 @@ $td->runtest("merge for compare", | ||
| 172 | " split-out-shared-form*.pdf -- a.pdf"}, | 172 | " split-out-shared-form*.pdf -- a.pdf"}, |
| 173 | {$td->STRING => "", $td->EXIT_STATUS => 0}); | 173 | {$td->STRING => "", $td->EXIT_STATUS => 0}); |
| 174 | $td->runtest("check output", | 174 | $td->runtest("check output", |
| 175 | - {$td->FILE => "a.pdf"}, | ||
| 176 | - {$td->FILE => "shared-form-images-merged.pdf"}); | 175 | + {$td->COMMAND => "qpdf-test-compare a.pdf shared-form-images-merged.pdf"}, |
| 176 | + {$td->FILE => "shared-form-images-merged.pdf", $td->EXIT_STATUS => 0}); | ||
| 177 | compare_pdfs($td, "shared-form-images.pdf", "a.pdf"); | 177 | compare_pdfs($td, "shared-form-images.pdf", "a.pdf"); |
| 178 | 178 | ||
| 179 | $td->runtest("shared form xobject subkey", | 179 | $td->runtest("shared form xobject subkey", |
qpdf/qtest/stream-replacements.test
| @@ -28,8 +28,8 @@ $td->runtest("replace stream data compressed", | @@ -28,8 +28,8 @@ $td->runtest("replace stream data compressed", | ||
| 28 | {$td->FILE => "test8.out", $td->EXIT_STATUS => 0}, | 28 | {$td->FILE => "test8.out", $td->EXIT_STATUS => 0}, |
| 29 | $td->NORMALIZE_NEWLINES); | 29 | $td->NORMALIZE_NEWLINES); |
| 30 | $td->runtest("check output", | 30 | $td->runtest("check output", |
| 31 | - {$td->FILE => "a.pdf"}, | ||
| 32 | - {$td->FILE => "replaced-stream-data-flate.pdf"}); | 31 | + {$td->COMMAND => "qpdf-test-compare a.pdf replaced-stream-data-flate.pdf"}, |
| 32 | + {$td->FILE => "replaced-stream-data-flate.pdf", $td->EXIT_STATUS => 0}); | ||
| 33 | $td->runtest("new streams", | 33 | $td->runtest("new streams", |
| 34 | {$td->COMMAND => "test_driver 9 minimal.pdf"}, | 34 | {$td->COMMAND => "test_driver 9 minimal.pdf"}, |
| 35 | {$td->FILE => "test9.out", $td->EXIT_STATUS => 0}, | 35 | {$td->FILE => "test9.out", $td->EXIT_STATUS => 0}, |
qpdf/qtest/zopfli.test
0 โ 100644
| 1 | +#!/usr/bin/env perl | ||
| 2 | +require 5.008; | ||
| 3 | +use warnings; | ||
| 4 | +use strict; | ||
| 5 | + | ||
| 6 | +unshift(@INC, '.'); | ||
| 7 | +require qpdf_test_helpers; | ||
| 8 | + | ||
| 9 | +chdir("qpdf") or die "chdir testdir failed: $!\n"; | ||
| 10 | + | ||
| 11 | +require TestDriver; | ||
| 12 | + | ||
| 13 | +my $dev_null = File::Spec->devnull(); | ||
| 14 | +cleanup(); | ||
| 15 | + | ||
| 16 | +my $td = new TestDriver('zopfli'); | ||
| 17 | + | ||
| 18 | +my $n_tests = 0; | ||
| 19 | + | ||
| 20 | +my $zopfli_enabled = (system("qpdf --zopfli >$dev_null 2>&1") == 0); | ||
| 21 | + | ||
| 22 | +if (! $zopfli_enabled) { | ||
| 23 | + # Variables are not checked | ||
| 24 | + $n_tests = 8; | ||
| 25 | + $td->runtest("zopfli not enabled", | ||
| 26 | + {$td->COMMAND => "QPDF_ZOPFLI=force qpdf --zopfli"}, | ||
| 27 | + {$td->STRING => "zopfli support is not enabled\n", | ||
| 28 | + $td->EXIT_STATUS => 2}, | ||
| 29 | + $td->NORMALIZE_NEWLINES); | ||
| 30 | + | ||
| 31 | + $td->runtest("zopfli disabled", | ||
| 32 | + {$td->COMMAND => "QPDF_ZOPFLI=disabled qpdf minimal.pdf a.pdf"}, | ||
| 33 | + {$td->STRING => "", $td->EXIT_STATUS => 0}, | ||
| 34 | + $td->NORMALIZE_NEWLINES); | ||
| 35 | + $td->runtest("check output", | ||
| 36 | + {$td->COMMAND => "qpdf-test-compare a.pdf minimal-out.pdf"}, | ||
| 37 | + {$td->FILE => "minimal-out.pdf", $td->EXIT_STATUS => 0}); | ||
| 38 | + $td->runtest("zopfli silent", | ||
| 39 | + {$td->COMMAND => "QPDF_ZOPFLI=silent qpdf minimal.pdf a.pdf"}, | ||
| 40 | + {$td->STRING => "", $td->EXIT_STATUS => 0}, | ||
| 41 | + $td->NORMALIZE_NEWLINES); | ||
| 42 | + $td->runtest("check output", | ||
| 43 | + {$td->COMMAND => "qpdf-test-compare a.pdf minimal-out.pdf"}, | ||
| 44 | + {$td->FILE => "minimal-out.pdf", $td->EXIT_STATUS => 0}); | ||
| 45 | + | ||
| 46 | + $td->runtest("zopfli warning", | ||
| 47 | + {$td->COMMAND => "QPDF_ZOPFLI=on qpdf minimal.pdf a.pdf"}, | ||
| 48 | + {$td->FILE => "zopfli-warning.out", $td->EXIT_STATUS => 3}, | ||
| 49 | + $td->NORMALIZE_NEWLINES); | ||
| 50 | + $td->runtest("check output", | ||
| 51 | + {$td->COMMAND => "qpdf-test-compare a.pdf minimal-out.pdf"}, | ||
| 52 | + {$td->FILE => "minimal-out.pdf", $td->EXIT_STATUS => 0}); | ||
| 53 | + | ||
| 54 | + $td->runtest("zopfli error", | ||
| 55 | + {$td->COMMAND => "QPDF_ZOPFLI=force qpdf minimal.pdf a.pdf"}, | ||
| 56 | + {$td->REGEXP => "QPDF_ZOPFLI=force, and zopfli support is not enabled", | ||
| 57 | + $td->EXIT_STATUS => 2}, | ||
| 58 | + $td->NORMALIZE_NEWLINES); | ||
| 59 | + | ||
| 60 | +} else { | ||
| 61 | + # Check variables | ||
| 62 | + $n_tests = 4; | ||
| 63 | + $td->runtest("zopfli supported and enabled", | ||
| 64 | + {$td->COMMAND => "QPDF_ZOPFLI=on qpdf --zopfli"}, | ||
| 65 | + {$td->STRING => "zopfli support is enabled, and zopfli is active\n", | ||
| 66 | + $td->EXIT_STATUS => 0}, | ||
| 67 | + $td->NORMALIZE_NEWLINES); | ||
| 68 | + $td->runtest("zopfli supported and disabled", | ||
| 69 | + {$td->COMMAND => "QPDF_ZOPFLI=disabled qpdf --zopfli"}, | ||
| 70 | + {$td->REGEXP => "(?s)zopfli support is enabled but not active.*QPDF_ZOPFLI.*\n", | ||
| 71 | + $td->EXIT_STATUS => 0}, | ||
| 72 | + $td->NORMALIZE_NEWLINES); | ||
| 73 | + # CI runs the whole test suite with QPDF_ZOPFLI=force, but run one | ||
| 74 | + # for a guarantee. | ||
| 75 | + $td->runtest("run with zopfli", | ||
| 76 | + {$td->COMMAND => "QPDF_ZOPFLI=force qpdf minimal.pdf a.pdf"}, | ||
| 77 | + {$td->STRING => "", $td->EXIT_STATUS => 0}, | ||
| 78 | + $td->NORMALIZE_NEWLINES); | ||
| 79 | + $td->runtest("check output", | ||
| 80 | + {$td->COMMAND => "qpdf-test-compare a.pdf minimal-out.pdf"}, | ||
| 81 | + {$td->FILE => "minimal-out.pdf", $td->EXIT_STATUS => 0}); | ||
| 82 | +} | ||
| 83 | + | ||
| 84 | +cleanup(); | ||
| 85 | +$td->report($n_tests); |
zlib-flate/qtest/zf.test
| @@ -21,6 +21,9 @@ for (my $i = 0; $i < 100; $i++) | @@ -21,6 +21,9 @@ for (my $i = 0; $i < 100; $i++) | ||
| 21 | } | 21 | } |
| 22 | close(F); | 22 | close(F); |
| 23 | 23 | ||
| 24 | +my $dev_null = File::Spec->devnull(); | ||
| 25 | +my $n_tests = 9; | ||
| 26 | + | ||
| 24 | foreach my $level ('', '=1', '=9') | 27 | foreach my $level ('', '=1', '=9') |
| 25 | { | 28 | { |
| 26 | my $f = $level; | 29 | my $f = $level; |
| @@ -35,11 +38,18 @@ foreach my $level ('', '=1', '=9') | @@ -35,11 +38,18 @@ foreach my $level ('', '=1', '=9') | ||
| 35 | {$td->FILE => "a.uncompressed", $td->EXIT_STATUS => 0}); | 38 | {$td->FILE => "a.uncompressed", $td->EXIT_STATUS => 0}); |
| 36 | } | 39 | } |
| 37 | 40 | ||
| 41 | +chomp(my $zopfli = `zlib-flate --_zopfli`); | ||
| 38 | my $size1 = (stat("a.=1"))[7]; | 42 | my $size1 = (stat("a.=1"))[7]; |
| 39 | my $size9 = (stat("a.=9"))[7]; | 43 | my $size9 = (stat("a.=9"))[7]; |
| 40 | -$td->runtest("higher compression is smaller", | ||
| 41 | - {$td->STRING => ($size9 < $size1 ? "YES\n" : "$size9 $size1\n")}, | ||
| 42 | - {$td->STRING => "YES\n"}); | 44 | +if ($zopfli =~ m/1$/) { |
| 45 | + $td->runtest("compression level is ignored with zopfli", | ||
| 46 | + {$td->STRING => ($size9 == $size1 ? "YES\n" : "$size9 $size1\n")}, | ||
| 47 | + {$td->STRING => "YES\n"}); | ||
| 48 | +} else { | ||
| 49 | + $td->runtest("higher compression is smaller", | ||
| 50 | + {$td->STRING => ($size9 < $size1 ? "YES\n" : "$size9 $size1\n")}, | ||
| 51 | + {$td->STRING => "YES\n"}); | ||
| 52 | +} | ||
| 43 | 53 | ||
| 44 | $td->runtest("error", | 54 | $td->runtest("error", |
| 45 | {$td->COMMAND => "zlib-flate -uncompress < 1.uncompressed"}, | 55 | {$td->COMMAND => "zlib-flate -uncompress < 1.uncompressed"}, |
| @@ -54,7 +64,40 @@ $td->runtest("corrupted input", | @@ -54,7 +64,40 @@ $td->runtest("corrupted input", | ||
| 54 | $td->EXIT_STATUS => 3}, | 64 | $td->EXIT_STATUS => 3}, |
| 55 | $td->NORMALIZE_NEWLINES); | 65 | $td->NORMALIZE_NEWLINES); |
| 56 | 66 | ||
| 57 | -$td->report(9); | 67 | +# Exercise different values of the QPDF_ZOPFLI variable |
| 68 | +if ($zopfli =~ m/^0/) { | ||
| 69 | + $n_tests += 4; | ||
| 70 | + $td->runtest("disabled", | ||
| 71 | + {$td->COMMAND => "QPDF_ZOPFLI=disabled zlib-flate --_zopfli"}, | ||
| 72 | + {$td->STRING => "00\n", $td->EXIT_STATUS => 0}, | ||
| 73 | + $td->NORMALIZE_NEWLINES); | ||
| 74 | + $td->runtest("force", | ||
| 75 | + {$td->COMMAND => "QPDF_ZOPFLI=force zlib-flate -compress < a.uncompressed"}, | ||
| 76 | + {$td->REGEXP => "QPDF_ZOPFLI=force, and zopfli support is not enabled", | ||
| 77 | + $td->EXIT_STATUS => 2}, | ||
| 78 | + $td->NORMALIZE_NEWLINES); | ||
| 79 | + $td->runtest("silent", | ||
| 80 | + {$td->COMMAND => "QPDF_ZOPFLI=silent zlib-flate -compress < a.uncompressed > $dev_null"}, | ||
| 81 | + {$td->STRING => "", $td->EXIT_STATUS => 0}, | ||
| 82 | + $td->NORMALIZE_NEWLINES); | ||
| 83 | + $td->runtest("other", | ||
| 84 | + {$td->COMMAND => "QPDF_ZOPFLI=other zlib-flate -compress < a.uncompressed > $dev_null"}, | ||
| 85 | + {$td->REGEXP => "QPDF_ZOPFLI is set, but libqpdf was not built with zopfli support", | ||
| 86 | + $td->EXIT_STATUS => 0}, | ||
| 87 | + $td->NORMALIZE_NEWLINES); | ||
| 88 | +} else { | ||
| 89 | + $n_tests += 2; | ||
| 90 | + $td->runtest("disabled", | ||
| 91 | + {$td->COMMAND => "QPDF_ZOPFLI=disabled zlib-flate --_zopfli"}, | ||
| 92 | + {$td->STRING => "10\n", $td->EXIT_STATUS => 0}, | ||
| 93 | + $td->NORMALIZE_NEWLINES); | ||
| 94 | + $td->runtest("force", | ||
| 95 | + {$td->COMMAND => "QPDF_ZOPFLI=force zlib-flate --_zopfli"}, | ||
| 96 | + {$td->STRING => "11\n", $td->EXIT_STATUS => 0}, | ||
| 97 | + $td->NORMALIZE_NEWLINES); | ||
| 98 | +} | ||
| 99 | + | ||
| 100 | +$td->report($n_tests); | ||
| 58 | 101 | ||
| 59 | cleanup(); | 102 | cleanup(); |
| 60 | 103 |
zlib-flate/zlib-flate.cc
| @@ -6,7 +6,6 @@ | @@ -6,7 +6,6 @@ | ||
| 6 | #include <cstdio> | 6 | #include <cstdio> |
| 7 | #include <cstdlib> | 7 | #include <cstdlib> |
| 8 | #include <cstring> | 8 | #include <cstring> |
| 9 | -#include <fcntl.h> | ||
| 10 | #include <iostream> | 9 | #include <iostream> |
| 11 | 10 | ||
| 12 | static char const* whoami = nullptr; | 11 | static char const* whoami = nullptr; |
| @@ -50,21 +49,29 @@ main(int argc, char* argv[]) | @@ -50,21 +49,29 @@ main(int argc, char* argv[]) | ||
| 50 | action = Pl_Flate::a_deflate; | 49 | action = Pl_Flate::a_deflate; |
| 51 | int level = QUtil::string_to_int(argv[1] + 10); | 50 | int level = QUtil::string_to_int(argv[1] + 10); |
| 52 | Pl_Flate::setCompressionLevel(level); | 51 | Pl_Flate::setCompressionLevel(level); |
| 52 | + } else if (strcmp(argv[1], "--_zopfli") == 0) { | ||
| 53 | + // Undocumented option, but that doesn't mean someone doesn't use it... | ||
| 54 | + // This is primarily here to support the test suite. | ||
| 55 | + std::cout << (Pl_Flate::zopfli_supported() ? "1" : "0") | ||
| 56 | + << (Pl_Flate::zopfli_enabled() ? "1" : "0") << std::endl; | ||
| 57 | + return 0; | ||
| 53 | } else { | 58 | } else { |
| 54 | usage(); | 59 | usage(); |
| 55 | } | 60 | } |
| 56 | 61 | ||
| 57 | - QUtil::binary_stdout(); | ||
| 58 | - QUtil::binary_stdin(); | ||
| 59 | - auto out = std::make_shared<Pl_StdioFile>("stdout", stdout); | ||
| 60 | - auto flate = std::make_shared<Pl_Flate>("flate", out.get(), action); | ||
| 61 | bool warn = false; | 62 | bool warn = false; |
| 62 | - flate->setWarnCallback([&warn](char const* msg, int code) { | ||
| 63 | - warn = true; | ||
| 64 | - std::cerr << whoami << ": WARNING: zlib code " << code << ", msg = " << msg << std::endl; | ||
| 65 | - }); | ||
| 66 | - | ||
| 67 | try { | 63 | try { |
| 64 | + QUtil::binary_stdout(); | ||
| 65 | + QUtil::binary_stdin(); | ||
| 66 | + Pl_Flate::zopfli_check_env(); | ||
| 67 | + auto out = std::make_shared<Pl_StdioFile>("stdout", stdout); | ||
| 68 | + auto flate = std::make_shared<Pl_Flate>("flate", out.get(), action); | ||
| 69 | + flate->setWarnCallback([&warn](char const* msg, int code) { | ||
| 70 | + warn = true; | ||
| 71 | + std::cerr << whoami << ": WARNING: zlib code " << code << ", msg = " << msg | ||
| 72 | + << std::endl; | ||
| 73 | + }); | ||
| 74 | + | ||
| 68 | unsigned char buf[10000]; | 75 | unsigned char buf[10000]; |
| 69 | bool done = false; | 76 | bool done = false; |
| 70 | while (!done) { | 77 | while (!done) { |