mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-11-04 01:47:47 +03:00 
			
		
		
		
	Merge branch 'main' into font
This commit is contained in:
		
						commit
						9a6c47a9d2
					
				
							
								
								
									
										1
									
								
								.ci/requirements-cibw.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.ci/requirements-cibw.txt
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					cibuildwheel==2.16.2
 | 
				
			||||||
							
								
								
									
										48
									
								
								.github/workflows/test-windows.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										48
									
								
								.github/workflows/test-windows.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -72,10 +72,10 @@ jobs:
 | 
				
			||||||
    - name: Install dependencies
 | 
					    - name: Install dependencies
 | 
				
			||||||
      id: install
 | 
					      id: install
 | 
				
			||||||
      run: |
 | 
					      run: |
 | 
				
			||||||
        7z x winbuild\depends\nasm-2.16.01-win64.zip "-o$env:RUNNER_WORKSPACE\"
 | 
					        choco install nasm --no-progress
 | 
				
			||||||
        echo "$env:RUNNER_WORKSPACE\nasm-2.16.01" >> $env:GITHUB_PATH
 | 
					        echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        choco install ghostscript --version=10.0.0.20230317
 | 
					        choco install ghostscript --version=10.0.0.20230317 --no-progress
 | 
				
			||||||
        echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH
 | 
					        echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Install extra test images
 | 
					        # Install extra test images
 | 
				
			||||||
| 
						 | 
					@ -167,7 +167,6 @@ jobs:
 | 
				
			||||||
    - name: Build Pillow
 | 
					    - name: Build Pillow
 | 
				
			||||||
      run: |
 | 
					      run: |
 | 
				
			||||||
        $FLAGS="-C raqm=vendor -C fribidi=vendor"
 | 
					        $FLAGS="-C raqm=vendor -C fribidi=vendor"
 | 
				
			||||||
        if ('${{ github.event_name }}' -ne 'pull_request') { $FLAGS+=" -C imagequant=disable" }
 | 
					 | 
				
			||||||
        cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS ."
 | 
					        cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS ."
 | 
				
			||||||
        & $env:pythonLocation\python.exe selftest.py --installed
 | 
					        & $env:pythonLocation\python.exe selftest.py --installed
 | 
				
			||||||
      shell: pwsh
 | 
					      shell: pwsh
 | 
				
			||||||
| 
						 | 
					@ -209,47 +208,6 @@ jobs:
 | 
				
			||||||
        flags: GHA_Windows
 | 
					        flags: GHA_Windows
 | 
				
			||||||
        name: ${{ runner.os }} Python ${{ matrix.python-version }}
 | 
					        name: ${{ runner.os }} Python ${{ matrix.python-version }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    - name: Build wheel
 | 
					 | 
				
			||||||
      id: wheel
 | 
					 | 
				
			||||||
      if: "github.event_name != 'pull_request'"
 | 
					 | 
				
			||||||
      run: |
 | 
					 | 
				
			||||||
        mkdir fribidi
 | 
					 | 
				
			||||||
        copy winbuild\build\bin\fribidi* fribidi
 | 
					 | 
				
			||||||
        setlocal EnableDelayedExpansion
 | 
					 | 
				
			||||||
        for %%f in (winbuild\build\license\*) do (
 | 
					 | 
				
			||||||
          set x=%%~nf
 | 
					 | 
				
			||||||
          rem Skip FriBiDi license, it is not included in the wheel.
 | 
					 | 
				
			||||||
          set fribidi=!x:~0,7!
 | 
					 | 
				
			||||||
          if NOT !fribidi!==fribidi (
 | 
					 | 
				
			||||||
            rem Skip imagequant license, it is not included in the wheel.
 | 
					 | 
				
			||||||
            set libimagequant=!x:~0,13!
 | 
					 | 
				
			||||||
            if NOT !libimagequant!==libimagequant (
 | 
					 | 
				
			||||||
              echo. >> LICENSE
 | 
					 | 
				
			||||||
              echo ===== %%~nf ===== >> LICENSE
 | 
					 | 
				
			||||||
              echo. >> LICENSE
 | 
					 | 
				
			||||||
              type %%f >> LICENSE
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
          )
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo dist=dist-%%a >> %GITHUB_OUTPUT%
 | 
					 | 
				
			||||||
        call winbuild\\build\\build_env.cmd
 | 
					 | 
				
			||||||
        %pythonLocation%\python.exe -m pip wheel -v -C raqm=vendor -C fribidi=vendor -C imagequant=disable .
 | 
					 | 
				
			||||||
      shell: cmd
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    - name: Upload wheel
 | 
					 | 
				
			||||||
      uses: actions/upload-artifact@v3
 | 
					 | 
				
			||||||
      if: "github.event_name != 'pull_request'"
 | 
					 | 
				
			||||||
      with:
 | 
					 | 
				
			||||||
        name: ${{ steps.wheel.outputs.dist }}
 | 
					 | 
				
			||||||
        path: "*.whl"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    - name: Upload fribidi.dll
 | 
					 | 
				
			||||||
      if: "github.event_name != 'pull_request' && matrix.python-version == 3.11"
 | 
					 | 
				
			||||||
      uses: actions/upload-artifact@v3
 | 
					 | 
				
			||||||
      with:
 | 
					 | 
				
			||||||
        name: fribidi
 | 
					 | 
				
			||||||
        path: fribidi\*
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  success:
 | 
					  success:
 | 
				
			||||||
    permissions:
 | 
					    permissions:
 | 
				
			||||||
      contents: none
 | 
					      contents: none
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										4
									
								
								.github/workflows/wheels-dependencies.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/wheels-dependencies.sh
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -16,13 +16,13 @@ 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=8.2.1
 | 
					HARFBUZZ_VERSION=8.3.0
 | 
				
			||||||
LIBPNG_VERSION=1.6.40
 | 
					LIBPNG_VERSION=1.6.40
 | 
				
			||||||
JPEGTURBO_VERSION=3.0.1
 | 
					JPEGTURBO_VERSION=3.0.1
 | 
				
			||||||
OPENJPEG_VERSION=2.5.0
 | 
					OPENJPEG_VERSION=2.5.0
 | 
				
			||||||
XZ_VERSION=5.4.5
 | 
					XZ_VERSION=5.4.5
 | 
				
			||||||
TIFF_VERSION=4.6.0
 | 
					TIFF_VERSION=4.6.0
 | 
				
			||||||
LCMS2_VERSION=2.15
 | 
					LCMS2_VERSION=2.16
 | 
				
			||||||
if [[ -n "$IS_MACOS" ]]; then
 | 
					if [[ -n "$IS_MACOS" ]]; then
 | 
				
			||||||
    GIFLIB_VERSION=5.1.4
 | 
					    GIFLIB_VERSION=5.1.4
 | 
				
			||||||
else
 | 
					else
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										22
									
								
								.github/workflows/wheels-test.ps1
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.github/workflows/wheels-test.ps1
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,22 @@
 | 
				
			||||||
 | 
					param ([string]$venv, [string]$pillow="C:\pillow")
 | 
				
			||||||
 | 
					$ErrorActionPreference  = 'Stop'
 | 
				
			||||||
 | 
					$ProgressPreference = 'SilentlyContinue'
 | 
				
			||||||
 | 
					Set-PSDebug -Trace 1
 | 
				
			||||||
 | 
					if ("$venv" -like "*\cibw-run-*\pp*-win_amd64\*") {
 | 
				
			||||||
 | 
					    # unlike CPython, PyPy requires Visual C++ Redistributable to be installed
 | 
				
			||||||
 | 
					    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
 | 
				
			||||||
 | 
					    Invoke-WebRequest -Uri 'https://aka.ms/vs/15/release/vc_redist.x64.exe' -OutFile 'vc_redist.x64.exe'
 | 
				
			||||||
 | 
					    C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					$env:path += ";$pillow\winbuild\build\bin\"
 | 
				
			||||||
 | 
					& "$venv\Scripts\activate.ps1"
 | 
				
			||||||
 | 
					& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
 | 
				
			||||||
 | 
					cd $pillow
 | 
				
			||||||
 | 
					& python -VV
 | 
				
			||||||
 | 
					if (!$?) { exit $LASTEXITCODE }
 | 
				
			||||||
 | 
					& python selftest.py
 | 
				
			||||||
 | 
					if (!$?) { exit $LASTEXITCODE }
 | 
				
			||||||
 | 
					& python -m pytest -vx Tests\check_wheel.py
 | 
				
			||||||
 | 
					if (!$?) { exit $LASTEXITCODE }
 | 
				
			||||||
 | 
					& python -m pytest -vx Tests
 | 
				
			||||||
 | 
					if (!$?) { exit $LASTEXITCODE }
 | 
				
			||||||
							
								
								
									
										22
									
								
								.github/workflows/wheels-test.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								.github/workflows/wheels-test.sh
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -1,10 +1,6 @@
 | 
				
			||||||
#!/bin/bash
 | 
					#!/bin/bash
 | 
				
			||||||
set -e
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
EXP_CODECS="jpg jpg_2000 libtiff zlib"
 | 
					 | 
				
			||||||
EXP_MODULES="freetype2 littlecms2 pil tkinter webp"
 | 
					 | 
				
			||||||
EXP_FEATURES="fribidi harfbuzz libjpeg_turbo raqm transp_webp webp_anim webp_mux xcb"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
 | 
					if [[ "$OSTYPE" == "darwin"* ]]; then
 | 
				
			||||||
    brew install fribidi
 | 
					    brew install fribidi
 | 
				
			||||||
    export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
 | 
					    export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
 | 
				
			||||||
| 
						 | 
					@ -25,21 +21,5 @@ fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Runs tests
 | 
					# Runs tests
 | 
				
			||||||
python3 selftest.py
 | 
					python3 selftest.py
 | 
				
			||||||
 | 
					python3 -m pytest Tests/check_wheel.py
 | 
				
			||||||
python3 -m pytest
 | 
					python3 -m pytest
 | 
				
			||||||
 | 
					 | 
				
			||||||
# Test against expected codecs, modules and features
 | 
					 | 
				
			||||||
codecs=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_codecs())))')
 | 
					 | 
				
			||||||
if [ "$codecs" != "$EXP_CODECS" ]; then
 | 
					 | 
				
			||||||
    echo "Codecs should be: '$EXP_CODECS'; but are '$codecs'"
 | 
					 | 
				
			||||||
    exit 1
 | 
					 | 
				
			||||||
fi
 | 
					 | 
				
			||||||
modules=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_modules())))')
 | 
					 | 
				
			||||||
