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
|
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.
|
size need ``realised_px``, not the size they asked for.
|
||||||
|
|
||||||
Faces are cached and shared: a ``freetype.Face`` holds per-glyph state,
|
Faces are cached per thread. A ``freetype.Face`` holds per-glyph state
|
||||||
so treat one as owned by the thread that draws. Raises if the file can't
|
(``load_char`` rewrites its glyph slot), and FreeType does not allow two
|
||||||
be loaded at either size.
|
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:
|
if freetype is None:
|
||||||
raise RuntimeError("freetype-py is not installed; BDF fonts need it")
|
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)
|
abs_path = os.path.abspath(path)
|
||||||
try:
|
try:
|
||||||
st = os.stat(abs_path)
|
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:
|
except OSError:
|
||||||
key = None # let freetype raise its own error below
|
key = None # let freetype raise its own error below
|
||||||
|
|
||||||
|
|||||||
@@ -263,6 +263,20 @@ def test_cache_is_bounded(monkeypatch):
|
|||||||
bdf_font.clear_face_cache()
|
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():
|
def test_native_size_prefers_pixel_size_over_point_size():
|
||||||
# 6x13.bdf is defined at 75dpi: SIZE says 12 (points), PIXEL_SIZE 13.
|
# 6x13.bdf is defined at 75dpi: SIZE says 12 (points), PIXEL_SIZE 13.
|
||||||
assert read_bdf_native_size(str(FONTS_DIR / "6x13.bdf")) == 13
|
assert read_bdf_native_size(str(FONTS_DIR / "6x13.bdf")) == 13
|
||||||
|
|||||||
Reference in New Issue
Block a user