These docs describe features that exist as documented in the doc but either never wired up or regressed when v3 shipped. Each gets a clear status banner so plugin authors don't waste time chasing features that don't actually work. FONT_MANAGER.md - The "For Plugin Developers / Plugin Font Registration" section documents adding a "fonts" block to manifest.json that gets registered via FontManager.register_plugin_fonts(). The method exists at src/font_manager.py:150 but is **never called from anywhere** in the codebase (verified: zero callers). A plugin shipping a manifest "fonts" block has its fonts silently ignored. Added a status warning and a note about how to actually ship plugin fonts (regular files in the plugin dir, loaded directly). PLUGIN_IMPLEMENTATION_SUMMARY.md - Added a top-level status banner. - Architecture diagram referenced src/plugin_system/registry_manager.py (which doesn't exist) and listed plugins/ as the install location. Replaced with the real file list (plugin_loader, schema_manager, health_monitor, operation_queue, state_manager) and pointed at plugin-repos/ as the default install location. - "Dependency Management: Virtual Environments" — verified there's no per-plugin venv. Removed the bullet and added a note that plugin Python deps install into the system Python environment, with no conflict resolution. - "Permission System: File Access Control / Network Access / Resource Limits / CPU and memory constraints" — none of these exist. There's a resource_monitor.py and health_monitor.py for metrics/warnings, but no hard caps or sandboxing. Replaced the section with what's actually implemented and a clear note that plugins run in the same process with full file/network access. PLUGIN_CUSTOM_ICONS.md and PLUGIN_CUSTOM_ICONS_FEATURE.md - The custom-icon feature was implemented in the v2 web interface via a getPluginIcon() helper in templates/index_v2.html that read the manifest "icon" field. When the v3 web interface was built, that helper wasn't ported. Verified in web_interface/templates/v3/base.html:515 and :774, plugin tab icons are hardcoded to `fas fa-puzzle-piece`. The "icon" field in plugin manifests is currently silently ignored (verified with grep across web_interface/ and src/plugin_system/ — zero non-action- related reads of plugin.icon or manifest.icon). - Added a status banner to both docs noting the regression so plugin authors don't think their custom icons are broken in their own plugin code. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
11 KiB
FontManager Usage Guide
Overview
The enhanced FontManager provides comprehensive font management for the LEDMatrix application with support for:
- Manager font registration and detection
- Plugin font management
- Manual font overrides via web interface
- Performance monitoring and caching
- Dynamic font discovery
Architecture
Manager-Centric Design
Managers define their own fonts, but the FontManager:
- Loads and caches fonts for performance
- Detects font usage for visibility
- Allows manual overrides when needed
- Supports plugin fonts with namespacing
Font Resolution Flow
Manager requests font → Check manual overrides → Apply manager choice → Cache & return
For Manager Developers
Basic Font Usage
from src.font_manager import FontManager
class MyManager:
def __init__(self, config, display_manager, cache_manager):
self.font_manager = display_manager.font_manager # Access shared FontManager
self.manager_id = "my_manager"
def display(self):
# Define your font choices
element_key = "my_manager.title"
font_family = "press_start"
font_size_px = 10
color = (255, 255, 255) # RGB white
# Register your font choice (for detection and future overrides)
self.font_manager.register_manager_font(
manager_id=self.manager_id,
element_key=element_key,
family=font_family,
size_px=font_size_px,
color=color
)
# Get the font (checks for manual overrides automatically)
font = self.font_manager.resolve_font(
element_key=element_key,
family=font_family,
size_px=font_size_px
)
# Use the font for rendering
self.display_manager.draw_text(
"Hello World",
x=10, y=10,
color=color,
font=font
)
Advanced Font Usage
class AdvancedManager:
def __init__(self, config, display_manager, cache_manager):
self.font_manager = display_manager.font_manager
self.manager_id = "advanced_manager"
# Define your font specifications
self.font_specs = {
"title": {"family": "press_start", "size_px": 12, "color": (255, 255, 0)},
"body": {"family": "four_by_six", "size_px": 8, "color": (255, 255, 255)},
"footer": {"family": "five_by_seven", "size_px": 7, "color": (128, 128, 128)}
}
# Register all font specs
for element_type, spec in self.font_specs.items():
element_key = f"{self.manager_id}.{element_type}"
self.font_manager.register_manager_font(
manager_id=self.manager_id,
element_key=element_key,
family=spec["family"],
size_px=spec["size_px"],
color=spec["color"]
)
def get_font(self, element_type: str):
"""Helper method to get fonts with override support."""
spec = self.font_specs[element_type]
element_key = f"{self.manager_id}.{element_type}"
return self.font_manager.resolve_font(
element_key=element_key,
family=spec["family"],
size_px=spec["size_px"]
)
def display(self):
# Get fonts (automatically checks for overrides)
title_font = self.get_font("title")
body_font = self.get_font("body")
footer_font = self.get_font("footer")
# Render with fonts
self.display_manager.draw_text("Title", font=title_font, color=self.font_specs["title"]["color"])
self.display_manager.draw_text("Body Text", font=body_font, color=self.font_specs["body"]["color"])
self.display_manager.draw_text("Footer", font=footer_font, color=self.font_specs["footer"]["color"])
Using Size Tokens
# Get available size tokens
tokens = self.font_manager.get_size_tokens()
# Returns: {'xs': 6, 'sm': 8, 'md': 10, 'lg': 12, 'xl': 14, 'xxl': 16}
# Use token to get size
size_px = tokens.get('md', 10) # 10px
# Then use in font resolution
font = self.font_manager.resolve_font(
element_key="my_manager.text",
family="press_start",
size_px=size_px
)
For Plugin Developers
⚠️ Status: the plugin-font registration described below is implemented in
src/font_manager.py:150(register_plugin_fonts()) but is not currently wired into the plugin loader. Adding a"fonts"block to your plugin'smanifest.jsonwill silently have no effect — the FontManager method exists but nothing calls it.Until that's connected, plugin authors should ship custom fonts as regular files inside the plugin directory (e.g.,
assets/myfont.ttf) and reference them by relative path from the plugin'smanager.pyviadisplay_manager.font_manager.resolve_font(...)or by loading with PIL directly. The user-facing font override system in the Fonts tab still works for any element that's been registered viaregister_manager_font().
Plugin Font Registration (planned)
In your plugin's manifest.json:
{
"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": {
"description": "Downloaded font",
"checksum": "sha256:abc123..."
}
}
]
}
}
Using Plugin Fonts
class PluginManager:
def __init__(self, config, display_manager, cache_manager, plugin_id):
self.font_manager = display_manager.font_manager
self.plugin_id = plugin_id
def display(self):
# Use plugin font (automatically namespaced)
font = self.font_manager.resolve_font(
element_key=f"{self.plugin_id}.text",
family="custom_font", # Will be resolved as "my-plugin::custom_font"
size_px=10,
plugin_id=self.plugin_id
)
self.display_manager.draw_text("Plugin Text", font=font)
Manual Font Overrides
Users can override any font through the web interface:
- Navigate to Fonts tab
- View Detected Manager Fonts to see what's currently in use
- In Element Overrides section:
- Select the element (e.g., "nfl.live.score")
- Choose a different font family
- Choose a different size
- Click Add Override
Overrides are stored in config/font_overrides.json and persist across restarts.
Programmatic Overrides
# Set override
font_manager.set_override(
element_key="nfl.live.score",
family="four_by_six",
size_px=8
)
# Remove override
font_manager.remove_override("nfl.live.score")
# Get all overrides
overrides = font_manager.get_overrides()
Font Discovery
Available Fonts
The FontManager automatically scans assets/fonts/ for TTF and BDF fonts:
# Get all available fonts
fonts = font_manager.get_available_fonts()
# Returns: {'press_start': 'assets/fonts/PressStart2P-Regular.ttf', ...}
# Check if font exists
if "my_font" in fonts:
font = font_manager.get_font("my_font", 10)
Adding Custom Fonts
Place font files in assets/fonts/ directory:
- Supported formats:
.ttf,.bdf - Font family name is derived from filename (without extension)
- Will be automatically discovered on next initialization
Performance Monitoring
# Get performance stats
stats = font_manager.get_performance_stats()
print(f"Cache hit rate: {stats['cache_hit_rate']*100:.1f}%")
print(f"Total fonts cached: {stats['total_fonts_cached']}")
print(f"Failed loads: {stats['failed_loads']}")
print(f"Manager fonts: {stats['manager_fonts']}")
print(f"Plugin fonts: {stats['plugin_fonts']}")
Text Measurement
# Measure text dimensions
width, height, baseline = font_manager.measure_text("Hello", font)
# Get font height
font_height = font_manager.get_font_height(font)
Best Practices
For Managers
- Register all fonts you use for visibility
- Use consistent element keys (e.g.,
{manager_id}.{element_type}) - Cache font references if using same font multiple times
- Use
resolve_font()notget_font()directly to support overrides - Define sensible defaults that work well on LED matrix
For Plugins
- Use plugin-relative paths (
plugin://fonts/...) - Include font metadata (license, description)
- Provide fallback fonts if custom fonts fail to load
- Test with different display sizes
General
- BDF fonts are often better for small sizes on LED matrices
- TTF fonts work well for larger sizes
- Monospace fonts are easier to align
- Test on actual hardware - what looks good on screen may not work on LED matrix
Migration from Old System
Old Way (Direct Font Loading)
self.font = ImageFont.truetype("assets/fonts/PressStart2P-Regular.ttf", 8)
New Way (FontManager)
element_key = f"{self.manager_id}.text"
self.font_manager.register_manager_font(
manager_id=self.manager_id,
element_key=element_key,
family="pressstart2p-regular",
size_px=8
)
self.font = self.font_manager.resolve_font(
element_key=element_key,
family="pressstart2p-regular",
size_px=8
)
Troubleshooting
Font Not Found
- Check font file exists in
assets/fonts/ - Verify font family name matches filename (without extension, lowercase)
- Check logs for font discovery errors
Override Not Working
- Verify element key matches exactly what manager registered
- Check
config/font_overrides.jsonfor correct syntax - Restart application to ensure overrides are loaded
Performance Issues
- Check cache hit rate in performance stats
- Reduce number of unique font/size combinations
- Clear cache if it grows too large:
font_manager.clear_cache()
Plugin Fonts Not Loading
- Verify plugin manifest syntax
- Check plugin directory structure
- Review logs for download/registration errors
- Ensure font URLs are accessible
API Reference
FontManager Methods
register_manager_font(manager_id, element_key, family, size_px, color=None)- Register font usageresolve_font(element_key, family, size_px, plugin_id=None)- Get font with override supportget_font(family, size_px)- Get font directly (bypasses overrides)measure_text(text, font)- Measure text dimensionsget_font_height(font)- Get font heightset_override(element_key, family=None, size_px=None)- Set manual overrideremove_override(element_key)- Remove overrideget_overrides()- Get all overridesget_detected_fonts()- Get all detected font usageget_manager_fonts(manager_id=None)- Get fonts by managerget_available_fonts()- Get font catalogget_size_tokens()- Get size token definitionsget_performance_stats()- Get performance metricsclear_cache()- Clear font cacheregister_plugin_fonts(plugin_id, font_manifest)- Register plugin fontsunregister_plugin_fonts(plugin_id)- Unregister plugin fonts
Example: Complete Manager Implementation
See test/font_manager_example.py for a complete working example.