FontManager
FontManager is a singleton framework for loading, caching, and composing LVGL fonts — including built-in bitmap fonts, TrueType fonts, and emoji image fonts. It uses LVGL's imgfont fallback mechanism to render 32×32 emoji PNGs inline with text, with nearest-neighbour scaling to match any font size.
Overview
FontManager centralizes all font concerns in a single class:
- Unified API - One call (
getFont) to get any font, with or without emoji support - Emoji Compositing - Transparently layers 32×32 emoji PNGs via LVGL's imgfont fallback, with nearest-neighbour scaling to match any font size
- Lazy Caching - Fonts and scaled image descriptors are cached on first use; no redundant work on subsequent calls
- Android-Inspired - Follows the same singleton/class-method pattern as other MicroPythonOS frameworks
Quick Start
from mpos import FontManager
# Get a built-in Montserrat font at 16px (emoji disabled by default)
font = FontManager.getFont(size=16, family="Montserrat")
# Get the same font with emoji support
emoji_font = FontManager.getFont(size=16, family="Montserrat", emoji=True)
# Load a TrueType font from a file
ttf_font = FontManager.getFont(size=42, ttf="M:apps/com.myapp/assets/MyFont.ttf")
# List all available built-in fonts (pass emojis=True for composed fonts)
for info in FontManager.listFonts():
print(info["name"], info["size"])
# Get all available emoji codepoints (base codepoints only)
for cp in FontManager.getEmojiCodepoints():
print(hex(cp))
# Get all available emoji strings (full sequences, including flag pairs)
for s in FontManager.getEmojiStrings():
print(s)
Architecture
FontManager is implemented as a singleton using class variables and class methods. No instance creation is needed.
Font composition
When emoji=True, getFont() wraps the requested base font in an LVGL imgfont that renders emoji as scaled PNG images. The imgfont's fallback is set to the base font, so LVGL automatically falls through to the base font for any codepoint that is not an emoji.
getFont(size=16, emoji=True)
└── base_font = font_montserrat_16 (builtin bitmap)
└── emoji_font = lv.imgfont_create(...) (PNG-based imgfont)
└── emoji_font.fallback = base_font
└── returns emoji_font
Emoji size tiers
Emoji PNGs are stored in builtin/res/emojis/:
| Directory | Used for |
|---|---|
32x32/ |
All fonts — emoji are rendered at 32×32 px and nearest-neighbour scaled up or down by LVGL as needed |
_imgfont_path_cb receives the rendering font's pixel height and returns the 32×32 emoji source. LVGL's software renderer performs nearest-neighbour scaling to fit the target font size.
Caching layers
| Cache | Key | Value |
|---|---|---|
_composed_font_cache |
(font_id, emoji_size) |
Composed imgfont object |
_ttf_font_cache |
(path, size) |
lv.tiny_ttf_create_file result |
_emoji_map |
(none) | {codepoint: src_path} dict |
_emoji_strings |
(none) | Sorted list of complete emoji strings |
_imgfont_scaled_src_cache |
(src, target_height) |
Scaled lv.image_dsc_t or original src |
_imgfont_source_size_cache |
src |
(width, height) tuple |
_imgfont_empty_src_cache |
target_height |
1×h transparent lv.image_dsc_t |
API Reference
getFont(size=None, ttf=None, family=None, emoji=False)
Return a font object suitable for use with set_style_text_font().
Parameters:
size(int, optional): Target pixel size. Snapped to the nearest available builtin size. Defaults to 12.ttf(str, optional): Path to a.ttffile (e.g."M:apps/myapp/assets/MyFont.ttf"). When provided,familyis ignored.family(str, optional): Font family name —"Montserrat"(default) or"Unscii".emoji(bool, optional): IfTrue, the returned font transparently renders emoji via a PNG imgfont fallback. Defaults toFalse. PassTrueto enable inline emoji rendering (adds a small rendering cost per emoji glyph).
Returns: lv.font_t — the requested font, possibly wrapped with emoji support.
If the requested family is not available, FontManager falls back to Montserrat, then to the first available built-in font, and finally to lv.font_montserrat_12.
Example:
from mpos import FontManager
import lvgl as lv
font = FontManager.getFont(size=24, family="Montserrat", emoji=True)
label = lv.label(screen)
label.set_style_text_font(font, lv.PART.MAIN)
label.set_text("Hello ❤️ 😀")
listFonts(emojis=False)
Return a list of all available built-in fonts. By default returns raw base fonts; pass emojis=True to wrap each font with emoji composition.
Returns: list of dicts, each with keys:
"name"(str): Human-readable name, e.g."Montserrat 16""family"(str): Family name, e.g."Montserrat""size"(int): Nominal point size"font"(lv.font_t): Composed font (with emoji)"base_font"(lv.font_t): Raw base font (without emoji)
Example:
from mpos import FontManager
import lvgl as lv
for info in FontManager.listFonts(emojis=True):
label = lv.label(screen)
label.set_style_text_font(info["font"], lv.PART.MAIN)
label.set_text(info["name"] + ": ABC 😀 ❤️")
getEmojiCodepoints()
Return a sorted list of the base emoji codepoints available in the emoji map.
For multi-codepoint emoji such as flag sequences (e.g. "🇸🇻"), only the first codepoint is returned. Use getEmojiStrings() when you need complete, renderable emoji sequences.
Returns: list of int
Example:
from mpos import FontManager
for cp in FontManager.getEmojiCodepoints():
print(hex(cp), chr(cp))
getEmojiStrings()
Return a sorted list of all available, complete emoji strings.
Unlike getEmojiCodepoints(), this returns full sequences: flag emoji include both regional indicators, and emoji with variation selectors keep their trailing selector. This is the preferred API for building a visual list of every supported emoji.
Returns: list of str
Example:
from mpos import FontManager
for s in FontManager.getEmojiStrings():
print(s)
normalizeEmojiText(text)
Strip Unicode variation selectors (U+FE0E text selector, U+FE0F emoji selector) from a string. Useful before storing or comparing text that may have been pasted from a source that appends these codepoints.
Parameters:
text(str): Input string
Returns: str — cleaned string
Example:
from mpos import FontManager
clean = FontManager.normalizeEmojiText("❤️") # removes U+FE0F after ❤
Emoji Assets
To keep firmware image size small, the OS bundles ~50 of the most frequently-used emoji codepoints. Individual apps can bundle additional emoji PNGs in their own assets directory. Emoji PNGs are stored in internal_filesystem/builtin/res/emojis/ and are included in the firmware image at /builtin/res/emojis/ on the device filesystem:
builtin/res/emojis/
└── 32x32/ # Pre-rendered at 32×32 px
├── 1F600.png
├── 1F3CE-FE0F.png
├── 1F1F8-1F1FB.png
└── ...
Files are named by their Unicode codepoint(s) in uppercase hex, with multiple codepoints joined by - (e.g. 1F600.png for 😀, 1F3CE-FE0F.png for 🏎️, 1F1F8-1F1FB.png for 🇸🇻). FontManager scans the directory at runtime and builds a {codepoint: path} map.
Missing emoji fallback
If the exact PNG for an emoji is not bundled, FontManager tries visually similar emoji before giving up. For example, several kissing-face variants can substitute for each other. This keeps text readable even when only a small emoji set is included in the firmware.
Adding new emoji
- Add a PNG named
<CODEPOINT_HEX>.pngto32x32/:
cp original.png 32x32/CODEPOINT.png
- The new emoji will be picked up automatically on next boot — no code changes needed.
Emoji reference
For a canonical frequency-ordered listing of all available emoji codepoints, see emoji_frequency_canonical.html.
File Structure
internal_filesystem/
├── lib/mpos/ui/
│ └── font_manager.py # FontManager class
└── builtin/res/emojis/
└── 32x32/ # 32×32 px emoji PNGs
Related Frameworks
- AppearanceManager - Theme colors and light/dark mode
- DisplayMetrics - Display width, height, DPI