diff --git a/font_configurator/font_configurator.py b/font_configurator/font_configurator.py index 22bd343..9c4c262 100644 --- a/font_configurator/font_configurator.py +++ b/font_configurator/font_configurator.py @@ -22,6 +22,10 @@ gi.require_version("Pango", "1.0") gi.require_version("PangoCairo", "1.0") +# Pillow's FreeType must be loaded before Pango: GI loads Pango with RTLD_GLOBAL, after which Pillow's +# bundled FreeType/HarfBuzz would bind to the system libraries and corrupt SignWriting rendering. +# See https://github.com/sign/pixel-renderer/issues/16 +from PIL import ImageFont # noqa: E402, F401, I001 from gi.repository import Pango, PangoCairo # type: ignore # noqa: E402, F401 FONTCONFIG_CACHE_DIR = pathlib.Path(user_cache_dir("font_configurator")) diff --git a/pixel_renderer/renderer.py b/pixel_renderer/renderer.py index 868fa39..25c7a84 100644 --- a/pixel_renderer/renderer.py +++ b/pixel_renderer/renderer.py @@ -11,6 +11,10 @@ gi.require_version("Pango", "1.0") gi.require_version("PangoCairo", "1.0") gi.require_foreign("cairo") +# Pillow's FreeType must be loaded before Pango: GI loads Pango with RTLD_GLOBAL, after which Pillow's +# bundled FreeType/HarfBuzz would bind to the system libraries and corrupt SignWriting rendering. +# See https://github.com/sign/pixel-renderer/issues/16 +from PIL import ImageFont # noqa: E402, F401, I001 from gi.repository import Pango, PangoCairo # noqa: E402 # Reusable measurement context - avoids creating new surface/context/layout per call diff --git a/tests/pixel_renderer/test_freetype_isolation.py b/tests/pixel_renderer/test_freetype_isolation.py new file mode 100644 index 0000000..b7d37e0 --- /dev/null +++ b/tests/pixel_renderer/test_freetype_isolation.py @@ -0,0 +1,17 @@ +import subprocess +import sys + +import pytest + +FREETYPE_VERSION = "from PIL import ImageFont, features; print(features.version('freetype2'))" + + +def freetype_version_after(setup: str) -> str: + code = f"{setup}\n{FREETYPE_VERSION}" + return subprocess.run([sys.executable, "-c", code], capture_output=True, text=True, check=True).stdout.strip() + + +@pytest.mark.parametrize("module", ["pixel_renderer", "pixel_renderer.renderer", "font_configurator.font_configurator"]) +def test_pillow_keeps_its_own_freetype(module): + """Pillow must not bind to the system FreeType loaded by Pango (issue #16).""" + assert freetype_version_after(f"import {module}") == freetype_version_after("")