if [ "$modules" != "$EXP_MODULES" ]; then
 | 
					 | 
				
			||||||
    echo "Modules should be: '$EXP_MODULES'; but are '$modules'"
 | 
					 | 
				
			||||||
    exit 1
 | 
					 | 
				
			||||||
fi
 | 
					 | 
				
			||||||
features=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_features())))')
 | 
					 | 
				
			||||||
if [ "$features" != "$EXP_FEATURES" ]; then
 | 
					 | 
				
			||||||
    echo "Features should be: '$EXP_FEATURES'; but are '$features'"
 | 
					 | 
				
			||||||
    exit 1
 | 
					 | 
				
			||||||
fi
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										119
									
								
								.github/workflows/wheels.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										119
									
								
								.github/workflows/wheels.yml
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -3,14 +3,20 @@ name: Wheels
 | 
				
			||||||
on:
 | 
					on:
 | 
				
			||||||
  push:
 | 
					  push:
 | 
				
			||||||
    paths:
 | 
					    paths:
 | 
				
			||||||
      - ".github/workflows/wheels*.yml"
 | 
					      - ".ci/requirements-cibw.txt"
 | 
				
			||||||
 | 
					      - ".github/workflows/wheel*"
 | 
				
			||||||
      - "wheels/*"
 | 
					      - "wheels/*"
 | 
				
			||||||
 | 
					      - "winbuild/build_prepare.py"
 | 
				
			||||||
 | 
					      - "winbuild/fribidi.cmake"
 | 
				
			||||||
    tags:
 | 
					    tags:
 | 
				
			||||||
      - "*"
 | 
					      - "*"
 | 
				
			||||||
  pull_request:
 | 
					  pull_request:
 | 
				
			||||||
    paths:
 | 
					    paths:
 | 
				
			||||||
      - ".github/workflows/wheels*.yml"
 | 
					      - ".ci/requirements-cibw.txt"
 | 
				
			||||||
 | 
					      - ".github/workflows/wheel*"
 | 
				
			||||||
      - "wheels/*"
 | 
					      - "wheels/*"
 | 
				
			||||||
 | 
					      - "winbuild/build_prepare.py"
 | 
				
			||||||
 | 
					      - "winbuild/fribidi.cmake"
 | 
				
			||||||
  workflow_dispatch:
 | 
					  workflow_dispatch:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
permissions:
 | 
					permissions:
 | 
				
			||||||
