Merge branch 'main' into context_manager

This commit is contained in:
Andrew Murray 2024-12-22 21:30:10 +11:00
commit 3a8eaf5892
41 changed files with 271 additions and 187 deletions

View File

@ -21,7 +21,7 @@ set -e
if [[ $(uname) != CYGWIN* ]]; then if [[ $(uname) != CYGWIN* ]]; then
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\ sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
ghostscript libjpeg-turbo-progs libopenjp2-7-dev\ ghostscript libjpeg-turbo8-dev libopenjp2-7-dev\
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\ cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
sway wl-clipboard libopenblas-dev sway wl-clipboard libopenblas-dev
fi fi

View File

@ -1 +1 @@
cibuildwheel==2.21.3 cibuildwheel==2.22.0

View File

@ -1,4 +1,4 @@
mypy==1.13.0 mypy==1.14.0
IceSpringPySideStubs-PyQt6 IceSpringPySideStubs-PyQt6
IceSpringPySideStubs-PySide6 IceSpringPySideStubs-PySide6
ipython ipython

View File

@ -8,8 +8,8 @@ fi
brew install \ brew install \
freetype \ freetype \
ghostscript \ ghostscript \
jpeg-turbo \
libimagequant \ libimagequant \
libjpeg \
libtiff \ libtiff \
little-cms2 \ little-cms2 \
openjpeg \ openjpeg \

View File

@ -38,10 +38,10 @@ ARCHIVE_SDIR=pillow-depends-main
# Package versions for fresh source builds # Package versions for fresh source builds
FREETYPE_VERSION=2.13.2 FREETYPE_VERSION=2.13.2
HARFBUZZ_VERSION=10.0.1 HARFBUZZ_VERSION=10.1.0
LIBPNG_VERSION=1.6.44 LIBPNG_VERSION=1.6.44
JPEGTURBO_VERSION=3.0.4 JPEGTURBO_VERSION=3.1.0
OPENJPEG_VERSION=2.5.2 OPENJPEG_VERSION=2.5.3
XZ_VERSION=5.6.3 XZ_VERSION=5.6.3
TIFF_VERSION=4.6.0 TIFF_VERSION=4.6.0
LCMS2_VERSION=2.16 LCMS2_VERSION=2.16
@ -50,12 +50,8 @@ if [[ -n "$IS_MACOS" ]]; then
else else
GIFLIB_VERSION=5.2.1 GIFLIB_VERSION=5.2.1
fi fi
if [[ -n "$IS_MACOS" ]] || [[ "$MB_ML_VER" != 2014 ]]; then ZLIB_NG_VERSION=2.2.2
ZLIB_VERSION=1.3.1 LIBWEBP_VERSION=1.5.0
else
ZLIB_VERSION=1.2.8
fi
LIBWEBP_VERSION=1.4.0
BZIP2_VERSION=1.0.8 BZIP2_VERSION=1.0.8
LIBXCB_VERSION=1.17.0 LIBXCB_VERSION=1.17.0
BROTLI_VERSION=1.1.0 BROTLI_VERSION=1.1.0
@ -74,6 +70,16 @@ function build_pkg_config {
touch pkg-config-stamp touch pkg-config-stamp
} }
function build_zlib_ng {
if [ -e zlib-stamp ]; then return; fi
fetch_unpack https://github.com/zlib-ng/zlib-ng/archive/$ZLIB_NG_VERSION.tar.gz zlib-ng-$ZLIB_NG_VERSION.tar.gz
(cd zlib-ng-$ZLIB_NG_VERSION \
&& ./configure --prefix=$BUILD_PREFIX --zlib-compat \
&& make -j4 \
&& make install)
touch zlib-stamp
}
function build_brotli { function build_brotli {
if [ -e brotli-stamp ]; then return; fi if [ -e brotli-stamp ]; then return; fi
local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz) local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
@ -87,7 +93,7 @@ function build_harfbuzz {
if [ -e harfbuzz-stamp ]; then return; fi if [ -e harfbuzz-stamp ]; then return; fi
python3 -m pip install meson ninja python3 -m pip install meson ninja
local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz) local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz)
(cd $out_dir \ (cd $out_dir \
&& meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=release -Dfreetype=enabled -Dglib=disabled) && meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=release -Dfreetype=enabled -Dglib=disabled)
(cd $out_dir/build \ (cd $out_dir/build \
@ -100,12 +106,12 @@ function build {
if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then
yum remove -y zlib-devel yum remove -y zlib-devel
fi fi
build_new_zlib build_zlib_ng
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
if [ -n "$IS_MACOS" ]; then if [ -n "$IS_MACOS" ]; then
build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto
build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib build_simple libXau 1.0.12 https://www.x.org/pub/individual/lib
build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
else else
sed s/\${pc_sysrootdir\}// $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc sed s/\${pc_sysrootdir\}// $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc

View File

@ -85,7 +85,7 @@ jobs:
CIBW_ARCHS: "aarch64" CIBW_ARCHS: "aarch64"
# Likewise, select only one Python version per job to speed this up. # Likewise, select only one Python version per job to speed this up.
CIBW_BUILD: "${{ matrix.python-version }}-${{ matrix.spec == 'musllinux' && 'musllinux' || 'manylinux' }}*" CIBW_BUILD: "${{ matrix.python-version }}-${{ matrix.spec == 'musllinux' && 'musllinux' || 'manylinux' }}*"
CIBW_PRERELEASE_PYTHONS: True CIBW_ENABLE: cpython-prerelease
# Extra options for manylinux. # Extra options for manylinux.
CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.spec }} CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.spec }}
CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.spec }} CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.spec }}
@ -150,10 +150,9 @@ jobs:
env: env:
CIBW_ARCHS: ${{ matrix.cibw_arch }} CIBW_ARCHS: ${{ matrix.cibw_arch }}
CIBW_BUILD: ${{ matrix.build }} CIBW_BUILD: ${{ matrix.build }}
CIBW_FREE_THREADED_SUPPORT: True CIBW_ENABLE: cpython-prerelease cpython-freethreading
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_PRERELEASE_PYTHONS: True
CIBW_SKIP: pp39-* CIBW_SKIP: pp39-*
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
@ -228,8 +227,7 @@ jobs:
CIBW_ARCHS: ${{ matrix.cibw_arch }} CIBW_ARCHS: ${{ matrix.cibw_arch }}
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd" CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
CIBW_CACHE_PATH: "C:\\cibw" CIBW_CACHE_PATH: "C:\\cibw"
CIBW_FREE_THREADED_SUPPORT: True CIBW_ENABLE: cpython-prerelease cpython-freethreading
CIBW_PRERELEASE_PYTHONS: True
CIBW_SKIP: pp39-* CIBW_SKIP: pp39-*
CIBW_TEST_SKIP: "*-win_arm64" CIBW_TEST_SKIP: "*-win_arm64"
CIBW_TEST_COMMAND: 'docker run --rm CIBW_TEST_COMMAND: 'docker run --rm

