* chore(deprecation): remove the 35 APIs deprecated for 3.8.0 The usage scan (docs/DEPRECATIONS_3.8.md, regenerated 2026-10-01 and committed here) finds no call or override of any of them in the 46 monorepo plugins or the 8 third-party plugins plugins.json lists; the only core callers were other deprecated methods removed alongside. - CacheManager: 13 methods, plus the private helpers only has_data_changed used (_has_*_changed, _is_market_open). - DisplayManager: 7 methods, plus WEATHER_COLORS and the private _draw_sun/_cloud/_rain/_snow/_storm helpers only the icon methods used. - FontManager: 14 methods, plus size_tokens, _save_overrides and _clear_plugin_font_cache. font_overrides and _load_overrides stay: resolve_font() still applies config/font_overrides.json. performance_stats stays: get_font() keeps it and tests read it. - PluginManager.get_enabled_plugins. test_deprecation.py pins only the two 3.9.0 markers now; the scanner tests run against a stand-in core instead of the real markers. The memory-tier tests read stats through log_memory_cache_stats() and the component, and the test of the removed _clear_plugin_font_cache goes. Docs drop the removed methods' reference entries; the Deprecated APIs table becomes "Removed in 3.8.0". CHANGELOG gains a Removed section. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(deprecation): drop the test harness's copies of the removed icon methods VisualTestDisplayManager still drew weather icons that DisplayManager no longer has, so a plugin's visual tests could pass on calls that raise AttributeError on the real display. Its draw_sun/draw_cloud/draw_rain/ draw_snow/draw_weather_icon/draw_text_with_icons, WEATHER_COLORS and the private helpers go, with the tests that exercised them. The CHANGELOG's Deprecations entries no longer say nothing is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
8.3 KiB
FontManager Usage Guide
Picking a size automatically: if you want the largest font that fits a given area rather than a fixed size, use the adaptive layout system's font ladders, which resolve through this FontManager.
BasePluginsubclasses get this asself.layout.fit_text(...); other code can build aLayoutContext(width, height, font_manager)directly — see ADAPTIVE_LAYOUT.md.
Overview
src/font_manager.py loads and caches the TTF and
BDF fonts in assets/fonts/, registers fonts that plugins ship, and records
which plugin uses which font so the web UI can show it.
Several methods were removed in LEDMatrix 3.8.0 after a release of deprecation warnings; Removed methods below lists them with what to use instead.
Getting the FontManager
There is one shared FontManager per display process. The display controller
creates it and hands it to the PluginManager, so a plugin reaches it
through its plugin_manager:
class MyPlugin(BasePlugin):
def __init__(self, plugin_id, config, display_manager, cache_manager, plugin_manager):
super().__init__(plugin_id, config, display_manager, cache_manager, plugin_manager)
self.font_manager = self._get_font_manager()
BasePlugin._get_font_manager() returns plugin_manager.font_manager, or a
standalone FontManager when none is available (test harnesses, mocks).
DisplayManager has no font_manager attribute —
display_manager.font_manager raises AttributeError.
Resolving a font
element_key = f"{self.plugin_id}.title"
# Register the choice so the web UI's Fonts tab can list it.
self.font_manager.register_manager_font(
manager_id=self.plugin_id,
element_key=element_key,
family="press_start",
size_px=10,
color=(255, 255, 255),
)
font = self.font_manager.resolve_font(
element_key=element_key,
family="press_start",
size_px=10,
)
self.display_manager.draw_text("Hello", x=10, y=10, font=font)
resolve_font() applies any entry for element_key in
config/font_overrides.json, maps a plugin-local family to its namespaced
name when plugin_id is passed, and then calls get_font(family, size_px).
On error it returns a fallback font rather than raising.
get_font(family, size_px) looks the family up in font_catalog and loads
it (cached per family and size).
Font families
At start-up the FontManager scans assets/fonts/ for .ttf and .bdf
files. Each becomes a family named after the file, lower-cased and without
the extension (PressStart2P-Regular.ttf → pressstart2p-regular). Four
aliases are added on top:
| Alias | File |
|---|---|
press_start |
assets/fonts/PressStart2P-Regular.ttf |
four_by_six |
assets/fonts/4x6-font.ttf |
five_by_seven |
assets/fonts/5x7.bdf |
tom_thumb |
assets/fonts/tom-thumb.bdf |
Read the catalog directly: font_manager.font_catalog is a dict of family
name to file path. Files added later are picked up on the next start of the
display service.
Plugin fonts
Plugins that ship their own fonts declare them in a "fonts" block in
manifest.json. The plugin manager calls
FontManager.register_plugin_fonts() during plugin load. plugin://…
sources are resolved relative to the plugin's install directory.
{
"id": "my-plugin",
"name": "My Plugin",
"fonts": {
"fonts": [
{
"family": "custom_font",
"source": "plugin://fonts/custom.ttf",
"metadata": {"description": "Custom plugin font", "license": "MIT"}
},
{
"family": "web_font",
"source": "https://example.com/fonts/font.ttf",
"metadata": {"checksum": "sha256:abc123..."}
}
]
}
}
Registered families are namespaced as <plugin_id>::<family>. Pass
plugin_id to resolve_font() to use the short name:
font = self.font_manager.resolve_font(
element_key=f"{self.plugin_id}.text",
family="custom_font", # resolved as "my-plugin::custom_font"
size_px=10,
plugin_id=self.plugin_id,
)
Overrides
resolve_font() still honours config/font_overrides.json (a map of
element key to family and/or size_px), which is read once at start-up.
The methods that edited it — set_override(), remove_override(),
get_overrides() — were removed in 3.8.0, and there is no web UI or REST endpoint
for overrides (the override editor and /api/v3/fonts/overrides were
removed). To let users choose a font, add a field to your plugin's config
schema.
Font usage in the web UI
The web UI's Fonts tab lists, uploads, previews and deletes the font
files in assets/fonts/. The web interface runs in its own process and has
no FontManager, so the display service publishes which plugin uses which
font (src/font_usage.py), and the tab's Used by
column reads it:
- Source:
register_manager_font()registrations of the loaded plugins.get_font()andresolve_font()do not know the calling plugin and are not counted, and neither is a plugin that opens a font file directly with PIL — register the fonts your plugin draws with if you want them listed. - Names: a family, alias or path is resolved through
font_catalogto the file it loads and reported under that file's name without extension (PressStart2P-Regular,4x6-font,5x7,tom-thumb), which is how the Fonts tab keys its rows. Fonts outsideassets/fonts/(a plugin's ownplugin_id::familyfonts) and families that resolve to nothing are left out. - When: a daemon thread started once plugins have loaded checks every
10 seconds and writes the
font_usage_snapshotcache key only when the usage changed (and once a day, so the cache's cleanup never expires it). Unloading a plugin drops its registrations (forget_manager_fonts). - Unknown: until the display service has published, the column reads
"unknown" and
GET /api/v3/fonts/catalogreturnsused_by: null. - The tab warns before deleting a font that a loaded plugin registered.
Text measurement
width, height, baseline = font_manager.measure_text("Hello", font)
font_height = font_manager.get_font_height(font)
Tips
- BDF fonts usually look better than TTF at small sizes on LED panels.
- Use
{plugin_id}.{element}element keys. - Register the fonts you draw with, so the Fonts tab can warn before one is deleted.
- Replace direct
ImageFont.truetype("assets/fonts/...", 8)calls withresolve_font(): it caches, resolves paths against the install directory, and handles BDF files.
Troubleshooting
Font not found
- Check the file exists in
assets/fonts/. - The family name is the filename without extension, lower-cased.
- Check the display service log for font discovery errors.
Plugin fonts not loading
- Check the manifest's
"fonts"block. - Check the log for download or registration errors, and that font URLs are reachable.
API reference
Current methods:
| Method | Purpose |
|---|---|
register_manager_font(manager_id, element_key, family, size_px, color=None) |
Record a font choice (feeds the Fonts tab) |
forget_manager_fonts(manager_id) |
Drop a manager's registrations (core calls it when a plugin unloads) |
resolve_font(element_key, family, size_px, plugin_id=None) |
Get a font, applying overrides and plugin namespacing |
get_font(family, size_px) |
Get a font directly |
get_native_bdf_size(family) |
Native pixel size of a BDF family, or None |
measure_text(text, font) |
(width, height, baseline) |
get_font_height(font) |
Line height |
register_plugin_fonts(plugin_id, font_manifest) |
Register a plugin's fonts (core calls it at load) |
clear_cache() |
Drop cached fonts and metrics |
font_catalog (attribute) |
Family name → file path |
Removed methods
Removed in 3.8.0, after logging a deprecation warning on first call since 3.5.0.
| Method | Use instead |
|---|---|
get_available_fonts(), get_font_catalog() |
read font_catalog |
get_size_tokens() |
pass a pixel size |
get_performance_stats() |
— |
set_override(), remove_override(), get_overrides() |
a font field in your plugin's config schema |
get_manager_fonts(), get_detected_fonts() |
— |
get_plugin_fonts(), unregister_plugin_fonts() |
— |
add_font(), remove_font(), validate_font() |
the web UI's Fonts tab |