| 
						 | 
					@ -52,14 +58,17 @@ jobs:
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          submodules: true
 | 
					          submodules: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Build wheels
 | 
					      - uses: actions/setup-python@v4
 | 
				
			||||||
        uses: pypa/cibuildwheel@v2.16.2
 | 
					 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          output-dir: wheelhouse
 | 
					          python-version: "3.x"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Build wheels
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          python3 -m pip install -r .ci/requirements-cibw.txt
 | 
				
			||||||
 | 
					          python3 -m cibuildwheel --output-dir wheelhouse
 | 
				
			||||||
        env:
 | 
					        env:
 | 
				
			||||||
          CIBW_ARCHS: ${{ matrix.archs }}
 | 
					          CIBW_ARCHS: ${{ matrix.archs }}
 | 
				
			||||||
          CIBW_BUILD: ${{ matrix.build }}
 | 
					          CIBW_BUILD: ${{ matrix.build }}
 | 
				
			||||||
          CIBW_CONFIG_SETTINGS: raqm=enable raqm=vendor fribidi=vendor
 | 
					 | 
				
			||||||
          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_SKIP: pp38-*
 | 
					          CIBW_SKIP: pp38-*
 | 
				
			||||||
| 
						 | 
					@ -71,6 +80,102 @@ jobs:
 | 
				
			||||||
          name: dist
 | 
					          name: dist
 | 
				
			||||||
          path: ./wheelhouse/*.whl
 | 
					          path: ./wheelhouse/*.whl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  windows:
 | 
				
			||||||
 | 
					    name: Windows ${{ matrix.arch }}
 | 
				
			||||||
 | 
					    runs-on: windows-latest
 | 
				
			||||||
 | 
					    strategy:
 | 
				
			||||||
 | 
					      fail-fast: false
 | 
				
			||||||
 | 
					      matrix:
 | 
				
			||||||
 | 
					        include:
 | 
				
			||||||
 | 
					          - arch: x86
 | 
				
			||||||
 | 
					            cibw_arch: x86
 | 
				
			||||||
 | 
					          - arch: x64
 | 
				
			||||||
 | 
					            cibw_arch: AMD64
 | 
				
			||||||
 | 
					          - arch: ARM64
 | 
				
			||||||
 | 
					            cibw_arch: ARM64
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - uses: actions/checkout@v4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Checkout extra test images
 | 
				
			||||||
 | 
					        uses: actions/checkout@v4
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          repository: python-pillow/test-images
 | 
				
			||||||
 | 
					          path: Tests\test-images
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - uses: actions/setup-python@v4
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          python-version: "3.x"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Prepare for build
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          choco install nasm --no-progress
 | 
				
			||||||
 | 
					          echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          # Install extra test images
 | 
				
			||||||
 | 
					          xcopy /S /Y Tests\test-images\* Tests\images
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          & python.exe -m pip install -r .ci/requirements-cibw.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          # Cannot cross-compile FriBiDi (only used for tests)
 | 
				
			||||||
 | 
					          $FLAGS = ("--no-imagequant", "--architecture=${{ matrix.arch }}")
 | 
				
			||||||
 | 
					          if ('${{ matrix.arch }}' -eq 'ARM64') { $FLAGS += "--no-fribidi" }
 | 
				
			||||||
 | 
					          & python.exe winbuild\build_prepare.py -v @FLAGS
 | 
				
			||||||
 | 
					        shell: pwsh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Build wheels
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          setlocal EnableDelayedExpansion
 | 
				
			||||||
 | 
					          for %%f in (winbuild\build\license\*) do (
 | 
				
			||||||
 | 
					            set x=%%~nf
 | 
				
			||||||
 | 
					            rem Skip FriBiDi license, it is not included in the wheel.
 | 
				
			||||||
 | 
					            set fribidi=!x:~0,7!
 | 
				
			||||||
 | 
					            if NOT !fribidi!==fribidi (
 | 
				
			||||||
 | 
					              rem Skip imagequant license, it is not included in the wheel.
 | 
				
			||||||
 | 
					              set libimagequant=!x:~0,13!
 | 
				
			||||||
 | 
					              if NOT !libimagequant!==libimagequant (
 | 
				
			||||||
 | 
					                echo. >> LICENSE
 | 
				
			||||||
 | 
					                echo ===== %%~nf ===== >> LICENSE
 | 
				
			||||||
 | 
					                echo. >> LICENSE
 | 
				
			||||||
 | 
					                type %%f >> LICENSE
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          call winbuild\\build\\build_env.cmd
 | 
				
			||||||
 | 
					          %pythonLocation%\python.exe -m cibuildwheel . --output-dir wheelhouse
 | 
				
			||||||
 | 
					        env:
 | 
				
			||||||
 | 
					          CIBW_ARCHS: ${{ matrix.cibw_arch }}
 | 
				
			||||||
 | 
					          CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
 | 
				
			||||||
 | 
					          CIBW_CACHE_PATH: "C:\\cibw"
 | 
				
			||||||
 | 
					          CIBW_TEST_SKIP: "*-win_arm64"
 | 
				
			||||||
 | 
					          CIBW_TEST_COMMAND: 'docker run --rm
 | 
				
			||||||
 | 
					            -v {project}:C:\pillow
 | 
				
			||||||
 | 
					            -v C:\cibw:C:\cibw
 | 
				
			||||||
 | 
					            -v %CD%\..\venv-test:%CD%\..\venv-test
 | 
				
			||||||
 | 
					            -e CI -e GITHUB_ACTIONS
 | 
				
			||||||
 | 
					            mcr.microsoft.com/windows/servercore:ltsc2022
 | 
				
			||||||
 | 
					            powershell C:\pillow\.github\workflows\wheels-test.ps1 %CD%\..\venv-test'
 | 
				
			||||||
 | 
					        shell: cmd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Upload wheels
 | 
				
			||||||
 | 
					        uses: actions/upload-artifact@v3
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          name: dist
 | 
				
			||||||
 | 
					          path: ./wheelhouse/*.whl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Prepare to upload FriBiDi
 | 
				
			||||||
 | 
					        if: "matrix.arch != 'ARM64'"
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          mkdir fribidi\${{ matrix.arch }}
 | 
				
			||||||
 | 
					          copy winbuild\build\bin\fribidi* fribidi\${{ matrix.arch }}
 | 
				
			||||||
 | 
					        shell: cmd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Upload fribidi.dll
 | 
				
			||||||
 | 
					        if: "matrix.arch != 'ARM64'"
 | 
				
			||||||
 | 
					        uses: actions/upload-artifact@v3
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          name: fribidi
 | 
				
			||||||
 | 
					          path: fribidi\*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  sdist:
 | 
					  sdist:
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
| 
						 | 
					@ -93,7 +198,7 @@ jobs:
 | 
				
			||||||
  success:
 | 
					  success:
 | 
				
			||||||
    permissions:
 | 
					    permissions:
 | 
				
			||||||
      contents: none
 | 
					      contents: none
 | 
				
			||||||
    needs: [build, sdist]
 | 
					    needs: [build, windows, sdist]
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    name: Wheels Successful
 | 
					    name: Wheels Successful
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,6 @@ if: tag IS present OR type = api
 | 
				
			||||||
env:
 | 
					env:
 | 
				
			||||||
  global:
 | 
					  global:
 | 
				
			||||||
    - CIBW_ARCHS=aarch64
 | 
					    - CIBW_ARCHS=aarch64
 | 
				
			||||||
    - CIBW_CONFIG_SETTINGS="raqm=enable raqm=vendor fribidi=vendor"
 | 
					 | 
				
			||||||
    - CIBW_SKIP=pp38-*
 | 
					    - CIBW_SKIP=pp38-*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
language: python
 | 
					language: python
 | 
				
			||||||
| 
						 | 
					@ -35,7 +34,7 @@ jobs:
 | 
				
			||||||
        - CIBW_BUILD="*musllinux*"
 | 
					        - CIBW_BUILD="*musllinux*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
install:
 | 
					install:
 | 
				
			||||||
    - python3 -m pip install cibuildwheel==2.16.2
 | 
					    - python3 -m pip install -r .ci/requirements-cibw.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
script:
 | 
					script:
 | 
				
			||||||
    - python3 -m cibuildwheel --output-dir wheelhouse
 | 
					    - python3 -m cibuildwheel --output-dir wheelhouse
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,6 +5,15 @@ Changelog (Pillow)
 | 
				
			||||||
10.2.0 (unreleased)
 | 
					10.2.0 (unreleased)
 | 
				
			||||||
-------------------
 | 
					-------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Correct PDF palette size when saving #7555
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed closing file pointer with olefile 0.47 #7594
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Raise ValueError when TrueType font size is not greater than zero #7584, #7587
 | 
				
			||||||
 | 
					  [akx, radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- If absent, do not try to close fp when closing image #7557
 | 
					- If absent, do not try to close fp when closing image #7557
 | 
				
			||||||
  [RaphaelVRossi, radarhere]
 | 
					  [RaphaelVRossi, radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -94,7 +94,6 @@ Released as needed privately to individual vendors for critical security-related
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Source and Binary Distributions
 | 
					## Source and Binary Distributions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### macOS and Linux
 | 
					 | 
				
			||||||
* [ ] Download sdist and wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
 | 
					* [ ] Download sdist and wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
 | 
				
			||||||
  and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
 | 
					  and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
 | 
				
			||||||
  ```bash
 | 
					  ```bash
 | 
				
			||||||
| 
						 | 
					@ -104,14 +103,6 @@ Released as needed privately to individual vendors for critical security-related
 | 
				
			||||||
* [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases)
 | 
					* [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases)
 | 
				
			||||||
  and copy into `dist`.
 | 
					  and copy into `dist`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Windows
 | 
					 | 
				
			||||||
* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)
 | 
					 | 
				
			||||||
  and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
 | 
					 | 
				
			||||||
  ```bash
 | 
					 | 
				
			||||||
  gh run download --dir dist
 | 
					 | 
				
			||||||
  # select dist-x.y.z
 | 
					 | 
				
			||||||
  ```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Publicize Release
 | 
					## Publicize Release
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) and [Mastodon](https://fosstodon.org/@pillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010
 | 
					* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) and [Mastodon](https://fosstodon.org/@pillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										41
									
								
								Tests/check_wheel.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								Tests/check_wheel.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,41 @@
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from PIL import features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_wheel_modules():
 | 
				
			||||||
 | 
					    expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # tkinter is not available in cibuildwheel installed CPython on Windows
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        import tkinter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert tkinter
 | 
				
			||||||
 | 
					    except ImportError:
 | 
				
			||||||
 | 
					        expected_modules.remove("tkinter")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert set(features.get_supported_modules()) == expected_modules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_wheel_codecs():
 | 
				
			||||||
 | 
					    expected_codecs = {"jpg", "jpg_2000", "zlib", "libtiff"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert set(features.get_supported_codecs()) == expected_codecs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_wheel_features():
 | 
				
			||||||
 | 
					    expected_features = {
 | 
				
			||||||
 | 
					        "webp_anim",
 | 
				
			||||||
 | 
					        "webp_mux",
 | 
				
			||||||
 | 
					        "transp_webp",
 | 
				
			||||||
 | 
					        "raqm",
 | 
				
			||||||
 | 
					        "fribidi",
 | 
				
			||||||
 | 
					        "harfbuzz",
 | 
				
			||||||
 | 
					        "libjpeg_turbo",
 | 
				
			||||||
 | 
					        "xcb",
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if sys.platform == "win32":
 | 
				
			||||||
 | 
					        expected_features.remove("xcb")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert set(features.get_supported_features()) == expected_features
 | 
				
			||||||
| 
						 | 
					@ -5,6 +5,7 @@ Helper functions.
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import shutil
 | 
					import shutil
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import sysconfig
 | 
					import sysconfig
 | 
				
			||||||
import tempfile
 | 
					import tempfile
 | 
				
			||||||
| 
						 | 
					@ -258,11 +259,21 @@ def hopper(mode=None, cache={}):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def djpeg_available():
 | 
					def djpeg_available():
 | 
				
			||||||
    return bool(shutil.which("djpeg"))
 | 
					    if shutil.which("djpeg"):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            subprocess.check_call(["djpeg", "-version"])
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        except subprocess.CalledProcessError:  # pragma: no cover
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def cjpeg_available():
 | 
					def cjpeg_available():
 | 
				
			||||||
    return bool(shutil.which("cjpeg"))
 | 
					    if shutil.which("cjpeg"):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            subprocess.check_call(["cjpeg", "-version"])
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        except subprocess.CalledProcessError:  # pragma: no cover
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def netpbm_available():
 | 
					def netpbm_available():
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -67,7 +67,7 @@ def test_quantize_no_dither():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_quantize_no_dither2():
 | 
					def test_quantize_no_dither2():
 | 
				
			||||||
    im = Image.new("RGB", (9, 1))
 | 
					    im = Image.new("RGB", (9, 1))
 | 
				
			||||||
    im.putdata(list((p,) * 3 for p in range(0, 36, 4)))
 | 
					    im.putdata([(p,) * 3 for p in range(0, 36, 4)])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    palette = Image.new("P", (1, 1))
 | 
					    palette = Image.new("P", (1, 1))
 | 
				
			||||||
    data = (0, 0, 0, 32, 32, 32)
 | 
					    data = (0, 0, 0, 32, 32, 32)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1073,3 +1073,9 @@ def test_raqm_missing_warning(monkeypatch):
 | 
				
			||||||
        "Raqm layout was requested, but Raqm is not available. "
 | 
					        "Raqm layout was requested, but Raqm is not available. "
 | 
				
			||||||
        "Falling back to basic layout."
 | 
					        "Falling back to basic layout."
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.mark.parametrize("size", [-1, 0])
 | 
				
			||||||
 | 
					def test_invalid_truetype_sizes_raise_valueerror(layout_engine, size):
 | 
				
			||||||
 | 
					    with pytest.raises(ValueError):
 | 
				
			||||||
 | 
					        ImageFont.truetype(FONT_PATH, size, layout_engine=layout_engine)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,10 @@ from .helper import assert_image_equal_tofile, skip_unless_feature
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestImageGrab:
 | 
					class TestImageGrab:
 | 
				
			||||||
 | 
					    @pytest.mark.skipif(
 | 
				
			||||||
 | 
					        os.environ.get("USERNAME") == "ContainerAdministrator",
 | 
				
			||||||
 | 
					        reason="can't grab screen when running in Docker",
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    @pytest.mark.skipif(
 | 
					    @pytest.mark.skipif(
 | 
				
			||||||
        sys.platform not in ("win32", "darwin"), reason="requires Windows or macOS"
 | 
					        sys.platform not in ("win32", "darwin"), reason="requires Windows or macOS"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										10
									
								
								docs/conf.py
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								docs/conf.py
									
									
									
									
									
								
							| 
						 | 
					@ -166,6 +166,12 @@ html_static_path = ["resources"]
 | 
				
			||||||
# directly to the root of the documentation.
 | 
					# directly to the root of the documentation.
 | 
				
			||||||
# html_extra_path = []
 | 
					# html_extra_path = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					html_css_files = ["css/dark.css"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					html_js_files = [
 | 
				
			||||||
 | 
					    "js/activate_tab.js",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 | 
					# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 | 
				
			||||||
# using the given strftime format.
 | 
					# using the given strftime format.
 | 
				
			||||||
# html_last_updated_fmt = '%b %d, %Y'
 | 
					# html_last_updated_fmt = '%b %d, %Y'
 | 
				
			||||||
| 
						 | 
					@ -313,10 +319,6 @@ texinfo_documents = [
 | 
				
			||||||
# texinfo_no_detailmenu = False
 | 
					# texinfo_no_detailmenu = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def setup(app):
 | 
					 | 
				
			||||||
    app.add_css_file("css/dark.css")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
linkcheck_allowed_redirects = {
 | 
					linkcheck_allowed_redirects = {
 | 
				
			||||||
    r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*",
 | 
					    r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*",
 | 
				
			||||||
    r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg",
 | 
					    r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,14 @@
 | 
				
			||||||
Installation
 | 
					Installation
 | 
				
			||||||
============
 | 
					============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. raw:: html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					    document.addEventListener('DOMContentLoaded', function() {
 | 
				
			||||||
 | 
					      activateTab(getOS());
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Warnings
 | 
					Warnings
 | 
				
			||||||
--------
 | 
					--------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -87,11 +95,10 @@ and :pypi:`olefile` for Pillow to read FPX and MIC images::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. tab:: Windows
 | 
					.. tab:: Windows
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .. warning:: Pillow > 9.5.0 no longer includes 32-bit wheels.
 | 
					    We provide Pillow binaries for Windows compiled for the matrix of supported
 | 
				
			||||||
 | 
					    Pythons in the wheel format. These include x86, x86-64 and arm64 versions
 | 
				
			||||||
    We provide Pillow binaries for Windows compiled for the matrix of
 | 
					    (with the exception of Python 3.8 on arm64). These binaries include support
 | 
				
			||||||
    supported Pythons in 64-bit versions in the wheel format. These binaries include
 | 
					    for all optional libraries except libimagequant and libxcb. Raqm support
 | 
				
			||||||
    support for all optional libraries except libimagequant and libxcb. Raqm support
 | 
					 | 
				
			||||||
    requires FriBiDi to be installed separately::
 | 
					    requires FriBiDi to be installed separately::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        python3 -m pip install --upgrade pip
 | 
					        python3 -m pip install --upgrade pip
 | 
				
			||||||
| 
						 | 
					@ -168,7 +175,7 @@ Many of Pillow's features require external libraries:
 | 
				
			||||||
* **littlecms** provides color management
 | 
					* **littlecms** provides color management
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
 | 
					  * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
 | 
				
			||||||
    above uses liblcms2. Tested with **1.19** and **2.7-2.15**.
 | 
					    above uses liblcms2. Tested with **1.19** and **2.7-2.16**.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* **libwebp** provides the WebP format.
 | 
					* **libwebp** provides the WebP format.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										36
									
								
								docs/resources/js/activate_tab.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								docs/resources/js/activate_tab.js
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					// Based on https://stackoverflow.com/a/38241481/724176
 | 
				
			||||||
 | 
					function getOS() {
 | 
				
			||||||
 | 
					  const userAgent = window.navigator.userAgent,
 | 
				
			||||||
 | 
					    platform = window.navigator.userAgentData?.platform || window.navigator.platform,
 | 
				
			||||||
 | 
					    macosPlatforms = ["macOS", "Macintosh", "MacIntel", "MacPPC", "Mac68K"],
 | 
				
			||||||
 | 
					    windowsPlatforms = ["Win32", "Win64", "Windows", "WinCE"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (macosPlatforms.includes(platform)) {
 | 
				
			||||||
 | 
					    return "macOS";
 | 
				
			||||||
 | 
					  } else if (windowsPlatforms.includes(platform)) {
 | 
				
			||||||
 | 
					    return "Windows";
 | 
				
			||||||
 | 
					  } else if (/Android/.test(userAgent)) {
 | 
				
			||||||
 | 
					    return "Android";
 | 
				
			||||||
 | 
					  } else if (/Linux/.test(platform)) {
 | 
				
			||||||
 | 
					    return "Linux";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function activateTab(tabName) {
 | 
				
			||||||
 | 
					  // Find all label elements with the specified tab name
 | 
				
			||||||
 | 
					  const labels = document.querySelectorAll(".tab-label");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  labels.forEach((label) => {
 | 
				
			||||||
 | 
					    if (label.textContent == tabName) {
 | 
				
			||||||
 | 
					      // Find the associated input element using the "for" attribute
 | 
				
			||||||
 | 
					      const tabInputId = label.getAttribute("for");
 | 
				
			||||||
 | 
					      const tabInput = document.getElementById(tabInputId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Check if the input element exists before attempting to set the "checked" attribute
 | 
				
			||||||
 | 
					      if (tabInput) {
 | 
				
			||||||
 | 
					        // Activate the tab by setting its "checked" attribute to true
 | 
				
			||||||
 | 
					        tabInput.checked = true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -47,6 +47,12 @@ docs = [
 | 
				
			||||||
  "sphinx-removed-in",
 | 
					  "sphinx-removed-in",
 | 
				
			||||||
  "sphinxext-opengraph",
 | 
					  "sphinxext-opengraph",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					fpx = [
 | 
				
			||||||
 | 
					  "olefile",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					mic = [
 | 
				
			||||||
 | 
					  "olefile",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
tests = [
 | 
					tests = [
 | 
				
			||||||
  "check-manifest",
 | 
					  "check-manifest",
 | 
				
			||||||
  "coverage",
 | 
					  "coverage",
 | 
				
			||||||
| 
						 | 
					@ -59,6 +65,9 @@ tests = [
 | 
				
			||||||
  "pytest-cov",
 | 
					  "pytest-cov",
 | 
				
			||||||
  "pytest-timeout",
 | 
					  "pytest-timeout",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					xmp = [
 | 
				
			||||||
 | 
					  "defusedxml",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
[project.urls]
 | 
					[project.urls]
 | 
				
			||||||
Changelog = "https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst"
 | 
					Changelog = "https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst"
 | 
				
			||||||
Documentation = "https://pillow.readthedocs.io"
 | 
					Documentation = "https://pillow.readthedocs.io"
 | 
				
			||||||
| 
						 | 
					@ -79,12 +88,15 @@ version = {attr = "PIL.__version__"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.cibuildwheel]
 | 
					[tool.cibuildwheel]
 | 
				
			||||||
before-all = ".github/workflows/wheels-dependencies.sh"
 | 
					before-all = ".github/workflows/wheels-dependencies.sh"
 | 
				
			||||||
 | 
					build-verbosity = 1
 | 
				
			||||||
 | 
					config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable"
 | 
				
			||||||
test-command = "cd {project} && .github/workflows/wheels-test.sh"
 | 
					test-command = "cd {project} && .github/workflows/wheels-test.sh"
 | 
				
			||||||
test-extras = "tests"
 | 
					test-extras = "tests"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.ruff]
 | 
					[tool.ruff]
 | 
				
			||||||
line-length = 88
 | 
					line-length = 88
 | 
				
			||||||
select = [
 | 
					select = [
 | 
				
			||||||
 | 
					  "C4", # flake8-comprehensions
 | 
				
			||||||
  "E", # pycodestyle errors
 | 
					  "E", # pycodestyle errors
 | 
				
			||||||
  "EM", # flake8-errmsg
 | 
					  "EM", # flake8-errmsg
 | 
				
			||||||
  "F", # pyflakes errors
 | 
					  "F", # pyflakes errors
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										22
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								setup.py
									
									
									
									
									
								
							| 
						 | 
					@ -440,17 +440,17 @@ class pil_build_ext(build_ext):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        #
 | 
					        #
 | 
				
			||||||
        # add configured kits
 | 
					        # add configured kits
 | 
				
			||||||
        for root_name, lib_name in dict(
 | 
					        for root_name, lib_name in {
 | 
				
			||||||
            JPEG_ROOT="libjpeg",
 | 
					            "JPEG_ROOT": "libjpeg",
 | 
				
			||||||
            JPEG2K_ROOT="libopenjp2",
 | 
					            "JPEG2K_ROOT": "libopenjp2",
 | 
				
			||||||
            TIFF_ROOT=("libtiff-5", "libtiff-4"),
 | 
					            "TIFF_ROOT": ("libtiff-5", "libtiff-4"),
 | 
				
			||||||
            ZLIB_ROOT="zlib",
 | 
					            "ZLIB_ROOT": "zlib",
 | 
				
			||||||
            FREETYPE_ROOT="freetype2",
 | 
					            "FREETYPE_ROOT": "freetype2",
 | 
				
			||||||
            HARFBUZZ_ROOT="harfbuzz",
 | 
					            "HARFBUZZ_ROOT": "harfbuzz",
 | 
				
			||||||
            FRIBIDI_ROOT="fribidi",
 | 
					            "FRIBIDI_ROOT": "fribidi",
 | 
				
			||||||
            LCMS_ROOT="lcms2",
 | 
					            "LCMS_ROOT": "lcms2",
 | 
				
			||||||
            IMAGEQUANT_ROOT="libimagequant",
 | 
					            "IMAGEQUANT_ROOT": "libimagequant",
 | 
				
			||||||
        ).items():
 | 
					        }.items():
 | 
				
			||||||
            root = globals()[root_name]
 | 
					            root = globals()[root_name]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if root is None and root_name in os.environ:
 | 
					            if root is None and root_name in os.environ:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -396,7 +396,7 @@ def _save(im, fp, filename, bitmap_header=True):
 | 
				
			||||||
    dpi = info.get("dpi", (96, 96))
 | 
					    dpi = info.get("dpi", (96, 96))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # 1 meter == 39.3701 inches
 | 
					    # 1 meter == 39.3701 inches
 | 
				
			||||||
    ppm = tuple(map(lambda x: int(x * 39.3701 + 0.5), dpi))
 | 
					    ppm = tuple(int(x * 39.3701 + 0.5) for x in dpi)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3)
 | 
					    stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3)
 | 
				
			||||||
    header = 40  # or 64 for OS/2 version 2
 | 
					    header = 40  # or 64 for OS/2 version 2
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -64,8 +64,6 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
 | 
				
			||||||
        d, e, o, a = self.tile[0]
 | 
					        d, e, o, a = self.tile[0]
 | 
				
			||||||
        self.tile[0] = d, (0, 0) + self.size, o, a
 | 
					        self.tile[0] = d, (0, 0) + self.size, o, a
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# --------------------------------------------------------------------
 | 
					# --------------------------------------------------------------------
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -227,6 +227,7 @@ class FpxImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                    break  # isn't really required
 | 
					                    break  # isn't really required
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.stream = stream
 | 
					        self.stream = stream
 | 
				
			||||||
 | 
					        self._fp = self.fp
 | 
				
			||||||
        self.fp = None
 | 
					        self.fp = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def load(self):
 | 
					    def load(self):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,7 @@ from enum import IntEnum
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    import defusedxml.ElementTree as ElementTree
 | 
					    from defusedxml import ElementTree
 | 
				
			||||||
except ImportError:
 | 
					except ImportError:
 | 
				
			||||||
    ElementTree = None
 | 
					    ElementTree = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1160,7 +1160,7 @@ class Image:
 | 
				
			||||||
            if palette.mode != "P":
 | 
					            if palette.mode != "P":
 | 
				
			||||||
                msg = "bad mode for palette image"
 | 
					                msg = "bad mode for palette image"
 | 
				
			||||||
                raise ValueError(msg)
 | 
					                raise ValueError(msg)
 | 
				
			||||||
            if self.mode != "RGB" and self.mode != "L":
 | 
					            if self.mode not in {"RGB", "L"}:
 | 
				
			||||||
                msg = "only RGB or L mode images can be quantized to a palette"
 | 
					                msg = "only RGB or L mode images can be quantized to a palette"
 | 
				
			||||||
                raise ValueError(msg)
 | 
					                raise ValueError(msg)
 | 
				
			||||||
            im = self.im.convert("P", dither, palette.im)
 | 
					            im = self.im.convert("P", dither, palette.im)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -921,7 +921,7 @@ def floodfill(image, xy, value, border=None, thresh=0):
 | 
				
			||||||
                    if border is None:
 | 
					                    if border is None:
 | 
				
			||||||
                        fill = _color_diff(p, background) <= thresh
 | 
					                        fill = _color_diff(p, background) <= thresh
 | 
				
			||||||
                    else:
 | 
					                    else:
 | 
				
			||||||
                        fill = p != value and p != border
 | 
					                        fill = p not in (value, border)
 | 
				
			||||||
                    if fill:
 | 
					                    if fill:
 | 
				
			||||||
                        pixel[s, t] = value
 | 
					                        pixel[s, t] = value
 | 
				
			||||||
                        new_edge.add((s, t))
 | 
					                        new_edge.add((s, t))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -198,6 +198,10 @@ class FreeTypeFont:
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        # FIXME: use service provider instead
 | 
					        # FIXME: use service provider instead
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if size <= 0:
 | 
				
			||||||
 | 
					            msg = "font size must be greater than 0"
 | 
				
			||||||
 | 
					            raise ValueError(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.path = font
 | 
					        self.path = font
 | 
				
			||||||
        self.size = size
 | 
					        self.size = size
 | 
				
			||||||
        self.index = index
 | 
					        self.index = index
 | 
				
			||||||
| 
						 | 
					@ -800,6 +804,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
 | 
				
			||||||
                     .. versionadded:: 4.2.0
 | 
					                     .. versionadded:: 4.2.0
 | 
				
			||||||
    :return: A font object.
 | 
					    :return: A font object.
 | 
				
			||||||
    :exception OSError: If the file could not be read.
 | 
					    :exception OSError: If the file could not be read.
 | 
				
			||||||
 | 
					    :exception ValueError: If the font size is not greater than zero.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def freetype(font):
 | 
					    def freetype(font):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -334,10 +334,7 @@ def _save(im, fp, filename):
 | 
				
			||||||
    if quality_layers is not None and not (
 | 
					    if quality_layers is not None and not (
 | 
				
			||||||
        isinstance(quality_layers, (list, tuple))
 | 
					        isinstance(quality_layers, (list, tuple))
 | 
				
			||||||
        and all(
 | 
					        and all(
 | 
				
			||||||
            [
 | 
					            isinstance(quality_layer, (int, float)) for quality_layer in quality_layers
 | 
				
			||||||
                isinstance(quality_layer, (int, float))
 | 
					 | 
				
			||||||
                for quality_layer in quality_layers
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    ):
 | 
					    ):
 | 
				
			||||||
        msg = "quality_layers must be a sequence of numbers"
 | 
					        msg = "quality_layers must be a sequence of numbers"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -397,7 +397,7 @@ class JpegImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                    # self.__offset = self.fp.tell()
 | 
					                    # self.__offset = self.fp.tell()
 | 
				
			||||||
                    break
 | 
					                    break
 | 
				
			||||||
                s = self.fp.read(1)
 | 
					                s = self.fp.read(1)
 | 
				
			||||||
            elif i == 0 or i == 0xFFFF:
 | 
					            elif i in {0, 0xFFFF}:
 | 
				
			||||||
                # padded marker or junk; move on
 | 
					                # padded marker or junk; move on
 | 
				
			||||||
                s = b"\xff"
 | 
					                s = b"\xff"
 | 
				
			||||||
            elif i == 0xFF00:  # Skip extraneous data (escaped 0xFF)
 | 
					            elif i == 0xFF00:  # Skip extraneous data (escaped 0xFF)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -66,6 +66,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
 | 
				
			||||||
        self._n_frames = len(self.images)
 | 
					        self._n_frames = len(self.images)
 | 
				
			||||||
        self.is_animated = self._n_frames > 1
 | 
					        self.is_animated = self._n_frames > 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.__fp = self.fp
 | 
				
			||||||
        self.seek(0)
 | 
					        self.seek(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def seek(self, frame):
 | 
					    def seek(self, frame):
 | 
				
			||||||
| 
						 | 
					@ -87,10 +88,12 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
 | 
				
			||||||
        return self.frame
 | 
					        return self.frame
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def close(self):
 | 
					    def close(self):
 | 
				
			||||||
 | 
					        self.__fp.close()
 | 
				
			||||||
        self.ole.close()
 | 
					        self.ole.close()
 | 
				
			||||||
        super().close()
 | 
					        super().close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __exit__(self, *args):
 | 
					    def __exit__(self, *args):
 | 
				
			||||||
 | 
					        self.__fp.close()
 | 
				
			||||||
        self.ole.close()
 | 
					        self.ole.close()
 | 
				
			||||||
        super().__exit__()
 | 
					        super().__exit__()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -96,7 +96,7 @@ def _write_image(im, filename, existing_pdf, image_refs):
 | 
				
			||||||
        dict_obj["ColorSpace"] = [
 | 
					        dict_obj["ColorSpace"] = [
 | 
				
			||||||
            PdfParser.PdfName("Indexed"),
 | 
					            PdfParser.PdfName("Indexed"),
 | 
				
			||||||
            PdfParser.PdfName("DeviceRGB"),
 | 
					            PdfParser.PdfName("DeviceRGB"),
 | 
				
			||||||
            255,
 | 
					            len(palette) // 3 - 1,
 | 
				
			||||||
            PdfParser.PdfBinary(palette),
 | 
					            PdfParser.PdfBinary(palette),
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
        procset = "ImageI"  # indexed color
 | 
					        procset = "ImageI"  # indexed color
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -123,7 +123,7 @@ class SgiImageFile(ImageFile.ImageFile):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _save(im, fp, filename):
 | 
					def _save(im, fp, filename):
 | 
				
			||||||
    if im.mode != "RGB" and im.mode != "RGBA" and im.mode != "L":
 | 
					    if im.mode not in {"RGB", "RGBA", "L"}:
 | 
				
			||||||
        msg = "Unsupported SGI image mode"
 | 
					        msg = "Unsupported SGI image mode"
 | 
				
			||||||
        raise ValueError(msg)
 | 
					        raise ValueError(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -155,7 +155,7 @@ def _save(im, fp, filename):
 | 
				
			||||||
    # Z Dimension: Number of channels
 | 
					    # Z Dimension: Number of channels
 | 
				
			||||||
    z = len(im.mode)
 | 
					    z = len(im.mode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if dim == 1 or dim == 2:
 | 
					    if dim in {1, 2}:
 | 
				
			||||||
        z = 1
 | 
					        z = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # assert we've got the right number of bands.
 | 
					    # assert we've got the right number of bands.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -427,7 +427,7 @@ def _populate():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        TAGS_V2[k] = TagInfo(k, *v)
 | 
					        TAGS_V2[k] = TagInfo(k, *v)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for group, tags in TAGS_V2_GROUPS.items():
 | 
					    for tags in TAGS_V2_GROUPS.values():
 | 
				
			||||||
        for k, v in tags.items():
 | 
					        for k, v in tags.items():
 | 
				
			||||||
            tags[k] = TagInfo(k, *v)
 | 
					            tags[k] = TagInfo(k, *v)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -279,10 +279,10 @@ DEPS = {
 | 
				
			||||||
        "libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"],
 | 
					        "libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "lcms2": {
 | 
					    "lcms2": {
 | 
				
			||||||
        "url": SF_PROJECTS + "/lcms/files/lcms/2.15/lcms2-2.15.tar.gz/download",
 | 
					        "url": SF_PROJECTS + "/lcms/files/lcms/2.16/lcms2-2.16.tar.gz/download",
 | 
				
			||||||
        "filename": "lcms2-2.15.tar.gz",
 | 
					        "filename": "lcms2-2.16.tar.gz",
 | 
				
			||||||
        "dir": "lcms2-2.15",
 | 
					        "dir": "lcms2-2.16",
 | 
				
			||||||
        "license": "COPYING",
 | 
					        "license": "LICENSE",
 | 
				
			||||||
        "patch": {
 | 
					        "patch": {
 | 
				
			||||||
            r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": {
 | 
					            r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": {
 | 
				
			||||||
                # default is /MD for x86 and /MT for x64, we need /MD always
 | 
					                # default is /MD for x86 and /MT for x64, we need /MD always
 | 
				
			||||||
| 
						 | 
					@ -345,9 +345,9 @@ DEPS = {
 | 
				
			||||||
        "libs": [r"imagequant.lib"],
 | 
					        "libs": [r"imagequant.lib"],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "harfbuzz": {
 | 
					    "harfbuzz": {
 | 
				
			||||||
        "url": "https://github.com/harfbuzz/harfbuzz/archive/8.2.1.zip",
 | 
					        "url": "https://github.com/harfbuzz/harfbuzz/archive/8.3.0.zip",
 | 
				
			||||||
        "filename": "harfbuzz-8.2.1.zip",
 | 
					        "filename": "harfbuzz-8.3.0.zip",
 | 
				
			||||||
        "dir": "harfbuzz-8.2.1",
 | 
					        "dir": "harfbuzz-8.3.0",
 | 
				
			||||||
        "license": "COPYING",
 | 
					        "license": "COPYING",
 | 
				
			||||||
        "build": [
 | 
					        "build": [
 | 
				
			||||||
            *cmds_cmake(
 | 
					            *cmds_cmake(
 | 
				
			||||||
| 
						 | 
					@ -482,7 +482,7 @@ def extract_dep(url: str, filename: str) -> None:
 | 
				
			||||||
                    msg = "Attempted Path Traversal in Zip File"
 | 
					                    msg = "Attempted Path Traversal in Zip File"
 | 
				
			||||||
                    raise RuntimeError(msg)
 | 
					                    raise RuntimeError(msg)
 | 
				
			||||||
            zf.extractall(sources_dir)
 | 
					            zf.extractall(sources_dir)
 | 
				
			||||||
    elif filename.endswith(".tar.gz") or filename.endswith(".tgz"):
 | 
					    elif filename.endswith((".tar.gz", ".tgz")):
 | 
				
			||||||
        with tarfile.open(file, "r:gz") as tgz:
 | 
					        with tarfile.open(file, "r:gz") as tgz:
 | 
				
			||||||
            for member in tgz.getnames():
 | 
					            for member in tgz.getnames():
 | 
				
			||||||
                member_abspath = os.path.abspath(os.path.join(sources_dir, member))
 | 
					                member_abspath = os.path.abspath(os.path.join(sources_dir, member))
 | 
				
			||||||
| 
						 | 
					@ -586,14 +586,19 @@ def build_dep(name: str) -> str:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def build_dep_all() -> None:
 | 
					def build_dep_all() -> None:
 | 
				
			||||||
    lines = [r'call "{build_dir}\build_env.cmd"']
 | 
					    lines = [r'call "{build_dir}\build_env.cmd"']
 | 
				
			||||||
 | 
					    gha_groups = "GITHUB_ACTIONS" in os.environ
 | 
				
			||||||
    for dep_name in DEPS:
 | 
					    for dep_name in DEPS:
 | 
				
			||||||
        print()
 | 
					        print()
 | 
				
			||||||
        if dep_name in disabled:
 | 
					        if dep_name in disabled:
 | 
				
			||||||
            print(f"Skipping disabled dependency {dep_name}")
 | 
					            print(f"Skipping disabled dependency {dep_name}")
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
        script = build_dep(dep_name)
 | 
					        script = build_dep(dep_name)
 | 
				
			||||||
 | 
					        if gha_groups:
 | 
				
			||||||
 | 
					            lines.append(f"@echo ::group::Running {script}")
 | 
				
			||||||
        lines.append(rf'cmd.exe /c "{{build_dir}}\{script}"')
 | 
					        lines.append(rf'cmd.exe /c "{{build_dir}}\{script}"')
 | 
				
			||||||
        lines.append("if errorlevel 1 echo Build failed! && exit /B 1")
 | 
					        lines.append("if errorlevel 1 echo Build failed! && exit /B 1")
 | 
				
			||||||
 | 
					        if gha_groups:
 | 
				
			||||||
 | 
					            lines.append("@echo ::endgroup::")
 | 
				
			||||||
    print()
 | 
					    print()
 | 
				
			||||||
    lines.append("@echo All Pillow dependencies built successfully!")
 | 
					    lines.append("@echo All Pillow dependencies built successfully!")
 | 
				
			||||||
    write_script("build_dep_all.cmd", lines)
 | 
					    write_script("build_dep_all.cmd", lines)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user