View File

@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.7.2 rev: v0.8.1
hooks: hooks:
- id: ruff - id: ruff
args: [--exit-non-zero-on-fix] args: [--exit-non-zero-on-fix]
@ -11,7 +11,7 @@ repos:
- id: black - id: black
- repo: https://github.com/PyCQA/bandit - repo: https://github.com/PyCQA/bandit
rev: 1.7.10 rev: 1.8.0
hooks: hooks:
- id: bandit - id: bandit
args: [--severity-level=high] args: [--severity-level=high]
@ -24,7 +24,7 @@ repos:
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$) exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
- repo: https://github.com/pre-commit/mirrors-clang-format - repo: https://github.com/pre-commit/mirrors-clang-format
rev: v19.1.3 rev: v19.1.4
hooks: hooks:
- id: clang-format - id: clang-format
types: [c] types: [c]
@ -50,7 +50,7 @@ repos:
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/ exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
- repo: https://github.com/python-jsonschema/check-jsonschema - repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.29.4 rev: 0.30.0
hooks: hooks:
- id: check-github-workflows - id: check-github-workflows
- id: check-readthedocs - id: check-readthedocs
@ -67,7 +67,7 @@ repos:
- id: pyproject-fmt - id: pyproject-fmt
- repo: https://github.com/abravalheri/validate-pyproject - repo: https://github.com/abravalheri/validate-pyproject
rev: v0.22 rev: v0.23
hooks: hooks:
- id: validate-pyproject - id: validate-pyproject
additional_dependencies: [trove-classifiers>=2024.10.12] additional_dependencies: [trove-classifiers>=2024.10.12]

View File

@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is
Pillow is the friendly PIL fork. It is Pillow is the friendly PIL fork. It is
Copyright © 2010-2024 by Jeffrey A. Clark and contributors Copyright © 2010 by Jeffrey A. Clark and contributors
Like PIL, Pillow is licensed under the open source MIT-CMU License: Like PIL, Pillow is licensed under the open source MIT-CMU License:

View File

@ -34,6 +34,7 @@ def test_wheel_features() -> None:
"fribidi", "fribidi",
"harfbuzz", "harfbuzz",
"libjpeg_turbo", "libjpeg_turbo",
"zlib_ng",
"xcb", "xcb",
} }

View File

@ -388,10 +388,12 @@ class TestColorLut3DFilter:
table = numpy.ones((7 * 6 * 5, 3), dtype=numpy.float16) table = numpy.ones((7 * 6 * 5, 3), dtype=numpy.float16)
lut = ImageFilter.Color3DLUT((5, 6, 7), table) lut = ImageFilter.Color3DLUT((5, 6, 7), table)
assert isinstance(lut.table, numpy.ndarray)
assert lut.table.shape == (table.size,) assert lut.table.shape == (table.size,)
table = numpy.ones((7 * 6 * 5 * 3), dtype=numpy.float16) table = numpy.ones((7 * 6 * 5 * 3), dtype=numpy.float16)
lut = ImageFilter.Color3DLUT((5, 6, 7), table) lut = ImageFilter.Color3DLUT((5, 6, 7), table)
assert isinstance(lut.table, numpy.ndarray)
assert lut.table.shape == (table.size,) assert lut.table.shape == (table.size,)
# Check application # Check application

View File

@ -36,9 +36,10 @@ def test_version() -> None:
else: else:
assert function(name) == version assert function(name) == version
if name != "PIL": if name != "PIL":
if name == "zlib" and version is not None: if version is not None:
if name == "zlib" and features.check_feature("zlib_ng"):
version = re.sub(".zlib-ng$", "", version) version = re.sub(".zlib-ng$", "", version)
elif name == "libtiff" and version is not None: elif name == "libtiff":
version = re.sub("t$", "", version) version = re.sub("t$", "", version)
assert version is None or re.search(r"\d+(\.\d+)*$", version) assert version is None or re.search(r"\d+(\.\d+)*$", version)

View File

@ -4,6 +4,7 @@ import warnings
from collections.abc import Generator from collections.abc import Generator
from io import BytesIO from io import BytesIO
from pathlib import Path from pathlib import Path
from typing import Any
import pytest import pytest
@ -1487,7 +1488,8 @@ def test_saving_rgba(tmp_path: Path) -> None:
assert value[3] == 0 assert value[3] == 0
def test_optimizing_p_rgba(tmp_path: Path) -> None: @pytest.mark.parametrize("params", ({}, {"disposal": 2, "optimize": False}))
def test_p_rgba(tmp_path: Path, params: dict[str, Any]) -> None:
out = str(tmp_path / "temp.gif") out = str(tmp_path / "temp.gif")
im1 = Image.new("P", (100, 100)) im1 = Image.new("P", (100, 100))
@ -1499,7 +1501,7 @@ def test_optimizing_p_rgba(tmp_path: Path) -> None:
im2 = Image.new("P", (100, 100)) im2 = Image.new("P", (100, 100))
im2.putpalette(data, "RGBA") im2.putpalette(data, "RGBA")
im1.save(out, save_all=True, append_images=[im2]) im1.save(out, save_all=True, append_images=[im2], **params)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert isinstance(reloaded, GifImagePlugin.GifImageFile) assert isinstance(reloaded, GifImagePlugin.GifImageFile)

View File

@ -37,6 +37,13 @@ def test_load() -> None:
assert px[0, 0] == (255, 255, 255) assert px[0, 0] == (255, 255, 255)
def test_load_zero_inch() -> None:
b = BytesIO(b"\xd7\xcd\xc6\x9a\x00\x00" + b"\x00" * 10)
with pytest.raises(ValueError):
with Image.open(b):
pass
def test_register_handler(tmp_path: Path) -> None: def test_register_handler(tmp_path: Path) -> None:
class TestHandler(ImageFile.StubHandler): class TestHandler(ImageFile.StubHandler):
methodCalled = False methodCalled = False

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# install openjpeg # install openjpeg
archive=openjpeg-2.5.2 archive=openjpeg-2.5.3
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# install webp # install webp
archive=libwebp-1.4.0 archive=libwebp-1.5.0
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz

View File

@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is
Pillow is the friendly PIL fork. It is Pillow is the friendly PIL fork. It is
Copyright © 2010-2024 by Jeffrey A. Clark and contributors Copyright © 2010 by Jeffrey A. Clark and contributors
Like PIL, Pillow is licensed under the open source PIL Like PIL, Pillow is licensed under the open source PIL
Software License: Software License:

