Skip to content

Fast composed-font emoji rendering via lv_imgfont_set_range#148

Merged
ThomasFarstrike merged 1 commit into
MicroPythonOS:mainfrom
bitcoin3us:fix/imgfont-fast-emoji-render
May 31, 2026
Merged

Fast composed-font emoji rendering via lv_imgfont_set_range#148
ThomasFarstrike merged 1 commit into
MicroPythonOS:mainfrom
bitcoin3us:fix/imgfont-fast-emoji-render

Conversation

@bitcoin3us
Copy link
Copy Markdown
Contributor

Summary

Cuts the per-glyph cost of FontManager.getFont(emoji=True) labels from a C→Python round-trip per glyph to two int compares in C, for any codepoint that isn't in the emoji map. User-visible result: noticeably smoother scrolling of any label that uses an emoji-fallback font (Lightning Piggy transactions row, Files list, etc.) — even though most rows have no emoji in them.

How

Three pieces:

  • internal_filesystem/lib/mpos/ui/font_manager.py_create_emoji_font(size) now calls lv.imgfont_set_range(font, cp_min, cp_max, 0xE000, 0xF8FF) after lv.imgfont_create. cp_min/cp_max are computed dynamically from the loaded _emoji_maps keys, so the filter self-maintains as the emoji set grows — no hardcoded magic number. Guarded by try/except AttributeError so older lvgl_micropython pins still work (just without the speedup).
  • scripts/build_mpos.sh — applies lvgl_micropython/imgfont_set_range.patch against lib/lvgl during the build. Gated by a -f existence check, so older lvgl_micropython pins (no patch file present) still build cleanly.
  • lvgl_micropython submodule — bumped 916ec6be00d9d9. Picks up the new imgfont_set_range.patch plus a small drive-by brew-config parser fix in builder/macOS.py that was breaking macOS builds entirely.

Dependency

This PR depends on MicroPythonOS/lvgl_micropython#3. The submodule pointer here references e00d9d9, which currently lives only on the bitcoin3us/lvgl_micropython fork. Once #3 merges into MicroPythonOS/lvgl_micropython, this PR's submodule pointer will resolve from upstream and this can land. .gitmodules is intentionally untouched (still points at the canonical URL).

Test plan

  • macOS desktop build (build_mpos.sh macOS) — clean, lv.imgfont_set_range exposed in the LVGL binding
  • ESP32-S3 firmware build (build_mpos.sh esp32s3) — clean build, patch applies, all expected freezing happens
  • Flash + boot on Waveshare ESP32-S3-Touch-LCD-2 — boots cleanly, lv.imgfont_set_range callable, FontManager wiring works
  • FontManager bounds calculation on device — with the 400-emoji set loaded, bounds resolve to (0x203C, 0x1F9E2) (exact min/max across the loaded codepoints, as designed)
  • Backward-compat fallback — try/except AttributeError path verified mentally (covered by gating in build_mpos.sh)
  • User test — scrolling the Lightning Piggy transactions list feels noticeably smoother than the pre-patch firmware

Cuts the per-glyph cost of FontManager.getFont(emoji=True) labels
from "C->Python round-trip per glyph" to "two int compares in C"
for any codepoint that isn't in the emoji map.

Why this matters: a Lightning Piggy transactions row, the Files
list, basically any label using a composed (emoji-fallback) font
is overwhelmingly ASCII. Today every glyph crosses into the
MicroPython _imgfont_path_cb just to return None, then LVGL
falls back to the base Montserrat. With LVGL's imgfont gaining a
codepoint accept/exclude range, codepoints outside the emoji
range are rejected in C and skip the Python callback entirely;
LVGL falls back to the base font at C speed. Emoji codepoints
still hit the Python path (unchanged).

Three pieces:

* internal_filesystem/lib/mpos/ui/font_manager.py
  - _create_emoji_font(size) now calls
    lv.imgfont_set_range(font, cp_min, cp_max, 0xE000, 0xF8FF)
    after creating the imgfont. cp_min/cp_max are computed
    dynamically from the loaded _emoji_maps keys (so the filter
    self-maintains as the emoji set grows — no hardcoded magic).
  - Guarded by try/except AttributeError, so a build whose
    pinned lvgl_micropython SHA predates the patch keeps working,
    just without the speedup.

* scripts/build_mpos.sh
  - Applies lvgl_micropython/imgfont_set_range.patch into
    lib/lvgl during the build, gated by a -f existence check so
    older lvgl_micropython pins (no patch file present) still
    build cleanly.

* lvgl_micropython submodule bump
  - 916ec6b -> e00d9d9, picks up the new imgfont_set_range.patch
    plus a small unrelated brew-config parser fix in
    builder/macOS.py that was breaking macOS builds entirely.

Verified end-to-end on Waveshare ESP32-S3-Touch-LCD-2 (macOS
desktop build also OK). FontManager.getFont(emoji=True) on
device computes bounds (0x203C, 0x1F9E2) — exactly the actual
min/max across the loaded 400-emoji set. Scrolling the LP
transactions list is noticeably smoother than the pre-patch
build.

Backward-compat note: lvgl_micropython e00d9d9 is currently only
on bitcoin3us/lvgl_micropython. Submodule pointer here will
resolve once that branch lands on MicroPythonOS/lvgl_micropython;
.gitmodules intentionally still points at the upstream URL.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@ThomasFarstrike
Copy link
Copy Markdown
Contributor

That's very impressive! I like how the range can be set at runtime, so it's more generic. Not sure upstream lvgl_micropython or upstream lvgl_micropython/lib/lvgl would ever merge this, but that's not a dealbreaker!

Let me merge it, assuming you've already verified that it works. I'm excited!

@ThomasFarstrike ThomasFarstrike merged commit baafb7b into MicroPythonOS:main May 31, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants