diff options
151 files changed, 1552 insertions, 2199 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 03d6b929..b86f3a28 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -104,27 +104,18 @@ jobs: with: name: distribution path: distribution - Linux32: - runs-on: ubuntu-latest - needs: Prebuild - steps: - - uses: actions/checkout@v3 - - name: 'Linux 32-bit' - run: build-scripts/build-linux32 pikepdf: + strategy: + fail-fast: false + max-parallel: 1 + matrix: + future: ['', 'future'] runs-on: ubuntu-latest needs: Prebuild steps: - uses: actions/checkout@v3 - name: 'pikepdf' - run: build-scripts/test-pikepdf - Fuzzers: - runs-on: ubuntu-latest - needs: Prebuild - steps: - - uses: actions/checkout@v3 - - name: 'Build Fuzzer' - run: build-scripts/build-fuzzer + run: build-scripts/test-pikepdf ${{ matrix.future }} Sanitizers: runs-on: ubuntu-latest needs: Prebuild @@ -132,10 +123,20 @@ jobs: - uses: actions/checkout@v3 - name: 'Sanitizer Tests' run: build-scripts/test-sanitizers - UnsignedChar: + QuickJobs: runs-on: ubuntu-latest needs: Prebuild + strategy: + fail-fast: false + max-parallel: 3 + matrix: + script: + - build-fuzzer + - build-linux32 + - test-alt-zlib + - test-unsigned-char + - test-c++-next steps: - uses: actions/checkout@v3 - - name: 'Unsigned Char Tests' - run: build-scripts/test-unsigned-char + - name: ${{ matrix.script }} + run: build-scripts/${{ matrix.script }} diff --git a/.idea/.gitignore b/.idea/.gitignore index 13566b81..55b97637 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -6,3 +6,4 @@ # Datasource local storage ignored files /dataSources/ /dataSources.local.xml +/inspectionProfiles diff --git a/.idea/dictionaries/ejb.xml b/.idea/dictionaries/ejb.xml new file mode 100644 index 00000000..0dcc3d1a --- /dev/null +++ b/.idea/dictionaries/ejb.xml @@ -0,0 +1,7 @@ +<component name="ProjectDictionaryState"> + <dictionary name="ejb"> + <words> + <w>whoami</w> + </words> + </dictionary> +</component>
\ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e9f47a0..c264bfa3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,9 +3,10 @@ cmake_minimum_required(VERSION 3.16) # make_dist expects the version line to be on a line by itself after # the project line. When updating the version, check make_dist for all -# the places it has to be updated. +# the places it has to be updated. The doc configuration and CI build +# also find the version number here. project(qpdf - VERSION 11.6.3 + VERSION 11.7.0 LANGUAGES C CXX) # Enable correct rpath handling for MacOSX @@ -105,6 +106,7 @@ option(INSTALL_CMAKE_PACKAGE "Install cmake package files" ON) option(INSTALL_EXAMPLES "Install example files" ON) option(FUTURE "Include ABI-breaking changes CONSIDERED for the next major release" OFF) +option(CXX_NEXT "Build with next C++ standard version" OFF) # *** END OPTIONS *** @@ -147,8 +149,14 @@ endif() # increment SOVERSION every time we increment the project major # version. This works because qpdf uses semantic versioning. qpdf 10.x # was libqpdf28, so start from there. -math(EXPR qpdf_SOVERSION "${PROJECT_VERSION_MAJOR} + 18") -set(qpdf_LIBVERSION ${qpdf_SOVERSION}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}) + +if(FUTURE) + math(EXPR qpdf_SOVERSION 0) + set(qpdf_LIBVERSION 0) +else() + math(EXPR qpdf_SOVERSION "${PROJECT_VERSION_MAJOR} + 18") + set(qpdf_LIBVERSION ${qpdf_SOVERSION}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}) +endif() if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) message(FATAL_ERROR " @@ -159,7 +167,11 @@ Please build with cmake in a subdirectory, e.g. Please remove CMakeCache.txt and the CMakeFiles directories.") endif() -set(CMAKE_CXX_STANDARD 17) +if(CXX_NEXT) + set(CMAKE_CXX_STANDARD 20) +else() + set(CMAKE_CXX_STANDARD 17) +endif() set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_C_VISIBILITY_PRESET hidden) @@ -329,6 +341,7 @@ add_test( # add_subdirectory order affects test order add_subdirectory(include) add_subdirectory(libqpdf) +add_subdirectory(compare-for-test) add_subdirectory(qpdf) add_subdirectory(libtests) add_subdirectory(examples) @@ -1,3 +1,41 @@ +2023-12-20 Jay Berkenbilt <ejb@ql.org> + + * Update code and tests so that qpdf's test suite no longer + depends on the output of any specific zlib implementation. This + makes it possible to get a fully passing test suite with any + API-compatible zlib library. CI tests with the default zlib as + well as zlib-ng (including verifying that zlib-ng is not the + default), but any zlib implementation should work. Fixes #774. + + * Bug fix: with --compress-streams=n, don't compress object, XRef, + or linearization hint streams. + +2023-12-16 Jay Berkenbilt <ejb@ql.org> + + * Add new C++ functions "qpdf_c_get_qpdf" and "qpdf_c_wrap" to + qpdf-c.h that make it possible to write your own extern "C" + functions in C++ that interoperate with the C API. See + examples/extend-c-api for more information. + + * Bug fix from M. Holger: the default for /Columns in PNG filter + is 1, but libqpdf was acting like it was 0. + + * Enhancement from M. Holger: add methods to Buffer to work more + easily with std::string. + +2023-12-10 Jay Berkenbilt <ejb@ql.org> + + * 11.6.4: release + +2023-12-09 Jay Berkenbilt <ejb@ql.org> + + * Install fix: include cmake files with the dev component. + +2023-11-20 Jay Berkenbilt <ejb@ql.org> + + * Build AppImage with an older Linux distribution to support AWS + Lambda. Fixes #1086. + 2023-10-15 Jay Berkenbilt <ejb@ql.org> * 11.6.3: release diff --git a/README-maintainer.md b/README-maintainer.md index c7ae7296..4c8bd6e6 100644 --- a/README-maintainer.md +++ b/README-maintainer.md @@ -7,6 +7,7 @@ * [CHECKING DOCS ON readthedocs](#checking-docs-on-readthedocs) * [GOOGLE OSS-FUZZ](#google-oss-fuzz) * [CODING RULES](#coding-rules) +* [ZLIB COMPATIBILITY](#zlib-compatibility) * [HOW TO ADD A COMMAND-LINE ARGUMENT](#how-to-add-a-command-line-argument) * [RELEASE PREPARATION](#release-preparation) * [CREATING A RELEASE](#creating-a-release) @@ -272,6 +273,102 @@ Building docs from pull requests is also enabled. * Avoid attaching too much metadata to objects and object handles since those have to get copied around a lot. +## ZLIB COMPATIBILITY + +The qpdf test suite is designed to be independent of the output of any +particular version of zlib. There are several strategies to make this +work: + +* `build-scripts/test-alt-zlib` runs in CI and runs the test suite + with a non-default zlib. Please refer to that code for an example of + how to do this in case you want to test locally. + +* The test suite is full of cases that compare output PDF files with + expected PDF files in the test suite. If the file contains data that + was compressed by QPDFWriter, then the output file will depend on + the behavior of zlib. As such, using a simple comparison won't work. + There are several strategies used by the test suite. + + * A new program called `qpdf-test-compare`, in most cases, is a drop + in replacement for a simple file comparison. This code make sure + the two files have exactly the same number of objects with the + same object and generation numbers, and that corresponding objects + are identical with the following allowances (consult its source + code for all the details details): + * The `/Length` key is not compared in stream dictionaries. + * The second element of `/ID` is not compared. + * If the first and second element of `/ID` are the same, then the + first element if `/ID` is also not compared. + * If a stream is compressed with `/FlateDecode`, the + _uncompressed_ stream data is compared. Otherwise, the raw + stream data is compared. + * Generated fields in the `/Encrypt` dictionary are not compared, + though password-protected files must have the same password. + * Differences in the contents of `/XRef` streams are ignored. + + To use this, run `qpdf-test-compare actual.pdf expected.pdf`, and + expect the output to match `expected.pdf`. For example, if a test + used to be written like this; + ```perl + $td->runtest("check output", + {$td->FILE => "a.pdf"}, + {$td->FILE => "out.pdf"}); + ``` + then write it like this instead: + ```perl + $td->runtest("check output", + {$td->COMMAND => "qpdf-test-compare a.pdf out.pdf"}, + {$td->FILE => "out.pdf", $td->EXIT_STATUS => 0}); + ``` + You can look at `compare-for-test/qtest/compare.test` for + additional examples. + + Here's what's going on: + * If the files "match" according to the rules of + `qpdf-test-compare`, the output of the program is the expected + file. + * If the files do not match, the output is the actual file. The + reason is that, if a change is made that results in an expected + change to the expected file, the output of the comparison can be + used to replace the expected file (as long as it is definitely + known to be correct—no shortcuts here!). That way, it doesn't + matter which zlib you use to generate test files. + * As a special debugging tool, you can set the `QPDF_COMPARE_WHY` + environment variable to any value. In this case, if the files + don't match, the output is a description of the first thing in + the file that doesn't match. This is mostly useful for debugging + `qpdf-test-compare` itself, but it can also be helpful as a + sanity check that the differences are expected. If you are + trying to find out the _real_ differences, a suggestion is to + convert both files to qdf and compare them lexically. + + * There are some cases where `qpdf-test-compare` can't be used. For + example, if you need to actually test one of the things that + `qpdf-test-compare` ignores, you'll need some other mechanism. + There are tests for deterministic ID creation and xref streams + that have to implement other mechanisms. Also, linearization hint + streams and the linearization dictionary in a linearized file + contain file offsets. Rather than ignoring those, it can be + helpful to create linearized files using `--compress-streams=n`. + In that case, `QPDFWriter` won't compress any data, so the PDF + will be independent of the output of any particular zlib + implementation. + +You can find many examples of how tests were rewritten by looking at +the commits preceding the one that added this section of this README +file. + +Note about `/ID`: many test cases use `--static-id` to have a +predictable `/ID` for testing. Many other test cases use +`--deterministic-id`. While `--static-id` is unaffected by file +contents, `--deterministic-id` is based on file contents and so is +dependent on zlib output if there is any newly compressed data. By +using `qpdf-test-compare`, it's actually not necessary to use either +`--static-id` or `--deterministic-id`. It may still be necessary to +use `--static-aes-iv` if comparing encrypted files, but since +`qpdf-test-compare` ignores `/Perms`, a wider range of encrypted files +can be compared using `qpdf-test-compare`. + ## HOW TO ADD A COMMAND-LINE ARGUMENT Quick reminder: diff --git a/appimage/Dockerfile b/appimage/Dockerfile index c1914144..24c9c672 100644 --- a/appimage/Dockerfile +++ b/appimage/Dockerfile @@ -1,13 +1,19 @@ -FROM ubuntu:20.04 +FROM ubuntu:18.04 ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update RUN apt-get -y install screen git sudo \ - build-essential pkg-config cmake \ + build-essential pkg-config \ zlib1g-dev libjpeg-dev libgnutls28-dev \ python3-pip texlive-latex-extra latexmk \ inkscape imagemagick busybox-static wget fuse && \ apt-get clean && rm -rf /var/lib/apt/lists/* -RUN pip3 install sphinx sphinx_rtd_theme +# Get cmake from pypi. We need to keep Ubuntu 18.04 for a while longer +# since the glibc in Ubuntu 20.04 is too new (as of late 2023) for +# Amazon Linux 2 in Lambda and for some supported CentOS versions. +# When we are ready to update to 20.04 or newer, remove the version +# constraint on sphinx, and install the OS package for cmake. +RUN pip3 install --upgrade pip +RUN pip3 install sphinx==4 sphinx_rtd_theme cmake COPY entrypoint /entrypoint RUN chmod +x /entrypoint ENTRYPOINT [ "/entrypoint" ] diff --git a/appimage/build-appimage b/appimage/build-appimage index 0e86bff6..f970b7df 100755 --- a/appimage/build-appimage +++ b/appimage/build-appimage @@ -47,14 +47,14 @@ fi _osversion=$(cat /etc/os-release | grep PRETTY_NAME | awk -F'=' '{print $2}' | sed 's#"##g') # Warn users building the AppImage locally: -if [[ ! $_osversion =~ Ubuntu\ 20.04.*\ LTS ]]; then +if [[ ! $_osversion =~ Ubuntu\ 18.04.*\ LTS ]]; then set +x echo "" # 0 1 2 3 4 5 6 7 # 01234567890123456789012345678901234567890123456789012345678901234567890123456789 echo "+===========================================================================+" echo "|| WARNING: You are about to build a QPDF AppImage on a system which is ||" - echo "|| NOT Ubuntu 20.04 LTS. ||" + echo "|| NOT Ubuntu 18.04 LTS. ||" echo "|| ||" echo "|| It is recommended that you use a distribution that is at least a ||" echo "|| few years old to maximize the number of Linux distributions the ||" diff --git a/build-scripts/build-doc b/build-scripts/build-doc index e6c72757..7d45d4fa 100755 --- a/build-scripts/build-doc +++ b/build-scripts/build-doc @@ -10,7 +10,7 @@ pip3 install sphinx sphinx_rtd_theme cmake -S . -B build -DBUILD_DOC=1 cmake --build build --verbose --target doc_dist zip -r doc.zip build/manual/doc-dist -version=$(grep -E '^release' manual/conf.py | cut -d"'" -f 2) +version=$(grep -E '^ +VERSION [1-9]' CMakeLists.txt | awk '{print $2}') mv build/manual/doc-dist qpdf-${version}-doc mkdir distribution zip -r distribution/qpdf-${version}-doc-ci.zip qpdf-${version}-doc diff --git a/build-scripts/test-alt-zlib b/build-scripts/test-alt-zlib new file mode 100755 index 00000000..41b3bb31 --- /dev/null +++ b/build-scripts/test-alt-zlib @@ -0,0 +1,39 @@ +#!/bin/bash +set -eo pipefail +sudo apt-get update +sudo apt-get -y install \ + build-essential cmake \ + zlib1g-dev libjpeg-dev libgnutls28-dev libssl-dev + +# Build and install zlib-ng +rm -rf /tmp/zlib-ng +pushd /tmp +git clone https://github.com/zlib-ng/zlib-ng +cd zlib-ng +cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/tmp/inst -DZLIB_COMPAT=ON +cmake --build build -j$(nproc) +(cd build; ctest --verbose) +cmake --install build +popd + +cmake -S . -B build \ + -DCI_MODE=1 -DBUILD_STATIC_LIBS=0 -DCMAKE_BUILD_TYPE=Release \ + -DREQUIRE_CRYPTO_OPENSSL=1 -DREQUIRE_CRYPTO_GNUTLS=1 \ + -DENABLE_QTC=1 +cmake --build build --verbose -j$(nproc) -- -k + +# Make sure we can use zlib-ng +sum1="$(./build/zlib-flate/zlib-flate -compress < README-maintainer.md | sha256sum -)" +export LD_PRELOAD=/tmp/inst/lib/libz.so.1 +sum2="$(./build/zlib-flate/zlib-flate -compress < README-maintainer.md | sha256sum -)" +if [ "$sum1" = "$sum2" ]; then + # If this happens, see if zlib-ng has become the default. If + # that's the case, rework this test to use some other alternaive + # zlib, such as the old one or any other API-compatible + # implementation. + echo "Using zlib-ng didn't change compression output" + exit 2 +fi + +# If this fails, please see ZLIB COMPATIBILITY in README-maintainer.md. +(cd build; ctest --verbose) diff --git a/build-scripts/test-c++-next b/build-scripts/test-c++-next new file mode 100755 index 00000000..343a5a4d --- /dev/null +++ b/build-scripts/test-c++-next @@ -0,0 +1,14 @@ +#!/bin/bash +set -e +sudo apt-get update +sudo apt-get -y install \ + build-essential cmake \ + zlib1g-dev libjpeg-dev libgnutls28-dev libssl-dev +cmake -S . -B build \ + -DCXX_NEXT=ON \ + -DCI_MODE=1 -DBUILD_STATIC_LIBS=0 -DCMAKE_BUILD_TYPE=Release \ + -DREQUIRE_CRYPTO_OPENSSL=1 -DREQUIRE_CRYPTO_GNUTLS=1 \ + -DENABLE_QTC=1 +cmake --build build --verbose -j$(nproc) -- -k +cd build +ctest --verbose diff --git a/build-scripts/test-pikepdf b/build-scripts/test-pikepdf index e6c8a9a4..cdab79cf 100755 --- a/build-scripts/test-pikepdf +++ b/build-scripts/test-pikepdf @@ -1,10 +1,20 @@ #!/bin/bash set -ex +cmake_extra= +future=0 +if [ "$1" = "future" ]; then + future=1 + cmake_extra=-DFUTURE=ON +fi sudo apt-get update sudo apt-get -y install \ build-essential cmake zlib1g-dev libjpeg-dev libgnutls28-dev -cmake -S . -B build -DBUILD_STATIC_LIBS=0 -DCMAKE_BUILD_TYPE=RelWithDebInfo +cmake -S . -B build -DBUILD_STATIC_LIBS=0 -DCMAKE_BUILD_TYPE=RelWithDebInfo $cmake_extra cmake --build build --verbose -j$(nproc) --target libqpdf -- -k +if [ "$future" = "1" ]; then + # Run qpdf's test suite in FUTURE mode as well + ctest --verbose +fi export QPDF_SOURCE_TREE=$PWD export QPDF_BUILD_LIBDIR=$QPDF_SOURCE_TREE/build/libqpdf export LD_LIBRARY_PATH=$QPDF_BUILD_LIBDIR diff --git a/compare-for-test/CMakeLists.txt b/compare-for-test/CMakeLists.txt new file mode 100644 index 00000000..c5ebbbbc --- /dev/null +++ b/compare-for-test/CMakeLists.txt @@ -0,0 +1,15 @@ +# This directory is called compare-for-test rather than +# qpdf-test-compare to make shell completion easier. +add_executable(qpdf-test-compare qpdf-test-compare.cc) +target_link_libraries(qpdf-test-compare libqpdf) + +add_test( + NAME compare-for-test + COMMAND ${RUN_QTEST} + --top ${qpdf_SOURCE_DIR} + --bin $<TARGET_FILE_DIR:qpdf-test-compare> + --bin $<TARGET_FILE_DIR:libqpdf> # for Windows to find DLL + --code ${qpdf_SOURCE_DIR}/compare-for-test + --color ${QTEST_COLOR} + --show-on-failure ${SHOW_FAILED_TEST_OUTPUT} + --tc "${qpdf_SOURCE_DIR}/compare-for-test/*.cc") diff --git a/compare-for-test/compare.testcov b/compare-for-test/compare.testcov new file mode 100644 index 00000000..b58dd2c8 --- /dev/null +++ b/compare-for-test/compare.testcov @@ -0,0 +1,9 @@ +objects with different type 0 +different stream dictionaries 0 +uncompressing 0 +not uncompressing 0 +differing data size 1 +different data 1 +different non-stream 0 +different trailer 0 +ignore data for xref stream 0 diff --git a/compare-for-test/qpdf-test-compare.cc b/compare-for-test/qpdf-test-compare.cc new file mode 100644 index 00000000..5d3e6e28 --- /dev/null +++ b/compare-for-test/qpdf-test-compare.cc @@ -0,0 +1,244 @@ +#include <qpdf/Pl_StdioFile.hh> +#include <qpdf/QPDF.hh> +#include <qpdf/QTC.hh> +#include <qpdf/QUtil.hh> + +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <iostream> + +static char const* whoami = nullptr; + +void +usage() +{ + std::cerr << "Usage: " << whoami << " actual expected" << std::endl + << R"(Where "actual" is the actual output and "expected" is the expected)" + << std::endl + << "output of a test, compare the two PDF files. The files are considered" + << std::endl + << "to match if all their objects are identical except that, if a stream is" + << std::endl + << "compressed with FlateDecode, the uncompressed data must match." << std::endl + << std::endl + << "If the files match, the output is the expected file. Otherwise, it is" + << std::endl + << "the actual file. Read comments in the code for rationale." << std::endl; + exit(2); +} + +void +cleanEncryption(QPDF& q) +{ + auto enc = q.getTrailer().getKey("/Encrypt"); + if (!enc.isDictionary()) { + return; + } + enc.removeKey("/O"); + enc.removeKey("/OE"); + enc.removeKey("/U"); + enc.removeKey("/UE"); + enc.removeKey("/Perms"); +} + +std::string +compareObjects(std::string const& label, QPDFObjectHandle act, QPDFObjectHandle exp) +{ + if (act.getTypeCode() != exp.getTypeCode()) { + QTC::TC("compare", "objects with different type"); + return label + ": different types"; + } + if (act.isStream()) { + // Disregard stream lengths. The length of stream data is compared later, and we don't care + // about the length of compressed data as long as the uncompressed data matches. + auto act_dict = act.getDict(); + auto exp_dict = exp.getDict(); + act_dict.removeKey("/Length"); + exp_dict.removeKey("/Length"); + if (act_dict.unparse() != exp_dict.unparse()) { + QTC::TC("compare", "different stream dictionaries"); + return label + ": stream dictionaries differ"; + } + if (act_dict.getKey("/Type").isNameAndEquals("/XRef")) { + // Cross-reference streams will generally not match, but we have numerous tests that + // meaningfully ensure that xref streams are correct. + QTC::TC("compare", "ignore data for xref stream"); + return ""; + } + auto act_filters = act_dict.getKey("/Filter"); + bool uncompress = false; + if (act_filters.isName()) { + act_filters = act_filters.wrapInArray(); + } + if (act_filters.isArray()) { + for (auto& filter: act_filters.aitems()) { + if (filter.isNameAndEquals("/FlateDecode")) { + uncompress = true; + break; + } + } + } + std::shared_ptr<Buffer> act_data; + std::shared_ptr<Buffer> exp_data; + if (uncompress) { + QTC::TC("compare", "uncompressing"); + act_data = act.getStreamData(); + exp_data = exp.getStreamData(); + } else { + QTC::TC("compare", "not uncompressing"); + act_data = act.getRawStreamData(); + exp_data = exp.getRawStreamData(); + } + if (act_data->getSize() != exp_data->getSize()) { + QTC::TC("compare", "differing data size", uncompress ? 0 : 1); + return label + ": stream data size differs"; + } + auto act_buf = act_data->getBuffer(); + auto exp_buf = exp_data->getBuffer(); + if (memcmp(act_buf, exp_buf, act_data->getSize()) != 0) { + QTC::TC("compare", "different data", uncompress ? 0 : 1); + return label + ": stream data differs"; + } + } else if (act.unparseResolved() != exp.unparseResolved()) { + QTC::TC("compare", "different non-stream"); + return label + ": object contents differ"; + } + return ""; +} + +void +cleanTrailer(QPDFObjectHandle& trailer) +{ + // If the trailer is an object stream, it will have /Length. + trailer.removeKey("/Length"); + // Disregard the second half of /ID. This doesn't have anything directly to do with zlib, but + // lots of tests use --deterministic-id, and that is affected. The deterministic ID tests + // meaningfully exercise that deterministic IDs behave as expected, so for the rest of the + // tests, it's okay to ignore /ID[1]. If the two halves of /ID are the same, ignore both since + // this means qpdf completely generated the /ID rather than preserving the first half. + auto id = trailer.getKey("/ID"); + if (id.isArray() && id.getArrayNItems() == 2) { + auto id0 = id.getArrayItem(0).unparse(); + auto id1 = id.getArrayItem(1).unparse(); + id.setArrayItem(1, "()"_qpdf); + if (id0 == id1) { + id.setArrayItem(0, "()"_qpdf); + } + } +} + +std::string +compare(char const* actual_filename, char const* expected_filename, char const* password) +{ + QPDF actual; + actual.processFile(actual_filename, password); + QPDF expected; + expected.processFile(expected_filename, password); + // The motivation behind this program is to compare files in a way that allows for + // differences in the exact bytes of zlib compression. If all zlib implementations produced + // exactly the same output, we would just be able to use straight comparison, but since they + // don't, we use this. As such, we are enforcing a standard of "sameness" that goes beyond + // showing semantic equivalence. The only difference we are allowing is compressed data. + + auto act_trailer = actual.getTrailer(); + auto exp_trailer = expected.getTrailer(); + cleanTrailer(act_trailer); + cleanTrailer(exp_trailer); + auto trailer_diff = compareObjects("trailer", act_trailer, exp_trailer); + if (!trailer_diff.empty()) { + QTC::TC("compare", "different trailer"); + return trailer_diff; + } + + cleanEncryption(actual); + cleanEncryption(expected); + + auto actual_objects = actual.getAllObjects(); + auto expected_objects = expected.getAllObjects(); + if (actual_objects.size() != expected_objects.size()) { + // Not exercised in the test suite since the trailers will differ in this case. + return "different number of objects"; + } + for (size_t i = 0; i < actual_objects.size(); ++i) { + auto act = actual_objects[i]; + auto exp = expected_objects[i]; + auto act_og = act.getObjGen(); + auto exp_og = exp.getObjGen(); + if (act_og != exp_og) { + // not reproduced in the test suite + return "different object IDs"; + } + auto ret = compareObjects(act_og.unparse(), act, exp); + if (!ret.empty()) { + return ret; + } + } + return ""; +} + +int +main(int argc, char* argv[]) +{ + if ((whoami = strrchr(argv[0], '/')) == nullptr) { + whoami = argv[0]; + } else { + ++whoami; + } + + if ((argc == 2) && (strcmp(argv[1], "--version") == 0)) { + std::cout << whoami << " from qpdf version " << QPDF::QPDFVersion() << std::endl; + exit(0); + } + + if (argc < 3 || argc > 4) { + usage(); + } + + bool show_why = QUtil::get_env("QPDF_COMPARE_WHY"); + try { + char const* to_output; + char const* actual = argv[1]; + char const* expected = argv[2]; + char const* password{nullptr}; + if (argc == 4) { + password = argv[3]; + } + auto difference = compare(actual, expected, password); + if (difference.empty()) { + // The files are identical; write the expected file. This way, tests can be written + // that compare the output of this program to the expected file. + to_output = expected; + } else { + if (show_why) { + std::cerr << difference << std::endl; + exit(2); + } + // The files differ; write the actual file. If it is determined that the actual file + // is correct because of changes that result in intended differences, this enables + // the output of this program to replace the expected file in the test suite. + to_output = actual; + } + auto f = QUtil::safe_fopen(to_output, "rb"); + QUtil::FileCloser fc(f); + QUtil::binary_stdout(); + auto out = std::make_unique<Pl_StdioFile>("stdout", stdout); + unsigned char buf[2048]; + bool done = false; + while (!done) { + size_t len = fread(buf, 1, sizeof(buf), f); + if (len <= 0) { + done = true; + } else { + out->write(buf, len); + } + } + if (!difference.empty()) { + exit(2); + } + } catch (std::exception& e) { + std::cerr << whoami << ": " << e.what() << std::endl; + exit(2); + } + return 0; +} diff --git a/compare-for-test/qtest/compare.test b/compare-for-test/qtest/compare.test new file mode 100644 index 00000000..198cdf6b --- /dev/null +++ b/compare-for-test/qtest/compare.test @@ -0,0 +1,109 @@ +#!/usr/bin/env perl +require 5.008; +BEGIN { $^W = 1; } +use strict; + +chdir("compare") or die "chdir testdir failed: $!\n"; + +require TestDriver; + +my $td = new TestDriver('compare'); + +# The comparison tool is designed so that you can write tests that run +# `compare actual expected` and compare the result to expected. This +# allows you to just replace the actual file in a comparison with the +# comparison command. If the files match, the output is the expected +# file, which means that if the actual file is the expected file with +# different zlib compression, the test will pass. If the files differ, +# the actual output shown will be the real actual output. If it is +# determined to be correct and used to replace the expected output, +# the test will pass next time regardless of whether the same zlib +# implementation is used. + +# These files are the same file compressed with a different +# compression level and/or a different zlib implementation. +my @same = qw(zlib.pdf zlib-9.pdf zlib-ng.pdf); +my $comparisons = (scalar(@same) * (scalar(@same) + 1))/2; +my $n_tests = 2 * $comparisons; + +for (my $i = 0; $i < scalar(@same); $i++) +{ + for (my $j = $i; $j < scalar(@same); $j++) + { + # Make sure the files are byte-wise different (unless they are the same file). + $td->runtest("byte-wise compare $i and $j", + {$td->COMMAND => "cmp $same[$i] $same[$j]"}, + {$td->REGEXP => ".*", $td->EXIT_STATUS => $i == $j ? 0 : "!0"}); + # Make sure they match. This is how compare should be used: + # the expected output is the same file as the second argument + # to the command. + $td->runtest("compare $i and $j", + {$td->COMMAND => "qpdf-test-compare $same[$i] $same[$j]"}, + {$td->FILE => $same[$j], $td->EXIT_STATUS => 0}); + } +} + +my @diff = ( + ["diff-num-objects.pdf", "trailer: object contents differ"], + ["diff-non-stream.pdf", "3,0: object contents differ"], + ["diff-data-size.pdf", "4,0: stream data size differs"], + ["diff-data.pdf", "4,0: stream data differs"], + ["diff-data-size-unc.pdf", "5,0: stream data size differs"], + ["diff-data-unc.pdf", "5,0: stream data differs"], + ["diff-stream-dict.pdf", "4,0: stream dictionaries differ"], + ["diff-object-type.pdf", "6,0: different types"], + ["diff-id.pdf", "trailer: object contents differ"], + ); +$n_tests += 2 * scalar(@diff); + +foreach my $f (@diff) +{ + # In a real test, the expected output would be the expected file + # as above. Here, we are actually testing the comparison tool to + # verify that it returns a non-zero status and the actual file + # when there is mismatch. Don't copy this test. + $td->runtest("$f->[0] is different", + {$td->COMMAND => "qpdf-test-compare $f->[0] zlib.pdf"}, + {$td->FILE => $f->[0], $td->EXIT_STATUS => 2}); + $td->runtest("$f->[0] is different (why)", + {$td->COMMAND => "env QPDF_COMPARE_WHY=1" . + " qpdf-test-compare $f->[0] zlib.pdf"}, + {$td->STRING => "$f->[1]\n", $td->EXIT_STATUS => 2}, + $td->NORMALIZE_NEWLINES); +} + +# Repeat for encrypted files. +$n_tests += 5; +$td->runtest("byte-wise compare encrypted files", + {$td->COMMAND => "cmp enc1.pdf enc2.pdf"}, + {$td->REGEXP => ".*", $td->EXIT_STATUS => "!0"}); +$td->runtest("compare encrypted files (same)", + {$td->COMMAND => "env QPDF_COMPARE_WHY=1 qpdf-test-compare enc1.pdf enc2.pdf"}, + {$td->FILE => "enc2.pdf", $td->EXIT_STATUS => 0}); +$td->runtest("compare encrypted files (different)", + {$td->COMMAND => "env QPDF_COMPARE_WHY=1 qpdf-test-compare enc1.pdf diff-data-enc.pdf"}, + {$td->STRING => "4,0: stream data differs\n", $td->EXIT_STATUS => 2}, + $td->NORMALIZE_NEWLINES); +$td->runtest("with password (same)", + {$td->COMMAND => "env QPDF_COMPARE_WHY=1 qpdf-test-compare enc1.pdf enc2.pdf o"}, + {$td->FILE => "enc2.pdf", $td->EXIT_STATUS => 0}); +$td->runtest("with password (different)", + {$td->COMMAND => "env QPDF_COMPARE_WHY=1 qpdf-test-compare enc1.pdf diff-data-enc.pdf o"}, + {$td->STRING => "4,0: stream data differs\n", $td->EXIT_STATUS => 2}, + $td->NORMALIZE_NEWLINES); + +# Object streams +$n_tests += 1; +$td->runtest("compare object stream files (same)", + {$td->COMMAND => "env QPDF_COMPARE_WHY=1 qpdf-test-compare ostream1.pdf ostream2.pdf"}, + {$td->FILE => "ostream2.pdf", $td->EXIT_STATUS => 0}); + +$n_tests += 2; +$td->runtest("files identical except /ID[1]", + {$td->COMMAND => "env QPDF_COMPARE_WHY=1 qpdf-test-compare zlib.pdf zlib-new-id.pdf"}, + {$td->FILE => "zlib-new-id.pdf", $td->EXIT_STATUS => 0}); +$td->runtest("/ID[0] = /ID[1]", + {$td->COMMAND => "env QPDF_COMPARE_WHY=1 qpdf-test-compare zlib-new-id1.pdf zlib-new-id2.pdf"}, + {$td->FILE => "zlib-new-id2.pdf", $td->EXIT_STATUS => 0}); + +$td->report($n_tests); diff --git a/compare-for-test/qtest/compare/diff-data-enc.pdf b/compare-for-test/qtest/compare/diff-data-enc.pdf Binary files differnew file mode 100644 index 00000000..ae2b23b0 --- /dev/null +++ b/compare-for-test/qtest/compare/diff-data-enc.pdf diff --git a/compare-for-test/qtest/compare/diff-data-size-unc.pdf b/compare-for-test/qtest/compare/diff-data-size-unc.pdf Binary files differnew file mode 100644 index 00000000..61444ee2 --- /dev/null +++ b/compare-for-test/qtest/compare/diff-data-size-unc.pdf diff --git a/compare-for-test/qtest/compare/diff-data-size.pdf b/compare-for-test/qtest/compare/diff-data-size.pdf Binary files differnew file mode 100644 index 00000000..5dc2f5d1 --- /dev/null +++ b/compare-for-test/qtest/compare/diff-data-size.pdf diff --git a/compare-for-test/qtest/compare/diff-data-unc.pdf b/compare-for-test/qtest/compare/diff-data-unc.pdf Binary files differnew file mode 100644 index 00000000..b79f7afb --- /dev/null +++ b/compare-for-test/qtest/compare/diff-data-unc.pdf diff --git a/compare-for-test/qtest/compare/diff-data.pdf b/compare-for-test/qtest/compare/diff-data.pdf Binary files differnew file mode 100644 index 00000000..04216efa --- /dev/null +++ b/compare-for-test/qtest/compare/diff-data.pdf diff --git a/compare-for-test/qtest/compare/diff-id.pdf b/compare-for-test/qtest/compare/diff-id.pdf Binary files differnew file mode 100644 index 00000000..58df0db7 --- /dev/null +++ b/compare-for-test/qtest/compare/diff-id.pdf diff --git a/compare-for-test/qtest/compare/diff-non-stream.pdf b/compare-for-test/qtest/compare/diff-non-stream.pdf Binary files differnew file mode 100644 index 00000000..2e7e6e80 --- /dev/null +++ b/compare-for-test/qtest/compare/diff-non-stream.pdf diff --git a/compare-for-test/qtest/compare/diff-num-objects.pdf b/compare-for-test/qtest/compare/diff-num-objects.pdf Binary files differnew file mode 100644 index 00000000..ec904d15 --- /dev/null +++ b/compare-for-test/qtest/compare/diff-num-objects.pdf diff --git a/compare-for-test/qtest/compare/diff-object-type.pdf b/compare-for-test/qtest/compare/diff-object-type.pdf Binary files differnew file mode 100644 index 00000000..8380e88d --- /dev/null +++ b/compare-for-test/qtest/compare/diff-object-type.pdf diff --git a/compare-for-test/qtest/compare/diff-stream-dict.pdf b/compare-for-test/qtest/compare/diff-stream-dict.pdf Binary files differnew file mode 100644 index 00000000..cf40d323 --- /dev/null +++ b/compare-for-test/qtest/compare/diff-stream-dict.pdf diff --git a/compare-for-test/qtest/compare/enc1.pdf b/compare-for-test/qtest/compare/enc1.pdf new file mode 100644 index 00000000..50f4f1da --- /dev/null +++ b/compare-for-test/qtest/compare/enc1.pdf @@ -0,0 +1,41 @@ +%PDF-2.0 +% +1 0 obj +<< /Pages 2 0 R /Type /Catalog >> +endobj +2 0 obj +<< /Count 1 /Kids [ 3 0 R ] /Type /Pages >> +endobj +3 0 obj +<< /Contents [ 4 0 R 5 0 R ] /MediaBox [ 0 0 612 792 ] /Parent 2 0 R /Resources << /Font << /F1 6 0 R >> >> /Type /Page >> +endobj +4 0 obj +<< /Filter /FlateDecode /Length 64 >> +stream +*8FTbp~0(Ѣ#'őדp;*ZBjHU[gendstream +endobj +5 0 obj +<< /Length 80 /Filter /FlateDecode >> +stream +*8FTbp~0(Ѣ#'őד́i4bzKST$ EzaI<@,w6edendstream +endobj +6 0 obj +<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >> +endobj +7 0 obj +<< /CF << /StdCF << /AuthEvent /DocOpen /CFM /AESV3 /Length 32 >> >> /Filter /Standard /Length 256 /O <b45814c26b7159b191abe2237afbcf1c0448ac23339f0916f754e3c99e4c67a3296960be084c567f03357be63fc0b335> /OE <9423f87d42392b07fc90b6a2329545a1c877ecec680adc8cbc80a5ad5c3abb6c> /P -4 /Perms <84770c5fdc078585b95e8592bb0b38a3> /R 6 /StmF /StdCF /StrF /StdCF /U <4165270c9c8795068aba2bae6f89673992c6ed0e0c2d2bfca6189293a5ba3c4817f0c7a4eb476c53ac29382cea765534> /UE <7991ebbe79a40d5dfb1a1bc87394a81dbefc6ab9a1b19ee7845099ed6e7de14b> /V 5 >> +endobj +xref +0 8 +0000000000 65535 f +0000000015 00000 n +0000000064 00000 n +0000000123 00000 n +0000000261 00000 n +0000000395 00000 n +0000000545 00000 n +0000000642 00000 n +trailer << /Root 1 0 R /Size 8 /ID [<42841c13bbf709d79a200fa1691836f8><31415926535897932384626433832795>] /Encrypt 7 0 R >> +startxref +1189 +%%EOF diff --git a/compare-for-test/qtest/compare/enc2.pdf b/compare-for-test/qtest/compare/enc2.pdf new file mode 100644 index 00000000..5a025491 --- /dev/null +++ b/compare-for-test/qtest/compare/enc2.pdf @@ -0,0 +1,41 @@ +%PDF-2.0 +% +1 0 obj +<< /Pages 2 0 R /Type /Catalog >> +endobj +2 0 obj +<< /Count 1 /Kids [ 3 0 R ] /Type /Pages >> +endobj +3 0 obj +<< /Contents [ 4 0 R 5 0 R ] /MediaBox [ 0 0 612 792 ] /Parent 2 0 R /Resources << /Font << /F1 6 0 R >> >> /Type /Page >> +endobj +4 0 obj +<< /Filter /FlateDecode /Length 80 >> +stream +*8FTbp~_7a7ҧ'\}??OsvZ<NȋD$nN}bendstream +endobj +5 0 obj +<< /Length 80 /Filter /FlateDecode >> +stream +*8FTbp~_7a7ҧ'\ߥR1"'GRrЭHY_&ˢ2
ߴs<HХ: endstream +endobj +6 0 obj +<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >> +endobj +7 0 obj +<< /CF << /StdCF << /AuthEvent /DocOpen /CFM /AESV3 /Length 32 >> >> /Filter /Standard /Length 256 /O <08cc676b1f1cc805ee97abf33aab0f77cb195093c52b65ebf04b1dce93531d8d11b6cd60da17599e4d3679513b957140> /OE <f0a631fa9024e58c72ec7a899aa137b5da0ec491849e433c7dca87a614a045e6> /P -4 /Perms <973bd88c774165b5e58f722b3ced7bf4> /R 6 /StmF /StdCF /StrF /StdCF /U <8203fcce3446c8747d515ac3368fb817e0b7a290e1298d2a0246cd3b559d4544aebba6df7a97f0c8e74f98638f658468> /UE <689a534cf6e2ea26b9a5f9073ccfcf268700cc129779a5d1bbabc9eae77c72f0> /V 5 >> +endobj +xref +0 8 +0000000000 65535 f +0000000015 00000 n +0000000064 00000 n +0000000123 00000 n +0000000261 00000 n +0000000411 00000 n +0000000561 00000 n +0000000658 00000 n +trailer << /Root 1 0 R /Size 8 /ID [<42841c13bbf709d79a200fa1691836f8><31415926535897932384626433832795>] /Encrypt 7 0 R >> +startxref +1205 +%%EOF diff --git a/compare-for-test/qtest/compare/ostream1.pdf b/compare-for-test/qtest/compare/ostream1.pdf Binary files differnew file mode 100644 index 00000000..b340ae33 --- /dev/null +++ b/compare-for-test/qtest/compare/ostream1.pdf diff --git a/compare-for-test/qtest/compare/ostream2.pdf b/compare-for-test/qtest/compare/ostream2.pdf Binary files differnew file mode 100644 index 00000000..27d6b2c2 --- /dev/null +++ b/compare-for-test/qtest/compare/ostream2.pdf diff --git a/compare-for-test/qtest/compare/start.pdf b/compare-for-test/qtest/compare/start.pdf new file mode 100644 index 00000000..79001f24 --- /dev/null +++ b/compare-for-test/qtest/compare/start.pdf @@ -0,0 +1,47 @@ +%PDF-2.0 +% +1 0 obj +<< /Pages 2 0 R /Type /Catalog >> +endobj +2 0 obj +<< /Count 1 /Kids [ 3 0 R ] /Type /Pages >> +endobj +3 0 obj +<< /Contents [ 4 0 R 5 0 R ] /MediaBox [ 0 0 612 792 ] /Parent 2 0 R /Resources << /Font << /F1 6 0 R >> >> /Type /Page >> +endobj +4 0 obj +<< /Length 48 /Filter /FlateDecode >> +stream +BT + /F1 24 Tf + 72 720 Td + (WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW) Tj +ET +endstream +endobj +5 0 obj +<< /Length 43 >> +stream +BT + /F1 24 Tf + 72 681 Td + (Potato) Tj +ET +endstream +endobj +6 0 obj +<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >> +endobj +xref +0 7 +0000000000 65535 f +0000000015 00000 n +0000000064 00000 n +0000000123 00000 n +0000000261 00000 n +0000000379 00000 n +0000000471 00000 n +trailer << /Root 1 0 R /Size 7 /ID [<42841c13bbf709d79a200fa1691836f8><31415926535897932384626433832795>] >> +startxref +568 +%%EOF diff --git a/compare-for-test/qtest/compare/zlib-9.pdf b/compare-for-test/qtest/compare/zlib-9.pdf Binary files differnew file mode 100644 index 00000000..16187f31 --- /dev/null +++ b/compare-for-test/qtest/compare/zlib-9.pdf diff --git a/compare-for-test/qtest/compare/zlib-new-id.pdf b/compare-for-test/qtest/compare/zlib-new-id.pdf Binary files differnew file mode 100644 index 00000000..8618f369 --- /dev/null +++ b/compare-for-test/qtest/compare/zlib-new-id.pdf diff --git a/compare-for-test/qtest/compare/zlib-new-id1.pdf b/compare-for-test/qtest/compare/zlib-new-id1.pdf Binary files differnew file mode 100644 index 00000000..5dd7a823 --- /dev/null +++ b/compare-for-test/qtest/compare/zlib-new-id1.pdf diff --git a/compare-for-test/qtest/compare/zlib-new-id2.pdf b/compare-for-test/qtest/compare/zlib-new-id2.pdf Binary files differnew file mode 100644 index 00000000..1dd13b89 --- /dev/null +++ b/compare-for-test/qtest/compare/zlib-new-id2.pdf diff --git a/compare-for-test/qtest/compare/zlib-ng.pdf b/compare-for-test/qtest/compare/zlib-ng.pdf Binary files differnew file mode 100644 index 00000000..9d8c4329 --- /dev/null +++ b/compare-for-test/qtest/compare/zlib-ng.pdf diff --git a/compare-for-test/qtest/compare/zlib.pdf b/compare-for-test/qtest/compare/zlib.pdf Binary files differnew file mode 100644 index 00000000..9a24beb4 --- /dev/null +++ b/compare-for-test/qtest/compare/zlib.pdf diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 9af85fe2..6c1ca0f3 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -34,12 +34,18 @@ foreach(PROG ${EXAMPLE_C_PROGRAMS}) endforeach() target_include_directories(pdf-create PRIVATE ${JPEG_INCLUDE}) +# extend-c-api contains a mixture of C and C++ files. +add_executable(extend-c-api extend-c-api-impl.cc extend-c-api.c) +set_property(TARGET extend-c-api PROPERTY LINKER_LANGUAGE CXX) +target_link_libraries(extend-c-api libqpdf) + add_test( NAME examples COMMAND ${RUN_QTEST} --top ${qpdf_SOURCE_DIR} --bin $<TARGET_FILE_DIR:pdf-create> --bin $<TARGET_FILE_DIR:qpdf> + --bin $<TARGET_FILE_DIR:qpdf-test-compare> --bin $<TARGET_FILE_DIR:libqpdf> # for Windows to find DLL --code ${qpdf_SOURCE_DIR}/examples --color ${QTEST_COLOR} @@ -47,7 +53,7 @@ add_test( --tc "${qpdf_SOURCE_DIR}/examples/*.cc" --tc "${qpdf_SOURCE_DIR}/examples/*.c") -file(GLOB EXAMPLES_SRC "*.c" "*.cc") +file(GLOB EXAMPLES_SRC "*.c" "*.cc" "*.h") if(INSTALL_EXAMPLES) install(FILES ${EXAMPLES_SRC} DESTINATION ${CMAKE_INSTALL_DOCDIR}/examples diff --git a/examples/extend-c-api-impl.cc b/examples/extend-c-api-impl.cc new file mode 100644 index 00000000..680d957b --- /dev/null +++ b/examples/extend-c-api-impl.cc @@ -0,0 +1,29 @@ +// This is an example of how to write C++ functions and make them usable with the qpdf C API. It +// consists of three files: +// - extend-c-api.h -- a plain C header file +// - extend-c-api.c -- a C program that calls the function +// - extend-c-api.cc -- a C++ file that implements the function + +#include "extend-c-api.h" + +// Here, we add a function to get the number of pages in a PDF file and make it callable through the +// C API. + +// This is a normal C++ function that works with QPDF in a normal way. It doesn't do anything +// special to be callable from C. +int +numPages(std::shared_ptr<QPDF> qpdf) +{ + return qpdf->getRoot().getKey("/Pages").getKey("/Count").getIntValueAsInt(); +} + +// Now we define the glue that makes our function callable using the C API. + +// This is the C++ implementation of the C function. +QPDF_ERROR_CODE +num_pages(qpdf_data qc, int* npages) +{ + // Call qpdf_c_wrap to convert any exception our function might through to a QPDF_ERROR_CODE + // and attach it to the qpdf_data object in the same way as other functions in the C API. + return qpdf_c_wrap(qc, [&qc, &npages]() { *npages = numPages(qpdf_c_get_qpdf(qc)); }); +} diff --git a/examples/extend-c-api.c b/examples/extend-c-api.c new file mode 100644 index 00000000..aa603845 --- /dev/null +++ b/examples/extend-c-api.c @@ -0,0 +1,67 @@ +/* + * This is an example of how to write C++ functions and make them usable with the qpdf C API. It + * consists of three files: + * - extend-c-api.h -- a plain C header file + * - extend-c-api.c -- a C program that calls the function + * - extend-c-api.cc -- a C++ file that implements the function + */ + +#include "extend-c-api.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static char const* whoami = 0; + +static void +usage() +{ + fprintf(stderr, "Usage: %s infile\n", whoami); + exit(2); +} + +int +main(int argc, char* argv[]) +{ + char* infile = NULL; + qpdf_data qpdf = qpdf_init(); + int warnings = 0; + int errors = 0; + char* p = NULL; + + if ((p = strrchr(argv[0], '/')) != NULL) { + whoami = p + 1; + } else if ((p = strrchr(argv[0], '\\')) != NULL) { + whoami = p + 1; + } else { + whoami = argv[0]; + } + + if (argc != 2) { + usage(); + } + + infile = argv[1]; + + if ((qpdf_read(qpdf, infile, NULL) & QPDF_ERRORS) == 0) { + int npages; + if ((num_pages(qpdf, &npages) & QPDF_ERRORS) == 0) { + printf("num pages = %d\n", npages); + } + } + if (qpdf_more_warnings(qpdf)) { + warnings = 1; + } + if (qpdf_has_error(qpdf)) { + errors = 1; + printf("error: %s\n", qpdf_get_error_full_text(qpdf, qpdf_get_error(qpdf))); + } + qpdf_cleanup(&qpdf); + if (errors) { + return 2; + } else if (warnings) { + return 3; + } + + return 0; +} diff --git a/examples/extend-c-api.h b/examples/extend-c-api.h new file mode 100644 index 00000000..3b2d12d4 --- /dev/null +++ b/examples/extend-c-api.h @@ -0,0 +1,25 @@ +#ifndef EXAMPLE_C_EXTEND_H +#define EXAMPLE_C_EXTEND_H + +/* + * This is an example of how to write C++ functions and make them usable with the qpdf C API. It + * consists of three files: + * - extend-c-api.h -- a plain C header file + * - extend-c-api.c -- a C program that calls the function + * - extend-c-api.cc -- a C++ file that implements the function + */ +#include <qpdf/qpdf-c.h> + +/* Declare your custom function to return QPDF_ERROR_CODE and take qpdf_data and anything else you + * need. Any errors are retrievable through the qpdf C APIs normal error-handling mechanism. + */ + +#ifdef __cplusplus +extern "C" { +#endif + QPDF_ERROR_CODE num_pages(qpdf_data qc, int* npages); +#ifdef __cplusplus +} +#endif + +#endif /* EXAMPLE_C_EXTEND_H */ diff --git a/examples/qpdf-job.cc b/examples/qpdf-job.cc index be868a17..99b853ea 100644 --- a/examples/qpdf-job.cc +++ b/examples/qpdf-job.cc @@ -44,6 +44,7 @@ main(int argc, char* argv[]) ->endPages() ->linearize() ->staticId() // for testing only + ->compressStreams("n") // avoid dependency on zlib output ->checkConfiguration(); j.run(); std::cout << "out1 status: " << j.getExitCode() << std::endl; @@ -63,6 +64,7 @@ main(int argc, char* argv[]) "1", "--", "--static-id", + "--compress-streams=n", // avoid dependency on zlib output nullptr}; QPDFJob j; j.initializeFromArgv(new_argv); @@ -81,6 +83,7 @@ main(int argc, char* argv[]) "outputFile": "out3.pdf", "staticId": "", "linearize": "", + "compressStreams": "n", "pages": [ { "file": ".", diff --git a/examples/qpdfjob-c.c b/examples/qpdfjob-c.c index 62528392..6dd68283 100644 --- a/examples/qpdfjob-c.c +++ b/examples/qpdfjob-c.c @@ -19,7 +19,7 @@ main(int argc, char* argv[]) { char* infile = NULL; char* outfile = NULL; - char const* new_argv[6]; + char const* new_argv[7]; int r = 0; char* p = 0; @@ -43,7 +43,8 @@ main(int argc, char* argv[]) new_argv[2] = outfile; new_argv[3] = "--linearize"; new_argv[4] = "--static-id"; /* for testing only */ - new_argv[5] = NULL; + new_argv[5] = "--compress-streams=n"; /* avoid dependency on zlib output */ + new_argv[6] = NULL; /* See qpdf-job.cc for a C++ example of using the json interface. To use that from C just like * the argv one, call qpdfjob_run_from_json instead and pass the json string as a single char diff --git a/examples/qtest/c-objects.test b/examples/qtest/c-objects.test index 9cd496f6..f10f60c7 100644 --- a/examples/qtest/c-objects.test +++ b/examples/qtest/c-objects.test @@ -17,8 +17,8 @@ foreach my $i (qw(1 2)) {$td->COMMAND => "pdf-c-objects $i.pdf '' a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "$i-out.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf $i-out.pdf"}, + {$td->FILE => "$i-out.pdf", $td->EXIT_STATUS => 0}); } cleanup(); diff --git a/examples/qtest/custom-filter.test b/examples/qtest/custom-filter.test index e674ea66..2d8acec6 100644 --- a/examples/qtest/custom-filter.test +++ b/examples/qtest/custom-filter.test @@ -24,8 +24,8 @@ $td->runtest("custom filter, decode generalized", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "generalized.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf generalized.pdf"}, + {$td->FILE => "generalized.pdf", $td->EXIT_STATUS => 0}); $td->runtest("custom filter, decode specialized", {$td->COMMAND => @@ -34,8 +34,8 @@ $td->runtest("custom filter, decode specialized", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "specialized.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf specialized.pdf"}, + {$td->FILE => "specialized.pdf", $td->EXIT_STATUS => 0}); cleanup(); diff --git a/examples/qtest/extend-c-api.test b/examples/qtest/extend-c-api.test new file mode 100644 index 00000000..3dd82193 --- /dev/null +++ b/examples/qtest/extend-c-api.test @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +require 5.008; +use warnings; +use strict; + +chdir("extend-c-api") or die "chdir testdir failed: $!\n"; + +require TestDriver; + +cleanup(); + +my $td = new TestDriver('extend-c-api'); + +$td->runtest("extend C API (good)", + {$td->COMMAND => "extend-c-api good.pdf"}, + {$td->FILE => "good.out", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +$td->runtest("extend C API (bad)", + {$td->COMMAND => "extend-c-api bad.pdf"}, + {$td->FILE => "bad.out", $td->EXIT_STATUS => 2}, + $td->NORMALIZE_NEWLINES); + +cleanup(); + +$td->report(2); + +sub cleanup +{ + unlink "a.pdf"; +} diff --git a/examples/qtest/extend-c-api/bad.out b/examples/qtest/extend-c-api/bad.out new file mode 100644 index 00000000..51f1d56b --- /dev/null +++ b/examples/qtest/extend-c-api/bad.out @@ -0,0 +1,5 @@ +WARNING: bad.pdf: can't find PDF header +WARNING: bad.pdf: file is damaged +WARNING: bad.pdf: can't find startxref +WARNING: bad.pdf: Attempting to reconstruct cross-reference table +error: bad.pdf: unable to find trailer dictionary while recovering damaged file diff --git a/examples/qtest/extend-c-api/bad.pdf b/examples/qtest/extend-c-api/bad.pdf new file mode 100644 index 00000000..a3d2d925 --- /dev/null +++ b/examples/qtest/extend-c-api/bad.pdf @@ -0,0 +1 @@ +not even a pdf file diff --git a/examples/qtest/extend-c-api/good.out b/examples/qtest/extend-c-api/good.out new file mode 100644 index 00000000..98205fae --- /dev/null +++ b/examples/qtest/extend-c-api/good.out @@ -0,0 +1 @@ +num pages = 1 diff --git a/examples/qtest/extend-c-api/good.pdf b/examples/qtest/extend-c-api/good.pdf new file mode 100644 index 00000000..42867b96 --- /dev/null +++ b/examples/qtest/extend-c-api/good.pdf @@ -0,0 +1,64 @@ +%PDF-2.0 +1 0 obj +<< + /Pages 2 0 R + /Type /Catalog +>> +endobj +2 0 obj +<< + /Count 1 + /Kids [ + 3 0 R + ] + /Type /Pages +>> +endobj +3 0 obj +<< + /Contents 4 0 R + /MediaBox [ 0 0 612 792 ] + /Parent 2 0 R + /Resources << + /Font << /F1 5 0 R >> + >> + /Type /Page +>> +endobj +4 0 obj +<< + /Length 44 +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato) Tj +ET +endstream +endobj +5 0 obj +<< + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding + /Subtype /Type1 + /Type /Font +>> +endobj + +xref +0 6 +0000000000 65535 f +0000000009 00000 n +0000000062 00000 n +0000000133 00000 n +0000000277 00000 n +0000000372 00000 n +trailer << + /Root 1 0 R + /Size 6 + /ID [<42841c13bbf709d79a200fa1691836f8><b1d8b5838eeafe16125317aa78e666aa>] +>> +startxref +478 +%%EOF diff --git a/examples/qtest/invert-images.test b/examples/qtest/invert-images.test index d1e3c939..7986d0fd 100644 --- a/examples/qtest/invert-images.test +++ b/examples/qtest/invert-images.test @@ -20,8 +20,8 @@ $td->runtest("invert images", $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "out.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf out.pdf"}, + {$td->FILE => "out.pdf", $td->EXIT_STATUS => 0}); cleanup(); diff --git a/examples/qtest/overlay-page.test b/examples/qtest/overlay-page.test index be9bcb10..6440a367 100644 --- a/examples/qtest/overlay-page.test +++ b/examples/qtest/overlay-page.test @@ -15,15 +15,15 @@ $td->runtest("overlay-page", {$td->COMMAND => "pdf-overlay-page in.pdf stamp.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("compare files", - {$td->FILE => "a.pdf"}, - {$td->FILE => "out.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf out.pdf"}, + {$td->FILE => "out.pdf", $td->EXIT_STATUS => 0}); $td->runtest("overlay-page with fields/ annotations", {$td->COMMAND => "pdf-overlay-page in.pdf annotations.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("compare files", - {$td->FILE => "a.pdf"}, - {$td->FILE => "annotations-out.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf annotations-out.pdf"}, + {$td->FILE => "annotations-out.pdf", $td->EXIT_STATUS => 0}); cleanup(); diff --git a/examples/qtest/qpdf-job/out.pdf b/examples/qtest/qpdf-job/out.pdf Binary files differindex c432ac59..d8ac1509 100644 --- a/examples/qtest/qpdf-job/out.pdf +++ b/examples/qtest/qpdf-job/out.pdf diff --git a/examples/qtest/qpdfjob-remove-annotations.test b/examples/qtest/qpdfjob-remove-annotations.test index 32abf3ef..06f8f07a 100644 --- a/examples/qtest/qpdfjob-remove-annotations.test +++ b/examples/qtest/qpdfjob-remove-annotations.test @@ -15,8 +15,8 @@ $td->runtest("remove-annotations", {$td->COMMAND => "qpdfjob-remove-annotations --static-id annotations.pdf out.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("compare files", - {$td->FILE => "out.pdf"}, - {$td->FILE => "annotations-out.pdf"}); + {$td->COMMAND => "qpdf-test-compare out.pdf annotations-out.pdf"}, + {$td->FILE => "annotations-out.pdf", $td->EXIT_STATUS => 0}); cleanup(); diff --git a/examples/qtest/set-form-values.test b/examples/qtest/set-form-values.test index 8c103379..8d2fd2c8 100644 --- a/examples/qtest/set-form-values.test +++ b/examples/qtest/set-form-values.test @@ -15,8 +15,8 @@ $td->runtest("set form values", {$td->COMMAND => "pdf-set-form-values form-in.pdf a.pdf soup"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("compare files", - {$td->FILE => "a.pdf"}, - {$td->FILE => "form-out.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf form-out.pdf"}, + {$td->FILE => "form-out.pdf", $td->EXIT_STATUS => 0}); cleanup(); diff --git a/include/qpdf/Buffer.hh b/include/qpdf/Buffer.hh index c0669c6c..d0e6eda0 100644 --- a/include/qpdf/Buffer.hh +++ b/include/qpdf/Buffer.hh @@ -24,6 +24,7 @@ #include <cstddef> #include <memory> +#include <string> class Buffer { @@ -35,11 +36,15 @@ class Buffer // object is destroyed. QPDF_DLL Buffer(size_t size); + QPDF_DLL + Buffer(std::string&& content); // Create a Buffer object whose memory is owned by the caller and will not be freed when the // Buffer is destroyed. QPDF_DLL Buffer(unsigned char* buf, size_t size); + QPDF_DLL + Buffer(std::string& content); [[deprecated("Move Buffer or use Buffer::copy instead")]] QPDF_DLL Buffer(Buffer const&); [[deprecated("Move Buffer or use Buffer::copy instead")]] QPDF_DLL Buffer& @@ -75,8 +80,10 @@ class Buffer private: Members(size_t size, unsigned char* buf, bool own_memory); + Members(std::string&& content); Members(Members const&) = delete; + std::string str; bool own_memory; size_t size; unsigned char* buf; diff --git a/include/qpdf/DLL.h b/include/qpdf/DLL.h index 74c3c62e..8279da03 100644 --- a/include/qpdf/DLL.h +++ b/include/qpdf/DLL.h @@ -25,9 +25,14 @@ /* The first version of qpdf to include the version constants is 10.6.0. */ #define QPDF_MAJOR_VERSION 11 -#define QPDF_MINOR_VERSION 6 -#define QPDF_PATCH_VERSION 3 -#define QPDF_VERSION "11.6.3" +#define QPDF_MINOR_VERSION 7 +#define QPDF_PATCH_VERSION 0 + +#ifdef QPDF_FUTURE +# define QPDF_VERSION "11.7.0+future" +#else +# define QPDF_VERSION "11.7.0" +#endif /* * This file defines symbols that control the which functions, diff --git a/include/qpdf/Pl_Buffer.hh b/include/qpdf/Pl_Buffer.hh index 5030e10e..5def8b4f 100644 --- a/include/qpdf/Pl_Buffer.hh +++ b/include/qpdf/Pl_Buffer.hh @@ -33,7 +33,7 @@ #include <qpdf/PointerHolder.hh> // unused -- remove in qpdf 12 (see #785) #include <memory> -#include <vector> +#include <string> class QPDF_DLL_CLASS Pl_Buffer: public Pipeline { @@ -64,6 +64,10 @@ class QPDF_DLL_CLASS Pl_Buffer: public Pipeline QPDF_DLL void getMallocBuffer(unsigned char** buf, size_t* len); + // Same as getBuffer but returns the result as a string. + QPDF_DLL + std::string getString(); + private: class QPDF_DLL_PRIVATE Members { @@ -78,7 +82,7 @@ class QPDF_DLL_CLASS Pl_Buffer: public Pipeline Members(Members const&) = delete; bool ready{true}; - std::vector<unsigned char> data; + std::string data; }; std::shared_ptr<Members> m; diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh index 2ca6005c..392a153a 100644 --- a/include/qpdf/QPDF.hh +++ b/include/qpdf/QPDF.hh @@ -745,9 +745,10 @@ class QPDF std::map<int, int> const& obj_renumber, std::shared_ptr<Buffer>& hint_stream, int& S, - int& O) + int& O, + bool compressed) { - return qpdf.generateHintStream(xref, lengths, obj_renumber, hint_stream, S, O); + return qpdf.generateHintStream(xref, lengths, obj_renumber, hint_stream, S, O, compressed); } static void @@ -1094,7 +1095,8 @@ class QPDF std::map<int, int> const& obj_renumber, std::shared_ptr<Buffer>& hint_stream, int& S, - int& O); + int& O, + bool compressed); // Map object to object stream that contains it void getObjectStreamData(std::map<int, int>&); @@ -1133,7 +1135,7 @@ class QPDF Pipeline*& pipeline, QPDFObjGen const& og, QPDFObjectHandle& stream_dict, - std::vector<std::shared_ptr<Pipeline>>& heap); + std::unique_ptr<Pipeline>& heap); // Methods to support object copying void reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier, bool top); diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh index ca135125..270c28bb 100644 --- a/include/qpdf/QPDFObjectHandle.hh +++ b/include/qpdf/QPDFObjectHandle.hh @@ -1178,13 +1178,13 @@ class QPDFObjectHandle // looks like a name or indirect object from an actual name or indirect object. // * JSON v2: // * Unicode strings and strings encoded with PDF Doc encoding that can be bidirectionally - // mapped two Unicode (which is all strings without undefined characters) are represented + // mapped to Unicode (which is all strings without undefined characters) are represented // as "u:" followed by the UTF-8 encoded string. Example: // "u:potato". // * All other strings are represented as "b:" followed by a hexadecimal encoding of the // string. Example: "b:0102cacb" // * Streams - // * JSON v1: Only the stream's dictionary is encoded. There is no way tell a stream from a + // * JSON v1: Only the stream's dictionary is encoded. There is no way to tell a stream from a // dictionary other than context. // * JSON v2: A stream is encoded as {"dict": {...}} with the value being the encoding of the // stream's dictionary. Since "dict" does not otherwise represent anything, this is diff --git a/include/qpdf/qpdf-c.h b/include/qpdf/qpdf-c.h index d4305602..f989ba15 100644 --- a/include/qpdf/qpdf-c.h +++ b/include/qpdf/qpdf-c.h @@ -21,21 +21,23 @@ #define QPDF_C_H /* - * This file defines a basic "C" API for qpdf. It provides access to a subset of the QPDF library's + * This file defines a basic "C" API for qpdf. It provides access to a subset of the QPDF library's * capabilities to make them accessible to callers who can't handle calling C++ functions or working - * with C++ classes. This may be especially useful to Windows users who are accessing the qpdf DLL + * with C++ classes. This may be especially useful to Windows users who are accessing the qpdf DLL * directly or to other people programming in non-C/C++ languages that can call C code but not C++ - * code. + * code. Starting with qpdf 11.7, it is possible to write your own `extern "C"` functions that + * interoperate with the C API. * * There are several things to keep in mind when using the C API. * * Error handling is tricky because the underlying C++ API uses exception handling. See "ERROR * HANDLING" below for a detailed explanation. * - * The C API is not as rich as the C++ API. For any operations that involve actually - * manipulating PDF objects, you must use the C++ API. The C API is primarily useful for doing - * basic transformations on PDF files similar to what you might do with the qpdf command-line - * tool. + * The C API is not as rich as the C++ API. For many operations, you must use the C++ API. The C + * API is primarily useful for doing basic transformations on PDF files similar to what you + * might do with the qpdf command-line tool. You can write your own `extern "C"` functions in + * C++ that interoperate with the C API by using qpdf_c_get_qpdf and qpdf_c_wrap which were + * introduced in qpdf 11.7.0. * * These functions store their state in a qpdf_data object. Individual instances of qpdf_data * are not thread-safe: although you may access different qpdf_data objects from different @@ -990,6 +992,23 @@ extern "C" { QPDF_ERROR_CODE qpdf_remove_page(qpdf_data qpdf, qpdf_oh page); #ifdef __cplusplus } + +// These C++ functions make it easier to write C++ code that interoperates with the C API. +// See examples/extend-c-api. + +# include <functional> +# include <memory> + +# include <qpdf/QPDF.hh> + +// Retrieve the real QPDF object attached to this qpdf_data. +QPDF_DLL +std::shared_ptr<QPDF> qpdf_c_get_qpdf(qpdf_data qpdf); + +// Wrap a C++ function that may throw an exception to translate the exception for retrieval using +// the normal QPDF C API methods. +QPDF_DLL +QPDF_ERROR_CODE qpdf_c_wrap(qpdf_data qpdf, std::function<void()> fn); #endif #endif /* QPDF_C_H */ @@ -14,4 +14,4 @@ libqpdf/qpdf/auto_job_json_decl.hh 06caa46eaf71db8a50c046f91866baa8087745a947431 libqpdf/qpdf/auto_job_json_init.hh 85ac7e5c66f14c767419823eac84bdea4bd72d690bfe12b533321e5708e644b7 libqpdf/qpdf/auto_job_schema.hh 5e0f5cb7d462716fe52548b2ae1a8eb6f3c900016e915140eea37f78cee45b2b manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580 -manual/cli.rst 35d32800cee1871e74c85419c481cae1c544c8b0eb462df82cf373a32619ab73 +manual/cli.rst 4f2806f7cf77f167fd41b065a8916f02fdfaab35ad1a74587578bf2954228464 diff --git a/libqpdf/Buffer.cc b/libqpdf/Buffer.cc index 3dddd5db..20453a40 100644 --- a/libqpdf/Buffer.cc +++ b/libqpdf/Buffer.cc @@ -27,6 +27,14 @@ Buffer::Members::Members(size_t size, unsigned char* buf, bool own_memory) : } } +Buffer::Members::Members(std::string&& content) : + str(std::move(content)), + own_memory(false), + size(str.size()), + buf(reinterpret_cast<unsigned char*>(str.data())) +{ +} + Buffer::Members::~Members() { if (this->own_memory) { @@ -44,11 +52,21 @@ Buffer::Buffer(size_t size) : { } +Buffer::Buffer(std::string&& content) : + m(new Members(std::move(content))) +{ +} + Buffer::Buffer(unsigned char* buf, size_t size) : m(new Members(size, buf, false)) { } +Buffer::Buffer(std::string& content) : + m(new Members(content.size(), reinterpret_cast<unsigned char*>(content.data()), false)) +{ +} + Buffer::Buffer(Buffer const& rhs) { assert(test_mode); diff --git a/libqpdf/CMakeLists.txt b/libqpdf/CMakeLists.txt index c6a122c6..eb30b622 100644 --- a/libqpdf/CMakeLists.txt +++ b/libqpdf/CMakeLists.txt @@ -590,10 +590,12 @@ if(INSTALL_CMAKE_PACKAGE) install(EXPORT libqpdfTargets NAMESPACE qpdf:: FILE libqpdfTargets.cmake + COMPONENT ${COMPONENT_DEV} DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/qpdf) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qpdfConfigVersion.cmake ${CMAKE_CURRENT_BINARY_DIR}/qpdfConfig.cmake + COMPONENT ${COMPONENT_DEV} DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/qpdf) endif() diff --git a/libqpdf/Pl_Buffer.cc b/libqpdf/Pl_Buffer.cc index 766c04b5..9994bedd 100644 --- a/libqpdf/Pl_Buffer.cc +++ b/libqpdf/Pl_Buffer.cc @@ -19,7 +19,7 @@ Pl_Buffer::~Pl_Buffer() // NOLINT (modernize-use-equals-default) void Pl_Buffer::write(unsigned char const* buf, size_t len) { - m->data.insert(m->data.end(), buf, buf + len); + m->data.append(reinterpret_cast<char const*>(buf), len); m->ready = false; if (getNext(true)) { @@ -42,15 +42,20 @@ Pl_Buffer::getBuffer() if (!m->ready) { throw std::logic_error("Pl_Buffer::getBuffer() called when not ready"); } + auto* b = new Buffer(std::move(m->data)); + m->data.clear(); + return b; +} - auto size = m->data.size(); - auto* b = new Buffer(size); - if (size > 0) { - unsigned char* p = b->getBuffer(); - memcpy(p, m->data.data(), size); +std::string +Pl_Buffer::getString() +{ + if (!m->ready) { + throw std::logic_error("Pl_Buffer::getString() called when not ready"); } + auto s = std::move(m->data); m->data.clear(); - return b; + return s; } std::shared_ptr<Buffer> diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index 473cf5f0..f6badc35 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -2413,29 +2413,23 @@ QPDF::pipeStreamData( bool suppress_warnings, bool will_retry) { - std::vector<std::shared_ptr<Pipeline>> to_delete; + std::unique_ptr<Pipeline> to_delete; if (encp->encrypted) { decryptStream(encp, file, qpdf_for_warning, pipeline, og, stream_dict, to_delete); } bool attempted_finish = false; - bool success = false; try { file->seek(offset, SEEK_SET); - char buf[10240]; - while (length > 0) { - size_t to_read = (sizeof(buf) < length ? sizeof(buf) : length); - size_t len = file->read(buf, to_read); - if (len == 0) { - throw damagedPDF( - file, "", file->getLastOffset(), "unexpected EOF reading stream data"); - } - length -= len; - pipeline->write(buf, len); + auto buf = std::make_unique<char[]>(length); + if (auto read = file->read(buf.get(), length); read != length) { + throw damagedPDF( + file, "", offset + toO(read), "unexpected EOF reading stream data"); } + pipeline->write(buf.get(), length); attempted_finish = true; pipeline->finish(); - success = true; + return true; } catch (QPDFExc& e) { if (!suppress_warnings) { qpdf_for_warning.warn(e); @@ -2458,8 +2452,7 @@ QPDF::pipeStreamData( file, "", file->getLastOffset(), - "stream will be re-processed without" - " filtering to avoid data loss")); + "stream will be re-processed without filtering to avoid data loss")); } } } @@ -2470,7 +2463,7 @@ QPDF::pipeStreamData( // ignore } } - return success; + return false ; } bool diff --git a/libqpdf/QPDFAcroFormDocumentHelper.cc b/libqpdf/QPDFAcroFormDocumentHelper.cc index 5a54bf75..f2daa0c7 100644 --- a/libqpdf/QPDFAcroFormDocumentHelper.cc +++ b/libqpdf/QPDFAcroFormDocumentHelper.cc @@ -584,8 +584,7 @@ QPDFAcroFormDocumentHelper::adjustDefaultAppearances( ResourceReplacer rr(dr_map, rf.getNamesByResourceType()); Pl_Buffer buf_pl("filtered DA"); da_stream.filterAsContents(&rr, &buf_pl); - auto buf = buf_pl.getBufferSharedPointer(); - std::string new_da(reinterpret_cast<char*>(buf->getBuffer()), buf->getSize()); + std::string new_da = buf_pl.getString(); obj.replaceKey("/DA", QPDFObjectHandle::newString(new_da)); } diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index d5ac137a..d543f98e 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -1708,8 +1708,7 @@ QPDFObjectHandle::pipeContentStreams( need_newline = (lc.getLastChar() != static_cast<unsigned char>('\n')); QTC::TC("qpdf", "QPDFObjectHandle need_newline", need_newline ? 0 : 1); } - std::unique_ptr<Buffer> b(buf.getBuffer()); - p->write(b->getBuffer(), b->getSize()); + p->writeString(buf.getString()); p->finish(); } diff --git a/libqpdf/QPDFPageObjectHelper.cc b/libqpdf/QPDFPageObjectHelper.cc index fd6e5215..54bb5cac 100644 --- a/libqpdf/QPDFPageObjectHelper.cc +++ b/libqpdf/QPDFPageObjectHelper.cc @@ -175,14 +175,11 @@ InlineImageTracker::handleToken(QPDFTokenizer::Token const& token) size_t len = image_data.length(); if (len >= this->min_size) { QTC::TC("qpdf", "QPDFPageObjectHelper externalize inline image"); - Pl_Buffer b("image_data"); - b.writeString(image_data); - b.finish(); QPDFObjectHandle dict = convertIIDict(QPDFObjectHandle::parse(dict_str)); dict.replaceKey("/Length", QPDFObjectHandle::newInteger(QIntC::to_longlong(len))); std::string name = resources.getUniqueResourceName("/IIm", this->min_suffix); QPDFObjectHandle image = - QPDFObjectHandle::newStream(this->qpdf, b.getBufferSharedPointer()); + QPDFObjectHandle::newStream(this->qpdf, std::make_shared<Buffer>(std::move(image_data))); image.replaceDict(dict); resources.getKey("/XObject").replaceKey(name, image); write(name); diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index db3b42e0..981fc755 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -1579,10 +1579,7 @@ QPDFWriter::unparseObject( m->cur_data_key.length()); pl.writeString(val); pl.finish(); - auto buf = bufpl.getBufferSharedPointer(); - val = QPDF_String( - std::string(reinterpret_cast<char*>(buf->getBuffer()), buf->getSize())) - .unparse(true); + val = QPDF_String(bufpl.getString()).unparse(true); } else { auto tmp_ph = QUtil::make_unique_cstr(val); char* tmp = tmp_ph.get(); @@ -1661,8 +1658,7 @@ QPDFWriter::writeObjectStream(QPDFObjectHandle object) // Set up a stream to write the stream data into a buffer. Pipeline* next = pushPipeline(new Pl_Buffer("object stream")); - if ((m->compress_streams || (m->stream_decode_level == qpdf_dl_none)) && - (!m->qdf_mode)) { + if (m->compress_streams && !m->qdf_mode) { compressed = true; next = pushPipeline(new Pl_Flate("compress object stream", next, Pl_Flate::a_deflate)); @@ -2293,15 +2289,20 @@ QPDFWriter::writeHintStream(int hint_id) std::shared_ptr<Buffer> hint_buffer; int S = 0; int O = 0; + bool compressed = (m->compress_streams && !m->qdf_mode); QPDF::Writer::generateHintStream( - m->pdf, m->xref, m->lengths, m->obj_renumber_no_gen, hint_buffer, S, O); + m->pdf, m->xref, m->lengths, m->obj_renumber_no_gen, hint_buffer, S, O, compressed); openObject(hint_id); setDataKey(hint_id); size_t hlen = hint_buffer->getSize(); - writeString("<< /Filter /FlateDecode /S "); + writeString("<< "); + if (compressed) { + writeString("/Filter /FlateDecode "); + } + writeString("/S "); writeString(std::to_string(S)); if (O) { writeString(" /O "); @@ -2420,7 +2421,7 @@ QPDFWriter::writeXRefStream( Pipeline* p = pushPipeline(new Pl_Buffer("xref stream")); bool compressed = false; - if ((m->compress_streams || (m->stream_decode_level == qpdf_dl_none)) && (!m->qdf_mode)) { + if (m->compress_streams && !m->qdf_mode) { compressed = true; if (!skip_compression) { // Write the stream dictionary for compression but don't actually compress. This helps diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc index 45d6fb70..a43d91ff 100644 --- a/libqpdf/QPDF_Stream.cc +++ b/libqpdf/QPDF_Stream.cc @@ -216,29 +216,28 @@ QPDF_Stream::getStreamJSON( auto dict = this->stream_dict; JSON result = JSON::makeDictionary(); if (json_data != qpdf_sj_none) { - std::shared_ptr<Buffer> buf; + Pl_Discard discard; + Pl_Buffer buf_pl{"stream data"}; + // buf_pl contains valid data and is ready for retrieval of the data. + bool buf_pl_ready = false; bool filtered = false; bool filter = (decode_level != qpdf_dl_none); for (int attempt = 1; attempt <= 2; ++attempt) { - Pl_Discard discard; - std::shared_ptr<Pl_Buffer> buf_pl; - Pipeline* data_pipeline = nullptr; + Pipeline* data_pipeline = &discard; if (json_data == qpdf_sj_file) { // We need to capture the data to write - buf_pl = std::make_shared<Pl_Buffer>("stream data"); - data_pipeline = buf_pl.get(); - } else { - data_pipeline = &discard; + data_pipeline = &buf_pl; } bool succeeded = pipeStreamData(data_pipeline, &filtered, 0, decode_level, false, (attempt == 1)); - if ((!succeeded) || (filter && (!filtered))) { + if (!succeeded || (filter && !filtered)) { // Try again filter = false; decode_level = qpdf_dl_none; + buf_pl.getString(); // reset buf_pl } else { - if (buf_pl.get()) { - buf = buf_pl->getBufferSharedPointer(); + if (json_data == qpdf_sj_file) { + buf_pl_ready = true; } break; } @@ -252,10 +251,10 @@ QPDF_Stream::getStreamJSON( } if (json_data == qpdf_sj_file) { result.addDictionaryMember("datafile", JSON::makeString(data_filename)); - if (!buf.get()) { + if (!buf_pl_ready) { throw std::logic_error("QPDF_Stream: failed to get stream data in json file mode"); } - p->write(buf->getBuffer(), buf->getSize()); + p->writeString(buf_pl.getString()); } else if (json_data == qpdf_sj_inline) { result.addDictionaryMember( "data", JSON::makeBlob(StreamBlobProvider(this, decode_level))); diff --git a/libqpdf/QPDF_encryption.cc b/libqpdf/QPDF_encryption.cc index b5cc44ee..1dd7b963 100644 --- a/libqpdf/QPDF_encryption.cc +++ b/libqpdf/QPDF_encryption.cc @@ -229,13 +229,11 @@ process_with_aes( aes.writeString(data); } aes.finish(); - auto bufp = buffer.getBufferSharedPointer(); if (outlength == 0) { - outlength = bufp->getSize(); + return buffer.getString(); } else { - outlength = std::min(outlength, bufp->getSize()); + return buffer.getString().substr(0, outlength); } - return {reinterpret_cast<char*>(bufp->getBuffer()), outlength}; } static std::string @@ -1021,8 +1019,7 @@ QPDF::decryptString(std::string& str, QPDFObjGen const& og) key.length()); pl.writeString(str); pl.finish(); - auto buf = bufpl.getBufferSharedPointer(); - str = std::string(reinterpret_cast<char*>(buf->getBuffer()), buf->getSize()); + str = bufpl.getString(); } else { QTC::TC("qpdf", "QPDF_encryption rc4 decode string"); size_t vlen = str.length(); @@ -1041,6 +1038,9 @@ QPDF::decryptString(std::string& str, QPDFObjGen const& og) } } +// Prepend a decryption pipeline to 'pipeline'. The decryption pipeline (returned as +// 'decrypt_pipeline' must be owned by the caller to ensure that it stays alive while the pipeline +// is in use. void QPDF::decryptStream( std::shared_ptr<EncryptionParameters> encp, @@ -1049,7 +1049,7 @@ QPDF::decryptStream( Pipeline*& pipeline, QPDFObjGen const& og, QPDFObjectHandle& stream_dict, - std::vector<std::shared_ptr<Pipeline>>& heap) + std::unique_ptr<Pipeline>& decrypt_pipeline) { std::string type; if (stream_dict.getKey("/Type").isName()) { @@ -1085,8 +1085,7 @@ QPDF::decryptStream( crypt_params.getKey("/Name").isName()) { QTC::TC("qpdf", "QPDF_encrypt crypt array"); method = interpretCF(encp, crypt_params.getKey("/Name")); - method_source = "stream's Crypt " - "decode parameters (array)"; + method_source = "stream's Crypt decode parameters (array)"; } } } @@ -1135,10 +1134,9 @@ QPDF::decryptStream( } } std::string key = getKeyForObject(encp, og, use_aes); - std::shared_ptr<Pipeline> new_pipeline; if (use_aes) { QTC::TC("qpdf", "QPDF_encryption aes decode stream"); - new_pipeline = std::make_shared<Pl_AES_PDF>( + decrypt_pipeline = std::make_unique<Pl_AES_PDF>( "AES stream decryption", pipeline, false, @@ -1146,14 +1144,13 @@ QPDF::decryptStream( key.length()); } else { QTC::TC("qpdf", "QPDF_encryption rc4 decode stream"); - new_pipeline = std::make_shared<Pl_RC4>( + decrypt_pipeline = std::make_unique<Pl_RC4>( "RC4 stream decryption", pipeline, QUtil::unsigned_char_pointer(key), toI(key.length())); } - pipeline = new_pipeline.get(); - heap.push_back(new_pipeline); + pipeline = decrypt_pipeline.get(); } void diff --git a/libqpdf/QPDF_linearization.cc b/libqpdf/QPDF_linearization.cc index 469b9933..1657d54a 100644 --- a/libqpdf/QPDF_linearization.cc +++ b/libqpdf/QPDF_linearization.cc @@ -1748,10 +1748,10 @@ QPDF::writeHSharedObject(BitWriter& w) void QPDF::writeHGeneric(BitWriter& w, HGeneric& t) { - w.writeBitsInt(t.first_object, 32); // 1 - w.writeBits(toULL(t.first_object_offset), 32); // 2 - w.writeBitsInt(t.nobjects, 32); // 3 - w.writeBitsInt(t.group_length, 32); // 4 + w.writeBitsInt(t.first_object, 32); // 1 + w.writeBits(toULL(t.first_object_offset), 32); // 2 + w.writeBitsInt(t.nobjects, 32); // 3 + w.writeBitsInt(t.group_length, 32); // 4 } void @@ -1761,7 +1761,8 @@ QPDF::generateHintStream( std::map<int, int> const& obj_renumber, std::shared_ptr<Buffer>& hint_buffer, int& S, - int& O) + int& O, + bool compressed) { // Populate actual hint table values calculateHPageOffset(xref, lengths, obj_renumber); @@ -1771,8 +1772,14 @@ QPDF::generateHintStream( // Write the hint stream itself into a compressed memory buffer. Write through a counter so we // can get offsets. Pl_Buffer hint_stream("hint stream"); - Pl_Flate f("compress hint stream", &hint_stream, Pl_Flate::a_deflate); - Pl_Count c("count", &f); + Pipeline* next = &hint_stream; + std::shared_ptr<Pipeline> flate; + if (compressed) { + flate = + std::make_shared<Pl_Flate>("compress hint stream", &hint_stream, Pl_Flate::a_deflate); + next = flate.get(); + } + Pl_Count c("count", next); BitWriter w(&c); writeHPageOffset(w); diff --git a/libqpdf/SF_FlateLzwDecode.cc b/libqpdf/SF_FlateLzwDecode.cc index db663429..a291145f 100644 --- a/libqpdf/SF_FlateLzwDecode.cc +++ b/libqpdf/SF_FlateLzwDecode.cc @@ -11,7 +11,7 @@ SF_FlateLzwDecode::SF_FlateLzwDecode(bool lzw) : lzw(lzw), // Initialize values to their defaults as per the PDF spec predictor(1), - columns(0), + columns(1), colors(1), bits_per_component(8), early_code_change(true) diff --git a/libqpdf/qpdf-c.cc b/libqpdf/qpdf-c.cc index 1f7b85cb..b45b564b 100644 --- a/libqpdf/qpdf-c.cc +++ b/libqpdf/qpdf-c.cc @@ -1949,3 +1949,15 @@ qpdf_write_json( }); return status; } + +std::shared_ptr<QPDF> +qpdf_c_get_qpdf(qpdf_data qpdf) +{ + return qpdf->qpdf; +} + +QPDF_ERROR_CODE +qpdf_c_wrap(qpdf_data qpdf, std::function<void()> fn) +{ + return trap_errors(qpdf, [&fn](qpdf_data) { fn(); }); +} diff --git a/libtests/buffer.cc b/libtests/buffer.cc index e62a37ca..1b87bb7d 100644 --- a/libtests/buffer.cc +++ b/libtests/buffer.cc @@ -35,6 +35,21 @@ main() assert(bc2p != bc1p); assert(bc2p[0] == 'R'); assert(bc2p[1] == 'W'); + + // Test Buffer(std:string&&) + Buffer bc3("QW"); + unsigned char* bc3p = bc3.getBuffer(); + Buffer bc4(bc3.copy()); + bc3p[0] = 'R'; + unsigned char* bc4p = bc4.getBuffer(); + assert(bc4p != bc3p); + assert(bc4p[0] == 'Q'); + assert(bc4p[1] == 'W'); + bc4 = bc3.copy(); + bc4p = bc4.getBuffer(); + assert(bc4p != bc3p); + assert(bc4p[0] == 'R'); + assert(bc4p[1] == 'W'); } #ifdef _MSC_VER @@ -62,6 +77,37 @@ main() assert(bc2p != bc1p); assert(bc2p[0] == 'R'); assert(bc2p[1] == 'W'); + + // Test Buffer(std:string&&) + Buffer bc3("QW"); + unsigned char* bc3p = bc3.getBuffer(); + Buffer bc4(bc3); + bc3p[0] = 'R'; + unsigned char* bc4p = bc4.getBuffer(); + assert(bc4p != bc3p); + assert(bc4p[0] == 'Q'); + assert(bc4p[1] == 'W'); + bc4 = bc3; + bc4p = bc4.getBuffer(); + assert(bc4p != bc3p); + assert(bc2p[0] == 'R'); + assert(bc2p[1] == 'W'); + + // Test Buffer(std:string&) + std::string s{"QW"}; + Buffer bc5(s); + unsigned char* bc5p = bc5.getBuffer(); + Buffer bc6(bc5); + bc5p[0] = 'R'; + unsigned char* bc6p = bc6.getBuffer(); + assert(bc6p != bc5p); + assert(bc6p[0] == 'Q'); + assert(bc6p[1] == 'W'); + bc6 = bc5; + bc6p = bc6.getBuffer(); + assert(bc6p != bc5p); + assert(bc2p[0] == 'R'); + assert(bc2p[1] == 'W'); } #if (defined(__GNUC__) || defined(__clang__)) # pragma GCC diagnostic pop @@ -82,6 +128,19 @@ main() Buffer bm3 = std::move(bm2); unsigned char* bm3p = bm3.getBuffer(); assert(bm3p == bm2p); + + // Test Buffer(dtd::string&&) + Buffer bm4("QW"); + unsigned char* bm4p = bm4.getBuffer(); + Buffer bm5(std::move(bm4)); + bm4p[0] = 'R'; + unsigned char* bm5p = bm5.getBuffer(); + assert(bm5p == bm4p); + assert(bm5p[0] == 'R'); + + Buffer bm6 = std::move(bm5); + unsigned char* bm6p = bm6.getBuffer(); + assert(bm6p == bm5p); } try { diff --git a/libtests/logger_c.c b/libtests/logger_c.c index 4e9883a7..8f44864d 100644 --- a/libtests/logger_c.c +++ b/libtests/logger_c.c @@ -60,6 +60,7 @@ main() do_run( "{\"inputFile\": \"normal.pdf\"," " \"staticId\": \"\"," + " \"streamData\": \"uncompress\"," " \"outputFile\": \"-\"}", qpdf_exit_success); @@ -75,9 +76,7 @@ main() do_run("{\"inputFile\": \"2pages.pdf\", \"showNpages\": \"\"}", qpdf_exit_success); do_run("{\"inputFile\": \"warning.pdf\", \"showNpages\": \"\"}", qpdf_exit_warning); - do_run( - - "{\"inputFile\": \"missing.pdf\", \"showNpages\": \"\"}", qpdf_exit_error); + do_run("{\"inputFile\": \"missing.pdf\", \"showNpages\": \"\"}", qpdf_exit_error); do_run( "{\"inputFile\": \"attach.pdf\"," " \"showAttachment\": \"a\"}", diff --git a/libtests/qtest/flate.test b/libtests/qtest/flate.test index 3d99bc3a..c32b15b1 100644 --- a/libtests/qtest/flate.test +++ b/libtests/qtest/flate.test @@ -39,9 +39,13 @@ $td->runtest("run driver", check_file("farbage", "a6449c61db5b0645c0693b7560b77a60"); -$td->runtest("compressed file correct", - {$td->FILE => "farbage.1"}, - {$td->FILE => "compressed"}); +my $size_uncompressed = (stat("farbage"))[7]; +my $size_compressed = (stat("farbage.1"))[7]; +$td->runtest("compressed is smaller", + {$td->STRING => + ($size_compressed < $size_uncompressed + ? "YES\n" : "$size_compressed $size_uncompressed\n")}, + {$td->STRING => "YES\n"}); $td->runtest("uncompress filter works", {$td->FILE => "farbage"}, diff --git a/libtests/qtest/logger/c-exp-save b/libtests/qtest/logger/c-exp-save Binary files differindex b8c692ed..2636d712 100644 --- a/libtests/qtest/logger/c-exp-save +++ b/libtests/qtest/logger/c-exp-save @@ -59,7 +59,6 @@ cd($tmpdir); # Check versions my $cmakeversion = get_version_from_cmake(); my $code_version = get_version_from_source(); -my $doc_version = get_version_from_manual(); my $version_error = 0; if ($version ne $cmakeversion) @@ -72,11 +71,6 @@ if ($version ne $code_version) print "$whoami: QPDF.cc version = $code_version\n"; $version_error = 1; } -if ($version ne $doc_version) -{ - print "$whoami: doc version = $doc_version\n"; - $version_error = 1; -} if ($version_error) { die "$whoami: version numbers are not consistent\n"; @@ -157,22 +151,6 @@ sub get_version_from_source $code_version; } -sub get_version_from_manual -{ - my $fh = safe_open("manual/conf.py"); - my $doc_version = 'unknown'; - while (<$fh>) - { - if (m/release = '([^\']+)\'/) - { - $doc_version = $1; - last; - } - } - $fh->close(); - $doc_version; -} - sub safe_open { my $file = shift; diff --git a/manual/cli.rst b/manual/cli.rst index 4e01b65f..482d65f9 100644 --- a/manual/cli.rst +++ b/manual/cli.rst @@ -749,7 +749,7 @@ Related Options signature but leaves its visual appearance intact. Remove security restrictions associated with digitally signed PDF - files. This may be combined with :qpdf:option:--decrypt: to allow + files. This may be combined with :qpdf:ref:--decrypt: to allow free editing of previously signed/encrypted files. This option invalidates the signature but leaves its visual appearance intact. @@ -2176,7 +2176,7 @@ Related Options created in this way are insecure since they can be opened without a password, and restrictions will not be enforced. Users would ordinarily never want to create such files. If you are using qpdf - to intentionally created strange files for testing (a valid use of + to intentionally create strange files for testing (a valid use of qpdf!), this option allows you to create such insecure files. This option is only available with 256-bit encryption. diff --git a/manual/conf.py b/manual/conf.py index f0fd45c5..9d4eb5ec 100644 --- a/manual/conf.py +++ b/manual/conf.py @@ -15,8 +15,12 @@ sys.path.append(os.path.abspath("./_ext")) project = 'QPDF' copyright = '2005-2023, Jay Berkenbilt' author = 'Jay Berkenbilt' -# make_dist and the CI build lexically find the release version from this file. -release = '11.6.3' +here = os.path.dirname(os.path.realpath(__file__)) +with open(f'{here}/../CMakeLists.txt') as f: + for line in f.readlines(): + if line.strip().startswith('VERSION '): + release = line.replace('VERSION', '').strip() + break version = release extensions = [ 'sphinx_rtd_theme', diff --git a/manual/release-notes.rst b/manual/release-notes.rst index a03ce499..5e41fef7 100644 --- a/manual/release-notes.rst +++ b/manual/release-notes.rst @@ -38,6 +38,41 @@ Planned changes for future 12.x (subject to change): .. x.y.z: not yet released +11.7.0: not yet released + - Bug fixes: + + - With ``--compress-streams=n``, qpdf was still compressing cross + reference streams, linearization hint streams, and object + streams. This has been fixed. + + - Build Enhancements: + + - The qpdf test suite now passes when qpdf is linked with an + alternative ``zlib`` implementation. There are no dependencies + anywhere in the qpdf test suite on any particular ``zlib`` + output. Consult the ``ZLIB COMPATIBILITY`` section of + ``README-maintainer.md`` for a detailed explanation of how to + maintain this. + + - Library Enhancements: + + - Add C++ functions ``qpdf_c_wrap`` and ``qpdf_c_get_qpdf`` to the + C API to enable custom C++ code to interoperate more easily with + the the C API. See ``examples/extend-c-api``. + + - Add methods to ``Buffer`` to work more easily and efficiently + with ``std::string``. + +11.6.4: December 10, 2023 + - Bug fixes: + + - When running ``cmake --install --component dev``, install cmake + files, which were previously omitted from the ``dev`` component + + - Fix the Linux binary build to use older libraries so it + continues to work in AWS Lambda and other older execution + environments. + 11.6.3: October 15, 2023 - Bug fixes: @@ -48,7 +83,7 @@ Planned changes for future 12.x (subject to change): content streams with default settings. - The linearization specification precludes linearized files that - require offets past the 4 GB mark. A bug in qpdf was preventing + require offsets past the 4 GB mark. A bug in qpdf was preventing it from working when offsets had to pass the 2 GB mark. This has been corrected. diff --git a/qpdf/CMakeLists.txt b/qpdf/CMakeLists.txt index d089957b..ae3d07d5 100644 --- a/qpdf/CMakeLists.txt +++ b/qpdf/CMakeLists.txt @@ -55,6 +55,7 @@ add_test( --top ${qpdf_SOURCE_DIR} --bin $<TARGET_FILE_DIR:qpdf> --bin $<TARGET_FILE_DIR:libqpdf> # for Windows to find DLL + --bin $<TARGET_FILE_DIR:qpdf-test-compare> --code ${qpdf_SOURCE_DIR}/qpdf --color ${QTEST_COLOR} --show-on-failure ${SHOW_FAILED_TEST_OUTPUT} diff --git a/qpdf/qpdf-ctest.c b/qpdf/qpdf-ctest.c index ba177444..d0c60558 100644 --- a/qpdf/qpdf-ctest.c +++ b/qpdf/qpdf-ctest.c @@ -200,6 +200,7 @@ test05(char const* infile, char const* password, char const* outfile, char const qpdf_register_progress_reporter(qpdf, count_progress, (void*)&count); qpdf_set_static_ID(qpdf, QPDF_TRUE); qpdf_set_linearization(qpdf, QPDF_TRUE); + qpdf_set_compress_streams(qpdf, QPDF_FALSE); // Don't depend on zlib qpdf_write(qpdf); /* make sure progress reporter was called */ assert(count > 0); diff --git a/qpdf/qtest/attachments.test b/qpdf/qtest/attachments.test index 0d32ea0b..ccb985e1 100644 --- a/qpdf/qtest/attachments.test +++ b/qpdf/qtest/attachments.test @@ -207,8 +207,8 @@ $td->runtest("remove multiple attachments", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "remove-multiple-attachments.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf remove-multiple-attachments.pdf"}, + {$td->FILE => "remove-multiple-attachments.pdf", $td->EXIT_STATUS => 0}); $td->runtest("remove multiple attachments (json)", {$td->COMMAND => "qpdf --job-json-file=remove-multiple-attachments.json"}, @@ -216,8 +216,8 @@ $td->runtest("remove multiple attachments (json)", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "b.pdf"}, - {$td->FILE => "remove-multiple-attachments.pdf"}); + {$td->COMMAND => "qpdf-test-compare b.pdf remove-multiple-attachments.pdf"}, + {$td->FILE => "remove-multiple-attachments.pdf", $td->EXIT_STATUS => 0}); cleanup(); $td->report($n_tests); diff --git a/qpdf/qtest/c-api.test b/qpdf/qtest/c-api.test index d0776b16..21233c82 100644 --- a/qpdf/qtest/c-api.test +++ b/qpdf/qtest/c-api.test @@ -36,8 +36,8 @@ foreach my $d (@capi) {$td->STRING => "C test $n done\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check $description", - {$td->FILE => "a.pdf"}, - {$td->FILE => $outfile}); + {$td->COMMAND => "qpdf-test-compare a.pdf $outfile"}, + {$td->FILE => $outfile, $td->EXIT_STATUS => 0}); } $td->runtest("write to bad file name", {$td->COMMAND => "qpdf-ctest 2 hybrid-xref.pdf '' /:a:/:b:"}, diff --git a/qpdf/qtest/coalesce-contents.test b/qpdf/qtest/coalesce-contents.test index 57890f28..bd02a97e 100644 --- a/qpdf/qtest/coalesce-contents.test +++ b/qpdf/qtest/coalesce-contents.test @@ -46,8 +46,8 @@ $td->runtest("coalesce contents without qdf", " --coalesce-contents coalesce.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "coalesce-out.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf coalesce-out.pdf"}, + {$td->FILE => "coalesce-out.pdf", $td->EXIT_STATUS => 0}); cleanup(); $td->report($n_tests); diff --git a/qpdf/qtest/copy-annotations.test b/qpdf/qtest/copy-annotations.test index 6279ca5a..582d3709 100644 --- a/qpdf/qtest/copy-annotations.test +++ b/qpdf/qtest/copy-annotations.test @@ -110,8 +110,8 @@ $td->runtest("keeping some fields", {$td->STRING => "", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "kept-some-fields.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf kept-some-fields.pdf"}, + {$td->FILE => "kept-some-fields.pdf", $td->EXIT_STATUS => 0}); $td->runtest("not keeping any fields", {$td->COMMAND => "qpdf --static-id kept-some-fields.pdf" . diff --git a/qpdf/qtest/custom-pipeline.test b/qpdf/qtest/custom-pipeline.test index 7cf94784..ac5edd8e 100644 --- a/qpdf/qtest/custom-pipeline.test +++ b/qpdf/qtest/custom-pipeline.test @@ -21,8 +21,8 @@ $td->runtest("output to custom pipeline", {$td->STRING => "test 33 done\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "custom-pipeline.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf custom-pipeline.pdf"}, + {$td->FILE => "custom-pipeline.pdf", $td->EXIT_STATUS => 0}); cleanup(); $td->report($n_tests); diff --git a/qpdf/qtest/dangling-refs.test b/qpdf/qtest/dangling-refs.test index 18c2cf6b..5a431b83 100644 --- a/qpdf/qtest/dangling-refs.test +++ b/qpdf/qtest/dangling-refs.test @@ -24,8 +24,8 @@ foreach my $f (@dangling) {$td->FILE => "$f-dangling.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "$f-dangling-out.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf $f-dangling-out.pdf"}, + {$td->FILE => "$f-dangling-out.pdf", $td->EXIT_STATUS => 0}); } cleanup(); $td->report($n_tests); diff --git a/qpdf/qtest/decode-parameters.test b/qpdf/qtest/decode-parameters.test index 8fed1a12..10ac64c1 100644 --- a/qpdf/qtest/decode-parameters.test +++ b/qpdf/qtest/decode-parameters.test @@ -34,8 +34,8 @@ $td->runtest("stream with indirect decode parms", "qpdf --static-id indirect-decode-parms.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check file", - {$td->FILE => "a.pdf"}, - {$td->FILE => "indirect-decode-parms-out.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf indirect-decode-parms-out.pdf"}, + {$td->FILE => "indirect-decode-parms-out.pdf", $td->EXIT_STATUS => 0}); $td->runtest("decode parameters empty list", {$td->COMMAND => "qpdf --static-id empty-decode-parms.pdf a.pdf"}, diff --git a/qpdf/qtest/deterministic-id.test b/qpdf/qtest/deterministic-id.test index 9aeb7f38..a975806a 100644 --- a/qpdf/qtest/deterministic-id.test +++ b/qpdf/qtest/deterministic-id.test @@ -2,6 +2,7 @@ require 5.008; use warnings; use strict; +use File::Copy; unshift(@INC, '.'); require qpdf_test_helpers; @@ -14,23 +15,41 @@ cleanup(); my $td = new TestDriver('deterministic-id'); -my $n_tests = 11; +my $n_tests = 19; + +# Do not use qpdf-test-compare in this test suite since it ignores +# /ID[1]. foreach my $d ('nn', 'ny', 'yn', 'yy') { my $linearize = ($d =~ m/^y/); my $ostream = ($d =~ m/y$/); - $td->runtest("deterministic ID: linearize/ostream=$d", - {$td->COMMAND => - "qpdf -deterministic-id" . - ($linearize ? " -linearize" : "") . - " -object-streams=" . ($ostream ? "generate" : "disable") . - " deterministic-id-in.pdf a.pdf"}, - {$td->STRING => "", - $td->EXIT_STATUS => 0}); + # The deterministic ID is a function of all the data in the file. + # As such, it is affected by which zlib implementation is in use. + # The important thing is that the ID is the same if a file is + # generated the same way more than once, so rather than comparing + # the output file to a known output, compare subsequent outputs + # with each other. + foreach my $out ('a.pdf', 'b.pdf') + { + $td->runtest("deterministic ID: linearize/ostream=$d", + {$td->COMMAND => + "qpdf -deterministic-id" . + ($linearize ? " -linearize" : "") . + " -object-streams=" . ($ostream ? "generate" : "disable") . + " deterministic-id-in.pdf $out"}, + {$td->STRING => "", + $td->EXIT_STATUS => 0}); + } $td->runtest("compare files", {$td->FILE => "a.pdf"}, - {$td->FILE => "deterministic-id-$d.pdf"}); + {$td->FILE => "b.pdf"}); + check_id('a.pdf'); + if ($d eq 'nn') + { + # Save for the C API test + copy("a.pdf", 'c.pdf'); + } } $td->runtest("deterministic ID with encryption", @@ -49,7 +68,27 @@ $td->runtest("deterministic ID (C API)", $td->NORMALIZE_NEWLINES); $td->runtest("compare files", {$td->FILE => "a.pdf"}, - {$td->FILE => "deterministic-id-nn.pdf"}); + {$td->FILE => "c.pdf"}); cleanup(); $td->report($n_tests); + +sub check_id +{ + my $f = shift; + chomp(my $id = `qpdf --show-object=trailer $f`); + if ($id =~ m,.*/ID \[ <(9b1c69409fc9a5f50e44b9588e3e60f8)> <(.{32})>,) + { + my $id0 = $1; + my $id1 = $2; + $td->runtest("ID fields differ", + {$td->STRING => $id0 ne $id1 ? "YES\n" : "$id0 $id1\n"}, + {$td->STRING => "YES\n"}); + } + else + { + $td->runtest("checked ID", + {$td->STRING => "YES\n"}, + {$td->STRING => "$id\n"}); + } +} diff --git a/qpdf/qtest/encryption-parameters.test b/qpdf/qtest/encryption-parameters.test index f6d852c0..eaf795f3 100644 --- a/qpdf/qtest/encryption-parameters.test +++ b/qpdf/qtest/encryption-parameters.test @@ -37,7 +37,8 @@ foreach my $file (qw(short-id long-id)) $td->runtest("linearize $file.pdf", {$td->COMMAND => - "qpdf --deterministic-id --linearize $file.pdf a.pdf"}, + "qpdf --deterministic-id --compress-streams=n" . + " --linearize $file.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); diff --git a/qpdf/qtest/encryption.test b/qpdf/qtest/encryption.test index 672eb995..fb4f993b 100644 --- a/qpdf/qtest/encryption.test +++ b/qpdf/qtest/encryption.test @@ -373,9 +373,9 @@ $td->runtest("show-encryption works invalid password", $td->NORMALIZE_NEWLINES); my @cenc = ( - [11, 'hybrid-xref.pdf', "''", 'r2', "", ""], - [12, 'hybrid-xref.pdf', "''", 'r3', "", ""], - [15, 'hybrid-xref.pdf', "''", 'r4', "", ""], + [11, 'hybrid-xref.pdf', "''", 'r2', "", "user1"], + [12, 'hybrid-xref.pdf', "''", 'r3', "", "user2"], + [15, 'hybrid-xref.pdf', "''", 'r4', "", "user2"], [17, 'hybrid-xref.pdf', "''", 'r5', "", "owner3"], [18, 'hybrid-xref.pdf', "''", 'r6', "", "user4"], [13, 'c-r2.pdf', 'user1', 'decrypt with user', @@ -404,8 +404,8 @@ foreach my $d (@cenc) if (-f $pdf_outfile) { $td->runtest("check $description content", - {$td->FILE => "a.pdf"}, - {$td->FILE => $pdf_outfile}); + {$td->COMMAND => "qpdf-test-compare a.pdf $pdf_outfile $checkpass"}, + {$td->FILE => $pdf_outfile, $td->EXIT_STATUS => 0}); } else { @@ -413,7 +413,8 @@ foreach my $d (@cenc) # /Perms static, so we have no way to predictably create a # /V=5 encrypted file. It's not worth adding this...the test # suite is adequate without having a statically predictable - # file. + # file. (qpdf-test-compare ignores /Perms, but it's not worth + # adding output files for these cases.) $td->runtest("check $description", {$td->COMMAND => "qpdf --check a.pdf --password=$checkpass"}, @@ -491,8 +492,8 @@ $td->runtest("convert encrypted to qdf", " --qdf a.pdf b.qdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("compare files", - {$td->FILE => 'a.qdf'}, - {$td->FILE => 'b.qdf'}); + {$td->COMMAND => "qpdf-test-compare a.qdf b.qdf"}, + {$td->FILE => 'b.qdf', $td->EXIT_STATUS => 0}); $td->runtest("linearize with AES and object streams", {$td->COMMAND => "qpdf --encrypt '' o 128 --use-aes=y --" . " --linearize --object-streams=generate enc-base.pdf a.pdf"}, @@ -564,8 +565,8 @@ foreach my $d (['--force-V4', 'V4'], " enc-base.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "$out.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf $out.pdf"}, + {$td->FILE => "$out.pdf", $td->EXIT_STATUS => 0}); $td->runtest("show encryption", {$td->COMMAND => "qpdf --show-encryption a.pdf"}, {$td->FILE => "$out-encryption.out", $td->EXIT_STATUS => 0}, diff --git a/qpdf/qtest/error-condition.test b/qpdf/qtest/error-condition.test index d9357a0f..097190e8 100644 --- a/qpdf/qtest/error-condition.test +++ b/qpdf/qtest/error-condition.test @@ -194,8 +194,8 @@ $td->runtest("xref loop with append", $td->EXIT_STATUS => 3}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "append-xref-loop-fixed.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf append-xref-loop-fixed.pdf"}, + {$td->FILE => "append-xref-loop-fixed.pdf", $td->EXIT_STATUS => 0}); $td->runtest("endobj not at newline", {$td->COMMAND => @@ -204,8 +204,8 @@ $td->runtest("endobj not at newline", $td->EXIT_STATUS => 3}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "endobj-at-eol-fixed.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf endobj-at-eol-fixed.pdf"}, + {$td->FILE => "endobj-at-eol-fixed.pdf", $td->EXIT_STATUS => 0}); cleanup(); $td->report($n_tests); diff --git a/qpdf/qtest/extensions-dictionary.test b/qpdf/qtest/extensions-dictionary.test index b16cce33..d96b29f4 100644 --- a/qpdf/qtest/extensions-dictionary.test +++ b/qpdf/qtest/extensions-dictionary.test @@ -46,16 +46,16 @@ foreach my $input (@ext_inputs) # Look at the actual file for a few cases to make sure # qdf and non-qdf output are okay $td->runtest("check file", - {$td->FILE => "a.pdf"}, - {$td->FILE => "$base-$op-$version.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf $base-$op-$version.pdf"}, + {$td->FILE => "$base-$op-$version.pdf", $td->EXIT_STATUS => 0}); $td->runtest("$input: $op version to $version", {$td->COMMAND => "qpdf --qdf --static-id" . " --$op-version=$version $input a.qdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check file", - {$td->FILE => "a.qdf"}, - {$td->FILE => "$base-$op-$version.qdf"}); + {$td->COMMAND => "qpdf-test-compare a.qdf $base-$op-$version.qdf"}, + {$td->FILE => "$base-$op-$version.qdf", $td->EXIT_STATUS => 0}); } } } diff --git a/qpdf/qtest/filter-abbreviations.test b/qpdf/qtest/filter-abbreviations.test index cb552b87..82db5b1c 100644 --- a/qpdf/qtest/filter-abbreviations.test +++ b/qpdf/qtest/filter-abbreviations.test @@ -23,8 +23,8 @@ $td->runtest("stream filter abbreviations", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "filter-abbreviation.out"}); + {$td->COMMAND => "qpdf-test-compare a.pdf filter-abbreviation.out"}, + {$td->FILE => "filter-abbreviation.out", $td->EXIT_STATUS => 0}); cleanup(); $td->report($n_tests); diff --git a/qpdf/qtest/form-xobject.test b/qpdf/qtest/form-xobject.test index 3183a219..2a9bac83 100644 --- a/qpdf/qtest/form-xobject.test +++ b/qpdf/qtest/form-xobject.test @@ -94,8 +94,8 @@ $td->runtest("overlay on page with no resources", {$td->STRING => "", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check overlay with no resources output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "overlay-no-resources.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf overlay-no-resources.pdf"}, + {$td->FILE => "overlay-no-resources.pdf", $td->EXIT_STATUS => 0}); cleanup(); $td->report($n_tests); diff --git a/qpdf/qtest/inline-images.test b/qpdf/qtest/inline-images.test index c8552ab3..20388da5 100644 --- a/qpdf/qtest/inline-images.test +++ b/qpdf/qtest/inline-images.test @@ -68,8 +68,8 @@ $td->runtest("named colorspace", {$td->STRING => "", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "inline-image-colorspace-lookup-out.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf inline-image-colorspace-lookup-out.pdf"}, + {$td->FILE => "inline-image-colorspace-lookup-out.pdf", $td->EXIT_STATUS => 0}); my @eii_tests = ( diff --git a/qpdf/qtest/linearize-pass1.test b/qpdf/qtest/linearize-pass1.test index dfdd2d9b..c59dd55f 100644 --- a/qpdf/qtest/linearize-pass1.test +++ b/qpdf/qtest/linearize-pass1.test @@ -18,6 +18,7 @@ my $n_tests = 3; $td->runtest("linearize pass 1 file", {$td->COMMAND => "qpdf --linearize --static-id" . + " --compress-streams=n" . " --linearize-pass1=b.pdf minimal.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", diff --git a/qpdf/qtest/many-nulls.test b/qpdf/qtest/many-nulls.test index c3eefa1f..744075f0 100644 --- a/qpdf/qtest/many-nulls.test +++ b/qpdf/qtest/many-nulls.test @@ -23,9 +23,8 @@ $td->runtest("create file with many nulls", {$td->STRING => "", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("compare output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "many-nulls.pdf"}, - $td->NORMALIZE_NEWLINES); + {$td->COMMAND => "qpdf-test-compare a.pdf many-nulls.pdf"}, + {$td->FILE => "many-nulls.pdf", $td->EXIT_STATUS => 0}); $td->runtest("run check file", {$td->COMMAND => "qpdf --check a.pdf"}, {$td->FILE => "many-nulls.out", $td->EXIT_STATUS => 0}, diff --git a/qpdf/qtest/merge-and-split.test b/qpdf/qtest/merge-and-split.test index 8de1f9da..a0b72ead 100644 --- a/qpdf/qtest/merge-and-split.test +++ b/qpdf/qtest/merge-and-split.test @@ -39,8 +39,8 @@ $td->runtest("merge three files", # as well as 20-pages have text on page n (from 1) that shows its page # position from 0, so page 1 says it's page 0. $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "merge-three-files-1.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf merge-three-files-1.pdf"}, + {$td->FILE => "merge-three-files-1.pdf", $td->EXIT_STATUS => 0}); # Select the same pages but add them to an empty file $td->runtest("merge three files", {$td->COMMAND => "qpdf --empty a.pdf" . @@ -49,8 +49,8 @@ $td->runtest("merge three files", # Manually verified about this file: it has the same pages but does # not contain outlines or other things from the original file. $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "merge-three-files-2.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf merge-three-files-2.pdf"}, + {$td->FILE => "merge-three-files-2.pdf", $td->EXIT_STATUS => 0}); $td->runtest("avoid respecification of password", {$td->COMMAND => "qpdf --empty a.pdf --copy-encryption=20-pages.pdf" . @@ -69,16 +69,16 @@ $td->runtest("merge with implicit ranges", " --static-id"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "merge-implicit-ranges.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf merge-implicit-ranges.pdf"}, + {$td->FILE => "merge-implicit-ranges.pdf", $td->EXIT_STATUS => 0}); $td->runtest("merge with . and implicit ranges", {$td->COMMAND => "qpdf minimal.pdf a.pdf --pages minimal.pdf . 1 --" . " --static-id"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "merge-dot-implicit-ranges.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf merge-dot-implicit-ranges.pdf"}, + {$td->FILE => "merge-dot-implicit-ranges.pdf", $td->EXIT_STATUS => 0}); $td->runtest("merge with multiple labels", {$td->COMMAND => "qpdf --empty a.pdf" . @@ -88,8 +88,8 @@ $td->runtest("merge with multiple labels", " --static-id"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "merge-multiple-labels.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf merge-multiple-labels.pdf"}, + {$td->FILE => "merge-multiple-labels.pdf", $td->EXIT_STATUS => 0}); $td->runtest("remove labels", {$td->COMMAND => "qpdf --empty a.pdf" . @@ -100,8 +100,8 @@ $td->runtest("remove labels", " --static-id"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "remove-labels.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf remove-labels.pdf"}, + {$td->FILE => "remove-labels.pdf", $td->EXIT_STATUS => 0}); $td->runtest("split with shared resources", {$td->COMMAND => @@ -178,8 +178,8 @@ $td->runtest("force full page duplication", {$td->STRING => "", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "deep-duplicate-pages.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf deep-duplicate-pages.pdf"}, + {$td->FILE => "deep-duplicate-pages.pdf", $td->EXIT_STATUS => 0}); cleanup(); diff --git a/qpdf/qtest/newline-before-endstream.test b/qpdf/qtest/newline-before-endstream.test index 667a0dd6..2066375e 100644 --- a/qpdf/qtest/newline-before-endstream.test +++ b/qpdf/qtest/newline-before-endstream.test @@ -36,8 +36,8 @@ foreach my $d ( {$td->STRING => "", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output ($description)", - {$td->FILE => "a.pdf"}, - {$td->FILE => "newline-before-endstream-$suffix.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf newline-before-endstream-$suffix.pdf"}, + {$td->FILE => "newline-before-endstream-$suffix.pdf", $td->EXIT_STATUS => 0}); if ($flags =~ /qdf/) { $td->runtest("fix-qdf", @@ -52,8 +52,8 @@ $td->runtest("newline before endstream (C)", {$td->STRING => "C test 22 done\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "newline-before-endstream-nl.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf newline-before-endstream-nl.pdf"}, + {$td->FILE => "newline-before-endstream-nl.pdf", $td->EXIT_STATUS => 0}); cleanup(); $td->report($n_tests); diff --git a/qpdf/qtest/page-errors.test b/qpdf/qtest/page-errors.test index 2eceb37f..fdda4bc1 100644 --- a/qpdf/qtest/page-errors.test +++ b/qpdf/qtest/page-errors.test @@ -32,8 +32,8 @@ $td->runtest("handle page with inherited MediaBox", {$td->STRING => "", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "page-inherit-mediabox-out.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf page-inherit-mediabox-out.pdf"}, + {$td->FILE => "page-inherit-mediabox-out.pdf", $td->EXIT_STATUS => 0}); $td->runtest("check no type key for page nodes", {$td->COMMAND => "qpdf --check no-pages-types.pdf"}, {$td->FILE => "no-pages-types.out", $td->EXIT_STATUS => 3}, @@ -43,8 +43,8 @@ $td->runtest("no type key for page nodes", {$td->FILE => "no-pages-types-fix.out", $td->EXIT_STATUS => 3}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a-split-out-1.pdf"}, - {$td->FILE => "no-pages-types-fixed.pdf"}); + {$td->COMMAND => "qpdf-test-compare a-split-out-1.pdf no-pages-types-fixed.pdf"}, + {$td->FILE => "no-pages-types-fixed.pdf", $td->EXIT_STATUS => 0}); $td->runtest("detect loops in pages structure", {$td->COMMAND => "qpdf --check pages-loop.pdf"}, {$td->FILE => "pages-loop.out", $td->EXIT_STATUS => 2}, diff --git a/qpdf/qtest/pages-tree.test b/qpdf/qtest/pages-tree.test index 9950b675..eb416fba 100644 --- a/qpdf/qtest/pages-tree.test +++ b/qpdf/qtest/pages-tree.test @@ -18,14 +18,14 @@ my $n_tests = 17; $td->runtest("linearize duplicated pages", {$td->COMMAND => - "qpdf --static-id --linearize" . + "qpdf --static-id --linearize --compress-streams=n" . " page_api_2.pdf a.pdf"}, {$td->FILE => "duplicate-page-warning.out", $td->EXIT_STATUS => 3}, $td->NORMALIZE_NEWLINES); $td->runtest("compare files", - {$td->FILE => "a.pdf"}, - {$td->FILE => "linearize-duplicate-page.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf linearize-duplicate-page.pdf"}, + {$td->FILE => "linearize-duplicate-page.pdf", $td->EXIT_STATUS => 0}); $td->runtest("extract duplicated pages", {$td->COMMAND => "qpdf --static-id page_api_2.pdf" . @@ -34,16 +34,16 @@ $td->runtest("extract duplicated pages", $td->EXIT_STATUS => 3}, $td->NORMALIZE_NEWLINES); $td->runtest("compare files", - {$td->FILE => "a.pdf"}, - {$td->FILE => "extract-duplicate-page.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf extract-duplicate-page.pdf"}, + {$td->FILE => "extract-duplicate-page.pdf", $td->EXIT_STATUS => 0}); $td->runtest("direct pages", {$td->COMMAND => "qpdf --static-id direct-pages.pdf --pages . -- a.pdf"}, {$td->FILE => "direct-page-warning.out", $td->EXIT_STATUS => 3}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "direct-pages-fixed.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf direct-pages-fixed.pdf"}, + {$td->FILE => "direct-pages-fixed.pdf", $td->EXIT_STATUS => 0}); $td->runtest("show direct pages", {$td->COMMAND => "qpdf --show-pages direct-pages.pdf"}, diff --git a/qpdf/qtest/parsing.test b/qpdf/qtest/parsing.test index 97cf9edf..bd1a7c6b 100644 --- a/qpdf/qtest/parsing.test +++ b/qpdf/qtest/parsing.test @@ -33,14 +33,14 @@ $td->runtest("extra header text", {$td->FILE => "test-32.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "extra-header-no-newline.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf extra-header-no-newline.pdf"}, + {$td->FILE => "extra-header-no-newline.pdf", $td->EXIT_STATUS => 0}); $td->runtest("check output", {$td->FILE => "b.pdf"}, {$td->FILE => "extra-header-lin-no-newline.pdf"}); $td->runtest("check output", - {$td->FILE => "c.pdf"}, - {$td->FILE => "extra-header-newline.pdf"}); + {$td->COMMAND => "qpdf-test-compare c.pdf extra-header-newline.pdf"}, + {$td->FILE => "extra-header-newline.pdf", $td->EXIT_STATUS => 0}); $td->runtest("check output", {$td->FILE => "d.pdf"}, {$td->FILE => "extra-header-lin-newline.pdf"}); diff --git a/qpdf/qtest/preserve-unref.test b/qpdf/qtest/preserve-unref.test index 3a290841..f5620559 100644 --- a/qpdf/qtest/preserve-unref.test +++ b/qpdf/qtest/preserve-unref.test @@ -20,22 +20,22 @@ $td->runtest("drop unused objects", {$td->COMMAND => "qpdf --static-id unreferenced-objects.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "unreferenced-dropped.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf unreferenced-dropped.pdf"}, + {$td->FILE => "unreferenced-dropped.pdf", $td->EXIT_STATUS => 0}); $td->runtest("keep unused objects", {$td->COMMAND => "qpdf --static-id --preserve-unreferenced" . " unreferenced-objects.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "unreferenced-preserved.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf unreferenced-preserved.pdf"}, + {$td->FILE => "unreferenced-preserved.pdf", $td->EXIT_STATUS => 0}); $td->runtest("keep unused objects (C)", {$td->COMMAND => "qpdf-ctest 21 unreferenced-objects.pdf '' a.pdf"}, {$td->STRING => "C test 21 done\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "unreferenced-preserved.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf unreferenced-preserved.pdf"}, + {$td->FILE => "unreferenced-preserved.pdf", $td->EXIT_STATUS => 0}); cleanup(); $td->report($n_tests); diff --git a/qpdf/qtest/qpdf-json.test b/qpdf/qtest/qpdf-json.test index 2867f8a7..961b507a 100644 --- a/qpdf/qtest/qpdf-json.test +++ b/qpdf/qtest/qpdf-json.test @@ -294,31 +294,31 @@ $td->runtest("C API create from json file", {$td->STRING => "C test 42 done\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check C API create from file", - {$td->FILE => "a.pdf"}, - {$td->FILE => "qpdf-ctest-42-43.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf qpdf-ctest-42-43.pdf"}, + {$td->FILE => "qpdf-ctest-42-43.pdf", $td->EXIT_STATUS => 0}); $td->runtest("C API create from json buffer", {$td->COMMAND => "qpdf-ctest 43 minimal.json '' a.pdf"}, {$td->STRING => "C test 43 done\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check C API create from buffer", - {$td->FILE => "a.pdf"}, - {$td->FILE => "qpdf-ctest-42-43.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf qpdf-ctest-42-43.pdf"}, + {$td->FILE => "qpdf-ctest-42-43.pdf", $td->EXIT_STATUS => 0}); $td->runtest("C API update from json file", {$td->COMMAND => "qpdf-ctest 44 minimal.pdf '' a.pdf minimal-update.json"}, {$td->STRING => "C test 44 done\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check C API update from file", - {$td->FILE => "a.pdf"}, - {$td->FILE => "qpdf-ctest-44-45.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf qpdf-ctest-44-45.pdf"}, + {$td->FILE => "qpdf-ctest-44-45.pdf", $td->EXIT_STATUS => 0}); $td->runtest("C API update from json buffer", {$td->COMMAND => "qpdf-ctest 45 minimal.pdf '' a.pdf minimal-update.json"}, {$td->STRING => "C test 45 done\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check C API update from buffer", - {$td->FILE => "a.pdf"}, - {$td->FILE => "qpdf-ctest-44-45.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf qpdf-ctest-44-45.pdf"}, + {$td->FILE => "qpdf-ctest-44-45.pdf", $td->EXIT_STATUS => 0}); $td->runtest("C API write to JSON 1", {$td->COMMAND => "qpdf-ctest 46 minimal.pdf '' a.json"}, diff --git a/qpdf/qtest/qpdf/c-linearized.pdf b/qpdf/qtest/qpdf/c-linearized.pdf Binary files differindex dbe62ffe..23c11901 100644 --- a/qpdf/qtest/qpdf/c-linearized.pdf +++ b/qpdf/qtest/qpdf/c-linearized.pdf diff --git a/qpdf/qtest/qpdf/deterministic-id-nn.pdf b/qpdf/qtest/qpdf/deterministic-id-nn.pdf deleted file mode 100644 index 0b71a444..00000000 --- a/qpdf/qtest/qpdf/deterministic-id-nn.pdf +++ /dev/null @@ -1,1852 +0,0 @@ -%PDF-1.4 -% -1 0 obj -<< /Lang (en) /Metadata 3 0 R /PageLabels 4 0 R /Pages 5 0 R /Type /Catalog >> -endobj -2 0 obj -<< /CreationDate (D:20150524172830-04'00') /Creator (Apache FOP Version 1.1) /Producer (Apache FOP Version 1.1) >> -endobj -3 0 obj -<< /Subtype /XML /Type /Metadata /Length 858 >> -stream -<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?><x:xmpmeta xmlns:x="adobe:ns:meta/"> -<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> -<rdf:Description xmlns:dc="http://purl.org/dc/elements/1.1/" rdf:about=""> -<dc:format>application/pdf</dc:format> -<dc:language>en</dc:language> -<dc:date>2015-05-24T17:28:30-04:00</dc:date> -</rdf:Description> -<rdf:Description xmlns:pdf="http://ns.adobe.com/pdf/1.3/" rdf:about=""> -<pdf:Producer>Apache FOP Version 1.1</pdf:Producer> -<pdf:PDFVersion>1.4</pdf:PDFVersion> -</rdf:Description> -<rdf:Description xmlns:xmp="http://ns.adobe.com/xap/1.0/" rdf:about=""> -<xmp:CreatorTool>Apache FOP Version 1.1</xmp:CreatorTool> -<xmp:MetadataDate>2015-05-24T17:28:30-04:00</xmp:MetadataDate> -<xmp:CreateDate>2015-05-24T17:28:30-04:00</xmp:CreateDate> -</rdf:Description> -</rdf:RDF> -</x:xmpmeta><?xpacket end="r"?> -endstream -endobj -4 0 obj -<< /Nums [ 0 << /P (i) >> 1 << /P (ii) >> 2 << /P (iii) >> 3 << /P (iv) >> 4 << /P (1) >> 5 << /P (2) >> 6 << /P (3) >> 7 << /P (4) >> 8 << /P (5) >> 9 << /P (6) >> 10 << /P (7) >> 11 << /P (8) >> 12 << /P (9) >> 13 << /P (10) >> 14 << /P (11) >> 15 << /P (12) >> 16 << /P (13) >> 17 << /P (14) >> 18 << /P (15) >> 19 << /P (16) >> 20 << /P (17) >> 21 << /P (18) >> 22 << /P (19) >> 23 << /P (20) >> 24 << /P (21) >> 25 << /P (22) >> 26 << /P (23) >> 27 << /P (24) >> 28 << /P (25) >> 29 << /P (26) >> 30 << /P (27) >> 31 << /P (28) >> 32 << /P (29) >> 33 << /P (30) >> 34 << /P (31) >> 35 << /P (32) >> 36 << /P (33) >> 37 << /P (34) >> 38 << /P (35) >> 39 << /P (36) >> 40 << /P (37) >> 41 << /P (38) >> 42 << /P (39) >> ] >> -endobj -5 0 obj -<< /Count 43 /Kids [ 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R 14 0 R 15 0 R 16 0 R 17 0 R 18 0 R 19 0 R 20 0 R 21 0 R 22 0 R 23 0 R 24 0 R 25 0 R 26 0 R 27 0 R 28 0 R 29 0 R 30 0 R 31 0 R 32 0 R 33 0 R 34 0 R 35 0 R 36 0 R 37 0 R 38 0 R 39 0 R 40 0 R 41 0 R 42 0 R 43 0 R 44 0 R 45 0 R 46 0 R 47 0 R 48 0 R ] /Type /Pages >> -endobj -6 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 49 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -7 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 51 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -8 0 obj -<< /Annots 52 0 R /BleedBox [ 0 0 612 792 ] /Contents 53 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -9 0 obj -<< /Annots 54 0 R /BleedBox [ 0 0 612 792 ] /Contents 55 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -10 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 56 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -11 0 obj -<< /Annots 57 0 R /BleedBox [ 0 0 612 792 ] /Contents 58 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -12 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 59 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -13 0 obj -<< /Annots 60 0 R /BleedBox [ 0 0 612 792 ] /Contents 61 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -14 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 62 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -15 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 63 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -16 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 64 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -17 0 obj -<< /Annots 65 0 R /BleedBox [ 0 0 612 792 ] /Contents 66 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -18 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 67 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -19 0 obj -<< /Annots 68 0 R /BleedBox [ 0 0 612 792 ] /Contents 69 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -20 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 70 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -21 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 71 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -22 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 72 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -23 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 73 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -24 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 74 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -25 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 75 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -26 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 76 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -27 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 77 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -28 0 obj -<< /Annots 78 0 R /BleedBox [ 0 0 612 792 ] /Contents 79 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -29 0 obj -<< /Annots 80 0 R /BleedBox [ 0 0 612 792 ] /Contents 81 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -30 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 82 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -31 0 obj -<< /Annots 83 0 R /BleedBox [ 0 0 612 792 ] /Contents 84 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -32 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 85 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -33 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 86 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -34 0 obj -<< /Annots 87 0 R /BleedBox [ 0 0 612 792 ] /Contents 88 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -35 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 89 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -36 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 90 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -37 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 91 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -38 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 92 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -39 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 93 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -40 0 obj -<< /Annots 94 0 R /BleedBox [ 0 0 612 792 ] /Contents 95 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -41 0 obj -<< /Annots 96 0 R /BleedBox [ 0 0 612 792 ] /Contents 97 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -42 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 98 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -43 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 99 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -44 0 obj -<< /Annots 100 0 R /BleedBox [ 0 0 612 792 ] /Contents 101 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -45 0 obj -<< /Annots 102 0 R /BleedBox [ 0 0 612 792 ] /Contents 103 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -46 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 104 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -47 0 obj -<< /BleedBox [ 0 0 612 792 ] /Contents 105 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -48 0 obj -<< /Annots 106 0 R /BleedBox [ 0 0 612 792 ] /Contents 107 0 R /CropBox [ 0 0 612 792 ] /MediaBox [ 0 0 612 792 ] /Parent 5 0 R /Resources 50 0 R /TrimBox [ 0 0 612 792 ] /Type /Page >> -endobj -49 0 obj -<< /Filter /FlateDecode /Length 225 >> -stream -xuMKA+rT1̵ -Pebk{ofWh&I3;c+ ~5**Mck$?r_"s8`9@<eKls"l}~hi7(C%FruCT^T0^g!E7SQ=͔gÙn5_Lœ0(eZPм}OǢњPSUendstream -endobj -50 0 obj -<< /ColorSpace << /DefaultRGB 108 0 R >> /Font << /F10 109 0 R /F11 110 0 R /F12 111 0 R /F3 112 0 R /F4 113 0 R /F5 114 0 R /F6 115 0 R /F7 116 0 R /F9 117 0 R >> /ProcSet [ /PDF /ImageB /ImageC /Text ] >> -endobj -51 0 obj -<< /Filter /FlateDecode /Length 336 >> -stream -xSMO@ﯘ&vjƶHUJ+Mf2ͼHB{L%x)IJ) CƂ$(
pcHIV&r·v9
}hEGBg0n#7Q/C#AAyچF 9p3c=q,s0Dk')Zu)`t2ݴ_Ti-J0RIa|
uP $.o@a8iFB=-|2|Wu+hq#-2Qx7kLF5]~..@3&<NzIv -endobj -52 0 obj -[ 118 0 R 119 0 R 120 0 R 121 0 R 122 0 R 123 0 R 124 0 R 125 0 R 126 0 R 127 0 R 128 0 R 129 0 R 130 0 R 131 0 R 132 0 R 133 0 R 134 0 R 135 0 R 136 0 R 137 0 R 138 0 R 139 0 R 140 0 R 141 0 R 142 0 R 143 0 R 144 0 R 145 0 R 146 0 R 147 0 R 148 0 R 149 0 R 150 0 R 151 0 R 152 0 R 153 0 R 154 0 R 155 0 R 156 0 R 157 0 R 158 0 R 159 0 R 160 0 R 161 0 R 162 0 R 163 0 R 164 0 R 165 0 R 166 0 R 167 0 R 168 0 R 169 0 R 170 0 R 171 0 R 172 0 R 173 0 R 174 0 R 175 0 R 176 0 R 177 0 R 178 0 R 179 0 R 180 0 R 181 0 R 182 0 R 183 0 R 184 0 R 185 0 R 186 0 R 187 0 R 188 0 R 189 0 R 190 0 R 191 0 R 192 0 R 193 0 R 194 0 R 195 0 R 196 0 R 197 0 R 198 0 R 199 0 R 200 0 R 201 0 R 202 0 R 203 0 R ] -endobj -53 0 obj -<< /Filter /FlateDecode /Length 15258 >> -stream -xOsdy>i=}ϿՄ-Yز/:zQ"!:Ȣ\,١sO&HIc;ʬ,A2_{<×_+>D=|ͮ>EW~_|ͯsxpſ?|wO'ť7/8?O3qyL?\a?ޓ_qo=κԇ?x7:Ox:O>xᛧ>|zGxSs<xG?}7#>~>\>~5[>gzYlG9UvUhm9)B8$RT+ -;KUuzJ}*|{M,Yd9r$I,Y.LjLujLLLldȲe'N,;Yd9r$I,Y.\by>B-"#by>B-#dddddddȲe#F,;Yvd9r$I,'Y.\d2}Z,/ǷE,/G2}|{]2},,i4I$}>IsO'iWmwqc}ޡ.:~v{_=yo{dz1n5qܮtoN{ -uPݩ^RonTe#F,;Yvdr A,'YN\drze=IJbYX"ebYX*eeeeeee#Fm[ [%uT7ԣPTAuPM,Y.\bX!qe"Q2XF˨b,,,,,,,,Y6ldɲe'A,Yd9r"EK,<2C,e"Y2Xf% $$$F,Y^3jerN~uyY^YzxAl{.~f8w^:f8%#k]q9*ՍjA,EKRıT,U$KReY+=JuzJzHN5Yvdr A,'YNdrze=IJbYX"e-bYX*eeeeeeee#F,;Yvd9r A,'YN\drebX!qe"Q2XF˨ddddddddȲe#N,;Yvd9r$I,Y.\d2C,,bE,eV*Y2i4I$}>IsO'i4Ykkn;r6l[}l_||3=ro~wmn\wJ+yX-_gY-Ii|_KW[nTvS:Lu4iT.S]Hul)'=RN{p+y%=TTTTTTTT6Sme7<-[(f.S]Hut)'=եT\rRݓ]+.Lj3nTvS:Lu4iT.S]HuO})'=jRBYI5+f%լLj3bSQǽFqd|5bso;|c~fD8ƽ7#N嗗7'嗗αV<,///;!oO//;!OTvS:Lu0iT.S]Luj=HZRTk!ZHVRTk%ZM5L5L5L5M5M5Mj3fTvS:Lu0iTNS]LujAQH5 -F!(TjTS
S
S
SMSMSMSMSmLj7nTS:Lu4iT.R̓T <H5RBYH5f%լTm6[J-͖fKiRl)m6[̰[[k{O/G3ܛ,ǝfر>*za}~d|_.c~f}L:;;f:=N%A~$yX-_Kj3nTS:Lu4iT.S]G9QNJxT(70000444fT륙G}Iwgq7'gq妺Lu2E:yRT('}<Iu_/rS
S
S
SMSMSMSmLj3nTS:Lu4iT.S]/G9KI^Hu_LrRݗӓjVRjaaaiiiiTuC(Ǻ40505|/~맗[{'~fz~%~͘wë́<= -?~>Kzyr}fPDy^L0
{Yw;˻q}koϗF7'#A[nTvS:Lu4iT.S]Hu|I9/%/wTΗWRݛrPnaaaiiiiTyyMjA8,˻Y-o2eTޔrRݛrPN{S+M9(70000444fTvSM0aTNSLu2E{SIuo!y!ս)多TjVRݛrHLuOXK*ˈϣܹ>}\8_ͽ姏}ˎ=Ɲ1^kmi?o˾:Iz_tS\Z(mMqv\,Or뒇EܺrSM0aTNS:Mu2E_IuK9#)'}Tjjjjjjjj3f2i1ޒ[(f|rS]Lu"} 0夺>rR݇SN`M5L5L5L5M5M5Mj3fTvS:Lu0iTNS]LujAYH5f!,TjVS
S
S
SMSMSMSMSm9zlJ<_-vv}1uϣ?|߽C"qޣ|ݶβ{ShwnAy|RLj3nTS:Lu4iT.S]єFSN{M9iT4rS
S
S
S
SMSMSMSmLmz2[~XwZ,byZnT.S]єFK^HuO)'=T4rS
S
S
SMSMSMSmLj3nTS:Lu4iT.S]AyjRBYH5f%լTTTTTTTTit`gkuo.|w>?
ϳ~}˛
ϗ!֝l8ΗLG)|B!yZ>(ߧPHw/8߯f -ɛ]H)rSM0aTNS:Mu2E9$'Rl!9^瀼e{M5L5L5L5M5M5M5Mj3fTvS:Lu0iT.S]H=zٞCrRl!9^瀼e{M5L5L5L5L5M5M5Mj3fTvS:Lu0iT.S]Lue{I=T/sHN9$'͖fKiRl)m6[8X2덒c>c<|7?ٝvuL;#Zw~G?rgmKNz)oOoZJ{ N9aaaiiiT6SmMj7aTNS:Mu2eT@rRq8夺WRqM5L5L5L5M5M5M5Mj3նUۚXayX-_jyT.S]┓SN{ N9T@rS
S
S
S
SMSMSMSmLj7nTS:Lu4iT.S]AyjRBYH5+f%լTTTTTTT<̚/z<YX杁xܗbv=|>|ݷ/}ݝyyYZ;7u2T|Q$Y>)[J2E ս;ZB{k+J>D000444fTVu^vzI7$Q=BTVPd9r"%qebXF(bE,eT*Q2222222ɲe#F,;Yvd9r I,'Y.\d2C,<2Xf,bU,eVLLLld7xiHηqFY+ jބ?Q_} >|r_ۿ}gClʝ{Y|=ﻏ#.7ngLi|sAyX-_o0(7Ex)Iu/%y!}夺>^rS
S
S
SMSMSMSMSmLmXsMEA8,˻Y-o2eTR>^rRKQNx)+(70000444fTvSM0aTNSLu2Ex)Iu/%y!}夺TRjjjjjjj3f~~&cPvݡ|eס|,{nWO/\γ,Yw~kG{n{f͒7'{fAޱYT.S]Hu('=WꞫQN{&y%=WTTTTTTTT6SmMj7aTS:Mu2eT\rRs5Iu('=WꞫQnaaaaiiiT6SMj7aTNS:Mu2eT\rRs5夺jꞫQnaaaiiiTh̵ƔeNZܛ#_eNv~=/S)/w,ד7_VȰ)^yf|cuoыi|)yX-_7%ojjjj3f.c{OnEIi|_ET.S]HRHN/ӿ('}T)`jjjjjjjj3fTvS:Lu0aTNS]Lu>7rRgQN0Iu!&y%}LåZy7w{`_|ktg]=͍/zypqoPKzoyůם_kU{9*W>+[(?)oOʛ6SmMj7nTS:Mu2eT('=UꞪRN{J9*Lj۪(QEy7'XayXnT.S]_SN{*y!]PN{*y%=UTTTTTTTT6SmMj7aTS:Mu2eTTrRSUIuOU)'=UjVRjaaaaiiiTw9sM{FY3gTnn8\榟>>}ݷ|<_<GcfnGayz-ߐwڒ7'i|Ӗ<,j7nTS:Mu4iT.RݧRNW_)'}T鯔jjjjjjjj3fTvS:Lu0aTNS]Lu>rRݧRNWIu*y%}+Lj3nTvS:Lu4iT.S]HuJ9_%/OTjVRJiRl)m6[J-͖fKkyo4sc?'$?\s=|xzw=l杇A^qG)wDo -M fXϯSŰXQ,EK}7U$QReeeeeeee#F,;Yvdr A,'YN\dr},b䆺>[jGqK-n222222ɲe#?{|QޫI(yX4eT#)'}T1M>r[J{.@6SmLj7nTS:Mu4eTAyjf!,TjVRjaaaaiiiTwmh~:x,ls;kyCt'x,+\w߿>}|뿼>~p9:nAn|g֝ucE~Nɛ{:i)IZBjZ+jaaaaiiiTo7~n|$y|R>i.S]LujAQH5 -F!ըTjTS
S
S
SMSMSMSmLj3nTS:Lu4iT.S]AyjRBYH5f%լTTTTTTTTuTF)Ӟ{<uwC^Cc_W"ƿ;jԹV{O 7c~{i|EEEyT.S]geꞕQN{VF9YTrS
S
S
S
SMSMSMSmLmչ-y?,˻Q-oOg<-7eT.Rݳ2Iu$/geꞕQN{VF6SmLj7nTS:Mu4iT.Rݳ2Iu$/geꞕI^Iu(70004444foސCՈGl+s< q>|JXk1^s#fw<-]$˻zXo.pay|QwYnTvS:Lu0iT.S]HuO)'=TԚrRSk+5Lj3nTvS:Lu4iT.S]HuO)'=ZSN{jM95Lj3fTvS:Lu4iT.S]H5R̓T ,TjRJYI5m6[J-͖fKiRl)mt9S1zkt<enc<|Oz>zt^/ty>c~Y^߬kkβ!ߔz
uR={ATw{?r$I,Y.\dbyu~Pe?dddddddȲe#F,;Yvd9r$I,'Y.\dibyٲuˆ}PeU,/AMAAAIIII,Y6dɲ A,YNd9r"%=ˎ|Pe?>*& $K$}>Krcܛj4:7ƝzMxlc_aw'gUwvc'y7cκMX-_P,_(C}夺T_>KJ/M5L5L5L5L5M5M5Mj3նUG?ayX-_jy|R>i.S]Lu>rRG|I^HuE9#('}Lj3fTvS:Lu4iT.S]HuE9#$/T_WRG|QnaaaiiiiTyʑk}5=FYkκ{#=}=rבӱݿ}x/~_a<rA<͈xl;Z;n'2ay|QY>%{+ Ay!ZHRTk%ZIVS
S
S
SMSMSMSMSmLj7nTS:Lu4iT.RT 8H5RBQH5 -F%ըTTTTTTTT6SmMj7nTS:Mu2eTAyjTjRJYI5+f5000444f{sZOv\8Z -endobj -54 0 obj -[ 204 0 R 205 0 R 206 0 R 207 0 R 208 0 R 209 0 R 210 0 R ] -endobj -55 0 obj -<< /Filter /FlateDecode /Length 1224 >> -stream -xVr&߯`Ozg7yԤ+7,Ľ"#}=$'>}i -}Ҝ9}tɤD"LcԠIQVy%@П'y -ӧ0Y -pFsCѹAnӛqv|/QF8.@`.d=\}fc Pae -cE(~$0$14}/ -=-.cx{ٮb17QW:;2al+M"vТ܆X~j<+>BŞ,7\dȊpzqY1Ypw̬h%#dMQ
Sp^/ړ=ϴWwfYljkNmW -KԸ2kh<siT
WB.3=<ZTg]P;#endstream -endobj -56 0 obj -<< /Filter /FlateDecode /Length 1572 >> -stream -xXK6W
`S>nO]tr%bV9IKC/iS4p/ge~9)>'cmw$$[Ҙ"")ɇEHaIRT{7On?`[['87IT8\kp<mqvGHEe-RR>oK/'/-D^\2+w٬ƶfMMr<K>M#fd PSM"'NbR9$ -Lm_V+-Vf(t -f -.W1ɺ"Fcc87z)ϳ'كDuh}o^hspL^{L51Ϳ[6n
:u̳LSl;EEq[>D<lhOcpIn&I14ZA&%C7dn"&Y_<vpSJrBpaTl4 -bg[rӳJm\L7v928mlP#8v~߉gD{sƺ6o\{o.O]d;ݭwDCw - -? -q8peSg-lSz;cf=-ꄪ/(Ƒ;7'"\ASA.gy~ɞ<vbP
e
%lDnYZ1XUhg@xSswc%߹Iv.;wrM''zv-B;(6@? -<On8_6ڳ$wſ,endstream -endobj -57 0 obj -[ 211 0 R 212 0 R 213 0 R 214 0 R 215 0 R 216 0 R 217 0 R 218 0 R 219 0 R 220 0 R 221 0 R ] -endobj -58 0 obj -<< /Filter /FlateDecode /Length 2926 >> -stream -xZY6~_ĕ$N)cL8$(a"doK"ɛj5F/4 -I -N٩?_}=7ߗ ReeT#Mpji@eW5 ߳2e=~k)4ZD/"wыoWf:QmW(hQByY+i]m'6wkx"Uߐ78U]HC&J<>~Hqs&%[ 7I -:XBz{&Rfp\#|6-uCǪ9<2TM3.tQ< 2?~gqE,(q 2-y۷e)#Zd&I\^o%ޟ(vCfT'>N`EOth&;0,ia!%6۰~/f@>A7YH3UX\m{`]5',e{f}YBöoX_s6RK}ۦc
DoE(u5/g6 -F%h(s}'¥qK# -2<'&diھT2=n
ՑEF2,m=E4)A0+{I>Te I_Kś[;i1pI0i9+[<vڃʉ -f2@mБ'6$Qn mȋځ9eNtqLbg<i -X^r/+ǒ|dܭZT}ޑUFmmP7p]Md=XNj^Dl6VDćieɁug2U?VW;(T} -AL0# 3&b5I;̆j[MV pf{b
Ɉ
:0s-\[i6-6Z08U-n`Hxޗl{ -ٚ -jAo -F>ܠont|y4xQۮZQT6l:Sr8++E˄Xu3&5x~D;>NODoCX~A1 /u{<$hHݵ?AvA&|wↅÒ J5[BF,/ -=+Tzzr_w8霷 4uy!C}*^"+5un*1y=CêN*J\i.3/ -FbU#X7デn㻄4ǸXVTp-9z2D0]ډ0f`dx+mPGSu|G٪+vy;/7Ҡ8Y8#:S!(=<dv4}f,..>8S#вHP*<)koџbp(_|q2 -@A딣2E$x D݈ø6li(: qo,58d_8ev$@m -W՝ -NPx!]uGG:_=eww-A&yJӘFx~Z˃vMyNa|t#&(E (ll-X,vR0̛krfw:T!_TW40G'E&՝zZDs"ƃm[Du%$?DV)g8ǻ>5cG_O\GM5'#;Q5#wK&Q7sn/T2B|'\ۚV}{˪HcgG6vtm!@^mgъ_٪j(OF>o:Z)!T/hhU|f߽gaI"26%$NKo!N E% !/]6hE6$B8w:"[yendstream -endobj -59 0 obj -<< /Filter /FlateDecode /Length 1447 >> -stream -xXKo8W)ݤE[,ʢ#QPHZMx!9/("Yaqoyebe%)1t@NPwD4Pa6G?_8 cqO@^!\?AśQV[E4yE(,*OVMWV!^oM*^U~z+Z[\Nc4m0G>'ILNWp|2ѭ*Di1|A$4I~y8?6_|g0j1[,Y/ bj%aqF{Ҕ5p`%1EVHXaQ^sJȋP07_
}wĠA j!ymQa0e\18%-MMWkYo]x<%R]gҙv3١@k^W`ӅLDINT6/Էo(|vFT F͛>%Z+U;x1 w],1dƴ(V*X*[bYEԀ a^TY$iy<V6Nh<R-cpSB֎|s]C `3I?=fހVx&#up$IY -yb)R\^zӾ=&wݐsK3(<M\.ux=N\e@ecx<`g+f-<>7-,_ైbZdP$1͡(3{N2hȦnzE#R=lٖ#W$d$iS):xrlt̍۽6[=7`(>UT%tW<Ʊ!U Yj:A{+˪1H ?)GVHIqFwC.7ڡ3tFC1 -r\:33]9v=ZJC[jf@(Iq288Tqv82endstream -endobj -60 0 obj -[ 222 0 R 223 0 R 224 0 R 225 0 R 226 0 R 227 0 R ] -endobj -61 0 obj -<< /Filter /FlateDecode /Length 2796 >> -stream -xے۶]_3+ [g;IΤDB+6)j6_߃;xvnsuCPl埬Qy|58ԠY'ElWGoi7X\6ќ:q!/Dߜ(%E)u/1?r&MR-?5D/>U="FnGc͛$D~<K<.btD^_?7?/lqӆ\>Q*B1qEQZ~E(F۶nЇ?\KX8"PMP4t*1}:t4v?h<Tѩz~D;*lPS[ ~3)Sj -*)ӌ)&C]wcW$InD Z)[Jx)@A>}4esa -)"g=v(jݵJGoV,y -> 頹ⓔepNW¼(ɍR8i_* -] -k#l0µ-U^7zc:y94.!z~hP6A?HKs%Q13
fj>2N).b9:ծZ* F5OSV}M8s5(މUz -P3.49Ul者
H=}+$7ɋ5K;u1J6>Ai4CG)*7ۦE!B7XiيR~[aR7O@S-&s|H"J^֑:# M
e F^V<L6 #VB}E1;Cu)F?yxp>Y Fx!@E -E77ΗSJyʨ0^(jaY67a W0(7"LVf V!IVu8ufF0f[(aF,IV>$HhnNHD>̪)gM=9NE -endobj -62 0 obj -<< /Filter /FlateDecode /Length 2639 >> -stream -xَ6oiY ,x=FlaZKcf؏""{.|xąO$6#OXxG" -H)VY?Bp"nK>w.N2ؼ/添ͫw\rw0! -c' ,KNgUӋ
u]G7^~!w8xiL%C/V
hJҀŧR%MECDE?\k{1-r=$#hN:bpi/{.juo,ٞ$N8%4VѳwLaų3,6HjZGS~3{vCh·fCQFN:{hm:oM_ڈ=Y1YP8ThO1r4v{ *y. K\ҥvϤ#"^8ifYGK~4y(FT<Gd<g}Ϫx5ز@2O}_|B?u8 -* -0 -u<)шJ"{.7"57526`m@˖ѽ+w2ɘ74bo8n`:>FWSz1+-&w"xtYDt$$Z^ΓY+<eєMjP0ӲϚj-)rݳzn%`ۘ5g'!%,t9omq<dR *~O 6kâ%p}Yв(IAeխ Ժ 9d+3G3kU9 bʪ
+ -K{ZԖ D.2l?wbHb|b,,D|[C6RK%{;¬61r=XKo[fvZݱoEGPo_g1D -VMn8o"RW<_0g:l0 h+A)JM~ߦi FQm->*Z,Mg
= mvAc0E0@{> Fbr!bԂi[3tAYC
jШCت/$B\_5fQãYWTX<?N1"{#ˇ3}_}~]'[tF&*]8&R8F#X1JWuFDbFa ƏcEtd~lqږ] ІxEv~>r/uL\dE<\LWm
L
8i{6Zյ~<Z_
g0;}C7CHeR!h:ya0FX̠}Ed&@C2*Ih֕kaa-}GF-Y{naMƂLq@g2qAMxv -ypGC%!p5W%֏'qSqDž bq -r!@Jzt@;pXʎ-C3EIr|o8?NVݛJA%d*Qq9P8{hAj6g -z5u_y[؟8ɧ+ċ~+.HW}sq
,JNmLϾ\4zn" -6tŃ:CFmhrNjX:;"./A<\ݳU8՚[J'G\iBypvѻEI} *KϐE϶g",+7A9fi<IWrP5 r|"p#D4˚]o!E+
β4to>0l⺕6+ -3Xq"@.)|9p0(v=ŞႸH<OsE_>/2%!
>]n7~w|$R4\3)`,JѤi"o_6T^V4I؋ih{fO',I|-5Ȅ͘ab+C</[b.p/8&<, ^K^Kb{?_QBi/uu1<gI?<$IxF:&* -ͮZGu3e{a{~lis*gx,t}HAMוir!~-Mt_roߖ-glӽM߹62B\|%Ђg^qTfendstream -endobj -63 0 obj -<< /Filter /FlateDecode /Length 2200 >> -stream -xYɒ+pLUjUCx$g!&&µ -މQL -RC/صu3LQ/h{3wݟvf(ɻ(qUUV$}åmy?~F01 -BB8O&49RHhr6A3?5Mj{M;"oFZ1M|;1\N2!w"R5qH%IyOO뗧'i"_A$7@[gI#`_p -ȟժ}{ȚG[k㲔I"nTm$@pg_J]nMrzQȓ5+l%?Ԍ} zުIvxO0 -0vBtlA -VBfr%tPQf9ÃHZhq&rM߷-IY>YCf7ӁB
xޮxɖ?eLkAt
:K=F&ӁgZ_ض?ݩ-ۙ+H?cbp]sI:#zU\1rჩf-M+bped:_ -< \ ټ#ٷ^G=YDQ ܲ!/)Qi\i2x** -<YT]+~z*fьNTMZy_Α>hZ -MkpxƔPp%mU0OVXySByWӾw -<3WeXqQzb}b#EŊPo`QAP*Ӝm5Gps:=z(s~M@PW 4lx -Là
R OCx|6`E -endobj -64 0 obj -<< /Filter /FlateDecode /Length 3843 >> -stream -x]}żu)r)@ۻd>d rl~}!Cywi.fP/7Rg?yӤ#gLJ3! JI0G9@!A -YK<hiq {5_P,BY6qE -mDSjTÃjQʽf2+S_KAƆ'DLpjj=WÈ -P֍!|L5{wϸ;Gq^.6m;%X -GG(3YRHߵ[Apf&d'0JUC2~K#ƕ9U2_ea-sv ̚˨V^-B9K1ic0XAk
+湛(@J(l(ϗ2,keAf}F^OFvR&&L #i\]1-|U1m?}bVrP'wo$eɘa, -,]Z;U#Yʡ*6]=:rv>gq>ĬGa:aks`uͼq_
~1&X?Y3A\@aaOj̓ +9l -ZlLӠ1Mi ;}jG
95F
qlv(\mM';&+}80ֵVik^[V{<a -FѩPS)N!iP>tvO`d?A7ٖ.ChoF2w3bdgtr4=>4#n2s5=v. XTߎu"הl5my7Ȳ(|su!ly2Zҟj&Πo2]`pʑhJem -#މ"kZCФã8.dxSW
DL&7ِQ2ʂ!U;os| *S{j|]|_v؋D.+҅˦~TGPb?>Qh -lg -!MRBpA;Y:Wg8"Vh<QUݏڿ<X22_طmD%3ՀzpeAebF^F/wiK`@PBEs_aQ{Йwmۭb\8&.Es1+VCHˈ_k
?1P=hBD*ѩ0=5Q,N{P~OY"Hvhzk3&ޤr#ӭ&¢X-܈h#H7"ۈ|#(7Px=ъ9!7'YFoq2[e3)P/'$tMBl[F}Vor"|c"c'-4B`\C'!38by)qKD/``TLE#7dpH},W N~$ncXItIfT_uӕ(-ΉwD>?#5薮B-w,vOy}a'҅wJz.j շ _1S|!%3TxV1|1"PCLd"m6ZulGqLWգ8_t:r-X1K -{ 6f~߲:Zj[#.M:Us88_xr궃[S0ɖ)~!Q'ky'VUEㅢ%:+ -GH,`qHV? -;^жjwv>Eo``;/_&I -&B6߱G;!~+Nz -ڔPvjoWSB;8PEůR2(`Ftd_7";?8XkmTϦZ"/[8zAǍ
EY=^`0;5stt]@W[qiwV)+^KqYBC)hQ.cְWpd2㢜R/q졠ŖKݨ.~7X.KDk̥P'T.3Dl-+"Oim^U
_yBF07q FǑgr<wR],u{gt̋Ahg߽ḭIռqHr_zc[,C -j+0*c-*-."4b]X2[
JiP>;NV8X;n1nLsqt+L {G30sM_m("1pKNbt1١cc"<9ćrzƞ&-"Њ6smJ{KL\ -Rf:C>.u -iWCYpN0J=$_hPmx3UP42X"5 s$[Q6EF{R5ڵxEe -endobj -65 0 obj -[ 228 0 R 229 0 R ] -endobj -66 0 obj -<< /Filter /FlateDecode /Length 2661 >> -stream -x˒6-vAʇlJ6LU1C&_Iq7NYhD?Ax-D1'|Hĉv[AH<Ay@rҭ[+1/@F&yæZ?6p<my. #{k"H8O$yqs*x"~ǻןÿ6<=PC+C+}{h{Σ`'ve=1QZk1Kj0WSP! -(Q9yfLjkf>~G^@W@]BVuY8^ʓΪhtbtC98>ƂG<,v -ŮGi TOYyN -dExGHEsNw,9Ww=t-sjA-<v\7*QOK/D,NK*K]zm<2+]Y99}Ƙ'Y(<e 9zsFcX"X? J=ևH,I<fXx>}OQz#EtĦGJcI i>`s -Oepm -j_l2v.{N_\v+췣-FJomZq c`$x&S#2P蛗ngܤ7aBb,"!4xM[<P(>m렳ҮޥKAs3ف[i}ڂz ؍KKkC7I4@0 -\w5#{rB&)u]Ԧ]螡 -X</T8^Q,w+=^8Z 62@X.SQU5'$_ı-Ab_ۨKׇۡʣޟB -/Գ&z<e}U`Zc"#~J<+p<T sFzZIfOF9|B)!m7Z˴h]ʳ:GB/h
MXBڴpefaAVYHsĄG]&=rֆY#\`4KLm -<'z' xq_Ã_D Ye'O!1B.p{ABFB[&`(V -tt'g2vKsK2z*qt#.ГpI]cY) -^WzA(^DZݨa<$-)DRϱI1mcwU"N{H7ENNŋVbaЛKWӇJj/-&|]4o?^k/f@Jڬ{Cu;k&gQ)80`GNV-:fUCB -!2'ip#͡K<,)*b,yJC_yN(/>cvh2T ,n~s/C?X -_D'@8,$<q[1+GIO -xAL¡zS+{Wq-FkvIs -1Ey<X1Co0An/5ߊV~v㜚+@=:ؐj+\:@9Sw+,XP~nT[x(Îc[@f=X>ãY -VuwY8a`irUsJ=P
NN0 -}(,Sls#XNR~<2i(NKѨodwл/ I;m<;~"M"a?endstream -endobj -67 0 obj -<< /Filter /FlateDecode /Length 4054 >> -stream -x[Y~_ovG -~'.|˯P.7+{_{Ѫ(<Iʲ(cTEOc߿}w'0Z-}=ϓiL8k-Ww#8طФ/ -$o#QLWzS_bʛuFշM>MXQv{}]_[7oPuWt --:^{[P|NIwLRdj'V*/~Q}}Ł<l>8Qxͧ1ucsueT=^h[PWq L1,B+&r݀SeJ5s&==*X4w3BVAtNܐ4WgC?Y4cP \c=%U$i%+dنW.g,IfyIg!+#.r{1sJ,zo,VLfA*G 2:Gڊ 京|=D'-UfMВY[T7Ej읏/E(<҇vn;{
.SFTءBt:9ur?asϾjƲ4jg0Băǥz Kg ܥH7w3B?!隦`M[XX#ߋ`9?Ҹåǯ kf"iN\M+lA};i;5&h\B?oDpbPFZ9_boC'cP-V&<,%v *h Y+`H;!C$i
v*!ZێbqKŅBdТ!clM>r!zwNuyԶA}[ԟZL5"JGVй>WNɽ<xzޟ'qmEUd/@{xΓ3\@/NW0`(Uf%{ - -.YM#KovT{lǝ=Z
mx{W.C+αM[
<j6yggxhC:EGY4Ց΅*f)DUZqnT'tWYk!E8M^H~%vB;M"ՅN+Z̓wpT^3hXJ5{rvqU[0쇹Y,7uwa4S%&qfXG*K>b*MΒ4ciY lezgP[&<ԈO
j]N<,WpK5
8,tLW<$VXe)L -I1ƃ0T?
MߏgWK˽3um`}4BA* -J 8Q8f38`MkPeHq?g'ܬ[rR˥0(zϺ=ތ,WTmjYY*S39 -)9o,oBjp(ŠM6j6uһrLUyс§tݟ,FX# -i;.1:b_؍ϵ?h?i1Q;Q|uH(Y.gSҡo,\$ـq- jJ".'rJgw< - 9Tz1۪\ -Ңd|sQPL]7=g>[Ô# i;p9p\f3v(T.
/J=*c9#M 1Քf^l茸8=PۻZ/=%A1E?i[~%H*-)yfQ -FFT퇑#YÀ=]+,m>QL')io]==f[E*Ț ;R>|eK3-i9earV&G(敇fx -Qs 1aPHFukbjT .HLA`ap:@O|jސ6GdXh0\
'SX'T4bk*' %p/IVUK5dL
{ف8=Ii7`zw`oT.Ɖa/ -@`7Ar)s-4նq̼U@s{ -!>}w>a7F|r}Iع@)VKr~Gcc'"!ʷSK%`%.4n8RhMgNDt檎#_"My2J9TY8Ctz'ծ;!^Ԩ8YL3<Z4iYZ*7ep-&Eَe/Mi\BDd+Mjl+@smS}!"L٢нhH6e7+
m%EƔ9]{S0S*\(~jWF57:jjpZTm&>8A5.RsSpV浀v=jTf`~֩+![W?-QyQҘ~3t9t"5V:C AЍen%Uكqd{o֯l1yi!J?M -0qa"Nj2+յLV-g̀jo4d2y8|Ɯ<:it4r6,؍ -endobj -68 0 obj -[ 230 0 R 231 0 R ] -endobj -69 0 obj -<< /Filter /FlateDecode /Length 2669 >> -stream -x]۸ݿoMK.E -.p<m%q}R$P M0grԷUDBV?Eq"R$
*IqFČ3ҨU #E08?V*$[}5 -4ѓf.?$կϫ72y\AUUV )$Կ<Wm=y/ߞ'$Q0Q렵!t'YNg&VS EEpM;BgF[1M*v> `rQx3?e(*ٷ )|`5)B|(TQTE-^wr!/L`ubw
q?VZk;<v5{lݳZvѲn7 -plG[7^X`Ҧ -*K<q0ҍvOIjk{s!m'm -6#*||Ty_ fzܻkU'ąI|pϨQ{2/w-1
Pv3!Q;@1-6
ːvn_~6YYWNg~-QX7.2}^1;.uTy%Q9A.P0j{qcb%^i ݮpLDchѵ' b۫jJo5劧t3N!u4+䭲 +0JSm@5`w.P ; )ᄗXeTaWmCg34Bǡ[;D1DF' - -lGeu9aiqeYK zDU>ݹKc" `/Ve$֡~]77'wZf3^[ SpHaȲIX4*gvJSC/ -endobj -70 0 obj -<< /Filter /FlateDecode /Length 1633 >> -stream -xXK6W;c1zK>I39ds%zFZwiY61 - -Wtϝg4?8FZ["30W]9Or.Z^D&֞{#̨Q8KE[hxL%Rt.DpZAKT,,[X9y-sY[3##;69O4T -l5>Xgn{,T .-^dr&fQb HX58{jn((WqTZ}yRx^dW7 -}2}d:7{3 N*+@sđ#'|<1vG=>3ZhλQՍ(o+~ W9γl!Ae5
\&ˣuMCeycvqmZohD$0OpJU;;oќM_{feAYqe= L|M!LGH0BNyOНH42!NnLͼmuO'穨<^09\t^^iEPI Ttr0Ik{F
M}K9(09QXQM[WN - -P[Ұ9562#5[5yh|h@Ŋkh7j$[<bSNL~ɾp:W%*^eΘ<=CQ邁,(pNR,XH*Rr1Q4I0+}km@B?=^ pe.wmY}(FwGnylX*/
-eendstream -endobj -71 0 obj -<< /Filter /FlateDecode /Length 3350 >> -stream -x˒>_K*
-&D{ln͘:'i8GlL"zgMs?zUFrs7{`:?wϠi6Sko3e -(\@!R)Fv;G33ӳc~Jj]v\Uƹ2v4jMP|``;TrS -մx#EA;)AV4
!HH+- k|oA/AstiZ:x..gO(g6GX̉j?jJ&:6;0}A\xR##![yL*S<HgKnWլ2Caa<q\a2^ȫh|)VT@5&AL>ϩ<2hf60qJ5=.
|Tv`kEa^t4> -5hu7vV_U0J[^xUU3vU1*v&J8&.2c2p:{[LEpfwW[pku>ka-9wFdg:;r`v`8C7$%26}iK\g4WHXX1ւ 8XlA7CΊ4HYv+QqSUa;miF$1`0D"r8ʪZIV\l<{y,IߥNҚhjW4Y~U(v^N@0tKv=ܚ}Zڃ88,<jEWqR?ѭc{=ncEWʦ&x`adMZqH7wlTC1ձk߸m@'ME.hPxYySٸJZzC-#R]W? r)]5jHyND2lUJemgulJOidY5f;1ݻ؛+dHX%\G+|H`SEDŽfk/!dd@bDoC}$S$8!Йj"̑Dԑӽ7K; -vOYS4Uc/(0TNL}!DzϣGC+pᰬ/~i>`3#z:zE0Ucc.h%c4OsF/ÅL{P4493,}
40jۚШfc+(e -Gk*
}6O?b`ᩆm!-;Ft~"ᕘ;8WrMd gCׂ%` -'7J,jb`aZmb>+QLTPiq/`&xEvG;`mVɣj0]Y<؍xA@oU͋3QdM B r~vwA on/Ptipvb Auc:]y:bd:RZUr?}S~4z"fA*HE6!\"\;$ xAwe8ϫ_}dpI-h/J0EM] -`P?b%{>R<GL3^^f &XJZ{&FAm:{;J|$\i+oaW/NyZa|o<s+\2[]'y~<dVEZo~}'bVM7NToяa?1endstream -endobj -72 0 obj -<< /Filter /FlateDecode /Length 439 >> -stream -xSn0+xu)ɪkѥ@ءI$-'YNbGpQD||"J({-y|2fpi0`#D{@N\,G>Gؒے\/P>] iQCpVAӗVNpeCy?^,g< '<ۇa` fYxcӅ(.dy|NWMsϋh2&#(R֮3|Hej\cR}JثX-֜Q#pTj&4Rm..-X9"ҜT:ykse-Tk Z5:Bw~;yp`;N{#فzQog1m[i>G]|kTfIf}endstream -endobj -73 0 obj -<< /Filter /FlateDecode /Length 1402 >> -stream -xXݏ4߿\;N/ !PxoC=qijd)/3c{7va w'כNI0v(&\ "5qtD*HĞ4F~ؼyapi.
#f}vAȃ0deK -H!PM<۳(c1[F|=46CAϲm]Y!NVE{UQiiO+ϣ9=eM=ޫgV=viWr/q[9B@3d,I -єP\'`<\ױ̒/^JH5ꚙ["M9eQOD?J]CF^3b,mFL:QB:Ng2ĸupљcY<EW=f)x="F܂٬AE3&b -pՔMbsMAu}Rx:LC/[[i.(t{5!@L͠lg*x7XNyceԽ{rmL}PyCK)UCn3j^|s$7cCi3OzδL+%vLEA>^x7&d\[Bmu1o/ؠ\4Dp:&mdHr5B5&B{ -
Ӽ1]j:{OHQv4+Rxõj^櫤ֱ7 -endobj -74 0 obj -<< /Filter /FlateDecode /Length 3768 >> -stream -xk -}KU(py_Q4IsE>b,/<lɋHspH?AJpiqP$AaR ,L0M`zEwM]tr} 8HeSsI"ùȳr;8KwT0%Pho=Ա/ B Co95u;Qxۻ/˃8 -)P2Iy=xo.NO6V4HD/t$h!ދ$`ȱ}7 s5ɑtFQXMdaXiaԪ0X8 -,,MII1x$$|'NC;MҍV
Ƀ'qWVg86n(
j35Q~alմV6k
rׁd0LL8N7Gu({T`ʏN[N<AÝtlǮ}ܓ
xA,Bf&X,:jDžY⨼N⌊>$ۈQCj<NԬ-ɟ9<8#$CG vw!pcrp^UR;OQOV+6ԗ҆D1 -mQ8mӗ=#ɥ x' ɔ`ypȨvӉ=(FD]K7zv4|D
_١ 䴼omy)*yŢ¢#]"ܸ##h 8ySU!>8Ԩ{JDVYjm']Fon6?{Y].ͥ<̀TG 1A1{]ۈ_7y;&yY֨B`1葌26ӧl -kG:C\3k_FZߎTѦ
`-zѰ+0٤PebQN W6;u^,ڒ3C#ڕxHGDkPV98FSs PG^;T>Z*t,lNdЭ܋'*T3GHNLHc0Ăbv8_xbkp=[GjBzfReQgtC -q\W<E"}" uINr25)LB0c(wsp!r>!Q\OSAdVb?vP-q]._OF(t,*aV4uAuZ>/%
w4tԮ;h]zöZpMn5ZȈzN귆JEځB o<$eYYU{HSJt3iQ3<4Ӿ3Yמu77D!ρ-N1`.-Ns]QmmBǟ2B(>!zSO̓<ӝxtFO'=i\LmDžW%hg_'sq<Jwg}= -TWyAun&r<Xk,?jkӶ<yV6a}j3FBG4uRRe˒2L+.ƃPSYFrCNGCvh2@P!R1g$OX{s)Tڃ6>N}$L(7 -+-OJd+l7c;g)eѕ} -5$-f;+r$}~fEc`ˇAchsxo|꽍Nb2髺=/m'WGRpvq%_MBvCb`cJo"ZBL-Ƣc5\qM!*bS_nMXR_WL_~tM՞y!Z믫EPfG?qH_aYEU'Ev֣qMfPy>rq&ti$ԥ?75@֖Y6"Y", -mĪdY<ұteKL~ՕGl 3G<(/1}^81L-TՕR0OŵԜtIFyQiv7mU.ib?wo*yrƴn16)?AELkd%p;j|w,M7!}kfp*k(78&cC|\>@TFId;m -x/Uf4QXA݈(v+>K.DŭK-tt{#9z+4\fATX$6^GA(gm2J/V -- -D'ɩ-oC} -!>?!uozK^Kaۑ!CoɊ^`8.8Ί>Vf4} gel'LJA$t~CFۊ4ϗmX[DZWyЫg#l:rA9xjclBGy2367t|YCOU~!fR 6Q|S>+#<}cP$brCSz(0$N/{'J,$% ơG" ]8ycTz_w,?ªȪx-?C8m"Cg$ݗB5 GPGq7kun'AM7IwYUI88 s -PZᝎ/WG)hu
T3M@v
jz6llvK-Б{aAOKpF42ZK6`R9˻Ϣx~k~KH]B t?`'6C55k$XĠ-m~ -endobj -75 0 obj -<< /Filter /FlateDecode /Length 4504 >> -stream -x;]6+(;0l6L\U._՝_HItٮE$@DERBQ;}&(3BY )v4kD4Ow]_>a]xMG6}.
dLDaR¿((Dؼ??N
CT>k%[7/}?,`
FiN0{px5ƈέ?5&ʃW!bj 붑ǀ&df^&ΊEycDY,Uw` -@%+TeV1`_0O{՞aЩׇz2A+ccCrRs՜VuaSee$l1/9=lծA0Qbgt=T;2p|9szhD09g4k*.Q>V`36Xzyo=WpHaL95cŅD3HRʡN+cOI^{5Vilp6!7qJѼaI*bmeKQ `
;lPOzR;@jEekv"$X@&٫A
I \DI~:*\<,ZbXgl8
1pZzvm45/muwU4MrWsߝ:ֳZGqZ --) z}Rdu]ַT!Zj0ͽ5Pz?aAsWη`nTA`{U7,D)SVf~ލJ)d*NtNFxF'i?d⊨ԏ k]v?[ -.SuCf;A+z -R! N:mqo(T20(#MfyN\!D~c<O>^A4:Q/a`aƽHeH F7/AfТ1=,;/ ڪxcÊe
T7Čp -Ɏl]PvG{Xp$S2<(&Q7b4:\8xwG@b\800|d`uQJ-.Vhx -'CX
`k/^$.DHtu\lP^sQbjK4U;wݙu!jﱩ/[Ee8)]ط]SCGPMtr=C}%
.6|lgu8sUC+[E`
N%v~p
v7}8l]eh -< Iۀ*0^>yH|@Ck wEq -A7(/gqM&|fݰ/,pbU]VtER -?_ꧪ1.<{w0_ Um+vNp@
Z%r -7]^HkXA]<[:XaU@I˸,ZQB#/Uin6N8mM6yNzcEk@̧W.#ivz&cs^?`%4_В^ʟhϴbW$ u=Q$EbxشmFx>2cni[}zڈhsƥ~2U?@Ixؚ%
Ӆnn@bvgh/^}#TYy&"f1tqF!uhg>a{w1Q_hdF{+bd<irACi$Y#=*utqw~
l,3o
~P~t{8y9iDfrfB7x2MWOo1⛃ -C; )H@,YwPo^!c))ke+:}ZمG:ITm3@4ʼ 8&Ow}TRQ)>IM!ҷ*6/G?(JC)`s< -f -]`R-Hҗ:R8\ܯ9DttV^Nɉ?"x2[W_}09X0ګ98^~(ڎʨꆇ0D/K7A[Amp'2_b9_1Gˮ<|da:+2S%Cԟ3H9ہC3=G3o-lǂfcK8)qh;=]r&k1~]/'b2UA5!HP-V ԹרCX7FYl -t1Vt<yްU{a*OTawĵ:
}z࠺O`&҂~W8@68!V4ڍ9%[z[sMendstream -endobj -76 0 obj -<< /Filter /FlateDecode /Length 3952 >> -stream -xkܶ -`Y -TrNK15tuch!&|ucy(^&QG~`}0060+V^*=$-0#Wf[O]0jg>Mzlx'iݳJqH$KD[g"#Yf첶Es֩3vZti`wZϬѰK76CAGf]͡hYI|a -IDı]UaUk[:Zߋ)Q<_#s=E*
cU'}}ম$& -?D|֛'q~88 ''ޡU{aʊmE5(358Mp3j-u=6hÄcDZ,!>v -ʪG!ξ^@a6$ 3. -o
wE<cj}%M5X_klG{ʁFsia4+"}O/m -Cede#Dѳx+c@ i!n!lPJ</-6`\A!ex)HL;-Y*t7={Xn9,^E Jk>*w,74Ǻq7gݷ`}F"47xY:p
{vNfkvn:v24Ɍ}sGej$]Mq#|@*(ܜh.~ -A98lؚ-WHB9ΣRgA"LaxӔZq!x_L'El5,polb02~@?*a}9Wq SIQBbֽ(vs"_e=bO kٓD=?!,iLu+:|=.l^Hlree]:0_ -RUC&@$zϧaBv@7Q -brF1@, '1ya6OnF)~kxSݛj"$"Vq0,DJ$ژ0T/S&f@:0r 9((+xJbqK>#75CZY5ys,TsT,^^
ѼUe1dRiP'0Ӆn6|QO N哆)mO2[OtD8"]}qhyf<,aW:E,h8 -\_:xmܳIukyaqj[:{> j4bZ|kĠfc" T3j m -L'W,d" }m;`fC|X\(!gtA-[!̈́[!,~MJE:fDӆeUR`N\/v11maMDlzPPJ3*y$1%i(bqа%c`ZƘiF)p@CglS:ߡS@`ΥD=EEdzw\z2Q䄆QYE J( ȗ(8Е53aeJ&;Ud3
cӽ[ -ŝU
(R0K5Yٿ¯}cTjfQq0 - w͈)^Z$[Azy6uz=rXE1S@JJ#S -k:K\CFv|[-k/4~>to"#s2rOVXV<U4ZjC'=}]c~tGe:CӲwψ;UdDBJ9*Aha -<یz3NfI51],iVEeNL<k(q? "IUMBT4[mēRVVgF.`?PY"dR6y9rΖ9)y\F,z,d%"R^Yl-Żg'H'~w羺?UvXwNrT-8\]Rĵ;ZSuagLyDE\,tXj0JV^rǵ1rk'V_zG;a@GDsq;lc\tƷTDԠ$QQeMnUKn>[ay6O1\2Kڥ?Et((-3.
Z!vԕL]ȌR}/?(uS}=Q]=(M#j`FΫ$\9
N~&{h1k1q-KTZ\E* -`kuv
w-jtIcJ ]m 3u`A8Ԍ9h1vʒQeK~ -v@WI\^qKY5m( -(HBG:7oe͢,1]'אx^UmE/y8WS+J
wM\W Lα^eڭqY+HO(G*H2 #>!Y|'7. TC*@lJIG']:a:WO`(0>e -1E;伶«)V?P(LeݠK/8aGلqq缹eHKXT֚GQBOOԉ]nuRK@ r8)❉! U(&mք[|a0a^? ˥^ C=|g۴cSpJJx*DdV<-2yA?|IOxc]Z4~4y7 -ɭvm ]!P2kLI!>D(iNblĽy3GϹeQOK1.09=8_D:$aLҴܟ} 7fe0J{rG9EO}79[s%D -endobj -77 0 obj -<< /Filter /FlateDecode /Length 5049 >> -stream -x\Y6~_ovodu>Dmz퇩 SԴNx~Q(A -+b 8DR[9\qАkb]A>˂9['lUW7ȫT%8Ys5 Nå5mT?^mg:}Gdc鰛[yaS=BH^`ue))bHڷH,Ƕʢ)(^>&Mp~q&h##(;-3eo`܆შ^ݮ;tk%A -z'ZP:mA7j`98to#D=?TΫjr}៝'+)
8e\bP7=e!آ8r"3=[̑=͵ƤZZ4Jy'N&"z=ʦBӱMJnIBRG>p"GhP{O@T-3ѽy+APSV9@_0x,kMGla>ɦKCUԴ`z -ЎP+Oz̥n#diJjcmߗ*[jC;MTfa)c P5&hMj=SҤ' - @n̊uq,cnZ]!CsT;Bn%D]z1b{"IHJ? -[l>gJ)CGu ÅP9˳̏j1$}&s+,X`ΐ/)^AcoD5uCZ΅aς oW}yom,m\ak@oY@7ƘU68OmG$:WqL,AwD:Gi%Dk7.1Ȍ{4?"yq Kl^N ҆2l}$LjwY`@CmOcU.\&+,7,&YdJD8ڂs8K.0דB(DWSm -d!&j0^7sݍ1֑%r"y/lӂ:lFG*Z˝v9e^x:K -2n}mW]ZydI0vneEyZ4E5j -w1ÐyBmB9R$tXAuNyCBݜ%cn0XmiUv΄*ڗu rw,O>Lc<}o({I3/
}<sm -CrCP{`ʹ2 HΝ+FwYyuq
E1ڎm,0N0:AP]q8-TOԎ%tuImkMYѐ/D ~1V\d:Ŀ:mYw^gޡDG$ -M]ݯWeAK%beBL1k[)/DA01`Z+HAO-gfpjϓ!{[ՏVE]6GZ[B8;^im,ZeXk'$%ܻhkA;~gDԨ1 7] --*ڙR`; q}$iq:xڎ03b!Dmb&s:/]ׅN])A~_l-nCLE(4`ӳߞyV)
0(/īfڬsN*kT{`YLe#u -3VRƠ;~-A]%n;MH$/wcjFd.*OkU)>;R~а&G@Aԉ^ߦ33N҄B@7l+fe+z~,GPvmx660;Cv2i3ڵz*mzSUU$R>Ad(
CU. t^;^bP{Ԇh0*M -MX2U'ߩ-`hΔ! 7$}W H4#09QE^;fـ-0e\ETZ*PjpP}{['ê~b?hYszHD*+U{[!!}tqji'8F9cr"{1%(@JT(+H@YgYF7'E03eSdNr)Z\;&B4>띲Ρ)Ԭ"%VĢ^iEmJ{[l52W&#ϮPljRj⇾F1wkVq0|ǹ -$}xsSta"Tg}[
$|Ԧ!)ی/d -_uM7?w<MTB2%Hӫ]a^&Sr$kNV|XN,W8qW -8mendstream -endobj -78 0 obj -[ 232 0 R 233 0 R ] -endobj -79 0 obj -<< /Filter /FlateDecode /Length 4039 >> -stream -x[Y6~_$UcE&zks؞-ĞF-u~A$>dm( 1B//ԏtxG^+ͼ0;yD^bDd(quwϿ@aPrްUIxOwyUꅁpp&2? -D^Q(:q}?_}XF:_1_I,a`l[4A?o#px/6~78~96ھݙ`_uP~}.I72Pcy?F4u.d8yZATE;ݪ'QtMȞ3W_08_JO"?q|ׯC`jm!,(|[gr'FY [1(@( Eh,>F@C˼7h -h jlpnȶ5y>ڊUw]i@8R<زA
dW#QFP(M04']ttHHep --Xz -`+kA2-cMn{A@JG38N3zt7V,VT,"5A'Ru -ʶu4t?)S$}UYOg/&yb+v3JnY\hX^Fc -#UCwp[kZeȭQ-FC}8GT"l~O&0LH4~x쮐B<r(l6J
t1I.u[˖|/˸_/U7Y^:woN ^z0}g4T磫Տ}^;(QjZ0F@CV 7RMƅ|z+1$}/!`mv`;$@h -sE+/#ɉÀCq!P"Q--i蛹LI<6"b:|QL,?c}D4;Y
GAq$$8u}wCR 9A<嗞Aֿv8b|UGu5R\$D43_kІ.f'H/-7 L<wyNA8]\bw1s#!%ILOw HN"#|'~jc?t> jw^Nh÷+YQy<agKfw -ػt#80+:=Ai~?òuюLu\$"/v*'̱0 8M$}H@vDi!THqѺ_njBtk -ѴGUHx%!&ƅZW]h 8GvigڑC\͂{h -=01 ]9/D/ -V$uB-3% Q9*XU" HwWfer#˨)8pNp6"A#Sr+icQvrwQ\5WcaZ@?<œ>ҏ3;`PÑ%=-Y#1n>;#ЎrlyGA ~h8+fny(sH%Vd[/A.n5wtV6y#ԍ-8π>|![;2ǩoEܩ}S=2A/dmQVC3ifu4A;YS}:]xf]访Ke(2 -endobj -80 0 obj -[ 234 0 R 235 0 R 236 0 R 237 0 R 238 0 R ] -endobj -81 0 obj -<< /Filter /FlateDecode /Length 2872 >> -stream -xْܶ}oV4^ت*Rr!v6_nf'k=@F(1Rp"rr҄gdO:#- -ܭ妻7?ք
$y_Ο>:#Go"'I.aO~}T]M6?IFq3i&_$g?d)=%ﱚ08SkhAcA4<@PQ$YLp?d/"YA8M -B -O yN#y={S5yݴ.#V$(c06}G'/A @ןu -(/&3nRA -/ḝ|`Pfj]T ]7^U] -ymUMa`¥f%JW(Hay5%lˢz5xjBg%ORljTvw{_\f -%F֛^[=u쟗Aj[=3%hC'``d%'9hD"YS%ԕ -kVI%~HT?odx -<3C,3UI+=o]bU"LDhhL?غk̅C7AΝ#*o0u4 ìP6#uSFvx=S`& -YatTG0=/p{_.=UVݑFS˳xR}A!S'D+r4DN7WX2G'C^R㨦~@:YE27pǣbySXONLD -1Wy-Av߽F`! o+ LFsaL!NBq(Rpk^"mUUU{Ttr%.6^|5ڄd^~5=^|n55L:z -qU*D;ТbV>]κߍqןSH,Bsqgiv \Gե0,}'{ITi60 -endobj -82 0 obj -<< /Filter /FlateDecode /Length 2662 >> -stream -xZY6~_ -7
ɧ{ڳK퐺pɎ^ߚ9O}xOBa -*/<5@c[h5WEY5衩~i^ph^Rn{\nzg!_=u),]PPve(W$f+b^7Cbuu@4Ŀw<;+ei&$w#+4!(Rkw2*"TB3{:OAKr3rg#M(q_L2ڃ -K-% -33U{I%[\Kq櫵&H+5л -)imֿ=K퍠.)k>ԟ;xC14myа4u5>=B'"Y5|e%@<W -2G٢nJbyf\Q>5qn&g*YGYeI/:Gp*Cbk]VoLx8@
bK<
ckA#=
YT"Y˩F:DkI'F*y\5'>)wT5ImENFO)+9ȏYƽiIGTLHf2i_d~L8XhBTgݐbOҗd2Z\D
yof][-̷ۆW͓,Bȳ@;y-ؖ ȐyAE/*M >]y^L\jWOVמTS&^ -ɾPW,~ XO3T#/:of{^_GI|k]RqMR:`~%pϊ=7 4r%qc~W%O]^lPgY7[!QȜhRQSL[65SHe*w GA]lS@s?L-}z3b7k!f!F!&!迲nf<fdM_4}ꚾu_JG0#9{:h)܍^WU@ʂ0s&o<d7%)_g5g6; D' 8ZRCcJ$b)<%w(k5jWKhtq\lOZ)tK ƴB*ɈHvM12tj(r}|oᮧ\
<dGVYC?P`ؙAgbꃰU%3_ǎv3='<Bߜy!7
\P;L{Pą˰(:qgQMDqYdmnpƊvmUcrhQxl%`+R`-XM7UBoIwhҝUdyE!~ -A#q,BbFRƂP$$=箟-4]QMW:õFXohD0_3Z%F_N;DSBdA!Js4[QC$!Oga=B':|en -b7y㗭-[@ -~fWF@'+J\>4#?l@,*>S-a5Wplp3`d zdwpdE]C,ga# -endobj -83 0 obj -[ 239 0 R 240 0 R ] -endobj -84 0 obj -<< /Filter /FlateDecode /Length 3013 >> -stream -xْܶ}ova&&QʩT,J^0cקn<fʃbj@_h7!7-`YȢHIʂ ;0֨U" -ju Y]TK'<R8ɠHqJ=֕e54)." AE_zIE!! V80H}Hy -Q(+*)'!<K -\]%;QF9[ߋ˘KO2wǠBP_m8-(jX@K-D08KPmFU ahR
w}&:Re7]zՕֆ/+y'#A%
:4%cWBϭ+Auar= ̠K:̈́Ɨ -pBrAoT2g<i4>4ҝM/Uc7LviҁF<[~Kˈ*Ds89Mp+Tb34+hrƅ"|_sOfA^Kf˂u?4j\XnV,r$I/"7eCWqFcRY]6_n%V T*<C o -B*b:<ş 6dl>iNhM <Ty뾍t;Za`aj-;Qq2sB8!: Ih]m߈6OM[֍@aia#v{H<|>_=fo"ۈ>59ܙd;`+'ܙ>1̑G&nkuau4[<Ir"3!}{∅?a!Rn+nu%(WPM}|5&pћulRWxm; -/XcZXSKC7Oͳ\ZZ!Bk>jjbΒt?`6r`{$;(f&{!lՈF:$zͰShI-([VՕ0W?dk6Cw+9;5.`xY{\M| '^wiǾ>R`L} -墕V㇀&1~5=[$u#} -endobj -85 0 obj -<< /Filter /FlateDecode /Length 3384 >> -stream -xZKܸ
ϯ-㪱VϖgSNja֎mQ=ɯHR|.7>$ ->
wQϻ~&]tݝiGo|]Gow?q<XEyY<(4$xj)bnM'|42'B/ -,ZxkL[uj ߛx?S!8aIzsdVQ&Ip ASY|ڹ ~l;p, "d9vIo`qpv$eݯWs>90ݫjN{:ƭ,'.irC|rGGGC -q==ARsi=4,jF0$9=8,PtET -~~㘏'NsG@swY$Wo82\#!Yy"{s=0JEݕLwo(C2$
5^hĀN'mI*9!sObK1(P1zgFdkPHjBNJ{ fw -t-G9ȉD@@ێNAR-Z*N9m~ļ-%Bm{PJ*_NOr*t[X/1Mk6B*5)w麓`ZeM;Geu̲0\~l$p'0g0ZGJ2uB7ܞ1\ !A7##eC;臍Jkј=c(az7']qMj15Ȣ}s$gIvǼp`>Mxb<zEу:;ɄV_<HOrL~@,yFF
9<՛ -Vr1#Z\0gb9h^8eqH -4/$'fJwHeuE2~]sȰ"BH -GM7a=P8/Rl=p!<_w 0zt}ZyrUm`in1iT/3 kv&A}4m -$:":Inwe.IP<=|SZ&ϵw`[<<<j;uVQgwbj nql<ZLfbwKuLpeb^hH4tj#,4*q}0$ -sh.#ږ\gDg3~viBDiWg۵3I(̋2*E]GQT#z_Dp~r|}`&XGԸ\kdZ09[%&⎗l.,yoVTSlnI3z:ֺą.MUo-.$01aҍ4O*CCt{~!-ʰ(wUYl?Ӎ/ŋ%6ɇÆs|`ދW2Rq#.Q4
2ݶz3j2'mޏ=tF=zEh4hLElޒ#ӍƫDGxFoIP~SUOO -0I8MLJWs?鮵ѝ:tmm(
t5QURts^ƸJn#İnD:^"&A 15]HЭN~Ax^T -i]AepH4(өW)ίZ:CZsY6io"mR+ZhO4[LZYfB>nNɚ,%ꓬx$:fylTH|)]+YIQC9c$)z] -{\r{.yD<Y&t;SݏFߓzϫgR\EE7WtfrљP&֙nhMMQ]?(
6>H~e;&<i3>(cås\QD-Jd!-"N"W+/t葼fH3$ј\h=4^oɛ>*z[-u$_ 8=)l]1}ϖ5ΣR-E3B%ݭo ҖylyD3!<wa'ٻ%l^j'e*(:avƽ -MxqE.Zxv;ʼnAu3r˼wMx5MFXo$:%8 -Xc7_тqYcʚͲfάoE9 LU$1e!fʯ[Of&?0ʡ"I*FT&LQƤx;p
XCFrT!?slCJUM6Ui8J -gc]+*o/ުdH&EP{W06\I1u{ew' xԆkßNͫo'NrZ台R~յqQWziyOA8z%/r1FTv)2J:Q$vxR(Ɓ
^-
-u3WdSOHIqL{eޘy*D] ڎY˰+}ygz{}U7$QІֲJk|aU}8endstream -endobj -86 0 obj -<< /Filter /FlateDecode /Length 972 >> -stream -xVYD~()bO>bE- ZixXwbajݾ-OCf$w}]WUEAPXO0྆<0 -kr5Z~N )bp3k?Xjz|'8ȣw [P -9+E\g2'd}l3ѓ
%ӝ|@ۀb\wM?Deǧ{Adb:@g;+lEېB -KH_cɌZ/rhE<^\nMF$ވLQ)EpV$㴯.ei&Zo@
sv%jT)mUWj,U"4W1qBoes÷8S8]isPCl22a\ftOG5#5u" -ce61NgQ@KeuXd3>[s`>yd˸E3L"tȢ*%.߀N*eߠFݼY|1͔YYݻ_1`۹2G뒙n1Z__1C69dzxؠh}֟/3
clw$Z5_q:(*ތZxendstream -endobj -87 0 obj -[ 241 0 R 242 0 R ] -endobj -88 0 obj -<< /Filter /FlateDecode /Length 2156 >> -stream -x˒ܶ>_WU+.&uHxSN(*,0$f1_"8}h --MuDsjtVW'<8(O' -/]2թ*X] xSB2ZZ,b0--e$-xj訳Zc -K -ljO^:8Y#wA5j{V -,yGSN|_fU,Ufc#Y0ԏ(ۆn6?M'2J#?^|9X[Ă`c;32do -;b&|EY0M!Sƨz-q&K5 t"E\\n@5fC7ۋbsk=Z :P|xIK`;{OWQ -*mKaV!\![B 1 -EkWE-5)vōC7-KchjAP|دa%aSsvZCkϧE5]qxD'mktBhSb֒4^#f!&^p} 63/r]|Ȁ")fQ #3&>ɺsP`zc~9(&0=*0{K⬸l/~TʑY^$lS
kjjAg3hM^C> -_2(zP}0([5{-D^A[ThAGaGnU,T8TDR~B?j1{01/ =7#/0_ OZ);nEj3!F*
*(k/P~5c](Mia۫nrs.>endstream -endobj -89 0 obj -<< /Filter /FlateDecode /Length 3127 >> -stream -xrͻ -d
ۡn{q F&\o(xE~W/do?SEsI)C44<vb`GmT1\p~߫!^F@S{'bd~|<jeYLJڰ8>gsB-/?|")7xfHRo+a?.rrZi)~?K=q|<VۈTռ9)>qyIe2o0; bڅ:szד05Q3c )d9dɌ2/zZ<m&ּ:.'D1?aXb9!'҄f+pGi i -
} -drFV_TۨnTNλZFA-$XwBI>CUڍZ7
9Xj-p%Qy&,5
NBsD8Г2 P_pEOJ2r8`ܩ3D
suu}k/&h:u3iXkdB^ݥrK"Ki}aL,øyT?r\H8²lE@CdqW JPJЛ -d.hWy@b֢keݨ37v7Bf?,Rֆ#Cl9fK/7T@Ԭ}^ƻ=um+?/qO]yBk*I]ĹĄAܠ.Ma^mZ$8gP$ĦC;&Ѧvb!^R7̺ -3;a]Ff֎fTS>a_rAD{nQ5!ʁ - -fGNgfr+GMWhO|F9~trl -[m'=W\+.7~͵9scFgmPQyR!j3+/5]Mvz</}U#Xjiub#I510^N_\;xs6'`[G?paH4oIC(-b
3c_/<455S=*zQ$RN$qdJES$at?BGB+[`9/;3;KHXd}<"/͍ª\qP6'YaA\+L(#J,X N*k(" - *uDwlytXE8"s#s]0@7Qxm(R9!= -h*d$GdfF6!%]m;|ȥV̓k%Xuie@zbZ[a4h4s}lF<{Ci|$z!jнGr!
\bhYf͉ Px7Mȋqz]³|1k/k_z!\k\!Hv-{^`&x% ^E -q~:&nTACwo+GY:X<vR@(sS -3>Dkk<V(l "E6;#fJ5ɇ - -w03sPB}=Z)/5AB7
Υ?/L -endobj -90 0 obj -<< /Filter /FlateDecode /Length 1415 >> -stream -xXr6+pWihKIı|%B"S\d4Drخ&zE#.~$ɮYHGdE1BǏHC$HWYW@IW%|qArѲ߸n%ՏCD<l&E(OJR'ɦ!_>ۂw/=mw<pl~[}\DxA$݇" -7Vah -Z|#1|.0\;JyWEqҋv^ƝU"gz^YgpD -kqI':
Ŋ>KjVJK<yOezuv-Cٍ%׃ݖ[I暼^%H\Z/3H#gSwm0$WCr[Cle {w]s0`m,{^`?W%ҴL;g# -':K*OU{0,ibm8y]buNk5Mo;VJf}̲ZzlUKvr_նl3fmOyx6DW OM^6Xʜa/w!#eɠ"k 9ִ-YյA)ү<8/ CԢK+{p"E^s_1NJ,$IxYq+b4IeazM|yqxMO7eiy7c$? l@[lٓ,A$&H[ dfUΞ$ѥvQĢj[ -ZpFLr
PL(I:^ˢ#Ep6e5jgn-#h0cL9JN6$N=û#CbUjD@#HH[7wRH\橋I\LHy#N -Ǐ}#u֊m0M]U_=ވc
xe ,guݝdtctxYwF`q_H\Oj:W 칮ØE?k50^bk64#OEBy45MxfYNFCۑSM'TBSW)^(Dv!p!c{q0-"~ps?˜8+`GW7mߛg -endobj -91 0 obj -<< /Filter /FlateDecode /Length 2600 >> -stream -xYI۸W6}雗xxܶ;qf -lMNxk| P(T_UA_7>VI -R#K;wa(_s,';<O+SfWH]mv\ֲ}צWYWO2EF~&=upkK~\E5g|X1G9bϋÁHP% =Ӏ -'D5c/ ɯ:`1-yψmVh{G>mrj mk^G'.xӃmKqY2pWw -gb$0Xt>T*8LWp99AK@s8ji 8ϰ0bӾ?~|tڍ8tFlyEu.l] -A6t<r@;Lw%YмYɄ3 -u|؎LHrr`-3sōA -E1iS%-x U
QO+Cm!ť2V'9dá\z9WbgII*&A[
M6. a&*0_VI%8|URQV+<nG -mqU\X2P͒A1R(5?kX&$ z\:N W]'NO3Mp`)]X23ekXr0p,*$Yqwx {MLrdxڱMq j4L]{uϿU4@\u`i;uWSW|pCW,x*x=
-QhcQ,}M<!v"@w~TL -ʶOJGҎ$n+UP+YVߙ`Kwn'yfL\UPÝ'gpGW(.,uw\赑Lz*6_?)-*,M>'RqP+HuY#aCtl m -v'*o;XX .:fTaUU4UFi."ُzCZO=MR5e96xE
VfG -_'Kݗ"vP+vgv *Q"HNkg2q^fW_\֎=/'ug -TMJܯCCB\iȝi*w^rچe$TEV=&$dμ\h2m4 -+lri̋ҫX{EI\t$N]HaboߠC~D2!Hc ݢ%G4L<`<2?b2ȞalfW{ۃ9N4&[)L -(ok3® -EoeVW4w>X<gydyTX;'.3ߺ$|WlU`Ջ__VQ'hٗKMlGߐpIZf]lSP%ءofq濎endstream -endobj -92 0 obj -<< /Filter /FlateDecode /Length 3288 >> -stream -x˒6>_=yR5IxM*Nxj}HrHHbL2Iy[|ȩ=dT
4FwCB
-FGIHjT0,!G ip7`f$. |H -yrluS=1EAy1Y -ù\vȽaUITh+bQ.nv3GU{~:5/ZsJh5yssx">ElTP9aXElvؒxDŜf#Z{S$lj˲`q<'G:0cO4@'ϳAkwXY!JR'x7F:!ؤ)-bTAZ܊6isSFsr*P4ˁxNڭFJq`MS;c5j'à>Շ\#tD,Mh)Xӆz[7x]\ȄђU| XJT-xY>Q#[{@p<эv6*"TzQQpL^Bx/Sns%=[vk;:Sݝf!D!U-#f
!ڷcsW)$}bp),v㗨4dzرYÀM4s[=X~!BUA$9}#V4
Ar+d}sV/02z?u2@
N )]T3N(L؉蚺ҋ0U.J_ן4O/>k<6:J!of9ޖ]18v!eANSr㡫U.A͉C - <YbMP@|ꦨ,è((] l9S\A2m0HNidsCQK !}݂"FNC'ѝT*i -\+(Uz{>p -~X+mhҰ9vod -IM7$5M$T06gDPMφ,(0-B;`yU5%EZvh=H}?L,U#hZoo&ҝ{吝xTҌ7;sˈ`0,@@$r<#e@pM!DJOxN}G
Dw -$w'C g~7j"a4/i3zFceyyGSỉi1U-@<;rٖ&k@a :V[EK+.l@<_g&1HK~L/;B.<iGlA
ꈯ Ԣ[zI~IIO$3nNb|ў5yo)c7.
8]$NoJn1 -9aU/s/oU|yNH_sXeJJgu7flhgK5A|U]ď_eщtkN)`/Kɉ4k#5t5Tai@uj{ՠS@Lla}@,PĨ?"xO?#d+
>\AɭÜyYQ^Y!K'"Ub{c!U8%EA;Ξ] K+tudH7 d%.5>X,kah*i2'TêSW9Ϧ4<}}} N{~%8\%FZ
v%[Ϝ_; 9@JγGY -+nvrԭtnL]&IB]:iWN
;^R ->mq)wvp,lg -̗D%'^y/h'+endstream -endobj -93 0 obj -<< /Filter /FlateDecode /Length 2980 >> -stream -xZY6~_8Û&>Nʼn홬S"2!oٗ* - /BN} ;ђlE'v=w8DP; Kr:+gnꚼ+xZޟ }뻵e|xirjf /^a%A7v7x@TSJ`XӍ/,6v6Sڜ/iS]7e=TZ8i(cQ1B^t<֞Y1<NY?##[TmhjhѰe_{cp]˚p<i|M0*KYٽ|t`mEc@,Ԫ!Њ!vyo7jmq6G|5/(+OǞ-#J&ƀht;|Z
dm0u)ƢM)Ɨc[~diqpej}tyڼui7;łlbc+[z8h?A8JJSI:Hͷ-mg[<tQԂhZ[F\%&F0>zJ,17"ЙɌ4o-,,e9=Sc$q>1.&'*@_lܑ` ={=XRg<g0d:2H)OrX9aX-s\l|QHdܼ~fuM%N]? -i"<Ne*;1ccD&Y,o<@ϖ+{;ORCb(y'-+'Ӗ7+MC##V2àJUHԇi+I>9VQy{uGVzpk.t\4݅2fkXN1DzF7~p=#o{(P#)țiPѴ='Q4YDIŎXu/n ew\l
Afǟ )іjyVUŘRS˫ -jO@M*83uc#w267
'ޞP[TX,(*#EMp T>@5Ҷ0A#znPu҆XS(6 Q@-BdӇ{~in0#@f/l1wuQypxQԢWdF[uW2/AߓV -<VӊWt=Y/kV2$<9g4|{~MUE}P/WmZUޙ*D+s?+W -ؔb6EoUan&FZ7U#uwJlx뉊pU `ګTo܍06NNRQ\=\iIr,WMc[H>سoA$Tk Inv ^G -HHUP;)8؈xRYZEH6ݯV(iDѾ<cVasd36'Fy7s'j^ߡ%WE]P$:0N/YĉA6K^~i;<ń/T'ʛ[gS»]DN{̽Yϵюx4s|}L2~/f@o2-}l?XeO`Yvtmc{&ӄuP4-C[E_n4oq$T, /..n_S~X4U^y -g3HWQ4id0Mٛ7I>&;տΜ$!n>9z>Ǜi-<yo<cT䣳9ڧg94}XdM۞Oeoh[8 -"ԩH
(Z5Z(Y`"O&h[GBo2ڍػGVWes7W<>^endstream -endobj -94 0 obj -[ 243 0 R 244 0 R 245 0 R ] -endobj -95 0 obj -<< /Filter /FlateDecode /Length 4187 >> -stream -x;ْ6r|M>Mqi[
T7,. dHF#H$ׇ8ߓSI?=JmqP&AZPS^q&yp -Vb@b! -XkPrս(&Q]Țjdx9am/DӨ 谦( -ۏR8L~?|x_~d7&[Klyc̏bAqsӱoXá(uQ4([]IvWko
<:t.˰v5m3@`\@AMra_UN4')YGi -W)0lsa$048Cp{ey\9E܍bYCǐQa;,;H4I`S,p
Z[/`nqDNxtkV&=EdߟN3bX`4l(FwX"LJ84.[,VP%I7<nԃ0/V@-]daШxy̧QUpʪXN a~xFۓå3xN. -2Yj?-ȼڦ&q -"s!ҀZ]ްqkС(Z6gm,VzWl(x/mѓxS8]zq+KbrڡayKAK#ѐ&t,:4
t49tbe48 Qt6]x'y}N1-;eBѷXSqO<܃Vf[ѽ\%FE#b$+[ӷ/7"*ENg/"}Y,ig]5[7T;%Bf~3vjEB-GP}A/)^- -
CeR,:"deYiA߀ՒSBp#Kg1Ll&%3V-'6WW61Z%2.@3;,1cFհnҪ oP(C>uS@dڋGBW`9+_]:sɇuEm<@+VgJ*WrVĴrZEF̨sA{Ⰲ]v;(sa;?d'd(eDM[BtSR x,EXbԾJ0/vX=7scnhoC4 ~DDn@=gw)'FJh4S99y`@ -#f"n"k 2gfmyڣ1p"+;Y^m|=MC{KKiB9hhGki5[cGܤdzFV4rm1g
W٭۸" -37<3u$&.aÊ^ZtQ]@Oh/%)"VV{|%EƱdenoŨd &gX<!**:ZQɘgTj,n7lJ#@9eRKAbӰs/I1fPJ@pJ8ۙlf;8%ʝ+e숳`(_9M[8IJ̊m蠴W]=s!xԛ0-ᢝEUkbw3gQj݇Z4L*(E^0hPQFUğD -0r"FE!G\-UzcI.{ OYyd\ -iFFmbq`.s\B.⩪(lIû5<}E~Gp-FctM VuEy]=Mc"ՄV#V3~b3 -1D*Z! -B&~e4ID\q ,3#L`ijQ>RI)x#(0*JYɦMAqR/4elm=ߌ!f
lv Ɗ^Ro`r.<\1z_H",uU#kx"J,GlTH3x=:KI{F*.iWÙ -moEPad&({ -}~z*Rqx
UUҸ%KLWaY- --GfIULe%e%t= -_P|c'L=Fߨm8Fl0gVM-,א)LSBVż Wy{m`bÀxmppk.Wu#M0Ugym!* })t1ѷЗ%ӗZ7pߖYX{.Mvp%I5:#elߴ~bZ(M|[~%Qfz}hEqz
G<q8N%cvtP(1
ԋ'͊i4<O/5&^vfQCxz!x磆k(] k&OuczµocS=|%jCfmz)8V&1M BڃVJTvx(G~S""JߗbG/\Xh,tP NRח'Al'ZQ%Uh}[*7hjG -
|kUQ1aby=fE#>VpٷR{foM -)cY;T.[?>7_#aendstream -endobj -96 0 obj -[ 246 0 R 247 0 R 248 0 R 249 0 R 250 0 R 251 0 R 252 0 R 253 0 R 254 0 R 255 0 R 256 0 R 257 0 R 258 0 R ] -endobj -97 0 obj -<< /Filter /FlateDecode /Length 3516 >> -stream -x˒6>_5ܜijVۙ! ;Ig -qt<f>WCdzS;8xh<Q*RI2:v1;pfo4sa=J1[.M@Ca\JS8АjÁW᧭ɣ҉n}jCnv/wa ( gʂ&_l Lu1Voߙ$DX}5琁gM`H.0R\`,KA(2quMTmAzgG*5TW*|Ew="! <ขZ5zЮ*ូmۃh'o -(ggP'i
FX୴C`с,ժvz|hsCM"Fwlx#j|P"=eQ!Tݱ]@WIqNs&d+X{ʓɍ\X_m4D؋tc
C_zlj&Q
*`t&{h -v~-a6p^M -<7S^4;y4$g3c
,K@#
쇥{Pނ"}
^%(!APH{M)4hwHBf3cPv} -afղٍʮ$ABܽuWRD<PBRB{Ɠ eq%,PgpՑuH~A邧i -j;xw, -8$SY1OyIcgJR|U[[U]nj -xH4Uݜum}>qh<$H(!IO*丳uFX4 - pB0⒌(/ ^s@b$8Z:ڂ%a]Q0BKb<jioQ
}ԛXS)Y\` - -rǺ~0\23g*Ӌ5|/Mҋ_AL"8@-(g|tb(Ӗ>Tks#G2WUj<E^9=n[?QD)>v3'_R -%P_?e7&`6O2^Ju"4Ǐ>B= +=NJ?^<endstream -endobj -98 0 obj -<< /Filter /FlateDecode /Length 3192 >> -stream -xَ6}o Ӳ[۳q`o|,`KiՒc[Ţ$vcc͢b/Wo?apEq:
0YL}*c#E0س߮+?alp/ѩ/?fW?_=n[q,\'?>\Fl|;n+J-g?1`gՁ.r5KАE>
*×
"taƊulE<{MaȆ?ӪQ"g˴amZY2Λ `{WfE+<v;YG5!q]S_b(XQț$U;P_rydEMonΊEZ[FDd6օDANlH&x"Rʼn}6Ng -y4,e"lqf/[)i*SܙgZԧki
031/N9kw/~G&OHsTwh,rL,b{ϣ/xoeS=+@}F<ngf<gyִ3\b=).S.'ThC{㦫n~nx5BL+~8l?М2!!MVj8BlsN'ʝb{yR`+Y5 -h%pr۷<8#6lMIj#,^|"1Y!ZRӭs]~1HlrM.?q[FEpv/Udn/A0EFEhZAڌW+t'8r -#!eF{}+"kȳiOYPv#=c3q -rd]_ǁG8b~Fp\à{c6ht&JJc& !+TCib&P
3!IKfEzvab@wB~*g<:= -`:T جi:(5kB|h%\ϷbC_壅7{VΗ˅rva;Yc`5 -cTQ5_=)DN=̙c\'}hSC%AImv:@ME+0|*ei4D87 `EF!(ȳt --8 <Yܽ%
kT"9=
'kWPթZ3sѓ -P2퇳U%Y^l*d,:GFNUU@<luF㙐A6<\ua*Gf<̮`rVırh9Ńt@xi`IL[Abh@.M[wIKMzkfuݣ88XLTLnaАk
Re -7@_{5}S'NNG8ۗu -8Ch֛A,#\;FP@7Ch~Q\xr_8H
~qL8#0ʛ`=|EAE^t -Bg -WoVh^J!^t()"@s,V!R@<:*H=#V[98Zb˱:cS/mފj0xV.Q|7Z3pۺW9t
A,:z0`t*D )H
DQ@.'Cv]ɺVx.պBsb+r<*ßA'ŧؕ>dhmnZRzy0t~Ŷ@Y-wu%*eB4@OC/1F%CTh r194M_2XϞQ{__=.Pq}rkʫl49km˻(ud͜j -Öz-9oOtX0K)Z0E'wF*=]Oځ>>IW{=Y4ʯQP0"Wćjt#(0DNՙ:@l8@cޠa?wtxzKkL=lWJ8Ta+JֻȳԿ_
iendstream -endobj -99 0 obj -<< /Filter /FlateDecode /Length 3175 >> -stream -xْ6}okWysJ|dI qM -I2@ -tyCS}wWi -~'spY~7eI4/4սn Sɍ7s:}{62
"^1xC݈ǮC{pH-!
O%vĸF*uHB[n={%a:Zx\7Xy~琩_ -P*q2/!c?;Wp#pY7]_i k GMgd@lÚ*rβI<a
9rz#IZ):C\r,@^NReZSfŪ`wڎ8U ^g%ޙAL&!á=,ۡ>-"ݪ\8!-ڝM}O60`@h릜AڭoaюvLlx<Z1j.$De<Q1@!4uC"`*uXLWˋ4-*Al`+(VF3w8e2ePq[Ǎ1`}w=ansy - i{QKO -r`HyHtA+,<nWNhNL( m
glH@y\j!-!)Vy=.z}|<SDWM(C)g=GQ -|fnB_nu%I -:(yJtW*m<@ -Lr[IxƇBrxY=|>ӘqNϊ^ -emϘ+{̀ihJtQ d{Ft̠T -endobj -100 0 obj -[ 259 0 R 260 0 R ] -endobj -101 0 obj -<< /Filter /FlateDecode /Length 2854 >> -stream -xr6REjOZ;N98~h+U!1CD| 5~6
3䬽t7~"`>wiȲ3Y"RbA +(-?8~Q_짋?9a?eg&x_.g?\ܾNXೇ8(NtqA+6PW/~|8& -5F}!]{f>$#}ҀV~Q=k?V*XG@6G!Jήwvl=zܱ%k֍
|/Mbgާ0LN>y슭e'TɬMf;Y;):z
V}0~($ZdB)={4ēWV!X~к]1BFQ
:۞# .Y[>Ⱦ 0(ep\7(/ 3.ӀeS= -B" O| Qn9NCY{wflm]b|}ۂz0OJ^PN~azٓ&hBU\,W ^1HQvjD(+v %N}>
$ 9ȆMVr_{ZXY6Mm6[횮R/e%{[S#ZN}1Z};B~#x7qñǫ^hΏkE5+L$1V2^p> D*'ܚt@2p
4cj&M[:'C>(L!»W'p~tj\N\hF3ƍnjB֏?sAJ(踋د<&47lk+}_n=Ah%:K%WkNs$n6S($ս+5ۿDf?ajs@P'?榇~|bD&\vZ;p孿!ǽj(BƄHS%mI|O~?]9?:!KajmGWCK":"樴XbXZۯ)Ge S.QmNEElymUB%c?zЅNplzff0X@XpAج48Gt';ՏX݈ji?Z -2KY9 -\9%XM8i> Jn I6%d`U3[}!{|fle`%ӕ
+(b-,.1K;֜ P("`|"jSADshMx%=쀬r̕.(vX)R -z5mϲ U"_pUǮڜpAU`來fۮyeϛ`PKLK,{^hoYŎnŞH^jOyB#ƫm=<Q(S"b!˫qLOipZ-!Vg\ls@/¹ӕǞlP>Ҁi5p#kM 5wo,XԀ[[5 -J^(bȂfJoB/뛗?@d4&jv -lgn( 5jj_{O|/3?Q
!JT.E3Eǭ0W,p2uVfgw7TLJ/]:9@[%4pHbG^dn!n:Y{@>Bz^ -[x hdn[U~vP%>ѡa׆(Q;gH)&x&":P|mJ;H.qY
8ʼvG\ qt!n!l:MA,Qywy&=b}L&5h_x~qxBk&[m ceg)xBSf[C3g
$S e)5^;+_MBL}펷ٝA(`r&9$%K4$OGnaXD'SHf4XDpW"l,RLTFPL $J!0|ΩhKAg -n55עDdNK(<L:]z{(Ktv'ӾOrjQhTՒ堍o'i+I -[Kih݇(ϑimaB.T˓S+
F۬N<G xQ:%"2X-x!֦H!_>--~tш}]CQͮ?NxIOظ'fM'蔏D!Sf10Uxex8珁V0ٿG8-7Ľ -endobj -102 0 obj -[ 261 0 R 262 0 R 263 0 R ] -endobj -103 0 obj -<< /Filter /FlateDecode /Length 2081 >> -stream -xYKs6ϯ-vD_㛜XN%){%W6Dbf& )~hV'ok4~Ać?'݇$vFZ@Ґd$ "/IEqLJe
i?}W|dA|Xj{I1Qb,&Gý/$?xYo?ϻ ?hYˠ>ӎ#r -n@4j6 8D}wSߨZ\Fw=|; R -/lz;:{[ s_a63Wr=%axyK') -jZu>ZԊ`=H]rVuE,HE{8ܺ]k[gџ,(o-;{vM)E;oBRdiS+DL̑756 -(Ҕ[[fS2RSetEۦSVﯖ%K,h<+~i-+HouheeP+"|?8}/UY48eE'>zw
M'KZ&^\>lz;+;zwC6Tu!bDUºc6{6S8L7rcD0f0a-l1OK:phʅAm4ȳʂh[n](%+!B}]t{^ӁNhl804o[Tm)1 y]}^p|>QWA}+%ՓC7FxlڕV,VPhKҫ -zՌ19|+y(F*3 -P;W"?<*$&Py^]VUV/g#>(E`8>
ʤƴIT|:REf2Z[ GpNkb]hyN"/Z}Ct.-P:2ԝ/wA-ϮmhBey9xd+:qC\KS^=)*zA\LJ3~W&
"f|7Al:GWG~/6Aٶ4Z8Z;Q7/^gS(=eN^{f|XD+6p%.dy?@֏N};^a^(c'U(&[2\8q4Aq|_?2>&vhX,H["նb>ʆ%h]#,.fe}bZכعd0apu
> ԍܴ> r`^^={n1o?:ģqi:&~aoaw +Gs̮D\u
<_hY"TVOJWwiE mfF_we|w_7c)E9/w-0endstream -endobj -104 0 obj -<< /Filter /FlateDecode /Length 2673 >> -stream -xkܶ -~hzqE.v= DYz}ܕt>q -LRvOӧ{2R!m2bG BUF٢2\`irxٟlvBO
JyR[1qP5 ;גz-F3+[(TV -!\?XPB-|T7kD0\HIzuGƒeԬK"XGcW}n=&NOTqeFvԋGlYlJuVb),YMNիWo(GP<IT>^ech:ͺSA'hgiA(76A4rPddtY>EeRoWg
OuúΞ;# #$YW`ZN+{6ZWvC#Zd%PmNbUY,1=I飪{rI#geΒgZ&"K=urI$DɊH6:u$A" -/Mn?2+6d\rL6S%8@g4S=65%sXwF$o% -`H;y)"̤Z݅Yl_BeW19K15Es -D -(D g؏m<PvW,2a*S/xT7v>#^-_Ou6}D:e3z *Vxyϧ*scHLpV -4@Npf,ҕF &XLw{
j˵At*7`%^8x~IKb=DZ_4<X=?SW!Uv+HO5EP!Eȁ*-:l7M%Hy\r5% p12KTؕ-Jr5JͮFsX.8Q7!KSp2@Ԕ¥û
n^
ȃ|sZx)0FYr2,SBIݧNYMyv%J?4=s1țEA -O
b/=Moϑ4bU\J%WÐA5;II)ջ~zendstream -endobj -105 0 obj -<< /Filter /FlateDecode /Length 1604 >> -stream -xXK6W
Q-m6n99m1+Z>$z4 p^i ?I<^Eablpjd. -~& Jil|X,.*N+ZbAEE%s+NF*GKՆhRG÷hne>m'p4k.J_|A?7g.EQ/p;(0M|ߏc}mK>_=w{⸠ "ח])R--z`(5E-~ew]OmqLxGYc`YbABE9H 8*,,ޗ;ąi:nsNZ!տmN4PFUn'w]ڶ`[5!kg ĂO$A}xxW?y9(8,jdLL`(#@ۙ8M\53-_54e BY7
TdÔm'BdŚ#QyD#{b5Y -.Kl"In;GPRKf}**1bD%>]B]-hX*w i-$sF(Fi~<2hEU-\(u(w]uYNjv&etɚ&w݊2Gvfm˸sj붢w?'U%y.2rY.HP6I,<z鵰
hm3_[Ɓ( }?ӅkG%E{50>v(A߽UC
KP[])+in\`v˸[7h|7@k/ -M2tZ+ekk -Plw$5YⳀ<NZԜ1J36OB+0|>g;jq5ϝ(qa.,)jy#bPP˧;捞x]s䥁z.Ffmtv&jxu$. ~?ucDendstream -endobj -106 0 obj -[ 264 0 R 265 0 R 266 0 R ] -endobj -107 0 obj -<< /Filter /FlateDecode /Length 712 >> -stream -xUKs0+dU -J;~)i3R$1kz mF^$>wǟA68v]r -ya\\%RX{FNB8˚Ϣ_ϋ}3QOXHO)orf'vy)VP} -1'!.~}ΝF^VI\@)JZ$^gę¼_(k-7p -3v\XTmPX+\sQ7b/=9=Aum)]m -+v'ʾf:<crdRhD|USf][7?czҋPZt=j5p`
Z=<0nήMOz,<Fr/MLȳe=Wƽ>@YԮY/i)Jb]kȇnvѠP0HnW]Nb^\OyIFC-2>
SME%1rll:qF¤Y<0~ܕ͂Ѳ=fF'yMݖp7UgJ q@endstream -endobj -108 0 obj -[ /ICCBased 267 0 R ] -endobj -109 0 obj -<< /BaseFont /Courier-Oblique /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >> -endobj -110 0 obj -<< /BaseFont /Courier-Bold /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >> -endobj -111 0 obj -<< /BaseFont /Courier-BoldOblique /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >> -endobj -112 0 obj -<< /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >> -endobj -113 0 obj -<< /BaseFont /Helvetica-BoldOblique /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >> -endobj -114 0 obj -<< /BaseFont /Times-Roman /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >> -endobj -115 0 obj -<< /BaseFont /Times-Italic /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >> -endobj -116 0 obj -<< /BaseFont /Times-Bold /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >> -endobj -117 0 obj -<< /BaseFont /Courier /Encoding /WinAnsiEncoding /Subtype /Type1 /Type /Font >> -endobj -118 0 obj -<< /A 268 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 680.124 154.961 689.124 ] /Subtype /Link /Type /Annot >> -endobj -119 0 obj -<< /A 268 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 534.3 680.124 542.08 689.124 ] /Subtype /Link /Type /Annot >> -endobj -120 0 obj -<< /A 269 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 668.124 148.536 677.124 ] /Subtype /Link /Type /Annot >> -endobj -121 0 obj -<< /A 269 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 537.134 668.124 542.134 677.124 ] /Subtype /Link /Type /Annot >> -endobj -122 0 obj -<< /A 270 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 656.124 205.901 665.124 ] /Subtype /Link /Type /Annot >> -endobj -123 0 obj -<< /A 270 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 536.802 656.124 541.802 665.124 ] /Subtype /Link /Type /Annot >> -endobj -124 0 obj -<< /A 271 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 644.124 202.929 653.124 ] /Subtype /Link /Type /Annot >> -endobj -125 0 obj -<< /A 271 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 536.926 644.124 541.926 653.124 ] /Subtype /Link /Type /Annot >> -endobj -126 0 obj -<< /A 272 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 632.124 187.474 641.124 ] /Subtype /Link /Type /Annot >> -endobj -127 0 obj -<< /A 272 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 537.019 632.124 542.019 641.124 ] /Subtype /Link /Type /Annot >> -endobj -128 0 obj -<< /A 273 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 620.124 146.658 629.124 ] /Subtype /Link /Type /Annot >> -endobj -129 0 obj -<< /A 273 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 537.146 620.124 542.146 629.124 ] /Subtype /Link /Type /Annot >> -endobj -130 0 obj -<< /A 274 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 608.124 183.041 617.124 ] /Subtype /Link /Type /Annot >> -endobj -131 0 obj -<< /A 274 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 537.047 608.124 542.047 617.124 ] /Subtype /Link /Type /Annot >> -endobj -132 0 obj -<< /A 275 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 596.124 172.009 605.124 ] /Subtype /Link /Type /Annot >> -endobj -133 0 obj -<< /A 275 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 537.115 596.124 542.115 605.124 ] /Subtype /Link /Type /Annot >> -endobj -134 0 obj -<< /A 276 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 584.124 194.093 593.124 ] /Subtype /Link /Type /Annot >> -endobj -135 0 obj -<< /A 276 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 536.98 584.124 541.98 593.124 ] /Subtype /Link /Type /Annot >> -endobj -136 0 obj -<< /A 277 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 572.124 209.663 581.124 ] /Subtype /Link /Type /Annot >> -endobj -137 0 obj -<< /A 277 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 536.884 572.124 541.884 581.124 ] /Subtype /Link /Type /Annot >> -endobj -138 0 obj -<< /A 278 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 560.124 255.333 569.124 ] /Subtype /Link /Type /Annot >> -endobj -139 0 obj -<< /A 278 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 536.604 560.124 541.604 569.124 ] /Subtype /Link /Type /Annot >> -endobj -140 0 obj -<< /A 279 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 548.124 294.063 557.124 ] /Subtype /Link /Type /Annot >> -endobj -141 0 obj -<< /A 279 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.338 548.124 541.338 557.124 ] /Subtype /Link /Type /Annot >> -endobj -142 0 obj -<< /A 280 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 536.124 130.045 545.124 ] /Subtype /Link /Type /Annot >> -endobj -143 0 obj -<< /A 280 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 532.212 536.124 542.212 545.124 ] /Subtype /Link /Type /Annot >> -endobj -144 0 obj -<< /A 281 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 524.124 184.956 533.124 ] /Subtype /Link /Type /Annot >> -endobj -145 0 obj -<< /A 281 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.894 524.124 541.894 533.124 ] /Subtype /Link /Type /Annot >> -endobj -146 0 obj -<< /A 282 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 512.124 189.335 521.124 ] /Subtype /Link /Type /Annot >> -endobj -147 0 obj -<< /A 282 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.868 512.124 541.868 521.124 ] /Subtype /Link /Type /Annot >> -endobj -148 0 obj -<< /A 283 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 500.124 164.008 509.124 ] /Subtype /Link /Type /Annot >> -endobj -149 0 obj -<< /A 283 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 532.134 500.124 542.134 509.124 ] /Subtype /Link /Type /Annot >> -endobj -150 0 obj -<< /A 284 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 488.124 169.762 497.124 ] /Subtype /Link /Type /Annot >> -endobj -151 0 obj -<< /A 284 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 532.098 488.124 542.098 497.124 ] /Subtype /Link /Type /Annot >> -endobj -152 0 obj -<< /A 285 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 476.124 174.195 485.124 ] /Subtype /Link /Type /Annot >> -endobj -153 0 obj -<< /A 285 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 532.072 476.124 542.072 485.124 ] /Subtype /Link /Type /Annot >> -endobj -154 0 obj -<< /A 286 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 464.124 159.023 473.124 ] /Subtype /Link /Type /Annot >> -endobj -155 0 obj -<< /A 286 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 532.164 464.124 542.164 473.124 ] /Subtype /Link /Type /Annot >> -endobj -156 0 obj -<< /A 287 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 452.124 232.17 461.124 ] /Subtype /Link /Type /Annot >> -endobj -157 0 obj -<< /A 287 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.716 452.124 541.716 461.124 ] /Subtype /Link /Type /Annot >> -endobj -158 0 obj -<< /A 288 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 440.124 233.86 449.124 ] /Subtype /Link /Type /Annot >> -endobj -159 0 obj -<< /A 288 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.706 440.124 541.706 449.124 ] /Subtype /Link /Type /Annot >> -endobj -160 0 obj -<< /A 289 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 428.124 225.016 437.124 ] /Subtype /Link /Type /Annot >> -endobj -161 0 obj -<< /A 289 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.76 428.124 541.76 437.124 ] /Subtype /Link /Type /Annot >> -endobj -162 0 obj -<< /A 290 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 416.124 277.557 425.124 ] /Subtype /Link /Type /Annot >> -endobj -163 0 obj -<< /A 290 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.44 416.124 541.44 425.124 ] /Subtype /Link /Type /Annot >> -endobj -164 0 obj -<< /A 291 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 404.124 190.364 413.124 ] /Subtype /Link /Type /Annot >> -endobj -165 0 obj -<< /A 291 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.972 404.124 541.972 413.124 ] /Subtype /Link /Type /Annot >> -endobj -166 0 obj -<< /A 292 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 392.124 186.331 401.124 ] /Subtype /Link /Type /Annot >> -endobj -167 0 obj -<< /A 292 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.998 392.124 541.998 401.124 ] /Subtype /Link /Type /Annot >> -endobj -168 0 obj -<< /A 293 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 380.124 136.409 389.124 ] /Subtype /Link /Type /Annot >> -endobj -169 0 obj -<< /A 293 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 532.176 380.124 542.176 389.124 ] /Subtype /Link /Type /Annot >> -endobj -170 0 obj -<< /A 294 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 368.124 244.815 377.124 ] /Subtype /Link /Type /Annot >> -endobj -171 0 obj -<< /A 294 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.64 368.124 541.64 377.124 ] /Subtype /Link /Type /Annot >> -endobj -172 0 obj -<< /A 295 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 356.124 227.215 365.124 ] /Subtype /Link /Type /Annot >> -endobj -173 0 obj -<< /A 295 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.746 356.124 541.746 365.124 ] /Subtype /Link /Type /Annot >> -endobj -174 0 obj -<< /A 296 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 344.124 167.338 353.124 ] /Subtype /Link /Type /Annot >> -endobj -175 0 obj -<< /A 296 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 532.114 344.124 542.114 353.124 ] /Subtype /Link /Type /Annot >> -endobj -176 0 obj -<< /A 297 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 332.124 214.562 341.124 ] /Subtype /Link /Type /Annot >> -endobj -177 0 obj -<< /A 297 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.824 332.124 541.824 341.124 ] /Subtype /Link /Type /Annot >> -endobj -178 0 obj -<< /A 298 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 320.124 239.324 329.124 ] /Subtype /Link /Type /Annot >> -endobj -179 0 obj -<< /A 298 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.672 320.124 541.672 329.124 ] /Subtype /Link /Type /Annot >> -endobj -180 0 obj -<< /A 299 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 308.124 249.211 317.124 ] /Subtype /Link /Type /Annot >> -endobj -181 0 obj -<< /A 299 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.612 308.124 541.612 317.124 ] /Subtype /Link /Type /Annot >> -endobj -182 0 obj -<< /A 300 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 296.124 181.908 305.124 ] /Subtype /Link /Type /Annot >> -endobj -183 0 obj -<< /A 300 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 532.024 296.124 542.024 305.124 ] /Subtype /Link /Type /Annot >> -endobj -184 0 obj -<< /A 301 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 284.124 232.703 293.124 ] /Subtype /Link /Type /Annot >> -endobj -185 0 obj -<< /A 301 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.618 284.124 541.618 293.124 ] /Subtype /Link /Type /Annot >> -endobj -186 0 obj -<< /A 302 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 272.124 176.938 281.124 ] /Subtype /Link /Type /Annot >> -endobj -187 0 obj -<< /A 302 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 532.054 272.124 542.054 281.124 ] /Subtype /Link /Type /Annot >> -endobj -188 0 obj -<< /A 303 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 260.124 216.675 269.124 ] /Subtype /Link /Type /Annot >> -endobj -189 0 obj -<< /A 303 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.812 260.124 541.812 269.124 ] /Subtype /Link /Type /Annot >> -endobj -190 0 obj -<< /A 304 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 120.0 248.124 266.259 257.124 ] /Subtype /Link /Type /Annot >> -endobj -191 0 obj -<< /A 304 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.602 248.124 541.602 257.124 ] /Subtype /Link /Type /Annot >> -endobj -192 0 obj -<< /A 305 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 236.124 248.115 245.124 ] /Subtype /Link /Type /Annot >> -endobj -193 0 obj -<< /A 305 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.62 236.124 541.62 245.124 ] /Subtype /Link /Type /Annot >> -endobj -194 0 obj -<< /A 306 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 96.0 224.124 204.549 233.124 ] /Subtype /Link /Type /Annot >> -endobj -195 0 obj -<< /A 306 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.886 224.124 541.886 233.124 ] /Subtype /Link /Type /Annot >> -endobj -196 0 obj -<< /A 307 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 212.124 143.288 221.124 ] /Subtype /Link /Type /Annot >> -endobj -197 0 obj -<< /A 307 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 532.134 212.124 542.134 221.124 ] /Subtype /Link /Type /Annot >> -endobj -198 0 obj -<< /A 308 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 200.124 193.284 209.124 ] /Subtype /Link /Type /Annot >> -endobj -199 0 obj -<< /A 308 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 531.846 200.124 541.846 209.124 ] /Subtype /Link /Type /Annot >> -endobj -200 0 obj -<< /A 309 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 188.124 154.828 197.124 ] /Subtype /Link /Type /Annot >> -endobj -201 0 obj -<< /A 309 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 532.068 188.124 542.068 197.124 ] /Subtype /Link /Type /Annot >> -endobj -202 0 obj -<< /A 310 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 176.124 155.373 185.124 ] /Subtype /Link /Type /Annot >> -endobj -203 0 obj -<< /A 310 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 532.064 176.124 542.064 185.124 ] /Subtype /Link /Type /Annot >> -endobj -204 0 obj -<< /A 311 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 81.72 657.641 191.15 666.641 ] /Subtype /Link /Type /Annot >> -endobj -205 0 obj -<< /A 312 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 367.94 657.641 483.23 666.641 ] /Subtype /Link /Type /Annot >> -endobj -206 0 obj -<< /A 313 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 248.721 635.641 387.438 644.641 ] /Subtype /Link /Type /Annot >> -endobj -207 0 obj -<< /A 313 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 393.349 635.641 539.999 644.641 ] /Subtype /Link /Type /Annot >> -endobj -208 0 obj -<< /A 313 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 623.641 163.65 632.641 ] /Subtype /Link /Type /Annot >> -endobj -209 0 obj -<< /A 314 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 589.641 141.053 598.641 ] /Subtype /Link /Type /Annot >> -endobj -210 0 obj -<< /A 314 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 146.786 589.641 273.986 598.641 ] /Subtype /Link /Type /Annot >> -endobj -211 0 obj -<< /A 315 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 102.28 552.539 182.84 561.539 ] /Subtype /Link /Type /Annot >> -endobj -212 0 obj -<< /A 316 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 104.49 531.449 188.37 540.449 ] /Subtype /Link /Type /Annot >> -endobj -213 0 obj -<< /A 317 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 184.2 510.359 324.74 519.359 ] /Subtype /Link /Type /Annot >> -endobj -214 0 obj -<< /A 318 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 187.53 489.269 269.75 498.269 ] /Subtype /Link /Type /Annot >> -endobj -215 0 obj -<< /A 319 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 197.68 468.179 351.01 477.179 ] /Subtype /Link /Type /Annot >> -endobj -216 0 obj -<< /A 320 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 110.06 284.909 260.06 293.909 ] /Subtype /Link /Type /Annot >> -endobj -217 0 obj -<< /A 321 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 224.76 263.819 336.43 272.819 ] /Subtype /Link /Type /Annot >> -endobj -218 0 obj -<< /A 322 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 516.1 152.549 540.0 161.549 ] /Subtype /Link /Type /Annot >> -endobj -219 0 obj -<< /A 322 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 140.549 219.75 149.549 ] /Subtype /Link /Type /Annot >> -endobj -220 0 obj -<< /A 323 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 516.1 140.549 540.0 149.549 ] /Subtype /Link /Type /Annot >> -endobj -221 0 obj -<< /A 323 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 128.549 152.54 137.549 ] /Subtype /Link /Type /Annot >> -endobj -222 0 obj -<< /A 277 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 333.717 519.578 484.943 528.578 ] /Subtype /Link /Type /Annot >> -endobj -223 0 obj -<< /A 277 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 484.943 519.578 515.831 528.578 ] /Subtype /Link /Type /Annot >> -endobj -224 0 obj -<< /A 276 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 319.381 163.171 460.719 172.171 ] /Subtype /Link /Type /Annot >> -endobj -225 0 obj -<< /A 276 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 460.719 163.171 494.664 172.171 ] /Subtype /Link /Type /Annot >> -endobj -226 0 obj -<< /A 277 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 329.293 85.499 488.903 94.499 ] /Subtype /Link /Type /Annot >> -endobj -227 0 obj -<< /A 277 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 488.903 85.499 523.981 94.499 ] /Subtype /Link /Type /Annot >> -endobj -228 0 obj -<< /A 280 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 366.92 188.609 456.9 197.609 ] /Subtype /Link /Type /Annot >> -endobj -229 0 obj -<< /A 280 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 456.9 188.609 493.28 197.609 ] /Subtype /Link /Type /Annot >> -endobj -230 0 obj -<< /A 280 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 208.09 637.5 298.07 646.5 ] /Subtype /Link /Type /Annot >> -endobj -231 0 obj -<< /A 280 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 298.07 637.5 334.45 646.5 ] /Subtype /Link /Type /Annot >> -endobj -232 0 obj -<< /A 290 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 160.538 85.505 386.813 94.505 ] /Subtype /Link /Type /Annot >> -endobj -233 0 obj -<< /A 290 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 386.813 85.505 425.379 94.505 ] /Subtype /Link /Type /Annot >> -endobj -234 0 obj -<< /A 293 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 497.962 450.438 539.999 459.438 ] /Subtype /Link /Type /Annot >> -endobj -235 0 obj -<< /A 293 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 438.558 125.9 447.438 ] /Subtype /Link /Type /Annot >> -endobj -236 0 obj -<< /A 293 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 125.9 438.438 163.703 447.438 ] /Subtype /Link /Type /Annot >> -endobj -237 0 obj -<< /A 280 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 295.01 426.438 384.99 435.438 ] /Subtype /Link /Type /Annot >> -endobj -238 0 obj -<< /A 280 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 384.99 426.438 421.37 435.438 ] /Subtype /Link /Type /Annot >> -endobj -239 0 obj -<< /A 296 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 334.947 468.365 448.053 477.365 ] /Subtype /Link /Type /Annot >> -endobj -240 0 obj -<< /A 296 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 448.053 468.365 485.879 477.365 ] /Subtype /Link /Type /Annot >> -endobj -241 0 obj -<< /A 305 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 299.614 431.318 491.36 440.318 ] /Subtype /Link /Type /Annot >> -endobj -242 0 obj -<< /A 305 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 491.36 431.318 528.338 440.318 ] /Subtype /Link /Type /Annot >> -endobj -243 0 obj -<< /A 278 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 378.272 673.5 539.999 682.5 ] /Subtype /Link /Type /Annot >> -endobj -244 0 obj -<< /A 278 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 100.0 661.5 136.11 670.5 ] /Subtype /Link /Type /Annot >> -endobj -245 0 obj -<< /A 278 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 136.11 661.5 167.49 670.5 ] /Subtype /Link /Type /Annot >> -endobj -246 0 obj -<< /A 309 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 429.325 422.206 539.998 431.206 ] /Subtype /Link /Type /Annot >> -endobj -247 0 obj -<< /A 309 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 100.0 410.326 112.5 419.206 ] /Subtype /Link /Type /Annot >> -endobj -248 0 obj -<< /A 309 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 112.5 410.206 151.668 419.206 ] /Subtype /Link /Type /Annot >> -endobj -249 0 obj -<< /A 277 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 116.94 305.47 269.15 314.47 ] /Subtype /Link /Type /Annot >> -endobj -250 0 obj -<< /A 277 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 269.15 305.47 300.53 314.47 ] /Subtype /Link /Type /Annot >> -endobj -251 0 obj -<< /A 275 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 116.94 271.102 232.21 280.102 ] /Subtype /Link /Type /Annot >> -endobj -252 0 obj -<< /A 275 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 232.21 271.102 263.59 280.102 ] /Subtype /Link /Type /Annot >> -endobj -253 0 obj -<< /A 288 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 455.55 248.384 539.998 257.384 ] /Subtype /Link /Type /Annot >> -endobj -254 0 obj -<< /A 288 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 100.0 236.384 188.88 245.384 ] /Subtype /Link /Type /Annot >> -endobj -255 0 obj -<< /A 288 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 188.88 236.384 225.26 245.384 ] /Subtype /Link /Type /Annot >> -endobj -256 0 obj -<< /A 290 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 492.575 213.666 540.002 222.666 ] /Subtype /Link /Type /Annot >> -endobj -257 0 obj -<< /A 290 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 100.0 201.666 268.61 210.666 ] /Subtype /Link /Type /Annot >> -endobj -258 0 obj -<< /A 290 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 268.61 201.666 304.99 210.666 ] /Subtype /Link /Type /Annot >> -endobj -259 0 obj -<< /A 324 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 359.15 295.375 455.27 304.375 ] /Subtype /Link /Type /Annot >> -endobj -260 0 obj -<< /A 324 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 321.485 190.125 417.605 199.125 ] /Subtype /Link /Type /Annot >> -endobj -261 0 obj -<< /A 308 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 405.397 573.15 539.999 582.15 ] /Subtype /Link /Type /Annot >> -endobj -262 0 obj -<< /A 308 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 100.0 561.27 122.78 570.15 ] /Subtype /Link /Type /Annot >> -endobj -263 0 obj -<< /A 308 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 122.78 561.15 159.16 570.15 ] /Subtype /Link /Type /Annot >> -endobj -264 0 obj -<< /A 307 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 455.792 645.641 540.001 654.641 ] /Subtype /Link /Type /Annot >> -endobj -265 0 obj -<< /A 307 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 72.0 633.761 94.78 642.641 ] /Subtype /Link /Type /Annot >> -endobj -266 0 obj -<< /A 307 0 R /Border [ 0 0 0 ] /C [ 0 0 0 ] /H /I /Rect [ 94.78 633.641 131.16 642.641 ] /Subtype /Link /Type /Annot >> -endobj -267 0 obj -<< /Filter /FlateDecode /N 3 /Length 2453 >> -stream -xgPTY{si249IЀ$Hn2-49AD$)8:EEyqpQQYU[[Ϗ:;羺Uz -0D[oo+k?Q - -R -se4=ơO$ĵmawGIp+z_o# -zañ)|l%"v;#p8'\ŕq=an/Wś|&߀O bu%KE D%чCI &^'?$-=)JG:ADzHzK&6r -y|A&+*!R-!2,Rl))YrYʬ(^TM^4\th91y1XXX
i*Furyc+ BSشݴU$CWc9qxxxqa1\b(㓄WbDİĂ$W@MrDSQ*NjTSitҳ2tLGl1y9y9gDJ+ryX2BBELq-3Yc)*(*)*.*++*)=U&(#˔{TT<UrTZTUYѪTUuMKg? kXk$ikh44kՂh&1ڇ֠ט᭩_3CұIiezvSۯׯUX?^A נOC-CaNkwZHۈkt1xqSIɌiifv7hnbb~8fuMX*Y[Y -VaVG֊mm866Sl__r@ -UϜZ服/]]ʹ]\Lݶ7W?{t{n<W][\x=VNS7ǷmÖ
=O
4,: - - -4otxpdqh~&Mnlʖ-gaaaý#\#j"CWNgk-NEZFFNGYF.y[w"n)>0-pGo:(H2O:4w7&CɛRTRӬҪ>edje͜r:fg(fn;=b{y;&w:<+n\www̛||~=ߣ~pڽ{p -n~.bE,6)>R)ᕌT4t灎2fYAٻ[(7*=D8zHPQURYR*jڮFfoa#6GZkjk?9ι^Xڱ
YǛ=d,\̜ -=uGZuZm/ -i?\Nk/:2;:;]]Cv[tˉ/_(!,]̺8)[z_ rϧoל]x7dercxA;wZ73l=|ýk]Y?24?`,tL`a7->gM`"00>|W%y2ETӴ/7|jq6wk^k?&,YVwFz矽OxPAɏ?M-~'K KKB. t]@B. t]V,r96_6 -7#edoMDEa&s|.7>s7 -endstream -endobj -268 0 obj -<< /D [ 9 0 R /XYZ 72.0 720.0 null ] /S /GoTo /Type /Action >> -endobj -269 0 obj -<< /D [ 10 0 R /XYZ 72.0 720.0 null ] /S /GoTo /Type /Action >> -endobj -270 0 obj -<< /D [ 11 0 R /XYZ 72.0 720.0 null ] /S /GoTo /Type /Action >> -endobj -271 0 obj -<< /D [ 11 0 R /XYZ 72.0 627.192 null ] /S /GoTo /Type /Action >> -endobj -272 0 obj -<< /D [ 11 0 R /XYZ 72.0 127.049 null ] /S /GoTo /Type /Action >> -endobj -273 0 obj -<< /D [ 13 0 R /XYZ 72.0 720.0 null ] /S /GoTo /Type /Action >> -endobj -274 0 obj -<< /D [ 13 0 R /XYZ 72.0 669.305 null ] /S /GoTo /Type /Action >> -endobj -275 0 obj -<< /D [ 13 0 R /XYZ 72.0 452.406 null ] /S /GoTo /Type /Action >> -endobj -276 0 obj -<< /D [ 14 0 R /XYZ 72.0 519.752 null ] /S /GoTo /Type /Action >> -endobj -277 0 obj -<< /D [ 15 0 R /XYZ 72.0 152.784 null ] /S /GoTo /Type /Action >> -endobj -278 0 obj -<< /D [ 17 0 R /XYZ 72.0 585.874 null ] /S /GoTo /Type /Action >> -endobj -279 0 obj -<< /D [ 19 0 R /XYZ 72.0 636.0 null ] /S /GoTo /Type /Action >> -endobj -280 0 obj -<< /D [ 21 0 R /XYZ 72.0 720.0 null ] /S /GoTo /Type /Action >> -endobj -281 0 obj -<< /D [ 23 0 R /XYZ 72.0 720.0 null ] /S /GoTo /Type /Action >> -endobj -282 0 obj -<< /D [ 24 0 R /XYZ 72.0 720.0 null ] /S /GoTo /Type /Action >> -endobj -283 0 obj -<< /D [ 24 0 R /XYZ 72.0 690.141 null ] /S /GoTo /Type /Action >> -endobj -284 0 obj -<< /D [ 24 0 R /XYZ 72.0 464.807 null ] /S /GoTo /Type /Action >> -endobj -285 0 obj -<< /D [ 26 0 R /XYZ 72.0 408.138 null ] /S /GoTo /Type /Action >> -endobj -286 0 obj -<< /D [ 27 0 R /XYZ 72.0 187.938 null ] /S /GoTo /Type /Action >> -endobj -287 0 obj -<< /D [ 28 0 R /XYZ 72.0 528.576 null ] /S /GoTo /Type /Action >> -endobj -288 0 obj -<< /D [ 28 0 R /XYZ 72.0 360.619 null ] /S /GoTo /Type /Action >> -endobj -289 0 obj -<< /D [ 28 0 R /XYZ 72.0 264.662 null ] /S /GoTo /Type /Action >> -endobj -290 0 obj -<< /D [ 29 0 R /XYZ 72.0 720.0 null ] /S /GoTo /Type /Action >> -endobj -291 0 obj -<< /D [ 29 0 R /XYZ 72.0 518.119 null ] /S /GoTo /Type /Action >> -endobj -292 0 obj -<< /D [ 30 0 R /XYZ 72.0 410.0 null ] /S /GoTo /Type /Action >> -endobj -293 0 obj -<< /D [ 31 0 R /XYZ 72.0 720.0 null ] /S /GoTo /Type /Action >> -endobj -294 0 obj -<< /D [ 31 0 R /XYZ 72.0 667.229 null ] /S /GoTo /Type /Action >> -endobj -295 0 obj -<< /D [ 31 0 R /XYZ 72.0 560.872 null ] /S /GoTo /Type /Action >> -endobj -296 0 obj -<< /D [ 31 0 R /XYZ 72.0 340.617 null ] /S /GoTo /Type /Action >> -endobj -297 0 obj -<< /D [ 32 0 R /XYZ 72.0 720.0 null ] /S /GoTo /Type /Action >> -endobj -298 0 obj -<< /D [ 32 0 R /XYZ 72.0 393.752 null ] /S /GoTo /Type /Action >> -endobj -299 0 obj -<< /D [ 32 0 R /XYZ 72.0 277.193 null ] /S /GoTo /Type /Action >> -endobj -300 0 obj -<< /D [ 33 0 R /XYZ 72.0 720.0 null ] /S /GoTo /Type /Action >> -endobj -301 0 obj -<< /D [ 34 0 R /XYZ 72.0 720.0 null ] /S /GoTo /Type /Action >> -endobj -302 0 obj -<< /D [ 34 0 R /XYZ 72.0 626.773 null ] /S /GoTo /Type /Action >> -endobj -303 0 obj -<< /D [ 34 0 R /XYZ 72.0 127.887 null ] /S /GoTo /Type /Action >> -endobj -304 0 obj -<< /D [ 35 0 R /XYZ 72.0 408.866 null ] /S /GoTo /Type /Action >> -endobj -305 0 obj -<< /D [ 35 0 R /XYZ 72.0 197.346 null ] /S /GoTo /Type /Action >> -endobj -306 0 obj -<< /D [ 36 0 R /XYZ 72.0 650.0 null ] /S /GoTo /Type /Action >> -endobj -307 0 obj -<< /D [ 37 0 R /XYZ 72.0 720.0 null ] /S /GoTo /Type /Action >> -endobj -308 0 obj -<< /D [ 46 0 R /XYZ 72.0 720.0 null ] /S /GoTo /Type /Action >> -endobj -309 0 obj -<< /D [ 47 0 R /XYZ 72.0 720.0 null ] /S /GoTo /Type /Action >> -endobj -310 0 obj -<< /D [ 48 0 R /XYZ 72.0 720.0 null ] /S /GoTo /Type /Action >> -endobj -311 0 obj -<< /S /URI /URI (http://qpdf.sourceforge.net/) >> -endobj -312 0 obj -<< /S /URI /URI (https://github.com/qpdf/qpdf) >> -endobj -313 0 obj -<< /S /URI /URI (http://www.opensource.org/licenses/artistic-license-2.0.php) >> -endobj -314 0 obj -<< /S /URI /URI (http://www.apexcovantage.com) >> -endobj -315 0 obj -<< /S /URI /URI (http://www.zlib.net/) >> -endobj -316 0 obj -<< /S /URI /URI (http://www.pcre.org/) >> -endobj -317 0 obj -<< /S /URI /URI (http://www.gnu.org/software/make) >> -endobj -318 0 obj -<< /S /URI /URI (http://www.perl.org/) >> -endobj -319 0 obj -<< /S /URI /URI (http://www.gnu.org/software/diffutils/) >> -endobj -320 0 obj -<< /S /URI /URI (http://www.remotesensing.org/libtiff/) >> -endobj -321 0 obj -<< /S /URI /URI (http://www.ghostscript.com) >> -endobj -322 0 obj -<< /S /URI /URI (http://downloads.sourceforge.net/docbook/) >> -endobj -323 0 obj -<< /S /URI /URI (http://xml.apache.org/fop/) >> -endobj -324 0 obj -<< /S /URI /URI (http://delphi.about.com/) >> -endobj -xref -0 325 -0000000000 65535 f -0000000015 00000 n -0000000109 00000 n -0000000239 00000 n -0000001177 00000 n -0000001920 00000 n -0000002271 00000 n -0000002455 00000 n -0000002639 00000 n -0000002838 00000 n -0000003037 00000 n -0000003222 00000 n -0000003422 00000 n -0000003607 00000 n -0000003807 00000 n -0000003992 00000 n -0000004177 00000 n -0000004362 00000 n -0000004562 00000 n -0000004747 00000 n -0000004947 00000 n -0000005132 00000 n -0000005317 00000 n -0000005502 00000 n -0000005687 00000 n -0000005872 00000 n -0000006057 00000 n -0000006242 00000 n -0000006427 00000 n -0000006627 00000 n -0000006827 00000 n -0000007012 00000 n -0000007212 00000 n -0000007397 00000 n -0000007582 00000 n -0000007782 00000 n -0000007967 00000 n -0000008152 00000 n -0000008337 00000 n -0000008522 00000 n -0000008707 00000 n -0000008907 00000 n -0000009107 00000 n -0000009292 00000 n -0000009477 00000 n -0000009679 00000 n -0000009881 00000 n -0000010067 00000 n -0000010253 00000 n -0000010455 00000 n -0000010752 00000 n -0000010975 00000 n -0000011383 00000 n -0000012091 00000 n -0000027423 00000 n -0000027499 00000 n -0000028796 00000 n -0000030441 00000 n -0000030549 00000 n -0000033548 00000 n -0000035068 00000 n -0000035136 00000 n -0000038005 00000 n -0000040717 00000 n -0000042990 00000 n -0000046906 00000 n -0000046942 00000 n -0000049676 00000 n -0000053803 00000 n -0000053839 00000 n -0000056581 00000 n -0000058287 00000 n -0000061710 00000 n -0000062221 00000 n -0000063696 00000 n -0000067537 00000 n -0000072114 00000 n -0000076139 00000 n -0000081261 00000 n -0000081297 00000 n -0000085409 00000 n -0000085469 00000 n -0000088414 00000 n -0000091149 00000 n -0000091185 00000 n -0000094271 00000 n -0000097728 00000 n -0000098772 00000 n -0000098808 00000 n -0000101037 00000 n -0000104237 00000 n -0000105725 00000 n -0000108398 00000 n -0000111759 00000 n -0000114812 00000 n -0000114856 00000 n -0000119116 00000 n -0000119240 00000 n -0000122829 00000 n -0000126094 00000 n -0000129342 00000 n -0000129379 00000 n -0000132307 00000 n -0000132352 00000 n -0000134507 00000 n -0000137254 00000 n -0000138932 00000 n -0000138977 00000 n -0000139762 00000 n -0000139801 00000 n -0000139906 00000 n -0000140008 00000 n -0000140117 00000 n -0000140221 00000 n -0000140332 00000 n -0000140433 00000 n -0000140535 00000 n -0000140635 00000 n -0000140732 00000 n -0000140870 00000 n -0000141008 00000 n -0000141146 00000 n -0000141287 00000 n -0000141425 00000 n -0000141566 00000 n -0000141704 00000 n -0000141845 00000 n -0000141983 00000 n -0000142124 00000 n -0000142262 00000 n -0000142403 00000 n -0000142541 00000 n -0000142682 00000 n -0000142820 00000 n -0000142961 00000 n -0000143099 00000 n -0000143238 00000 n -0000143376 00000 n -0000143517 00000 n -0000143655 00000 n -0000143796 00000 n -0000143934 00000 n -0000144075 00000 n -0000144213 00000 n -0000144354 00000 n -0000144492 00000 n -0000144633 00000 n -0000144771 00000 n -0000144912 00000 n -0000145050 00000 n -0000145191 00000 n -0000145329 00000 n -0000145470 00000 n -0000145608 00000 n -0000145749 00000 n -0000145887 00000 n -0000146028 00000 n -0000146165 00000 n -0000146306 00000 n -0000146443 00000 n -0000146584 00000 n -0000146722 00000 n -0000146861 00000 n -0000146999 00000 n -0000147138 00000 n -0000147276 00000 n -0000147417 00000 n -0000147555 00000 n -0000147696 00000 n -0000147834 00000 n -0000147975 00000 n -0000148113 00000 n -0000148252 00000 n -0000148390 00000 n -0000148531 00000 n -0000148669 00000 n -0000148810 00000 n -0000148948 00000 n -0000149089 00000 n -0000149227 00000 n -0000149368 00000 n -0000149506 00000 n -0000149647 00000 n -0000149785 00000 n -0000149926 00000 n -0000150064 00000 n -0000150205 00000 n -0000150343 00000 n -0000150484 00000 n -0000150622 00000 n -0000150763 00000 n -0000150902 00000 n -0000151043 00000 n -0000151181 00000 n -0000151320 00000 n -0000151458 00000 n -0000151599 00000 n -0000151737 00000 n -0000151878 00000 n -0000152016 00000 n -0000152157 00000 n -0000152295 00000 n -0000152436 00000 n -0000152574 00000 n -0000152715 00000 n -0000152853 00000 n -0000152992 00000 n -0000153133 00000 n -0000153274 00000 n -0000153411 00000 n -0000153549 00000 n -0000153690 00000 n -0000153829 00000 n -0000153968 00000 n -0000154106 00000 n -0000154245 00000 n -0000154384 00000 n -0000154523 00000 n -0000154662 00000 n -0000154799 00000 n -0000154936 00000 n -0000155073 00000 n -0000155210 00000 n -0000155351 00000 n -0000155492 00000 n -0000155633 00000 n -0000155774 00000 n -0000155913 00000 n -0000156052 00000 n -0000156190 00000 n -0000156328 00000 n -0000156463 00000 n -0000156598 00000 n -0000156737 00000 n -0000156876 00000 n -0000157017 00000 n -0000157153 00000 n -0000157292 00000 n -0000157431 00000 n -0000157570 00000 n -0000157711 00000 n -0000157852 00000 n -0000157992 00000 n -0000158132 00000 n -0000158269 00000 n -0000158403 00000 n -0000158538 00000 n -0000158679 00000 n -0000158816 00000 n -0000158955 00000 n -0000159092 00000 n -0000159229 00000 n -0000159368 00000 n -0000159507 00000 n -0000159647 00000 n -0000159785 00000 n -0000159924 00000 n -0000160065 00000 n -0000160203 00000 n -0000160342 00000 n -0000160481 00000 n -0000160622 00000 n -0000160761 00000 n -0000160897 00000 n -0000161034 00000 n -0000161175 00000 n -0000161311 00000 n -0000161449 00000 n -0000163981 00000 n -0000164061 00000 n -0000164142 00000 n -0000164223 00000 n -0000164306 00000 n -0000164389 00000 n -0000164470 00000 n -0000164553 00000 n -0000164636 00000 n -0000164719 00000 n -0000164802 00000 n -0000164885 00000 n -0000164966 00000 n -0000165047 00000 n -0000165128 00000 n -0000165209 00000 n -0000165292 00000 n -0000165375 00000 n -0000165458 00000 n -0000165541 00000 n -0000165624 00000 n -0000165707 00000 n -0000165790 00000 n -0000165871 00000 n -0000165954 00000 n -0000166035 00000 n -0000166116 00000 n -0000166199 00000 n -0000166282 00000 n -0000166365 00000 n -0000166446 00000 n -0000166529 00000 n -0000166612 00000 n -0000166693 00000 n -0000166774 00000 n -0000166857 00000 n -0000166940 00000 n -0000167023 00000 n -0000167106 00000 n -0000167187 00000 n -0000167268 00000 n -0000167349 00000 n -0000167430 00000 n -0000167511 00000 n -0000167578 00000 n -0000167645 00000 n -0000167743 00000 n -0000167810 00000 n -0000167869 00000 n -0000167928 00000 n -0000167999 00000 n -0000168058 00000 n -0000168135 00000 n -0000168211 00000 n -0000168276 00000 n -0000168356 00000 n -0000168421 00000 n -trailer << /Info 2 0 R /Root 1 0 R /Size 325 /ID [<9b1c69409fc9a5f50e44b9588e3e60f8><47e08688d23013c99057cfe5d79bca32>] >> -startxref -168484 -%%EOF diff --git a/qpdf/qtest/qpdf/deterministic-id-ny.pdf b/qpdf/qtest/qpdf/deterministic-id-ny.pdf Binary files differdeleted file mode 100644 index 40c4b382..00000000 --- a/qpdf/qtest/qpdf/deterministic-id-ny.pdf +++ /dev/null diff --git a/qpdf/qtest/qpdf/deterministic-id-yn.pdf b/qpdf/qtest/qpdf/deterministic-id-yn.pdf Binary files differdeleted file mode 100644 index bc05571b..00000000 --- a/qpdf/qtest/qpdf/deterministic-id-yn.pdf +++ /dev/null diff --git a/qpdf/qtest/qpdf/deterministic-id-yy.pdf b/qpdf/qtest/qpdf/deterministic-id-yy.pdf Binary files differdeleted file mode 100644 index c0baa660..00000000 --- a/qpdf/qtest/qpdf/deterministic-id-yy.pdf +++ /dev/null diff --git a/qpdf/qtest/qpdf/extra-header-lin-newline.pdf b/qpdf/qtest/qpdf/extra-header-lin-newline.pdf Binary files differindex 7a5ff3e6..b23c5152 100644 --- a/qpdf/qtest/qpdf/extra-header-lin-newline.pdf +++ b/qpdf/qtest/qpdf/extra-header-lin-newline.pdf diff --git a/qpdf/qtest/qpdf/extra-header-lin-no-newline.pdf b/qpdf/qtest/qpdf/extra-header-lin-no-newline.pdf Binary files differindex e115c462..da3e3e88 100644 --- a/qpdf/qtest/qpdf/extra-header-lin-no-newline.pdf +++ b/qpdf/qtest/qpdf/extra-header-lin-no-newline.pdf diff --git a/qpdf/qtest/qpdf/filter-xref-offsets.pl b/qpdf/qtest/qpdf/filter-xref-offsets.pl new file mode 100644 index 00000000..fa4e95c8 --- /dev/null +++ b/qpdf/qtest/qpdf/filter-xref-offsets.pl @@ -0,0 +1,8 @@ +use warnings; +use strict; + +while (<>) +{ + s/(uncompressed; offset =) \d+/$1 .../; + print; +} diff --git a/qpdf/qtest/qpdf/good17-not-recompressed.pdf b/qpdf/qtest/qpdf/good17-not-recompressed.pdf Binary files differindex b6e3a530..1f4924f7 100644 --- a/qpdf/qtest/qpdf/good17-not-recompressed.pdf +++ b/qpdf/qtest/qpdf/good17-not-recompressed.pdf diff --git a/qpdf/qtest/qpdf/job-json-misc-options.pdf b/qpdf/qtest/qpdf/job-json-misc-options.pdf Binary files differindex be88f1e1..809f18d0 100644 --- a/qpdf/qtest/qpdf/job-json-misc-options.pdf +++ b/qpdf/qtest/qpdf/job-json-misc-options.pdf diff --git a/qpdf/qtest/qpdf/lin-special.disable.exp b/qpdf/qtest/qpdf/lin-special.disable.exp Binary files differindex 14c2eaef..727341b1 100644 --- a/qpdf/qtest/qpdf/lin-special.disable.exp +++ b/qpdf/qtest/qpdf/lin-special.disable.exp diff --git a/qpdf/qtest/qpdf/lin-special.generate.exp b/qpdf/qtest/qpdf/lin-special.generate.exp Binary files differindex af7cb061..545ce977 100644 --- a/qpdf/qtest/qpdf/lin-special.generate.exp +++ b/qpdf/qtest/qpdf/lin-special.generate.exp diff --git a/qpdf/qtest/qpdf/lin-special.preserve.exp b/qpdf/qtest/qpdf/lin-special.preserve.exp Binary files differindex 14c2eaef..727341b1 100644 --- a/qpdf/qtest/qpdf/lin-special.preserve.exp +++ b/qpdf/qtest/qpdf/lin-special.preserve.exp diff --git a/qpdf/qtest/qpdf/linearize-duplicate-page.pdf b/qpdf/qtest/qpdf/linearize-duplicate-page.pdf Binary files differindex 27a8898e..093149c1 100644 --- a/qpdf/qtest/qpdf/linearize-duplicate-page.pdf +++ b/qpdf/qtest/qpdf/linearize-duplicate-page.pdf diff --git a/qpdf/qtest/qpdf/long-id-linearized.pdf b/qpdf/qtest/qpdf/long-id-linearized.pdf Binary files differindex 2993fd30..e7bc709f 100644 --- a/qpdf/qtest/qpdf/long-id-linearized.pdf +++ b/qpdf/qtest/qpdf/long-id-linearized.pdf diff --git a/qpdf/qtest/qpdf/minimal-linearize-pass1.pdf b/qpdf/qtest/qpdf/minimal-linearize-pass1.pdf Binary files differindex e851063a..ac7d71ed 100644 --- a/qpdf/qtest/qpdf/minimal-linearize-pass1.pdf +++ b/qpdf/qtest/qpdf/minimal-linearize-pass1.pdf diff --git a/qpdf/qtest/qpdf/minimal-linearized.pdf b/qpdf/qtest/qpdf/minimal-linearized.pdf Binary files differindex 15f643d4..809f18d0 100644 --- a/qpdf/qtest/qpdf/minimal-linearized.pdf +++ b/qpdf/qtest/qpdf/minimal-linearized.pdf diff --git a/qpdf/qtest/qpdf/newline-before-endstream-nl-objstm.pdf b/qpdf/qtest/qpdf/newline-before-endstream-nl-objstm.pdf Binary files differindex f412feda..13752cba 100644 --- a/qpdf/qtest/qpdf/newline-before-endstream-nl-objstm.pdf +++ b/qpdf/qtest/qpdf/newline-before-endstream-nl-objstm.pdf diff --git a/qpdf/qtest/qpdf/object-stream.disable.exp b/qpdf/qtest/qpdf/object-stream.disable.exp Binary files differindex a05e0485..9633275f 100644 --- a/qpdf/qtest/qpdf/object-stream.disable.exp +++ b/qpdf/qtest/qpdf/object-stream.disable.exp diff --git a/qpdf/qtest/qpdf/object-stream.generate.exp b/qpdf/qtest/qpdf/object-stream.generate.exp Binary files differindex 3b16f121..a7b4a448 100644 --- a/qpdf/qtest/qpdf/object-stream.generate.exp +++ b/qpdf/qtest/qpdf/object-stream.generate.exp diff --git a/qpdf/qtest/qpdf/object-stream.preserve.exp b/qpdf/qtest/qpdf/object-stream.preserve.exp Binary files differindex 3b16f121..a7b4a448 100644 --- a/qpdf/qtest/qpdf/object-stream.preserve.exp +++ b/qpdf/qtest/qpdf/object-stream.preserve.exp diff --git a/qpdf/qtest/qpdf/pages-is-page-out.pdf b/qpdf/qtest/qpdf/pages-is-page-out.pdf Binary files differindex 15f643d4..809f18d0 100644 --- a/qpdf/qtest/qpdf/pages-is-page-out.pdf +++ b/qpdf/qtest/qpdf/pages-is-page-out.pdf diff --git a/qpdf/qtest/qpdf/png-filters-1-column.pdf b/qpdf/qtest/qpdf/png-filters-1-column.pdf Binary files differnew file mode 100644 index 00000000..5d9a8793 --- /dev/null +++ b/qpdf/qtest/qpdf/png-filters-1-column.pdf diff --git a/qpdf/qtest/qpdf/png-filters-no-columns-decoded.pdf b/qpdf/qtest/qpdf/png-filters-no-columns-decoded.pdf Binary files differnew file mode 100644 index 00000000..db597042 --- /dev/null +++ b/qpdf/qtest/qpdf/png-filters-no-columns-decoded.pdf diff --git a/qpdf/qtest/qpdf/png-filters-no-columns.pdf b/qpdf/qtest/qpdf/png-filters-no-columns.pdf Binary files differnew file mode 100644 index 00000000..83e9911a --- /dev/null +++ b/qpdf/qtest/qpdf/png-filters-no-columns.pdf diff --git a/qpdf/qtest/qpdf/replaced-stream-data-flate.pdf b/qpdf/qtest/qpdf/replaced-stream-data-flate.pdf Binary files differindex 02931170..d51ffefe 100644 --- a/qpdf/qtest/qpdf/replaced-stream-data-flate.pdf +++ b/qpdf/qtest/qpdf/replaced-stream-data-flate.pdf diff --git a/qpdf/qtest/qpdf/short-id-linearized.pdf b/qpdf/qtest/qpdf/short-id-linearized.pdf Binary files differindex ec8829d7..10ef195b 100644 --- a/qpdf/qtest/qpdf/short-id-linearized.pdf +++ b/qpdf/qtest/qpdf/short-id-linearized.pdf diff --git a/qpdf/qtest/qpdf/xref-with-short-size-new.out b/qpdf/qtest/qpdf/xref-with-short-size-new.out index 9396e6c6..1c0f409a 100644 --- a/qpdf/qtest/qpdf/xref-with-short-size-new.out +++ b/qpdf/qtest/qpdf/xref-with-short-size-new.out @@ -1,4 +1,4 @@ -1/0: uncompressed; offset = 15 +1/0: uncompressed; offset = ... 2/0: compressed; stream = 1, index = 0 3/0: compressed; stream = 1, index = 1 4/0: compressed; stream = 1, index = 2 @@ -8,6 +8,6 @@ 8/0: compressed; stream = 1, index = 6 9/0: compressed; stream = 1, index = 7 10/0: compressed; stream = 1, index = 8 -11/0: uncompressed; offset = 674 -12/0: uncompressed; offset = 801 -13/0: uncompressed; offset = 16194 +11/0: uncompressed; offset = ... +12/0: uncompressed; offset = ... +13/0: uncompressed; offset = ... diff --git a/qpdf/qtest/qpdf_test_helpers.pm b/qpdf/qtest/qpdf_test_helpers.pm index eca6b712..f1e4a93c 100644 --- a/qpdf/qtest/qpdf_test_helpers.pm +++ b/qpdf/qtest/qpdf_test_helpers.pm @@ -46,8 +46,8 @@ sub check_pdf {$td->STRING => "", $td->EXIT_STATUS => $status}); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => $output}); + {$td->COMMAND => "qpdf-test-compare a.pdf $output"}, + {$td->FILE => $output, $td->EXIT_STATUS => 0}); } sub flush_tiff_cache diff --git a/qpdf/qtest/qpdfjob.test b/qpdf/qtest/qpdfjob.test index 2aea654b..57ad608a 100644 --- a/qpdf/qtest/qpdfjob.test +++ b/qpdf/qtest/qpdfjob.test @@ -30,18 +30,18 @@ my @bad_json = ( "json-error" ); my @good_json = ( - "choice-match", - "input-file-password", - "empty-input", - "replace-input", - "encrypt-40", - "encrypt-128", - "encrypt-256-with-restrictions", - "add-attachments", - "copy-attachments", - "underlay-overlay", - "underlay-overlay-password", - "misc-options", + ["choice-match", ""], + ["input-file-password", "user"], + ["empty-input", ""], + ["replace-input", ""], + ["encrypt-40", "u"], + ["encrypt-128", "u"], + ["encrypt-256-with-restrictions", "u"], + ["add-attachments", ""], + ["copy-attachments", ""], + ["underlay-overlay", ""], + ["underlay-overlay-password", ""], + ["misc-options", ""], ); my $n_tests = 11 + scalar(@bad_json) + (2 * scalar(@good_json)); @@ -56,28 +56,29 @@ foreach my $i (@bad_json) foreach my $i (@good_json) { - if ($i eq 'replace-input') + my ($base, $pass) = @$i; + if ($base eq 'replace-input') { copy("minimal.pdf", 'a.pdf'); } - $td->runtest("QPDFJob good json: $i", - {$td->COMMAND => "qpdf --job-json-file=job-json-$i.json"}, + $td->runtest("QPDFJob good json: $base", + {$td->COMMAND => "qpdf --job-json-file=job-json-$base.json"}, {$td->STRING => "", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); - if ($i =~ m/encrypt-256/) + if ($base =~ m/encrypt-256/) { - $td->runtest("check encryption $i", + $td->runtest("check encryption $base", {$td->COMMAND => "qpdf a.pdf --password=u" . " --job-json-file=job-show-encryption.json"}, - {$td->FILE => "job-json-$i.out", $td->EXIT_STATUS => 0}, + {$td->FILE => "job-json-$base.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); } else { - $td->runtest("check good json $i output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "job-json-$i.pdf"}); + $td->runtest("check good json $base output", + {$td->COMMAND => "qpdf-test-compare a.pdf job-json-$base.pdf $pass"}, + {$td->FILE => "job-json-$base.pdf", $td->EXIT_STATUS => 0}); } } @@ -107,8 +108,8 @@ $td->runtest("C job API", foreach my $i (['a.pdf', 1], ['b.pdf', 2], ['c.pdf', 3], ['d.pdf', 4]) { $td->runtest("check output", - {$td->FILE => $i->[0]}, - {$td->FILE => "qpdfjob-ctest$i->[1].pdf"}); + {$td->COMMAND => "qpdf-test-compare $i->[0] qpdfjob-ctest$i->[1].pdf"}, + {$td->FILE => "qpdfjob-ctest$i->[1].pdf", $td->EXIT_STATUS => 0}); } my $wide_out = `qpdfjob-ctest wide`; $td->runtest("qpdfjob-ctest wide", @@ -124,8 +125,8 @@ if ($wide_out =~ m/skipped/) else { $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "qpdfjob-ctest-wide.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf qpdfjob-ctest-wide.pdf"}, + {$td->FILE => "qpdfjob-ctest-wide.pdf", $td->EXIT_STATUS => 0}); } cleanup(); diff --git a/qpdf/qtest/replace-input.test b/qpdf/qtest/replace-input.test index 70a37cc2..5c1a9df2 100644 --- a/qpdf/qtest/replace-input.test +++ b/qpdf/qtest/replace-input.test @@ -32,10 +32,12 @@ foreach my $d (['auto-ü', 1], ['auto-öπ', 2]) " --object-streams=generate --replace-input ./$u.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); + # qpdf handles Unicode filenames on Windows, but qpdf-test-compare + # doesn't. + rename("$u.pdf", "u.pdf") or die; $td->runtest("check output ($u)", - {$td->FILE => "$u.pdf"}, - {$td->FILE => "replace-input.pdf"}, - $td->NORMALIZE_NEWLINES); + {$td->COMMAND => "qpdf-test-compare u.pdf replace-input.pdf"}, + {$td->FILE => "replace-input.pdf", $td->EXIT_STATUS => 0}); } system("cp xref-with-short-size.pdf auto-warn.pdf") == 0 or die; @@ -46,8 +48,8 @@ $td->runtest("replace input with warnings", $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "auto-warn.pdf"}, - {$td->FILE => "warn-replace.pdf"}); + {$td->COMMAND => "qpdf-test-compare auto-warn.pdf warn-replace.pdf"}, + {$td->FILE => "warn-replace.pdf", $td->EXIT_STATUS => 0}); $td->runtest("check orig output", {$td->FILE => "auto-warn.pdf.~qpdf-orig"}, {$td->FILE => "xref-with-short-size.pdf"}); diff --git a/qpdf/qtest/rotate-pages.test b/qpdf/qtest/rotate-pages.test index c5e293e8..2f43fed7 100644 --- a/qpdf/qtest/rotate-pages.test +++ b/qpdf/qtest/rotate-pages.test @@ -29,24 +29,24 @@ $td->runtest("page rotation", " --rotate=-90:3,15,17,18"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "rotated.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf rotated.pdf"}, + {$td->FILE => "rotated.pdf", $td->EXIT_STATUS => 0}); $td->runtest("remove rotation", {$td->COMMAND => "qpdf --static-id rotated.pdf a.pdf" . " --qdf --no-original-object-ids --rotate=0"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "unrotated.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf unrotated.pdf"}, + {$td->FILE => "unrotated.pdf", $td->EXIT_STATUS => 0}); $td->runtest("rotate all pages", {$td->COMMAND => "qpdf --static-id --rotate=180 minimal.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "minimal-rotated.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf minimal-rotated.pdf"}, + {$td->FILE => "minimal-rotated.pdf", $td->EXIT_STATUS => 0}); $td->runtest("flatten with inherited rotate", {$td->COMMAND => @@ -54,8 +54,8 @@ $td->runtest("flatten with inherited rotate", " inherited-rotate.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "inherited-flattened.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf inherited-flattened.pdf"}, + {$td->FILE => "inherited-flattened.pdf", $td->EXIT_STATUS => 0}); foreach my $angle (qw(90 180 270)) { diff --git a/qpdf/qtest/specialized-filter.test b/qpdf/qtest/specialized-filter.test index 284d5195..605dd7f2 100644 --- a/qpdf/qtest/specialized-filter.test +++ b/qpdf/qtest/specialized-filter.test @@ -16,7 +16,7 @@ cleanup(); my $td = new TestDriver('specialized-filter'); -my $n_tests = 3; +my $n_tests = 5; my $n_compare_pdfs = 1; # The PDF file was submitted on bug #83 on github. All the PNG filters @@ -39,6 +39,18 @@ $td->runtest("stream with tiff predictor", {$td->FILE => "tiff-predictor.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); - +# TC:SF_FlateLzwDecode PNG filter +# PDF:Table 8:Columns +# The test file is invalid as it does not actually use one column (as is implied by the missing +# key). However png-filters-1-column.pdf is the same file with /Columns explicitely set to 1, +# and displays the same as png-filters-no-columns.pdf and png-filters-no-columns-decoded.pdf. +$td->runtest("decode flate no /Columns", + {$td->COMMAND => "qpdf --static-id --compress-streams=n --decode-level=all" . + " png-filters-no-columns.pdf a.pdf"}, + {$td->STRING => "", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +$td->runtest("check output", + {$td->FILE => "a.pdf"}, + {$td->FILE => "png-filters-no-columns-decoded.pdf"}); cleanup(); $td->report(calc_ntests($n_tests, $n_compare_pdfs)); diff --git a/qpdf/qtest/specific-file.test b/qpdf/qtest/specific-file.test index 188f4dd5..5d77720a 100644 --- a/qpdf/qtest/specific-file.test +++ b/qpdf/qtest/specific-file.test @@ -33,21 +33,22 @@ $td->runtest("compress objstm and xref", {$td->STRING => "", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "compress-objstm-xref.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf compress-objstm-xref.pdf"}, + {$td->FILE => "compress-objstm-xref.pdf", $td->EXIT_STATUS => 0}); $td->runtest("qdf + preserved-unreferenced + xref streams", {$td->COMMAND => "qpdf --qdf --preserve-unreferenced" . " --static-id compress-objstm-xref.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "compress-objstm-xref-qdf.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf compress-objstm-xref-qdf.pdf"}, + {$td->FILE => "compress-objstm-xref-qdf.pdf", $td->EXIT_STATUS => 0}); $td->runtest("check fix-qdf idempotency", {$td->COMMAND => "fix-qdf a.pdf"}, {$td->FILE => "a.pdf", $td->EXIT_STATUS => 0}); $td->runtest("pages points to page", {$td->COMMAND => - "qpdf --static-id --linearize pages-is-page.pdf a.pdf"}, + "qpdf --static-id --linearize --compress-streams=n" . + " pages-is-page.pdf a.pdf"}, {$td->FILE => "pages-is-page.out", $td->EXIT_STATUS => 3}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", diff --git a/qpdf/qtest/split-pages.test b/qpdf/qtest/split-pages.test index 46515c4f..455a4ed3 100644 --- a/qpdf/qtest/split-pages.test +++ b/qpdf/qtest/split-pages.test @@ -70,8 +70,8 @@ $td->runtest("split page with labels", foreach my $i (qw(01-06 07-11)) { $td->runtest("check output ($i)", - {$td->FILE => "split-out-labels-$i.pdf"}, - {$td->FILE => "labels-split-$i.pdf"}); + {$td->COMMAND => "qpdf-test-compare split-out-labels-$i.pdf labels-split-$i.pdf"}, + {$td->FILE => "labels-split-$i.pdf", $td->EXIT_STATUS => 0}); } # See comments in TODO about these expected failures. Search for @@ -121,8 +121,8 @@ foreach my $d (@sp_cases) my $expected = $actual; $expected =~ s/split-out/split-exp/; $td->runtest("check output page $i ($description)", - {$td->FILE => $actual}, - {$td->FILE => $expected}); + {$td->COMMAND => "qpdf-test-compare $actual $expected u"}, + {$td->FILE => $expected, $td->EXIT_STATUS => 0}); } } @@ -201,8 +201,8 @@ foreach my $d (@fo_resources) {$td->STRING => "", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output ($f)", - {$td->FILE => "a.pdf"}, - {$td->FILE => "$f-out.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf $f-out.pdf"}, + {$td->FILE => "$f-out.pdf", $td->EXIT_STATUS => 0}); if ($compare) { compare_pdfs($td, "$f.pdf", "a.pdf"); diff --git a/qpdf/qtest/stream-replacements.test b/qpdf/qtest/stream-replacements.test index 8a9874dc..547e928d 100644 --- a/qpdf/qtest/stream-replacements.test +++ b/qpdf/qtest/stream-replacements.test @@ -21,8 +21,8 @@ $td->runtest("replace stream data", {$td->STRING => "test 7 done\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("check output", - {$td->FILE => "a.pdf"}, - {$td->FILE => "replaced-stream-data.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf replaced-stream-data.pdf"}, + {$td->FILE => "replaced-stream-data.pdf", $td->EXIT_STATUS => 0}); $td->runtest("replace stream data compressed", {$td->COMMAND => "test_driver 8 qstream.pdf"}, {$td->FILE => "test8.out", $td->EXIT_STATUS => 0}, diff --git a/qpdf/qtest/type-checks.test b/qpdf/qtest/type-checks.test index 17b3c994..73cb7fb6 100644 --- a/qpdf/qtest/type-checks.test +++ b/qpdf/qtest/type-checks.test @@ -27,8 +27,8 @@ $td->runtest("ensure object-types-os is up-to-date", " object-types.pdf a.pdf"}, {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("check file", - {$td->FILE => "a.pdf"}, - {$td->FILE => "object-types-os.pdf"}); + {$td->COMMAND => "qpdf-test-compare a.pdf object-types-os.pdf"}, + {$td->FILE => "object-types-os.pdf", $td->EXIT_STATUS => 0}); $td->runtest("type checks", {$td->COMMAND => "test_driver 42 object-types.pdf"}, {$td->FILE => "object-types.out", diff --git a/qpdf/qtest/xref-streams.test b/qpdf/qtest/xref-streams.test index 8d8e8bd9..68640bdf 100644 --- a/qpdf/qtest/xref-streams.test +++ b/qpdf/qtest/xref-streams.test @@ -28,7 +28,8 @@ $td->runtest("recover xref with short size", $td->EXIT_STATUS => 3}, $td->NORMALIZE_NEWLINES); $td->runtest("show new xref stream", - {$td->COMMAND => "qpdf --show-xref a.pdf"}, + {$td->COMMAND => "qpdf --show-xref a.pdf", + $td->FILTER => "perl filter-xref-offsets.pl"}, {$td->FILE => "xref-with-short-size-new.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index 319c80d2..2b8eb761 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -1218,6 +1218,9 @@ test_32(QPDF& pdf, char const* arg2) << "linearized: " << (linearized ? "yes" : "no") << std::endl << "newline: " << (newline ? "yes" : "no") << std::endl; w.setLinearization(linearized); + if (linearized) { + w.setCompressStreams(false); // avoid dependency on zlib's output + } w.setExtraHeaderText(newline ? "%% Comment with newline\n" : "%% Comment\n% No newline"); w.write(); } diff --git a/zlib-flate/qtest/1.compressed-1 b/zlib-flate/qtest/1.compressed-1 Binary files differdeleted file mode 100644 index 11150cf3..00000000 --- a/zlib-flate/qtest/1.compressed-1 +++ /dev/null diff --git a/zlib-flate/qtest/1.compressed-9 b/zlib-flate/qtest/1.compressed-9 Binary files differdeleted file mode 100644 index 25f4647b..00000000 --- a/zlib-flate/qtest/1.compressed-9 +++ /dev/null diff --git a/zlib-flate/qtest/zf.test b/zlib-flate/qtest/zf.test index d864a130..2fa5c1eb 100644 --- a/zlib-flate/qtest/zf.test +++ b/zlib-flate/qtest/zf.test @@ -7,22 +7,40 @@ require TestDriver; my $td = new TestDriver('zlib-flate'); +cleanup(); + +open(F, "<1.uncompressed") or die; +undef $/; +my $unc = <F>; +close(F); + +open(F, ">a.uncompressed") or die; +for (my $i = 0; $i < 100; $i++) +{ + print F $unc; +} +close(F); + foreach my $level ('', '=1', '=9') { my $f = $level; $f =~ s/=/-/; $td->runtest("compress", {$td->COMMAND => - "zlib-flate -compress$level < 1.uncompressed"}, - {$td->FILE => "1.compressed$f", - $td->EXIT_STATUS => 0}); + "zlib-flate -compress$level < a.uncompressed > a.$level"}, + {$td->STRING => "", $td->EXIT_STATUS => 0}); $td->runtest("uncompress", - {$td->COMMAND => "zlib-flate -uncompress < 1.compressed"}, - {$td->FILE => "1.uncompressed", - $td->EXIT_STATUS => 0}); + {$td->COMMAND => "zlib-flate -uncompress < a.$level"}, + {$td->FILE => "a.uncompressed", $td->EXIT_STATUS => 0}); } +my $size1 = (stat("a.=1"))[7]; +my $size9 = (stat("a.=9"))[7]; +$td->runtest("higher compression is smaller", + {$td->STRING => ($size9 < $size1 ? "YES\n" : "$size9 $size1\n")}, + {$td->STRING => "YES\n"}); + $td->runtest("error", {$td->COMMAND => "zlib-flate -uncompress < 1.uncompressed"}, {$td->REGEXP => "flate: inflate: data: .*\n", @@ -36,4 +54,11 @@ $td->runtest("corrupted input", $td->EXIT_STATUS => 3}, $td->NORMALIZE_NEWLINES); -$td->report(8); +$td->report(9); + +cleanup(); + +sub cleanup +{ + system("rm -f a.*"); +} |