View File

@ -55,7 +55,7 @@ master_doc = "index"
project = "Pillow (PIL Fork)" project = "Pillow (PIL Fork)"
copyright = ( copyright = (
"1995-2011 Fredrik Lundh and contributors, " "1995-2011 Fredrik Lundh and contributors, "
"2010-2024 Jeffrey A. Clark and contributors." "2010 Jeffrey A. Clark and contributors."
) )
author = "Fredrik Lundh (PIL), Jeffrey A. Clark (Pillow)" author = "Fredrik Lundh (PIL), Jeffrey A. Clark (Pillow)"

View File

@ -678,7 +678,7 @@ Reading from URL
from PIL import Image from PIL import Image
from urllib.request import urlopen from urllib.request import urlopen
url = "https://python-pillow.org/assets/images/pillow-logo.png" url = "https://python-pillow.github.io/assets/images/pillow-logo.png"
img = Image.open(urlopen(url)) img = Image.open(urlopen(url))

View File

@ -58,7 +58,7 @@ Many of Pillow's features require external libraries:
* **openjpeg** provides JPEG 2000 functionality. * **openjpeg** provides JPEG 2000 functionality.
* Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**, * Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**,
**2.4.0**, **2.5.0** and **2.5.2**. **2.4.0**, **2.5.0**, **2.5.2** and **2.5.3**.
* Pillow does **not** support the earlier **1.5** series which ships * Pillow does **not** support the earlier **1.5** series which ships
with Debian Jessie. with Debian Jessie.
@ -148,13 +148,7 @@ Many of Pillow's features require external libraries:
The easiest way to install external libraries is via `Homebrew The easiest way to install external libraries is via `Homebrew
<https://brew.sh/>`_. After you install Homebrew, run:: <https://brew.sh/>`_. After you install Homebrew, run::
brew install libjpeg libtiff little-cms2 openjpeg webp brew install libjpeg libraqm libtiff little-cms2 openjpeg webp
To install libraqm on macOS use Homebrew to install its dependencies::
brew install freetype harfbuzz fribidi
Then see ``depends/install_raqm_cmake.sh`` to install libraqm.
.. tab:: Windows .. tab:: Windows

View File

@ -55,7 +55,7 @@ These platforms are built and tested for every change.
| +----------------------------+---------------------+ | +----------------------------+---------------------+
| | 3.13 | x86 | | | 3.13 | x86 |
| +----------------------------+---------------------+ | +----------------------------+---------------------+
| | 3.9 (MinGW) | x86-64 | | | 3.12 (MinGW) | x86-64 |
| +----------------------------+---------------------+ | +----------------------------+---------------------+
| | 3.9 (Cygwin) | x86-64 | | | 3.9 (Cygwin) | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+

View File

@ -54,6 +54,7 @@ Feature version numbers are available only where stated.
Support for the following features can be checked: Support for the following features can be checked:
* ``libjpeg_turbo``: (compile time) Whether Pillow was compiled against the libjpeg-turbo version of libjpeg. Compile-time version number is available. * ``libjpeg_turbo``: (compile time) Whether Pillow was compiled against the libjpeg-turbo version of libjpeg. Compile-time version number is available.
* ``zlib_ng``: (compile time) Whether Pillow was compiled against the zlib-ng version of zlib. Compile-time version number is available.
* ``raqm``: Raqm library, required for ``ImageFont.Layout.RAQM`` in :py:func:`PIL.ImageFont.truetype`. Run-time version number is available for Raqm 0.7.0 or newer. * ``raqm``: Raqm library, required for ``ImageFont.Layout.RAQM`` in :py:func:`PIL.ImageFont.truetype`. Run-time version number is available for Raqm 0.7.0 or newer.
* ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. Run-time version number is available. * ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. Run-time version number is available.
* ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library. * ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library.

View File

@ -0,0 +1,59 @@
11.1.0
------
Security
========
TODO
^^^^
TODO
:cve:`YYYY-XXXXX`: TODO
^^^^^^^^^^^^^^^^^^^^^^^
TODO
Backwards Incompatible Changes
==============================
TODO
^^^^
Deprecations
============
TODO
^^^^
TODO
API Changes
===========
TODO
^^^^
TODO
API Additions
=============
Check for zlib-ng
^^^^^^^^^^^^^^^^^
You can check if Pillow has been built against the zlib-ng version of the
zlib library, and what version of zlib-ng is being used::
from PIL import features
features.check_feature("zlib_ng") # True or False
features.version_feature("zlib_ng") # "2.2.2" for example, or None
Other Changes
=============
zlib-ng in wheels
^^^^^^^^^^^^^^^^^
Wheels are now built against zlib-ng for improved speed. In tests, saving a PNG
was found to be more than twice as fast at higher compression levels.

View File

@ -14,6 +14,7 @@ expected to be backported to earlier versions.
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
11.1.0
11.0.0 11.0.0
10.4.0 10.4.0
10.3.0 10.3.0

View File

