From eb75089ba2fa5cc961b4a873c60a79fb1db7aafd Mon Sep 17 00:00:00 2001
From: pwarszawski
Date: Sun, 8 Nov 2026 21:12:35 +0100
Subject: [PATCH] Python screens: ship Pillow with the Windows build, and the
screen module for Pillow 10+
- The Windows build job installs Pillow into the Python 3.14 whose Lib, with
site-packages, CMake copies next to the executable as python64\Lib, as the
Linux job already does; the README says the same for a local build.
- Both build jobs copy staticFiles/python/local (the Python 3 version of the
module every cab screen subclasses) into python/local of the build.
- The module puts back what Pillow 10 removed and the screen scripts use:
ImageFont.getsize() and ImageDraw.textsize() on top of getbbox() /
textbbox(), and Image.ANTIALIAS as LANCZOS. With an older Pillow nothing
changes.
Part of #300.
---
.github/workflows/build_bin.yml | 25 ++++++++++++++++++-
README.md | 2 +-
.../python/local/abstractscreenrenderer.py | 20 ++++++++++++++-
3 files changed, 44 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/build_bin.yml b/.github/workflows/build_bin.yml
index 2f1781ed..d37817d5 100644
--- a/.github/workflows/build_bin.yml
+++ b/.github/workflows/build_bin.yml
@@ -45,6 +45,15 @@ jobs:
python-version: "3.14"
architecture: "x64"
+ # the cab screens are drawn with Pillow; CMake copies this Python's Lib, site-packages included, next to the
+ # executable as python64\Lib, so Pillow has to be installed here to come with the build (as on Linux)
+ - name: Install Pillow for the screens
+ shell: pwsh
+ run: |
+ $pythonExe = "${{ steps.python.outputs.python-path }}"
+ & $pythonExe -m pip install --no-cache-dir Pillow
+ & $pythonExe -c "from PIL import Image; print('Pillow:', Image.__version__)"
+
- name: Show environment
shell: pwsh
run: |
@@ -114,6 +123,18 @@ jobs:
-Recurse `
-Force
+ # the Python 3 version of the module every cab screen builds on; the game data still has the Python 2 one
+ New-Item `
+ -ItemType Directory `
+ -Force `
+ -Path "$output/python/local" | Out-Null
+
+ Copy-Item `
+ -Path "staticFiles/python/local/*" `
+ -Destination "$output/python/local" `
+ -Recurse `
+ -Force
+
- name: Upload Windows build
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
@@ -251,9 +272,11 @@ jobs:
- name: Copy shaders and translations
run: |
- mkdir -p build/bin/shaders build/bin/lang
+ mkdir -p build/bin/shaders build/bin/lang build/bin/python/local
cp -R shaders/. build/bin/shaders/
cp -R lang/. build/bin/lang/
+ # the Python 3 version of the module every cab screen builds on; the game data still has the Python 2 one
+ cp -R staticFiles/python/local/. build/bin/python/local/
- name: Upload Linux build
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
diff --git a/README.md b/README.md
index 47741a51..675026cc 100644
--- a/README.md
+++ b/README.md
@@ -110,7 +110,7 @@ MaSzyna compiles and runs natively on **Linux** and **Windows**. Other platforms
## Windows
-Install **Python 3.14 x64** from [python.org](https://www.python.org/downloads/windows/) and include the optional **Python native development** feature (headers and `.lib` for embedding). Pass `-DPython3_ROOT_DIR="C:\Path\To\Python314"` on the `cmake` line when you want the layout under that directory used for embedding and for copying `Lib` / `DLLs` next to the executable.
+Install **Python 3.14 x64** from [python.org](https://www.python.org/downloads/windows/) and include the optional **Python native development** feature (headers and `.lib` for embedding). Pass `-DPython3_ROOT_DIR="C:\Path\To\Python314"` on the `cmake` line when you want the layout under that directory used for embedding and for copying `Lib` / `DLLs` next to the executable. The cab screens are drawn with [Pillow](https://pypi.org/project/pillow/): install it into that Python (`python -m pip install Pillow`) before building, so it is copied along with `Lib`.
**AppVeyor CI** builds with the worker image’s preinstalled Python at `C:/Python314-x64` (see [`appveyor.yml`](./appveyor.yml): `-DPython3_ROOT_DIR=...`).
diff --git a/staticFiles/python/local/abstractscreenrenderer.py b/staticFiles/python/local/abstractscreenrenderer.py
index ea8be2c5..3ebf2081 100644
--- a/staticFiles/python/local/abstractscreenrenderer.py
+++ b/staticFiles/python/local/abstractscreenrenderer.py
@@ -1,7 +1,25 @@
-from PIL import Image
+from PIL import Image, ImageDraw, ImageFont
from math import ceil, floor, cos, sin, radians
import os
+# Pillow 10 removed ImageFont.getsize(), ImageDraw.textsize() and Image.ANTIALIAS, which the screen scripts use; for
+# Python 3.14 only a newer Pillow is available, so they are put back, on top of getbbox() / textbbox() and LANCZOS
+def _font_getsize(self, text, *args, **kwargs):
+ box = self.getbbox(text, *args, **kwargs)
+ return (box[2], box[3])
+
+def _draw_textsize(self, text, font=None, *args, **kwargs):
+ box = self.textbbox((0, 0), text, font, *args, **kwargs)
+ return (box[2], box[3])
+
+for _fontclass in (ImageFont.FreeTypeFont, ImageFont.ImageFont, getattr(ImageFont, 'TransposedFont', None)):
+ if _fontclass is not None and not hasattr(_fontclass, 'getsize') and hasattr(_fontclass, 'getbbox'):
+ _fontclass.getsize = _font_getsize
+if not hasattr(ImageDraw.ImageDraw, 'textsize'):
+ ImageDraw.ImageDraw.textsize = _draw_textsize
+if not hasattr(Image, 'ANTIALIAS'):
+ Image.ANTIALIAS = Image.LANCZOS
+
class abstractscreenrenderer():
def __init__(self, lookup_path):
self.width = 0