Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-pikepdf for openSUSE:Factory checked in at 2024-01-25 18:42:05 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pikepdf (Old) and /work/SRC/openSUSE:Factory/.python-pikepdf.new.1815 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pikepdf" Thu Jan 25 18:42:05 2024 rev:22 rq:1141499 version:8.11.2 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pikepdf/python-pikepdf.changes 2023-12-28 23:03:47.335541986 +0100 +++ /work/SRC/openSUSE:Factory/.python-pikepdf.new.1815/python-pikepdf.changes 2024-01-25 18:42:42.869887824 +0100 @@ -1,0 +2,20 @@ +Mon Jan 22 22:41:11 UTC 2024 - Dirk Müller <dmuel...@suse.com> + +- update to 8.11.2: + * Fixed handling of XMP metadata when metadata contains objects + in a default namespace. + * Replaced all relative imports with absolute imports. + * Rebuilt with QPDF 11.7.0. + * Added support for setting page boxes to a rectangle directly, + e.g. ``page.mediabox = rectangle`` - previously rectangle had + to manually converted to an array. + * Fixed rendering of PDF and individual pages in Jupyter/IPython. + Newer versions of these tools are now pickier about what types + of data they render, and don't render PDFs directly; we now + provide SVG which works well. Requires installation of MuPDF + as before. + * Fixed rendering of inline images in Jupyter/IPython, which was + not implemented. + * Fixed build process to use new artifacts v4 actions on GitHub. + +------------------------------------------------------------------- Old: ---- pikepdf-8.10.1.tar.gz New: ---- pikepdf-8.11.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pikepdf.spec ++++++ --- /var/tmp/diff_new_pack.J4VsTy/_old 2024-01-25 18:42:43.573912942 +0100 +++ /var/tmp/diff_new_pack.J4VsTy/_new 2024-01-25 18:42:43.573912942 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-pikepdf # -# Copyright (c) 2023 SUSE LLC +# Copyright (c) 2024 SUSE LLC # Copyright (c) 2020-2021, Martin Hauke <mar...@gmx.de> # # All modifications and additions to the file contributed by third parties @@ -19,7 +19,7 @@ %{?sle15_python_module_pythons} Name: python-pikepdf -Version: 8.10.1 +Version: 8.11.2 Release: 0 Summary: Read and write PDFs with Python, powered by qpdf License: MPL-2.0 ++++++ pikepdf-8.10.1.tar.gz -> pikepdf-8.11.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/PKG-INFO new/pikepdf-8.11.2/PKG-INFO --- old/pikepdf-8.10.1/PKG-INFO 2023-12-17 10:33:05.458814400 +0100 +++ new/pikepdf-8.11.2/PKG-INFO 2024-01-01 05:15:45.578745000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: pikepdf -Version: 8.10.1 +Version: 8.11.2 Summary: Read and write PDFs with Python, powered by qpdf Author-email: "James R. Barlow" <ja...@purplerock.ca> License: MPL-2.0 @@ -23,7 +23,8 @@ License-File: LICENSE.txt Requires-Dist: Pillow>=10.0.1 Requires-Dist: Deprecated -Requires-Dist: lxml>=4.8 +Requires-Dist: lxml>=4.8; python_version >= "3.10" +Requires-Dist: lxml<5,>=4.8; python_version < "3.10" Requires-Dist: packaging Provides-Extra: dev Requires-Dist: pre-commit; extra == "dev" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/docs/binary-wheels.csv new/pikepdf-8.11.2/docs/binary-wheels.csv --- old/pikepdf-8.10.1/docs/binary-wheels.csv 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/docs/binary-wheels.csv 2024-01-01 05:14:08.000000000 +0100 @@ -1,8 +1,8 @@ ,3.8,3.9,3.10,3.11,3.12,PyPy 3.9, PyPy 3.10 -:fa:`apple` macOS Intel,â ,â ,â ,â ,â ,â³,â +:fa:`apple` macOS Intel,â ,â ,â ,â ,â ,â ,â³ :fa:`apple` macOS Apple Silicon,â,â ,â ,â ,â ,â³,â³ :fa:`windows` Windows x64,â ,â ,â ,â ,â ,â ,â³ :fa:`linux` manylinux2014 x64,â ,â ,â ,â ,â ,â ,â³ :fa:`linux` manylinux2014 aarch64 (ARM64),â ,â ,â ,â ,â ,â³,â³ :fa:`linux` musllinux x64,â,â ,â ,â ,â ,â³,â³ -:fa:`linux` musllinux aarch64 (ARM64),â,â ,â ,â ,â ,â³,â³ \ No newline at end of file +:fa:`linux` musllinux aarch64 (ARM64),â,â³,â³,â³,â³,â³,â³ \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/docs/conf.py new/pikepdf-8.11.2/docs/conf.py --- old/pikepdf-8.10.1/docs/conf.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/docs/conf.py 2024-01-01 05:14:08.000000000 +0100 @@ -92,7 +92,7 @@ # |version| and |release|, also used in various other places throughout the # built documents. -release = "8.10.1" +release = "8.11.2" version = '.'.join(release.split('.')[:2]) # The language for content autogenerated by Sphinx. Refer to documentation diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/docs/index.rst new/pikepdf-8.11.2/docs/index.rst --- old/pikepdf-8.10.1/docs/index.rst 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/docs/index.rst 2024-01-01 05:14:08.000000000 +0100 @@ -171,6 +171,7 @@ :name: reference_toc references/arch + references/build references/contributing references/debugging references/resources diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/docs/references/build.rst new/pikepdf-8.11.2/docs/references/build.rst --- old/pikepdf-8.10.1/docs/references/build.rst 1970-01-01 01:00:00.000000000 +0100 +++ new/pikepdf-8.11.2/docs/references/build.rst 2024-01-01 05:14:08.000000000 +0100 @@ -0,0 +1,69 @@ +Build process notes +=================== + +This section contains notes on complexities in the GitHub Actions +build-deploy workflow. + +macOS crypto provider +--------------------- + +Users reported trouble with open legacy encrypted files on macOS +specifically, e.g. https://github.com/pikepdf/pikepdf/issues/520 + +It appears this is because we were using Homebrew's qpdf which is +currently linked against Homebrew's openssl. The error came from +qpdf specifically not finding openssl's legacy crypto provider. How +exactly that comes about is unclear - it may be that delocate-wheel +is inconsistent, it may be Homebrew has disabled the legacy +provider, etc. Since we use gnutls for crypto and build libqpdf on +the fly for Linux wheels, might as well do the same for macOS +to address this. + +So now we build and link against our own libqpdf, which in turn is +linked against gnutls, which does not seem to have the same issues. +All of our Linux and macOS builds are doing the same thing now, +rather than being split on crypto provider. + +Some ugly complications emerged: + +1. QPDF now uses some C++17 features like std::get that are not +available on macOS 10.9, the default, which seems to support only a +subset of C++17. Because of that we need to explicitly set +MACOSX_DEPLOYMENT_TARGET="11.0" +when building QPDF. + +2. x86_64. For reasons unclear, when MACOSX_DEPLOYMENT_TARGET="11.0" is set +and pikepdf builds, the generated wheel is built without issue, +but installing on a macos-12-x86_64 runner gives this error: + +.. code-block:: + + ERROR: pikepdf-8.11.0-cp38-cp38-macosx_11_0_x86_64.whl is not a supported wheel on this platform. + +(Same for MACOSX_DEPLOYMENT_TARGET="12.0"). + +Thus, we have to pick a different value for this environment variable +for the pikepdf. Clearly something is wrong here, but this unintuitive +configuration is what works. When MACOSX_DEPLOYMENT_TARGET is not set +pybind11's Pybind11Extension class examines other settings and should +set -mmacosx-version-min=10.14 for when cxx_std=17. + +Adding to confusion, the build logs from setuptools show that are +targeting 10.9, and the wheels are generated for 10.9. + +There may be dragons here, still. + +3. arm64. MACOSX_DEPLOYMENT_TARGET="11.0" is the minimum version for arm64. +We set this to ensure that pikepdf builds for this target, and then +because of the aforementioned Pybind11Extension check, pikepdf's +deployment target would be set to 10.14. To override this, we set +MACOSX_DEPLOYMENT_TARGET="11.0" for each builder, as well as when +building libqpdf. + +4. Environment differences between GitHub runners and Cirrus CI runners +mean we need to use sudo for Cirrus CI, hence maybe_sudo. + +Taking a quick peek at numpy, it may be easier to build universal2 wheels +and use QEMU or Cirrus CI to confirm that they work. That would be another +big build overhaul, so let's wait for GitHub Actions to release arm64 +runners instead. \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/docs/releasenotes/version8.rst new/pikepdf-8.11.2/docs/releasenotes/version8.rst --- old/pikepdf-8.10.1/docs/releasenotes/version8.rst 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/docs/releasenotes/version8.rst 2024-01-01 05:14:08.000000000 +0100 @@ -1,3 +1,33 @@ +v8.11.2 +======= + +- Fixed handling of XMP metadata when metadata contains objects in a default + namespace. + +v8.11.1 +======= + +- macOS wheels are now linked against the GnuTLS crypto library instead of + OpenSSL. Hopefully this will alleviate situations where the legacy crypto + provider could not be loaded. :issue:`520` +- Replaced all relative imports with absolute imports. +- Excluded lxml 5.x for Python 3.8 and 3.9, since this project is not producing + wheels for 3.8 and 3.9 for the latest versions. + +v8.11.0 +======= + +- Rebuilt with QPDF 11.7.0. +- Added support for setting page boxes to a rectangle directly, e.g. + ``page.mediabox = rectangle`` - previously rectangle had to + manually converted to an array. +- Fixed rendering of PDF and individual pages in Jupyter/IPython. Newer versions + of these tools are now pickier about what types of data they render, and don't + render PDFs directly; we now provide SVG which works well. Requires installation + of MuPDF as before. +- Fixed rendering of inline images in Jupyter/IPython, which was not implemented. +- Fixed build process to use new artifacts v4 actions on GitHub. + v8.10.1 ======= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/docs/topics/attachments.rst new/pikepdf-8.11.2/docs/topics/attachments.rst --- old/pikepdf-8.10.1/docs/topics/attachments.rst 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/docs/topics/attachments.rst 2024-01-01 05:14:08.000000000 +0100 @@ -22,7 +22,7 @@ >>> pdf.attachments['README.md'] = filespec >>> pdf.attachments - <pikepdf._core.Attachments with 1 attached files> + <pikepdf._core.Attachments: ['README.md']> This creates an attached file named ``README.md``, which holds the data in ``filespec``. Now we can retrieve the data. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/pyproject.toml new/pikepdf-8.11.2/pyproject.toml --- old/pikepdf-8.10.1/pyproject.toml 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/pyproject.toml 2024-01-01 05:14:08.000000000 +0100 @@ -7,7 +7,7 @@ [project] name = "pikepdf" -version = "8.10.1" +version = "8.11.2" description = "Read and write PDFs with Python, powered by qpdf" readme = "README.md" requires-python = ">=3.8" @@ -26,7 +26,13 @@ "Topic :: Multimedia :: Graphics", "Topic :: Software Development :: Libraries", ] -dependencies = ["Pillow>=10.0.1", "Deprecated", "lxml>=4.8", "packaging"] +dependencies = [ + "Pillow>=10.0.1", + "Deprecated", + "lxml>=4.8; python_version >= '3.10'", + "lxml>=4.8,<5; python_version < '3.10'", # lxml doesn't make 5.0 wheels for python < 3.10 + "packaging", +] [project.urls] documentation = "https://pikepdf.readthedocs.io/" @@ -41,7 +47,7 @@ "sphinx-design", "sphinx-issues", "sphinx-rtd-theme", - "tomli;python_version < '3.11'", + "tomli; python_version < '3.11'", ] mypy = ["lxml-stubs", "types-Pillow", "types-requests", "types-setuptools"] test = [ @@ -114,15 +120,21 @@ [tool.cibuildwheel.environment] QPDF_MIN_VERSION = "11.5.0" -QPDF_VERSION = "11.6.4" +QPDF_VERSION = "11.7.0" QPDF_PATTERN = "https://github.com/qpdf/qpdf/releases/download/vVERSION/qpdf-VERSION.tar.gz" [tool.cibuildwheel.linux] before-all = [ - "yum install -y wget libxml2-devel libxslt-devel libjpeg-turbo-devel zlib-devel", + """yum install -y \ + libjpeg-turbo-devel \ + libxml2-devel \ + libxslt-devel \ + wget \ + zlib-devel \ + """, "sh -c 'yum install -y libxmp || true'", - "bash build-scripts/linux-download-qpdf.bash $QPDF_VERSION", - "bash build-scripts/linux-build-wheel-deps.bash", + "bash build-scripts/posix-download-qpdf.bash $QPDF_VERSION", + "bash build-scripts/posix-build-wheel-deps.bash", ] environment-pass = ["CI"] manylinux-x86_64-image = "manylinux2014" @@ -133,17 +145,36 @@ [[tool.cibuildwheel.overrides]] select = "*-musllinux*" before-all = [ - "apk add py3-lxml py3-pillow python3 py3-setuptools py3-pybind11-dev python3-dev py3-wheel exempi-dev wget cmake make zlib-dev jpeg-dev", - "bash build-scripts/linux-download-qpdf.bash $QPDF_VERSION", - "bash build-scripts/linux-build-wheel-deps.bash", + """apk add \ + cmake \ + exempi-dev \ + jpeg-dev \ + make \ + mupdf-tools \ + py3-lxml \ + py3-pillow \ + py3-pybind11-dev \ + py3-setuptools \ + py3-wheel \ + python3 \ + python3-dev \ + wget \ + zlib-dev \ + """, + "bash build-scripts/posix-download-qpdf.bash $QPDF_VERSION", + "bash build-scripts/posix-build-wheel-deps.bash", ] [tool.cibuildwheel.macos] -# Use workaround until https://github.com/actions/setup-python/issues/577 is fixed before-all = [ "sh -c 'brew update || true'", - "sh -c 'brew install qpdf little-cms2 || true'", -] + "sh -c 'brew install gnutls little-cms2 mupdf wget || true'", + "bash build-scripts/posix-download-qpdf.bash $QPDF_VERSION", + "bash build-scripts/posix-build-wheel-deps.bash", +] # Use 'brew whatever || true' workaround until https://github.com/actions/setup-python/issues/577 is fixed +before-build = [ + 'if [ "$(uname -m)" == "arm64" ]; then export MACOSX_DEPLOYMENT_TARGET="11.0"; fi', +] # Ensure each build is targeting 11.0 when we are on arm64. See docs/references/build.rst [tool.cibuildwheel.windows] @@ -193,9 +224,15 @@ "W", # pycodestyle "F", # pyflakes "I001", # isort + "TID", # flake8-tidy-imports "UP", # pyupgrade + "YTT", # flake8-2020 ] -ignore = ["D105"] # docstring in magic method +ignore = ["D105", "B028"] + +[tool.ruff.lint.flake8-tidy-imports] +ban-relative-imports = "all" +banned-module-level-imports = ["numpy"] [tool.ruff.lint.isort] known-first-party = ["pikepdf", "pikepdf._core"] @@ -220,4 +257,4 @@ "examples/*.py" = ["D103"] # [tool.ruff.format] -# quote-style = "single" +# quote-style = "preserve" # 2023-12-28 - ruff feature not ready yet diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/core/object.cpp new/pikepdf-8.11.2/src/core/object.cpp --- old/pikepdf-8.10.1/src/core/object.cpp 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/core/object.cpp 2024-01-01 05:14:08.000000000 +0100 @@ -132,7 +132,19 @@ case qpdf_object_type_e::ot_array: { // Call operator==() on each element of the arrays, meaning this // recurses into this function - return (self.getArrayAsVector() == other.getArrayAsVector()); + if (self.getArrayNItems() != other.getArrayNItems()) + return false; + { + auto self_aitems = self.aitems(); + auto other_aitems = other.aitems(); + auto iter_self = self_aitems.begin(); + auto iter_other = other_aitems.begin(); + for (; iter_self != self_aitems.end(); ++iter_self, ++iter_other) { + if (!objecthandle_equal(*iter_self, *iter_other)) + return false; + } + } + return true; } case qpdf_object_type_e::ot_dictionary: { // Call operator==() on each element of the arrays, meaning this @@ -193,9 +205,11 @@ if (!haystack.isArray()) throw std::logic_error("pikepdf.Object is not an Array"); // LCOV_EXCL_LINE - auto vec = haystack.getArrayAsVector(); - auto result = std::find(std::begin(vec), std::end(vec), needle); - return (result != std::end(vec)); + for (auto &item : haystack.aitems()) { + if (objecthandle_equal(item, needle)) + return true; + } + return false; } QPDFObjectHandle object_get_key(QPDFObjectHandle h, std::string const &key) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/core/object_convert.cpp new/pikepdf-8.11.2/src/core/object_convert.cpp --- old/pikepdf-8.10.1/src/core/object_convert.cpp 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/core/object_convert.cpp 2024-01-01 05:14:08.000000000 +0100 @@ -87,6 +87,10 @@ "Can't convert ObjectHelper (or subclass) to Object implicitly. " "Use .obj to get access the underlying object."); } + if (py::isinstance<QPDFObjectHandle::Rectangle>(handle)) { + auto rect = handle.cast<QPDFObjectHandle::Rectangle>(); + return QPDFObjectHandle::newFromRectangle(rect); + } // Special-case booleans since pybind11 coerces nonzero integers to boolean if (py::isinstance<py::bool_>(handle)) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/core/object_repr.cpp new/pikepdf-8.11.2/src/core/object_repr.cpp --- old/pikepdf-8.10.1/src/core/object_repr.cpp 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/core/object_repr.cpp 2024-01-01 05:14:08.000000000 +0100 @@ -218,7 +218,7 @@ { bool first_item = true; ss << " "; - for (auto item : h.getArrayAsVector()) { + for (auto &item : h.aitems()) { if (!first_item) ss << ", "; first_item = false; @@ -240,9 +240,7 @@ { bool first_item = true; ss << "\n"; - for (auto item : h.getDictAsMap()) { - auto &key = item.first; - auto &obj = item.second; + for (auto &[key, obj] : h.ditems()) { if (!first_item) ss << ",\n"; first_item = false; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/core/pikepdf.cpp new/pikepdf-8.11.2/src/core/pikepdf.cpp --- old/pikepdf-8.10.1/src/core/pikepdf.cpp 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/core/pikepdf.cpp 2024-01-01 05:14:08.000000000 +0100 @@ -59,8 +59,8 @@ match_replace{"QPDF", "pikepdf.Pdf"}, }; - for (auto mr : replacements) { - msg = std::regex_replace(msg, mr.first, mr.second); + for (auto [regex, replacement] : replacements) { + msg = std::regex_replace(msg, regex, replacement); } return msg; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/pikepdf/__init__.py new/pikepdf-8.11.2/src/pikepdf/__init__.py --- old/pikepdf-8.10.1/src/pikepdf/__init__.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/pikepdf/__init__.py 2024-01-01 05:14:08.000000000 +0100 @@ -7,15 +7,15 @@ from __future__ import annotations -from ._version import __version__ +from pikepdf._version import __version__ try: - from . import _core + from pikepdf import _core except ImportError as _e: # pragma: no cover _msg = "pikepdf's extension library failed to import" raise ImportError(_msg) from _e -from ._core import ( +from pikepdf._core import ( AccessMode, Annotation, AttachedFileSpec, @@ -41,8 +41,8 @@ TokenFilter, TokenType, ) -from ._exceptions import DependencyError -from .objects import ( +from pikepdf._exceptions import DependencyError +from pikepdf.objects import ( Array, Dictionary, Name, @@ -52,7 +52,7 @@ Stream, String, ) -from .models import ( +from pikepdf.models import ( Encryption, Outline, OutlineItem, @@ -73,8 +73,8 @@ # While _cpphelpers is intended to be called from our C++ code only, explicitly # importing helps introspection tools like PyInstaller figure out that the module # is necessary. -from . import _cpphelpers, _methods, codec # noqa: F401, F841 -from . import settings +from pikepdf import _cpphelpers, _methods, codec # noqa: F401, F841 +from pikepdf import settings __libqpdf_version__: str = _core.qpdf_version() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/pikepdf/_core.pyi new/pikepdf-8.11.2/src/pikepdf/_core.pyi --- old/pikepdf-8.10.1/src/pikepdf/_core.pyi 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/pikepdf/_core.pyi 2024-01-01 05:14:08.000000000 +0100 @@ -1238,7 +1238,7 @@ If the /ArtBox is not defined, the /CropBox is returned. """ @artbox.setter - def artbox(self, val: Array) -> None: ... + def artbox(self, val: Array | Rectangle) -> None: ... @property def bleedbox(self) -> Array: """Return page's effective /BleedBox, in PDF units. @@ -1250,7 +1250,7 @@ If the /BleedBox is not defined, the /CropBox is returned. """ @bleedbox.setter - def bleedbox(self, val: Array) -> None: ... + def bleedbox(self, val: Array | Rectangle) -> None: ... @property def cropbox(self) -> Array: """Return page's effective /CropBox, in PDF units. @@ -1264,7 +1264,7 @@ If the /CropBox is not defined, the /MediaBox is returned. """ @cropbox.setter - def cropbox(self, val: Array) -> None: ... + def cropbox(self, val: Array | Rectangle) -> None: ... @property def mediabox(self) -> Array: """Return page's /MediaBox, in PDF units. @@ -1274,7 +1274,7 @@ the page is to be printed." """ @mediabox.setter - def mediabox(self, val: Array) -> None: ... + def mediabox(self, val: Array | Rectangle) -> None: ... @property def obj(self) -> Dictionary: ... @property @@ -1291,7 +1291,7 @@ /CropBox is not defined, /MediaBox is returned). """ @trimbox.setter - def trimbox(self, val: Array) -> None: ... + def trimbox(self, val: Array | Rectangle) -> None: ... @property def resources(self) -> Dictionary: """Return this page's resources dictionary. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/pikepdf/_methods.py new/pikepdf-8.11.2/src/pikepdf/_methods.py --- old/pikepdf-8.10.1/src/pikepdf/_methods.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/pikepdf/_methods.py 2024-01-01 05:14:08.000000000 +0100 @@ -16,7 +16,7 @@ import mimetypes import shutil from collections.abc import KeysView, MutableMapping -from contextlib import ExitStack +from contextlib import ExitStack, suppress from decimal import Decimal from io import BytesIO, RawIOBase from pathlib import Path @@ -74,7 +74,7 @@ tmp_in.flush() proc = run( - ['mudraw', '-F', fmt, '-o', '-', tmp_in.name], + ['mutool', 'draw', '-F', fmt, '-o', '-', tmp_in.name], capture_output=True, check=True, ) @@ -157,14 +157,20 @@ @augments(Pdf) class Extend_Pdf: - def _repr_mimebundle_( - self, include=None, exclude=None - ): # pylint: disable=unused-argument + def _quick_save(self): bio = BytesIO() self.save(bio) bio.seek(0) + return bio - data = {'application/pdf': bio.read()} + def _repr_mimebundle_( + self, include=None, exclude=None + ): # pylint: disable=unused-argument + pdf_data = self._quick_save().read() + data = { + 'application/pdf': pdf_data, + 'image/svg+xml': _mudraw(pdf_data, 'svg').decode('utf-8'), + } return data @property @@ -430,19 +436,15 @@ def check_is_box(obj) -> None: - try: + with suppress(AttributeError): if obj.is_rectangle: return - except AttributeError: - pass - try: pdfobj = Array(obj) if pdfobj.is_rectangle: return except Exception as e: raise ValueError("object is not a rectangle") from e - raise ValueError("object is not a rectangle") @@ -661,7 +663,7 @@ def _repr_mimebundle_(self, include=None, exclude=None): data = {} - bundle = {'application/pdf', 'image/png'} + bundle = {'application/pdf', 'image/svg+xml'} if include: bundle = {k for k in bundle if k in include} if exclude: @@ -669,11 +671,9 @@ pagedata = _single_page_pdf(self) if 'application/pdf' in bundle: data['application/pdf'] = pagedata - if 'image/png' in bundle: - try: - data['image/png'] = _mudraw(pagedata, 'png') - except (FileNotFoundError, RuntimeError): - pass + if 'image/svg+xml' in bundle: + with suppress(FileNotFoundError, RuntimeError): + data['image/svg+xml'] = _mudraw(pagedata, 'svg').decode('utf-8') return data @@ -717,8 +717,6 @@ yield from self._get_all_filespecs() def __repr__(self): - if len(self) == 0: - return "<pikepdf._core.Attachments no attached files>" return f"<pikepdf._core.Attachments: {list(self)}>" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/pikepdf/_version.py new/pikepdf-8.11.2/src/pikepdf/_version.py --- old/pikepdf-8.10.1/src/pikepdf/_version.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/pikepdf/_version.py 2024-01-01 05:14:08.000000000 +0100 @@ -3,4 +3,4 @@ from __future__ import annotations -__version__ = "8.10.1" +__version__ = "8.11.2" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/pikepdf/_xml.py new/pikepdf-8.11.2/src/pikepdf/_xml.py --- old/pikepdf-8.10.1/src/pikepdf/_xml.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/pikepdf/_xml.py 2024-01-01 05:14:08.000000000 +0100 @@ -6,7 +6,7 @@ from typing import IO, Any, AnyStr from lxml.etree import XMLParser as _UnsafeXMLParser -from lxml.etree import _ElementTree +from lxml.etree import _Element, _ElementTree from lxml.etree import parse as _parse @@ -25,4 +25,4 @@ return _parse(source, parser=parser) -__all__ = ['parse_xml'] +__all__ = ['parse_xml', '_ElementTree', '_Element'] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/pikepdf/canvas.py new/pikepdf-8.11.2/src/pikepdf/canvas.py --- old/pikepdf-8.10.1/src/pikepdf/canvas.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/pikepdf/canvas.py 2024-01-01 05:14:08.000000000 +0100 @@ -65,6 +65,9 @@ the data, a reference should be set in at least one page's /Resources dictionary to retain the font in the output PDF and ensure it is usable on that page. + The returned Dictionary should be created as an indirect object, using + ``pdf.make_indirect()``. + Returns a Dictionary suitable for insertion into a /Resources /Font dictionary. """ @@ -78,10 +81,12 @@ def register(self, pdf: Pdf) -> Dictionary: """Register the font.""" - return Dictionary( - BaseFont=Name.Helvetica, - Type=Name.Font, - Subtype=Name.Type1, + return pdf.make_indirect( + Dictionary( + BaseFont=Name.Helvetica, + Type=Name.Font, + Subtype=Name.Type1, + ) ) @@ -435,6 +440,9 @@ self.do.push() return result + def _repr_mimebundle_(self, include=None, exclude=None): + return self.to_pdf()._repr_mimebundle_(include, exclude) + class Text: """Text object for rendering text on a pikepdf canvas.""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/pikepdf/codec.py new/pikepdf-8.11.2/src/pikepdf/codec.py --- old/pikepdf-8.10.1/src/pikepdf/codec.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/pikepdf/codec.py 2024-01-01 05:14:08.000000000 +0100 @@ -8,7 +8,7 @@ import codecs from typing import Any, Container -from ._core import pdf_doc_to_utf8, utf8_to_pdf_doc +from pikepdf._core import pdf_doc_to_utf8, utf8_to_pdf_doc # pylint: disable=redefined-builtin diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/pikepdf/models/__init__.py new/pikepdf-8.11.2/src/pikepdf/models/__init__.py --- old/pikepdf-8.10.1/src/pikepdf/models/__init__.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/pikepdf/models/__init__.py 2024-01-01 05:14:08.000000000 +0100 @@ -5,18 +5,18 @@ from __future__ import annotations -from ._content_stream import ( +from pikepdf.models._content_stream import ( ContentStreamInstructions, PdfParsingError, UnparseableContentStreamInstructions, parse_content_stream, unparse_content_stream, ) -from .encryption import Encryption, EncryptionInfo, Permissions -from .image import PdfImage, PdfInlineImage, UnsupportedImageTypeError -from .matrix import PdfMatrix -from .metadata import PdfMetadata -from .outlines import ( +from pikepdf.models.encryption import Encryption, EncryptionInfo, Permissions +from pikepdf.models.image import PdfImage, PdfInlineImage, UnsupportedImageTypeError +from pikepdf.models.matrix import PdfMatrix +from pikepdf.models.metadata import PdfMetadata +from pikepdf.models.outlines import ( Outline, OutlineItem, OutlineStructureError, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/pikepdf/models/encryption.py new/pikepdf-8.11.2/src/pikepdf/models/encryption.py --- old/pikepdf-8.10.1/src/pikepdf/models/encryption.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/pikepdf/models/encryption.py 2024-01-01 05:14:08.000000000 +0100 @@ -137,6 +137,14 @@ """ return len(self._encdict['encryption_key']) * 8 + def __repr__(self): + return ( + f'<{self.__class__.__name__}: {self.R=}, {self.V=}, {self.P=} ' + f'{self.stream_method=}, {self.string_method=}, ' + f'{self.file_method=}, {self.user_password=}, ' + f'{self.encryption_key=}, {self.bits=}>' + ) + class Encryption(NamedTuple): """Specify the encryption settings to apply when a PDF is saved.""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/pikepdf/models/image.py new/pikepdf-8.11.2/src/pikepdf/models/image.py --- old/pikepdf-8.10.1/src/pikepdf/models/image.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/pikepdf/models/image.py 2024-01-01 05:14:08.000000000 +0100 @@ -354,6 +354,13 @@ def as_pil_image(self) -> Image.Image: """Convert this PDF image to a Python PIL (Pillow) image.""" + def _repr_png_(self) -> bytes: + """Display hook for IPython/Jupyter.""" + b = BytesIO() + with self.as_pil_image() as im: + im.save(b, 'PNG') + return b.getvalue() + class PdfImage(PdfImageBase): """Support class to provide a consistent API for manipulating PDF images. @@ -810,13 +817,6 @@ f'size={self.width}x{self.height} at {hex(id(self))}>' ) - def _repr_png_(self) -> bytes: - """Display hook for IPython/Jupyter.""" - b = BytesIO() - with self.as_pil_image() as im: - im.save(b, 'PNG') - return b.getvalue() - class PdfJpxImage(PdfImage): """Support class for JPEG 2000 images. Implements the same API as :class:`PdfImage`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/pikepdf/models/metadata.py new/pikepdf-8.11.2/src/pikepdf/models/metadata.py --- old/pikepdf-8.10.1/src/pikepdf/models/metadata.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/pikepdf/models/metadata.py 2024-01-01 05:14:08.000000000 +0100 @@ -12,14 +12,14 @@ from datetime import datetime, timezone from functools import wraps from io import BytesIO -from typing import TYPE_CHECKING, Any, Callable, NamedTuple, Set +from typing import TYPE_CHECKING, Any, Callable, Iterator, NamedTuple, Set from warnings import warn from lxml import etree from lxml.etree import QName, XMLSyntaxError from pikepdf._version import __version__ as pikepdf_version -from pikepdf._xml import parse_xml +from pikepdf._xml import _Element, parse_xml from pikepdf.objects import Name, Stream, String if sys.version_info < (3, 9): # pragma: no cover @@ -383,11 +383,11 @@ ): """Construct PdfMetadata. Use Pdf.open_metadata() instead.""" self._pdf = pdf - self._xmp = None self.mark = pikepdf_mark self.sync_docinfo = sync_docinfo self._updating = False self.overwrite_invalid_xml = overwrite_invalid_xml + self._xmp = None def load_from_docinfo( self, docinfo, delete_missing: bool = False, raise_failure: bool = False @@ -600,12 +600,17 @@ try: prefix, tag = name.split(':', maxsplit=1) except ValueError: - # If missing the namespace, put it in the top level namespace - # To do this completely correct we actually need to figure out - # the namespace based on context defined by parent tags. That - # https://www.w3.org/2001/tag/doc/qnameids.html - prefix, tag = 'x', name - uri = cls.NS[prefix] + # If missing the namespace, it belongs in the default namespace. + # A tag such <xyz xmlns="http://example.com"> defines a default + # namespace of http://example.com for all enclosed tags that don't + # override the namespace with a colon prefix. + # XMP does not usually use the default namespace, so we can + # assume it's just blank. In practice a document that depends on + # defining a default namespace over some part of its content + # could introduce a collision. + # See: https://www.w3.org/TR/REC-xml-names/#dt-defaultNS + prefix, tag = '', name + uri = cls.NS.get(prefix, None) return str(QName(uri, tag)) def _prefix_from_uri(self, uriname): @@ -617,7 +622,7 @@ uri = uripart.replace('{', '') return self.REVERSE_NS[uri] + ':' + tag - def _get_subelements(self, node): + def _get_subelements(self, node: _Element) -> Any: """Gather the sub-elements attached to a node. Gather rdf:Bag and and rdf:Seq into set and list respectively. For @@ -641,7 +646,8 @@ return result return '' - def _get_rdf_root(self): + def _get_rdf_root(self) -> _Element: + assert self._xmp is not None rdf = self._xmp.find('.//rdf:RDF', self.NS) if rdf is None: rdf = self._xmp.getroot() @@ -649,7 +655,9 @@ raise ValueError("Metadata seems to be XML but not XMP") return rdf - def _get_elements(self, name: str | QName = ''): + def _get_elements( + self, name: str | QName = '' + ) -> Iterator[tuple[_Element, str | bytes | None, Any, _Element]]: """Get elements from XMP. Core routine to find elements matching name within the XMP and yield @@ -689,7 +697,7 @@ values = self._get_subelements(node) yield (node, None, values, rdfdesc) - def _get_element_values(self, name=''): + def _get_element_values(self, name: str | QName = '') -> Iterator[Any]: yield from (v[2] for v in self._get_elements(name)) @ensure_loaded @@ -839,7 +847,7 @@ if ( len(node.attrib) == 1 and len(node) == 0 - and QName(XMP_NS_RDF, 'about') in node.attrib + and QName(XMP_NS_RDF, 'about') in node.attrib.keys() ): # The only thing left on this node is rdf:about="", so remove it parent.remove(node) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/pikepdf/models/outlines.py new/pikepdf-8.11.2/src/pikepdf/models/outlines.py --- old/pikepdf-8.10.1/src/pikepdf/models/outlines.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/pikepdf/models/outlines.py 2024-01-01 05:14:08.000000000 +0100 @@ -299,6 +299,15 @@ def __repr__(self): return f'<pikepdf.{self.__class__.__name__}: {len(self.root)} items>' + def _repr_pretty_(self, p, cycle): + if cycle: + p.text("...") + else: + with p.group(2, "pikepdf.models.outlines.Outline<\n", "\n>"): + for _, item in enumerate(self.root): + p.breakable() + p.pretty(str(item)) + def __enter__(self): self._updating = True return self diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/pikepdf/objects.py new/pikepdf-8.11.2/src/pikepdf/objects.py --- old/pikepdf-8.10.1/src/pikepdf/objects.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/pikepdf/objects.py 2024-01-01 05:14:08.000000000 +0100 @@ -22,8 +22,8 @@ from secrets import token_urlsafe from typing import TYPE_CHECKING, Any, Iterable, Mapping, cast -from . import _core -from ._core import Matrix, Object, ObjectType, Rectangle +from pikepdf import _core +from pikepdf._core import Matrix, Object, ObjectType, Rectangle if TYPE_CHECKING: # pragma: no cover from pikepdf import Pdf diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/pikepdf/settings.py new/pikepdf-8.11.2/src/pikepdf/settings.py --- old/pikepdf-8.10.1/src/pikepdf/settings.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/src/pikepdf/settings.py 2024-01-01 05:14:08.000000000 +0100 @@ -5,7 +5,7 @@ from __future__ import annotations -from ._core import ( +from pikepdf._core import ( get_decimal_precision, set_decimal_precision, set_flate_compression_level, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/pikepdf.egg-info/PKG-INFO new/pikepdf-8.11.2/src/pikepdf.egg-info/PKG-INFO --- old/pikepdf-8.10.1/src/pikepdf.egg-info/PKG-INFO 2023-12-17 10:33:05.000000000 +0100 +++ new/pikepdf-8.11.2/src/pikepdf.egg-info/PKG-INFO 2024-01-01 05:15:45.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: pikepdf -Version: 8.10.1 +Version: 8.11.2 Summary: Read and write PDFs with Python, powered by qpdf Author-email: "James R. Barlow" <ja...@purplerock.ca> License: MPL-2.0 @@ -23,7 +23,8 @@ License-File: LICENSE.txt Requires-Dist: Pillow>=10.0.1 Requires-Dist: Deprecated -Requires-Dist: lxml>=4.8 +Requires-Dist: lxml>=4.8; python_version >= "3.10" +Requires-Dist: lxml<5,>=4.8; python_version < "3.10" Requires-Dist: packaging Provides-Extra: dev Requires-Dist: pre-commit; extra == "dev" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/pikepdf.egg-info/SOURCES.txt new/pikepdf-8.11.2/src/pikepdf.egg-info/SOURCES.txt --- old/pikepdf-8.10.1/src/pikepdf.egg-info/SOURCES.txt 2023-12-17 10:33:05.000000000 +0100 +++ new/pikepdf-8.11.2/src/pikepdf.egg-info/SOURCES.txt 2024-01-01 05:15:45.000000000 +0100 @@ -28,6 +28,7 @@ docs/images/save-pike.jpg docs/images/sushi.jpg docs/references/arch.rst +docs/references/build.rst docs/references/contributing.rst docs/references/debugging.rst docs/references/resources.rst diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/src/pikepdf.egg-info/requires.txt new/pikepdf-8.11.2/src/pikepdf.egg-info/requires.txt --- old/pikepdf-8.10.1/src/pikepdf.egg-info/requires.txt 2023-12-17 10:33:05.000000000 +0100 +++ new/pikepdf-8.11.2/src/pikepdf.egg-info/requires.txt 2024-01-01 05:15:45.000000000 +0100 @@ -1,8 +1,13 @@ Pillow>=10.0.1 Deprecated -lxml>=4.8 packaging +[:python_version < "3.10"] +lxml<5,>=4.8 + +[:python_version >= "3.10"] +lxml>=4.8 + [dev] pre-commit typer[all] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/tests/conftest.py new/pikepdf-8.11.2/tests/conftest.py --- old/pikepdf-8.10.1/tests/conftest.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/tests/conftest.py 2024-01-01 05:14:08.000000000 +0100 @@ -5,6 +5,7 @@ import os import platform +import shutil import sys from pathlib import Path @@ -53,6 +54,9 @@ skip_if_ci = pytest.mark.skipif( os.environ.get('CI', '') == 'true', reason="test too slow for CI" ) +fails_if_no_mutool = pytest.mark.xfail( + shutil.which("mutool") is None, reason="test fails without mutool" +) def needs_libqpdf_v(version: str, *, reason=None): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/tests/test_ipython.py new/pikepdf-8.11.2/tests/test_ipython.py --- old/pikepdf-8.10.1/tests/test_ipython.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/tests/test_ipython.py 2024-01-01 05:14:08.000000000 +0100 @@ -11,7 +11,7 @@ from io import BytesIO import pytest -from PIL import Image +from conftest import fails_if_no_mutool import pikepdf @@ -53,20 +53,17 @@ 'image/png' not in mimebundle ), "Generated image/png when mudraw() was rigged to fail" - def return_simple_png(prog_args, *args, **kwargs): - im = Image.new('1', (1, 1)) - bio = BytesIO() - im.save(bio, format='PNG') - bio.seek(0) + def return_simple_svg(prog_args, *args, **kwargs): + bio = BytesIO(b'<svg xmlns="http://www.w3.org/2000/svg"></svg>') return subprocess.CompletedProcess(prog_args, 0, stdout=bio.read(), stderr=b'') - monkeypatch.setattr(pikepdf._methods, 'run', return_simple_png) + monkeypatch.setattr(pikepdf._methods, 'run', return_simple_svg) mimebundle = page0._repr_mimebundle_( - include=['image/png'], exclude=['application/pdf'] + include=['image/svg+xml'], exclude=['application/pdf'] ) assert ( - 'image/png' in mimebundle - ), "Did not generate image/png when mudraw() was rigged to succeed" + 'image/svg+xml' in mimebundle + ), "Did not generate image/svg+xml when mudraw() was rigged to succeed" def test_display_image(pal): @@ -76,6 +73,7 @@ assert result[1:4] == b'PNG' +@fails_if_no_mutool def test_display_pdf(pal): mimebundle = pal._repr_mimebundle_( include=['application/pdf'], exclude=['text/css'] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/tests/test_metadata.py new/pikepdf-8.11.2/tests/test_metadata.py --- old/pikepdf-8.10.1/tests/test_metadata.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/tests/test_metadata.py 2024-01-01 05:14:08.000000000 +0100 @@ -733,7 +733,7 @@ def test_qname_no_namespace(): - assert PdfMetadata._qname('abc') == '{adobe:ns:meta/}abc' + assert PdfMetadata._qname('abc') == 'abc' def test_register_xmlns(): @@ -741,3 +741,73 @@ assert ( PdfMetadata._qname('pikepdf:foo') == '{http://github.com/pikepdf/pikepdf/}foo' ) + + +def test_undocumented_pdfx_identifier(trivial): + trivial.Root.Metadata = Stream( + trivial, + """\ +<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 4.0-c316 44.253921, Sun Oct 01 2006 17:14:39"> + <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:title> + <rdf:Alt> + <rdf:li xml:lang="x-default">A</rdf:li> + </rdf:Alt> + </dc:title> + <dc:creator> + <rdf:Seq> + <rdf:li>B</rdf:li> + <rdf:li>C</rdf:li> + </rdf:Seq> + </dc:creator> + <dc:description> + <rdf:Alt> + <rdf:li xml:lang="x-default">D</rdf:li> + </rdf:Alt> + </dc:description> + <dc:publisher> + <rdf:Bag> + <rdf:li>E</rdf:li> + </rdf:Bag> + </dc:publisher> + </rdf:Description> + <rdf:Description xmlns:prism="http://prismstandard.org/namespaces/basic/3.0/" rdf:about=""> + <prism:aggregationType>journal</prism:aggregationType> + <prism:publicationName>Journal</prism:publicationName> + <prism:copyright>© 2023</prism:copyright> + <prism:issn>1234-5678</prism:issn> + <prism:volume>1</prism:volume> + <prism:number>1</prism:number> + <prism:coverDisplayDate>March-April 2023</prism:coverDisplayDate> + <prism:pageRange>1-2</prism:pageRange> + <prism:startingPage>1</prism:startingPage> + <prism:endingPage>2</prism:endingPage> + </rdf:Description> + <rdf:Description xmlns:pdfx="http://ns.adobe.com/pdfx/1.3/" rdf:about=""> + <TGji2z9aLyPf_nweGmtj-nLNNnt2Nlwj7yteGngiQzd2Sy937zt-Jo9eQmdmKn9yJmMmTma/> + <pdfx:robots>noindex</pdfx:robots> + </rdf:Description> + <rdf:Description xmlns:xap="http://ns.adobe.com/xap/1.0/" rdf:about=""> + <xap:CreatorTool>NA</xap:CreatorTool> + <xap:CreateDate>2023-03-07T23:47:54+05:30</xap:CreateDate> + <xap:ModifyDate>2023-03-07T23:48:24+05:30</xap:ModifyDate> + <xap:MetadataDate>2023-03-07T23:48:24+05:30</xap:MetadataDate> + </rdf:Description> + <rdf:Description xmlns:xapRights="http://ns.adobe.com/xap/1.0/rights/" rdf:about=""> + <xapRights:Marked>True</xapRights:Marked> + </rdf:Description> + <rdf:Description xmlns:pdf="http://ns.adobe.com/pdf/1.3/" rdf:about=""> + <pdf:Producer>Acrobat Distiller 8.1.0 (Windows)</pdf:Producer> + </rdf:Description> + <rdf:Description xmlns:xapMM="http://ns.adobe.com/xap/1.0/mm/" rdf:about=""> + <xapMM:DocumentID>uuid:81b6d753-a810-4613-b207-111111111111</xapMM:DocumentID> + <xapMM:InstanceID>uuid:0d50ecbf-b7d1-4b76-b4a7-222222222222</xapMM:InstanceID> + </rdf:Description> + </rdf:RDF> +</x:xmpmeta> +""".encode(), + ) + with trivial.open_metadata() as m: + list(m.items()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/tests/test_page.py new/pikepdf-8.11.2/tests/test_page.py --- old/pikepdf-8.10.1/tests/test_page.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/tests/test_page.py 2024-01-01 05:14:08.000000000 +0100 @@ -55,9 +55,13 @@ inset = 20 page.trimbox = [inset, inset, page.mediabox[2] - inset, page.mediabox[3] - inset] inset = 30 - page.cropbox = [inset, inset, page.mediabox[2] - inset, page.mediabox[3] - inset] + page.cropbox = Array( + [inset, inset, page.mediabox[2] - inset, page.mediabox[3] - inset] + ) inset = 40 - page.artbox = [inset, inset, page.mediabox[2] - inset, page.mediabox[3] - inset] + page.artbox = Rectangle( + inset, inset, page.mediabox[2] - inset, page.mediabox[3] - inset + ) assert page.mediabox != page.bleedbox assert page.mediabox != page.artbox diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pikepdf-8.10.1/tests/test_repr.py new/pikepdf-8.11.2/tests/test_repr.py --- old/pikepdf-8.10.1/tests/test_repr.py 2023-12-17 10:31:00.000000000 +0100 +++ new/pikepdf-8.11.2/tests/test_repr.py 2024-01-01 05:14:08.000000000 +0100 @@ -24,7 +24,6 @@ '/Array': Array([1, 2, 3.14]), '/Operator': Operator('q'), '/Dictionary': Dictionary({'/Color': 'Red'}), - '/None': None, } ) short_pi = '3.14' @@ -37,7 +36,6 @@ "/Color": "Red" }, "/Integer": 42, - "/None": None, "/Operator": pikepdf.Operator("q"), "/Real": Decimal('42.42'), "/String": "hi"