@ -76,7 +76,7 @@ optional-dependencies.xmp = [
urls.Changelog = "https://github.com/python-pillow/Pillow/releases" urls.Changelog = "https://github.com/python-pillow/Pillow/releases"
urls.Documentation = "https://pillow.readthedocs.io" urls.Documentation = "https://pillow.readthedocs.io"
urls.Funding = "https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=pypi" urls.Funding = "https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=pypi"
urls.Homepage = "https://python-pillow.org" urls.Homepage = "https://python-pillow.github.io"
urls.Mastodon = "https://fosstodon.org/@pillow" urls.Mastodon = "https://fosstodon.org/@pillow"
urls."Release notes" = "https://pillow.readthedocs.io/en/stable/releasenotes/index.html" urls."Release notes" = "https://pillow.readthedocs.io/en/stable/releasenotes/index.html"
urls.Source = "https://github.com/python-pillow/Pillow" urls.Source = "https://github.com/python-pillow/Pillow"
@ -104,6 +104,7 @@ test-extras = "tests"
[tool.cibuildwheel.macos.environment] [tool.cibuildwheel.macos.environment]
PATH = "$(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" PATH = "$(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
DYLD_LIBRARY_PATH = "$(pwd)/build/deps/darwin/lib"
[tool.black] [tool.black]
exclude = "wheels/multibuild" exclude = "wheels/multibuild"

View File

@ -393,13 +393,14 @@ class pil_build_ext(build_ext):
self.feature.required.discard(x) self.feature.required.discard(x)
_dbg("Disabling %s", x) _dbg("Disabling %s", x)
if getattr(self, f"enable_{x}"): if getattr(self, f"enable_{x}"):
msg = f"Conflicting options: --enable-{x} and --disable-{x}" msg = f"Conflicting options: '-C {x}=enable' and '-C {x}=disable'"
raise ValueError(msg) raise ValueError(msg)
if x == "freetype": if x == "freetype":
_dbg("--disable-freetype implies --disable-raqm") _dbg("'-C freetype=disable' implies '-C raqm=disable'")
if getattr(self, "enable_raqm"): if getattr(self, "enable_raqm"):
msg = ( msg = (
"Conflicting options: --enable-raqm and --disable-freetype" "Conflicting options: "
"'-C raqm=enable' and '-C freetype=disable'"
) )
raise ValueError(msg) raise ValueError(msg)
setattr(self, "disable_raqm", True) setattr(self, "disable_raqm", True)
@ -407,15 +408,17 @@ class pil_build_ext(build_ext):
_dbg("Requiring %s", x) _dbg("Requiring %s", x)
self.feature.required.add(x) self.feature.required.add(x)
if x == "raqm": if x == "raqm":
_dbg("--enable-raqm implies --enable-freetype") _dbg("'-C raqm=enable' implies '-C freetype=enable'")
self.feature.required.add("freetype") self.feature.required.add("freetype")
for x in ("raqm", "fribidi"): for x in ("raqm", "fribidi"):
if getattr(self, f"vendor_{x}"): if getattr(self, f"vendor_{x}"):
if getattr(self, "disable_raqm"): if getattr(self, "disable_raqm"):
msg = f"Conflicting options: --vendor-{x} and --disable-raqm" msg = f"Conflicting options: '-C {x}=vendor' and '-C raqm=disable'"
raise ValueError(msg) raise ValueError(msg)
if x == "fribidi" and not getattr(self, "vendor_raqm"): if x == "fribidi" and not getattr(self, "vendor_raqm"):
msg = f"Conflicting options: --vendor-{x} and not --vendor-raqm" msg = (
f"Conflicting options: '-C {x}=vendor' and not '-C raqm=vendor'"
)
raise ValueError(msg) raise ValueError(msg)
_dbg("Using vendored version of %s", x) _dbg("Using vendored version of %s", x)
self.feature.vendor.add(x) self.feature.vendor.add(x)
@ -1047,7 +1050,7 @@ except DependencyException as err:
msg = f""" msg = f"""
The headers or library files could not be found for {str(err)}, The headers or library files could not be found for {str(err)},
which was requested by the option flag --enable-{str(err)} which was requested by the option flag '-C {str(err)}=enable'
""" """
sys.stderr.write(msg) sys.stderr.write(msg)

View File

@ -303,38 +303,38 @@ TAGS = {
class GPS(IntEnum): class GPS(IntEnum):
GPSVersionID = 0 GPSVersionID = 0x00
GPSLatitudeRef = 1 GPSLatitudeRef = 0x01
GPSLatitude = 2 GPSLatitude = 0x02
GPSLongitudeRef = 3 GPSLongitudeRef = 0x03
GPSLongitude = 4 GPSLongitude = 0x04
GPSAltitudeRef = 5 GPSAltitudeRef = 0x05
GPSAltitude = 6 GPSAltitude = 0x06
GPSTimeStamp = 7 GPSTimeStamp = 0x07
GPSSatellites = 8 GPSSatellites = 0x08
GPSStatus = 9 GPSStatus = 0x09
GPSMeasureMode = 10 GPSMeasureMode = 0x0A
GPSDOP = 11 GPSDOP = 0x0B
GPSSpeedRef = 12 GPSSpeedRef = 0x0C
GPSSpeed = 13 GPSSpeed = 0x0D
GPSTrackRef = 14 GPSTrackRef = 0x0E
GPSTrack = 15 GPSTrack = 0x0F
GPSImgDirectionRef = 16 GPSImgDirectionRef = 0x10
GPSImgDirection = 17 GPSImgDirection = 0x11
GPSMapDatum = 18 GPSMapDatum = 0x12
GPSDestLatitudeRef = 19 GPSDestLatitudeRef = 0x13
GPSDestLatitude = 20 GPSDestLatitude = 0x14
GPSDestLongitudeRef = 21 GPSDestLongitudeRef = 0x15
GPSDestLongitude = 22 GPSDestLongitude = 0x16
GPSDestBearingRef = 23 GPSDestBearingRef = 0x17
GPSDestBearing = 24 GPSDestBearing = 0x18
GPSDestDistanceRef = 25 GPSDestDistanceRef = 0x19
GPSDestDistance = 26 GPSDestDistance = 0x1A
GPSProcessingMethod = 27 GPSProcessingMethod = 0x1B
GPSAreaInformation = 28 GPSAreaInformation = 0x1C
GPSDateStamp = 29 GPSDateStamp = 0x1D
GPSDifferential = 30 GPSDifferential = 0x1E
GPSHPositioningError = 31 GPSHPositioningError = 0x1F
"""Maps EXIF GPS tags to tag names.""" """Maps EXIF GPS tags to tag names."""
@ -342,40 +342,40 @@ GPSTAGS = {i.value: i.name for i in GPS}
class Interop(IntEnum): class Interop(IntEnum):
InteropIndex = 1 InteropIndex = 0x0001
InteropVersion = 2 InteropVersion = 0x0002
RelatedImageFileFormat = 4096 RelatedImageFileFormat = 0x1000
RelatedImageWidth = 4097 RelatedImageWidth = 0x1001
RelatedImageHeight = 4098 RelatedImageHeight = 0x1002
class IFD(IntEnum): class IFD(IntEnum):
Exif = 34665 Exif = 0x8769
GPSInfo = 34853 GPSInfo = 0x8825
Makernote = 37500 MakerNote = 0x927C
Interop = 40965 Interop = 0xA005
IFD1 = -1 IFD1 = -1
class LightSource(IntEnum): class LightSource(IntEnum):
Unknown = 0 Unknown = 0x00
Daylight = 1 Daylight = 0x01
Fluorescent = 2 Fluorescent = 0x02
Tungsten = 3 Tungsten = 0x03
Flash = 4 Flash = 0x04
Fine = 9 Fine = 0x09
Cloudy = 10 Cloudy = 0x0A
Shade = 11 Shade = 0x0B
DaylightFluorescent = 12 DaylightFluorescent = 0x0C
DayWhiteFluorescent = 13 DayWhiteFluorescent = 0x0D
CoolWhiteFluorescent = 14 CoolWhiteFluorescent = 0x0E
WhiteFluorescent = 15 WhiteFluorescent = 0x0F
StandardLightA = 17 StandardLightA = 0x11
StandardLightB = 18 StandardLightB = 0x12
StandardLightC = 19 StandardLightC = 0x13
D55 = 20 D55 = 0x14
D65 = 21 D65 = 0x15
D75 = 22 D75 = 0x16
D50 = 23 D50 = 0x17
ISO = 24 ISO = 0x18
Other = 255 Other = 0xFF

View File

@ -703,8 +703,9 @@ def _write_multiple_frames(
) )
background = _get_background(im_frame, color) background = _get_background(im_frame, color)
background_im = Image.new("P", im_frame.size, background) background_im = Image.new("P", im_frame.size, background)
assert im_frames[0].im.palette is not None first_palette = im_frames[0].im.palette
background_im.putpalette(im_frames[0].im.palette) assert first_palette is not None
background_im.putpalette(first_palette, first_palette.mode)
bbox = _getbbox(background_im, im_frame)[1] bbox = _getbbox(background_im, im_frame)[1]
elif encoderinfo.get("optimize") and im_frame.mode != "1": elif encoderinfo.get("optimize") and im_frame.mode != "1":
if "transparency" not in encoderinfo: if "transparency" not in encoderinfo:

View File

@ -361,7 +361,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
name = "".join([name[: 92 - len(ext)], ext]) name = "".join([name[: 92 - len(ext)], ext])
fp.write(f"Name: {name}\r\n".encode("ascii")) fp.write(f"Name: {name}\r\n".encode("ascii"))
fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode("ascii")) fp.write(f"Image size (x*y): {im.size[0]}*{im.size[1]}\r\n".encode("ascii"))
fp.write(f"File size (no of images): {frames}\r\n".encode("ascii")) fp.write(f"File size (no of images): {frames}\r\n".encode("ascii"))
if im.mode in ["P", "PA"]: if im.mode in ["P", "PA"]:
fp.write(b"Lut: 1\r\n") fp.write(b"Lut: 1\r\n")

