Skip to content

fix: load Pillow's FreeType before Pango to stop symbol clash (#16) - #17

Merged
AmitMY merged 2 commits into
mainfrom
fix/pillow-freetype-symbol-clash
Oct 7, 2026
Merged

AmitMY merged 2 commits into
mainfrom
fix/pillow-freetype-symbol-clash

Conversation

@AmitMY

@AmitMY AmitMY commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #16.

Root cause

pixel_renderer/__init__ → processor → font_configurator.font_configurator imports gi.repository.Pango at module level. GI opens Pango (and with it the system libfreetype.so.6 / libharfbuzz.so.0) with RTLD_GLOBAL. renderer imports Pillow's ImageFont only afterwards, so when _imagingft loads, the dynamic linker resolves its FT_* / hb_* symbols against the system libraries, which are already in the global scope. Pillow's bundled FreeType even resolves its own internal exported calls (e.g. TT_RunIns) there.

So Pillow's FreeType 2.14 faces end up handled by system FreeType 2.13 functions. That's undefined behavior, and on CI it appears as a garbage getbbox and a SignWriting image hundreds of thousands of pixels wide.

Evidence (Ubuntu 24.04 container matching CI, LD_DEBUG=bindings):

features.version("freetype2") md5 of getbbox over all 62,592 SignWriting symbols
Pillow alone 2.14.3 91da38dd…
import pixel_renderer first (main) 2.13.2 a92a822b… (different bboxes)
import pixel_renderer first (this PR) 2.14.3 91da38dd…

Fix

Import PIL.ImageFont immediately before gi.repository in both modules that load Pango: font_configurator/font_configurator.py and pixel_renderer/renderer.py. (The first version only guarded pixel_renderer/__init__.py. CI still failed because on xdist worker gw1 the tests/font_configurator/* modules import font_configurator directly, before pixel_renderer.)

Test

tests/pixel_renderer/test_freetype_isolation.py runs subprocesses and checks that Pillow reports the same FreeType version after importing pixel_renderer, pixel_renderer.renderer or font_configurator.font_configurator as it does alone. It fails on main and passes here. The full suite passes (458).

Note: code that imports gi.repository.Pango itself before pixel_renderer will still hit this. Import order is the only lever available from Python.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Fy1wuWdqcKuebgj3St8dME


Note

Low Risk
Import-order-only change with no API or runtime behavior change beyond fixing incorrect FreeType binding; residual risk if callers import Pango before these modules.

Overview
Fixes SignWriting rendering corruption on CI caused by a dynamic-linker clash: GObject Introspection loads Pango with RTLD_GLOBAL, so if Pango loads first, Pillow’s bundled FreeType/HarfBuzz can bind to the system libraries instead of its own, breaking getbbox and producing huge bogus images.

The PR forces PIL.ImageFont to import before gi.repository Pango in font_configurator.font_configurator and pixel_renderer.renderer, with comments pointing to issue #16.

A new regression test test_freetype_isolation.py spawns subprocesses and asserts Pillow still reports the same FreeType version after importing pixel_renderer, pixel_renderer.renderer, or font_configurator.font_configurator.

Reviewed by Cursor Bugbot for commit 401210b. Bugbot is set up for automated code reviews on this repo. Configure here.

AmitMY and others added 2 commits October 7, 2026 15:00
font_configurator imports gi.repository.Pango at module level, which GI
loads with RTLD_GLOBAL. Pillow's _imagingft was loaded afterwards, so its
FT_*/hb_* calls (and even its bundled FreeType's internal calls) bound to
the system FreeType/HarfBuzz instead of Pillow's bundled copies. Pillow's
FreeType 2.14 faces were then driven by FreeType 2.13 functions - undefined
behavior that showed up as garbage SignWriting glyph bboxes on CI.

Importing PIL.ImageFont first makes Pillow bind to its own libraries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fy1wuWdqcKuebgj3St8dME
CI still failed when tests imported font_configurator directly (before
pixel_renderer), loading Pango ahead of Pillow. Import PIL.ImageFont right
before gi.repository in both modules that load Pango.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fy1wuWdqcKuebgj3St8dME
@AmitMY
AmitMY merged commit f5df32b into main Oct 7, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Flaky CI: test_signwriting_renders_correctly intermittently returns a garbage-width image

1 participant