mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-10 09:06:36 +00:00
fix(fonts): one BDF face per thread
The shared face cache now hands every loader (FontManager, element_style, DisplayManager, the harness) the same freetype.Face. FreeType does not allow two threads to use one face at once, since load_char rewrites its glyph slot, so key the cache by thread as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -101,9 +101,12 @@ def load_bdf_face(path: str, size_px: int) -> Tuple[Any, int]:
|
||||
font that was asked for at the size it can do. Callers that lay out by
|
||||
size need ``realised_px``, not the size they asked for.
|
||||
|
||||
Faces are cached and shared: a ``freetype.Face`` holds per-glyph state,
|
||||
so treat one as owned by the thread that draws. Raises if the file can't
|
||||
be loaded at either size.
|
||||
Faces are cached per thread. A ``freetype.Face`` holds per-glyph state
|
||||
(``load_char`` rewrites its glyph slot), and FreeType does not allow two
|
||||
threads to use one face at once, so the display thread and a plugin's
|
||||
update thread must never be handed the same object. Within a thread the
|
||||
face is shared by every caller. Raises if the file can't be loaded at
|
||||
either size.
|
||||
"""
|
||||
if freetype is None:
|
||||
raise RuntimeError("freetype-py is not installed; BDF fonts need it")
|
||||
@@ -111,7 +114,8 @@ def load_bdf_face(path: str, size_px: int) -> Tuple[Any, int]:
|
||||
abs_path = os.path.abspath(path)
|
||||
try:
|
||||
st = os.stat(abs_path)
|
||||
key = (abs_path, size_px, st.st_mtime_ns, st.st_size)
|
||||
key = (threading.get_ident(), abs_path, size_px,
|
||||
st.st_mtime_ns, st.st_size)
|
||||
except OSError:
|
||||
key = None # let freetype raise its own error below
|
||||
|
||||
|
||||
@@ -263,6 +263,20 @@ def test_cache_is_bounded(monkeypatch):
|
||||
bdf_font.clear_face_cache()
|
||||
|
||||
|
||||
def test_each_thread_gets_its_own_face():
|
||||
# FreeType forbids two threads using one face at once: load_char rewrites
|
||||
# the face's glyph slot. Within a thread the face is shared.
|
||||
import threading
|
||||
path = str(FONTS_DIR / "5x7.bdf")
|
||||
here, _ = load_bdf_face(path, 7)
|
||||
assert load_bdf_face(path, 7)[0] is here
|
||||
other = []
|
||||
worker = threading.Thread(target=lambda: other.append(load_bdf_face(path, 7)[0]))
|
||||
worker.start()
|
||||
worker.join()
|
||||
assert other and other[0] is not here
|
||||
|
||||
|
||||
def test_native_size_prefers_pixel_size_over_point_size():
|
||||
# 6x13.bdf is defined at 75dpi: SIZE says 12 (points), PIXEL_SIZE 13.
|
||||
assert read_bdf_native_size(str(FONTS_DIR / "6x13.bdf")) == 13
|
||||
|
||||
Reference in New Issue
Block a user