View File

@ -674,13 +674,10 @@ class Image:
) )
def __repr__(self) -> str: def __repr__(self) -> str:
return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % ( return (
self.__class__.__module__, f"<{self.__class__.__module__}.{self.__class__.__name__} "
self.__class__.__name__, f"image mode={self.mode} size={self.size[0]}x{self.size[1]} "
self.mode, f"at 0x{id(self):X}>"
self.size[0],
self.size[1],
id(self),
) )
def _repr_pretty_(self, p: PrettyPrinter, cycle: bool) -> None: def _repr_pretty_(self, p: PrettyPrinter, cycle: bool) -> None:
@ -689,14 +686,8 @@ class Image:
# Same as __repr__ but without unpredictable id(self), # Same as __repr__ but without unpredictable id(self),
# to keep Jupyter notebook `text/plain` output stable. # to keep Jupyter notebook `text/plain` output stable.
p.text( p.text(
"<%s.%s image mode=%s size=%dx%d>" f"<{self.__class__.__module__}.{self.__class__.__name__} "
% ( f"image mode={self.mode} size={self.size[0]}x{self.size[1]}>"
self.__class__.__module__,
self.__class__.__name__,
self.mode,
self.size[0],
self.size[1],
)
) )
def _repr_image(self, image_format: str, **kwargs: Any) -> bytes | None: def _repr_image(self, image_format: str, **kwargs: Any) -> bytes | None:
@ -3836,7 +3827,7 @@ class Exif(_ExifBase):
gps_ifd = exif.get_ifd(ExifTags.IFD.GPSInfo) gps_ifd = exif.get_ifd(ExifTags.IFD.GPSInfo)
print(gps_ifd) print(gps_ifd)
Other IFDs include ``ExifTags.IFD.Exif``, ``ExifTags.IFD.Makernote``, Other IFDs include ``ExifTags.IFD.Exif``, ``ExifTags.IFD.MakerNote``,
``ExifTags.IFD.Interop`` and ``ExifTags.IFD.IFD1``. ``ExifTags.IFD.Interop`` and ``ExifTags.IFD.IFD1``.
:py:mod:`~PIL.ExifTags` also has enum classes to provide names for data:: :py:mod:`~PIL.ExifTags` also has enum classes to provide names for data::
@ -3999,11 +3990,11 @@ class Exif(_ExifBase):
ifd = self._get_ifd_dict(offset, tag) ifd = self._get_ifd_dict(offset, tag)
if ifd is not None: if ifd is not None:
self._ifds[tag] = ifd self._ifds[tag] = ifd
elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.Makernote]: elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.MakerNote]:
if ExifTags.IFD.Exif not in self._ifds: if ExifTags.IFD.Exif not in self._ifds:
self.get_ifd(ExifTags.IFD.Exif) self.get_ifd(ExifTags.IFD.Exif)
tag_data = self._ifds[ExifTags.IFD.Exif][tag] tag_data = self._ifds[ExifTags.IFD.Exif][tag]
if tag == ExifTags.IFD.Makernote: if tag == ExifTags.IFD.MakerNote:
from .TiffImagePlugin import ImageFileDirectory_v2 from .TiffImagePlugin import ImageFileDirectory_v2
if tag_data[:8] == b"FUJIFILM": if tag_data[:8] == b"FUJIFILM":
@ -4090,7 +4081,7 @@ class Exif(_ExifBase):
ifd = { ifd = {
k: v k: v
for (k, v) in ifd.items() for (k, v) in ifd.items()
if k not in (ExifTags.IFD.Interop, ExifTags.IFD.Makernote) if k not in (ExifTags.IFD.Interop, ExifTags.IFD.MakerNote)
} }
return ifd return ifd

View File

@ -123,7 +123,7 @@ class ImageFile(Image.Image):
self.custom_mimetype: str | None = None self.custom_mimetype: str | None = None
self.tile: list[_Tile] = [] self.tile: list[_Tile] = []
""" A list of tile descriptors, or ``None`` """ """ A list of tile descriptors """
self.readonly = 1 # until we know better self.readonly = 1 # until we know better
@ -217,7 +217,7 @@ class ImageFile(Image.Image):
for subifd_offset in subifd_offsets: for subifd_offset in subifd_offsets:
ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset)) ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset))
ifd1 = exif.get_ifd(ExifTags.IFD.IFD1) ifd1 = exif.get_ifd(ExifTags.IFD.IFD1)
if ifd1 and ifd1.get(513): if ifd1 and ifd1.get(ExifTags.Base.JpegIFOffset):
assert exif._info is not None assert exif._info is not None
ifds.append((ifd1, exif._info.next)) ifds.append((ifd1, exif._info.next))
@ -230,12 +230,12 @@ class ImageFile(Image.Image):
fp = self.fp fp = self.fp
if ifd is not None: if ifd is not None:
thumbnail_offset = ifd.get(513) thumbnail_offset = ifd.get(ExifTags.Base.JpegIFOffset)
if thumbnail_offset is not None: if thumbnail_offset is not None:
thumbnail_offset += getattr(self, "_exif_offset", 0) thumbnail_offset += getattr(self, "_exif_offset", 0)
self.fp.seek(thumbnail_offset) self.fp.seek(thumbnail_offset)
length = ifd.get(514) length = ifd.get(ExifTags.Base.JpegIFByteCount)
assert isinstance(length, int) assert isinstance(length, int)
data = self.fp.read(length) data = self.fp.read(length)
fp = io.BytesIO(data) fp = io.BytesIO(data)

View File

@ -553,7 +553,7 @@ class Color3DLUT(MultibandFilter):
ch_out = channels or ch_in ch_out = channels or ch_in
size_1d, size_2d, size_3d = self.size size_1d, size_2d, size_3d = self.size
table = [0] * (size_1d * size_2d * size_3d * ch_out) table: list[float] = [0] * (size_1d * size_2d * size_3d * ch_out)
idx_in = 0 idx_in = 0
idx_out = 0 idx_out = 0
for b in range(size_3d): for b in range(size_3d):

View File

@ -74,7 +74,7 @@ def APP(self: JpegImageFile, marker: int) -> None:
n = i16(self.fp.read(2)) - 2 n = i16(self.fp.read(2)) - 2
s = ImageFile._safe_read(self.fp, n) s = ImageFile._safe_read(self.fp, n)
app = "APP%d" % (marker & 15) app = f"APP{marker & 15}"
self.app[app] = s # compatibility self.app[app] = s # compatibility
self.applist.append((app, s)) self.applist.append((app, s))

View File

@ -86,7 +86,7 @@ class PcxImageFile(ImageFile.ImageFile):
elif bits == 1 and planes in (2, 4): elif bits == 1 and planes in (2, 4):
mode = "P" mode = "P"
rawmode = "P;%dL" % planes rawmode = f"P;{planes}L"
self.palette = ImagePalette.raw("RGB", s[16:64]) self.palette = ImagePalette.raw("RGB", s[16:64])
elif version == 5 and bits == 8 and planes == 1: elif version == 5 and bits == 8 and planes == 1:

View File

@ -524,7 +524,7 @@ class PngStream(ChunkStream):
assert self.fp is not None assert self.fp is not None
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
raw_vals = struct.unpack(">%dI" % (len(s) // 4), s) raw_vals = struct.unpack(f">{len(s) // 4}I", s)
self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals) self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals)
return s return s

View File

@ -935,9 +935,9 @@ class ImageFileDirectory_v2(_IFDv2Base):
self._tagdata[tag] = data self._tagdata[tag] = data
self.tagtype[tag] = typ self.tagtype[tag] = typ
msg += " - value: " + ( msg += " - value: "
"<table: %d bytes>" % size if size > 32 else repr(data) msg += f"<table: {size} bytes>" if size > 32 else repr(data)
)
logger.debug(msg) logger.debug(msg)
(self.next,) = ( (self.next,) = (
@ -981,10 +981,8 @@ class ImageFileDirectory_v2(_IFDv2Base):
tagname = TiffTags.lookup(tag, self.group).name tagname = TiffTags.lookup(tag, self.group).name
typname = "ifd" if is_ifd else TYPES.get(typ, "unknown") typname = "ifd" if is_ifd else TYPES.get(typ, "unknown")
msg = f"save: {tagname} ({tag}) - type: {typname} ({typ})" msg = f"save: {tagname} ({tag}) - type: {typname} ({typ}) - value: "
msg += " - value: " + ( msg += f"<table: {len(data)} bytes>" if len(data) >= 16 else str(values)
"<table: %d bytes>" % len(data) if len(data) >= 16 else str(values)
)
logger.debug(msg) logger.debug(msg)
# count is sum of lengths for string and arbitrary data # count is sum of lengths for string and arbitrary data

View File

@ -94,6 +94,9 @@ class WmfStubImageFile(ImageFile.StubImageFile):
# get units per inch # get units per inch
self._inch = word(s, 14) self._inch = word(s, 14)
if self._inch == 0:
msg = "Invalid inch"
raise ValueError(msg)
# get bounding box # get bounding box
x0 = short(s, 6) x0 = short(s, 6)

View File

@ -44,7 +44,7 @@ _T_co = TypeVar("_T_co", covariant=True)
class SupportsRead(Protocol[_T_co]): class SupportsRead(Protocol[_T_co]):
def read(self, __length: int = ...) -> _T_co: ... def read(self, length: int = ..., /) -> _T_co: ...
StrOrBytesPath = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]] StrOrBytesPath = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]]

View File

@ -127,6 +127,7 @@ features: dict[str, tuple[str, str | bool, str | None]] = {
"fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"), "fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"),
"harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"), "harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"),
"libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"), "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"),
"zlib_ng": ("PIL._imaging", "HAVE_ZLIBNG", "zlib_ng_version"),
"libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"), "libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"),
"xcb": ("PIL._imaging", "HAVE_XCB", None), "xcb": ("PIL._imaging", "HAVE_XCB", None),
} }
@ -308,7 +309,11 @@ def pilinfo(out: IO[str] | None = None, supported_formats: bool = True) -> None:
# this check is also in src/_imagingcms.c:setup_module() # this check is also in src/_imagingcms.c:setup_module()
version_static = tuple(int(x) for x in v.split(".")) < (2, 7) version_static = tuple(int(x) for x in v.split(".")) < (2, 7)
t = "compiled for" if version_static else "loaded" t = "compiled for" if version_static else "loaded"
if name == "raqm": if name == "zlib":
zlib_ng_version = version_feature("zlib_ng")
if zlib_ng_version is not None:
v += ", compiled for zlib-ng " + zlib_ng_version
elif name == "raqm":
for f in ("fribidi", "harfbuzz"): for f in ("fribidi", "harfbuzz"):
v2 = version_feature(f) v2 = version_feature(f)
if v2 is not None: if v2 is not None:

View File

@ -4397,6 +4397,20 @@ setup_module(PyObject *m) {
} }
#endif #endif
PyObject *have_zlibng;
#ifdef ZLIBNG_VERSION
have_zlibng = Py_True;
{
PyObject *v = PyUnicode_FromString(ZLIBNG_VERSION);
PyDict_SetItemString(d, "zlib_ng_version", v ? v : Py_None);
Py_XDECREF(v);
}
#else
have_zlibng = Py_False;
#endif
Py_INCREF(have_zlibng);
PyModule_AddObject(m, "HAVE_ZLIBNG", have_zlibng);
#ifdef HAVE_LIBTIFF #ifdef HAVE_LIBTIFF
{ {
extern const char *ImagingTiffVersion(void); extern const char *ImagingTiffVersion(void);

@ -1 +1 @@
Subproject commit 9a9d1275f025f737cdaa3c451ba07129dd95f361 Subproject commit 42d761728d141d8462cd9943f4329f12fe62b155

View File

@ -7,6 +7,7 @@ import re
import shutil import shutil
import struct import struct
import subprocess import subprocess
import sys
from typing import Any from typing import Any
@ -112,27 +113,25 @@ V = {
"BROTLI": "1.1.0", "BROTLI": "1.1.0",
"FREETYPE": "2.13.3", "FREETYPE": "2.13.3",
"FRIBIDI": "1.0.16", "FRIBIDI": "1.0.16",
"HARFBUZZ": "10.0.1", "HARFBUZZ": "10.1.0",
"JPEGTURBO": "3.0.4", "JPEGTURBO": "3.1.0",
"LCMS2": "2.16", "LCMS2": "2.16",
"LIBPNG": "1.6.44", "LIBPNG": "1.6.44",
"LIBWEBP": "1.4.0", "LIBWEBP": "1.5.0",
"OPENJPEG": "2.5.2", "OPENJPEG": "2.5.3",
"TIFF": "4.6.0", "TIFF": "4.6.0",
"XZ": "5.6.3", "XZ": "5.6.3",
"ZLIB": "1.3.1", "ZLIBNG": "2.2.2",
} }
V["LIBPNG_DOTLESS"] = V["LIBPNG"].replace(".", "") V["LIBPNG_DOTLESS"] = V["LIBPNG"].replace(".", "")
V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2]) V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2])
V["ZLIB_DOTLESS"] = V["ZLIB"].replace(".", "")
# dependencies, listed in order of compilation # dependencies, listed in order of compilation
DEPS: dict[str, dict[str, Any]] = { DEPS: dict[str, dict[str, Any]] = {
"libjpeg": { "libjpeg": {
"url": f"{SF_PROJECTS}/libjpeg-turbo/files/{V['JPEGTURBO']}/FILENAME/download", "url": f"https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/{V['JPEGTURBO']}/libjpeg-turbo-{V['JPEGTURBO']}.tar.gz",
"filename": f"libjpeg-turbo-{V['JPEGTURBO']}.tar.gz", "filename": f"libjpeg-turbo-{V['JPEGTURBO']}.tar.gz",
"dir": f"libjpeg-turbo-{V['JPEGTURBO']}",
"license": ["README.ijg", "LICENSE.md"], "license": ["README.ijg", "LICENSE.md"],
"license_pattern": ( "license_pattern": (
"(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n==========" "(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n=========="
@ -155,28 +154,30 @@ DEPS: dict[str, dict[str, Any]] = {
cmd_copy("cjpeg-static.exe", "cjpeg.exe"), cmd_copy("cjpeg-static.exe", "cjpeg.exe"),
cmd_copy("djpeg-static.exe", "djpeg.exe"), cmd_copy("djpeg-static.exe", "djpeg.exe"),
], ],
"headers": ["j*.h"], "headers": ["jconfig.h", r"src\j*.h"],
"libs": ["libjpeg.lib"], "libs": ["libjpeg.lib"],
"bins": ["cjpeg.exe", "djpeg.exe"], "bins": ["cjpeg.exe", "djpeg.exe"],
}, },
"zlib": { "zlib": {
"url": "https://zlib.net/FILENAME", "url": f"https://github.com/zlib-ng/zlib-ng/archive/refs/tags/{V['ZLIBNG']}.tar.gz",
"filename": f"zlib{V['ZLIB_DOTLESS']}.zip", "filename": f"zlib-ng-{V['ZLIBNG']}.tar.gz",
"dir": f"zlib-{V['ZLIB']}", "license": "LICENSE.md",
"license": "README", "patch": {
"license_pattern": "Copyright notice:\n\n(.+)$", r"CMakeLists.txt": {
"set_target_properties(zlib PROPERTIES OUTPUT_NAME zlibstatic${{SUFFIX}})": "set_target_properties(zlib PROPERTIES OUTPUT_NAME zlib)", # noqa: E501
},
},
"build": [ "build": [
cmd_nmake(r"win32\Makefile.msc", "clean"), *cmds_cmake(
cmd_nmake(r"win32\Makefile.msc", "zlib.lib"), "zlib", "-DBUILD_SHARED_LIBS:BOOL=OFF", "-DZLIB_COMPAT:BOOL=ON"
cmd_copy("zlib.lib", "z.lib"), ),
], ],
"headers": [r"z*.h"], "headers": [r"z*.h"],
"libs": [r"*.lib"], "libs": [r"zlib.lib"],
}, },
"xz": { "xz": {
"url": f"https://github.com/tukaani-project/xz/releases/download/v{V['XZ']}/FILENAME", "url": f"https://github.com/tukaani-project/xz/releases/download/v{V['XZ']}/FILENAME",
"filename": f"xz-{V['XZ']}.tar.gz", "filename": f"xz-{V['XZ']}.tar.gz",
"dir": f"xz-{V['XZ']}",
"license": "COPYING", "license": "COPYING",
"build": [ "build": [
*cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"), *cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"),
@ -189,7 +190,6 @@ DEPS: dict[str, dict[str, Any]] = {
"libwebp": { "libwebp": {
"url": "http://downloads.webmproject.org/releases/webp/FILENAME", "url": "http://downloads.webmproject.org/releases/webp/FILENAME",
"filename": f"libwebp-{V['LIBWEBP']}.tar.gz", "filename": f"libwebp-{V['LIBWEBP']}.tar.gz",
"dir": f"libwebp-{V['LIBWEBP']}",
"license": "COPYING", "license": "COPYING",
"patch": { "patch": {
r"src\enc\picture_csp_enc.c": { r"src\enc\picture_csp_enc.c": {
@ -211,7 +211,6 @@ DEPS: dict[str, dict[str, Any]] = {
"libtiff": { "libtiff": {
"url": "https://download.osgeo.org/libtiff/FILENAME", "url": "https://download.osgeo.org/libtiff/FILENAME",
"filename": f"tiff-{V['TIFF']}.tar.gz", "filename": f"tiff-{V['TIFF']}.tar.gz",
"dir": f"tiff-{V['TIFF']}",
"license": "LICENSE.md", "license": "LICENSE.md",
"patch": { "patch": {
r"libtiff\tif_lzma.c": { r"libtiff\tif_lzma.c": {
@ -244,7 +243,6 @@ DEPS: dict[str, dict[str, Any]] = {
"url": f"{SF_PROJECTS}/libpng/files/libpng{V['LIBPNG_XY']}/{V['LIBPNG']}/" "url": f"{SF_PROJECTS}/libpng/files/libpng{V['LIBPNG_XY']}/{V['LIBPNG']}/"
f"lpng{V['LIBPNG_DOTLESS']}.zip/download", f"lpng{V['LIBPNG_DOTLESS']}.zip/download",
"filename": f"lpng{V['LIBPNG_DOTLESS']}.zip", "filename": f"lpng{V['LIBPNG_DOTLESS']}.zip",
"dir": f"lpng{V['LIBPNG_DOTLESS']}",
"license": "LICENSE", "license": "LICENSE",
"build": [ "build": [
*cmds_cmake("png_static", "-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF"), *cmds_cmake("png_static", "-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF"),
@ -258,7 +256,6 @@ DEPS: dict[str, dict[str, Any]] = {
"brotli": { "brotli": {
"url": f"https://github.com/google/brotli/archive/refs/tags/v{V['BROTLI']}.tar.gz", "url": f"https://github.com/google/brotli/archive/refs/tags/v{V['BROTLI']}.tar.gz",
"filename": f"brotli-{V['BROTLI']}.tar.gz", "filename": f"brotli-{V['BROTLI']}.tar.gz",
"dir": f"brotli-{V['BROTLI']}",
"license": "LICENSE", "license": "LICENSE",
"build": [ "build": [
*cmds_cmake(("brotlicommon", "brotlidec"), "-DBUILD_SHARED_LIBS:BOOL=OFF"), *cmds_cmake(("brotlicommon", "brotlidec"), "-DBUILD_SHARED_LIBS:BOOL=OFF"),
@ -269,7 +266,6 @@ DEPS: dict[str, dict[str, Any]] = {
"freetype": { "freetype": {
"url": "https://download.savannah.gnu.org/releases/freetype/FILENAME", "url": "https://download.savannah.gnu.org/releases/freetype/FILENAME",
"filename": f"freetype-{V['FREETYPE']}.tar.gz", "filename": f"freetype-{V['FREETYPE']}.tar.gz",
"dir": f"freetype-{V['FREETYPE']}",
"license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"], "license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"],
"patch": { "patch": {
r"builds\windows\vc2010\freetype.vcxproj": { r"builds\windows\vc2010\freetype.vcxproj": {
@ -304,7 +300,6 @@ DEPS: dict[str, dict[str, Any]] = {
"lcms2": { "lcms2": {
"url": f"{SF_PROJECTS}/lcms/files/lcms/{V['LCMS2']}/FILENAME/download", "url": f"{SF_PROJECTS}/lcms/files/lcms/{V['LCMS2']}/FILENAME/download",
"filename": f"lcms2-{V['LCMS2']}.tar.gz", "filename": f"lcms2-{V['LCMS2']}.tar.gz",
"dir": f"lcms2-{V['LCMS2']}",
"license": "LICENSE", "license": "LICENSE",
"patch": { "patch": {
r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": { r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": {
@ -330,7 +325,6 @@ DEPS: dict[str, dict[str, Any]] = {
"openjpeg": { "openjpeg": {
"url": f"https://github.com/uclouvain/openjpeg/archive/v{V['OPENJPEG']}.tar.gz", "url": f"https://github.com/uclouvain/openjpeg/archive/v{V['OPENJPEG']}.tar.gz",
"filename": f"openjpeg-{V['OPENJPEG']}.tar.gz", "filename": f"openjpeg-{V['OPENJPEG']}.tar.gz",
"dir": f"openjpeg-{V['OPENJPEG']}",
"license": "LICENSE", "license": "LICENSE",
"build": [ "build": [
*cmds_cmake( *cmds_cmake(
@ -345,7 +339,6 @@ DEPS: dict[str, dict[str, Any]] = {
# commit: Merge branch 'master' into msvc (matches 2.17.0 tag) # commit: Merge branch 'master' into msvc (matches 2.17.0 tag)
"url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip", "url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip",
"filename": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab.zip", "filename": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab.zip",
"dir": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab",
"license": "COPYRIGHT", "license": "COPYRIGHT",
"patch": { "patch": {
"CMakeLists.txt": { "CMakeLists.txt": {
@ -365,7 +358,6 @@ DEPS: dict[str, dict[str, Any]] = {
"harfbuzz": { "harfbuzz": {
"url": f"https://github.com/harfbuzz/harfbuzz/archive/{V['HARFBUZZ']}.zip", "url": f"https://github.com/harfbuzz/harfbuzz/archive/{V['HARFBUZZ']}.zip",
"filename": f"harfbuzz-{V['HARFBUZZ']}.zip", "filename": f"harfbuzz-{V['HARFBUZZ']}.zip",
"dir": f"harfbuzz-{V['HARFBUZZ']}",
"license": "COPYING", "license": "COPYING",
"build": [ "build": [
*cmds_cmake( *cmds_cmake(
@ -380,7 +372,6 @@ DEPS: dict[str, dict[str, Any]] = {
"fribidi": { "fribidi": {
"url": f"https://github.com/fribidi/fribidi/archive/v{V['FRIBIDI']}.zip", "url": f"https://github.com/fribidi/fribidi/archive/v{V['FRIBIDI']}.zip",
"filename": f"fribidi-{V['FRIBIDI']}.zip", "filename": f"fribidi-{V['FRIBIDI']}.zip",
"dir": f"fribidi-{V['FRIBIDI']}",
"license": "COPYING", "license": "COPYING",
"build": [ "build": [
cmd_copy(r"COPYING", rf"{{bin_dir}}\fribidi-{V['FRIBIDI']}-COPYING"), cmd_copy(r"COPYING", rf"{{bin_dir}}\fribidi-{V['FRIBIDI']}-COPYING"),
@ -517,6 +508,9 @@ def extract_dep(url: str, filename: str, prefs: dict[str, str]) -> None:
if sources_dir_abs != member_prefix: if sources_dir_abs != member_prefix:
msg = "Attempted Path Traversal in Tar File" msg = "Attempted Path Traversal in Tar File"
raise RuntimeError(msg) raise RuntimeError(msg)
if sys.version_info >= (3, 12):
tgz.extractall(sources_dir, filter="data")
else:
tgz.extractall(sources_dir) tgz.extractall(sources_dir)
else: else:
msg = "Unknown archive type: " + filename msg = "Unknown archive type: " + filename
@ -760,6 +754,8 @@ def main() -> None:
} }
for k, v in DEPS.items(): for k, v in DEPS.items():
if "dir" not in v:
v["dir"] = re.sub(r"\.(tar\.gz|zip)", "", v["filename"])
prefs[f"dir_{k}"] = os.path.join(sources_dir, v["dir"]) prefs[f"dir_{k}"] = os.path.join(sources_dir, v["dir"])
print() print()