Flattype on a Cheap Yellow Display (ESP32-2432S028R)
Full OpenType text shaping and rendering, live on a $10 microcontroller: Arabic, Nastaliq, Devanagari, Khmer, ligatures, bidi. 320 KB of RAM, 170 KB of code, no pre-baked assets.
flattype-cyd-demo-cHtd6wyP.mp4
What is this?
This repository is a ready-to-flash demo of the Rust port of Flattype, a very compact OpenType shaper and renderer, combined with a tiny rasterizer. It runs on the Cheap Yellow Display (ESP32-2432S028R), the popular ~$10 ESP32 dev board: 320 KB of RAM, 4 MB of flash, no PSRAM.
All the text in this demo is rendered live on the chip from TrueType fonts. No prerendered bitmaps, no glyph atlases, nothing pre-baked.
Put any standard OpenType or TrueType font on flash, and everything below happens live at render time:
- Bidirectional text ordering.
- Substitution: ligatures, contextual rules, alternates, reverse contextual.
- Positioning: kerning, mark-to-base, mark-to-mark, cursive attachment.
- Script-specific shaping: Arabic joining, Devanagari and other Indic reordering, Khmer, Myanmar, Hangul, Thai, and universal shaping for less common scripts.
- Both TrueType (glyf) and PostScript (CFF) outlines.
- Anti-aliased, gamma-corrected rendering, drawn to the LCD in horizontal strips.
Why this matters
Embedded displays have traditionally meant prerendered bitmaps (inflexible, storage-heavy) or commissioned custom fonts. This appears to be the first demonstration of full OpenType shaping with complex script support on hardware this constrained. The shaper itself is about 135 KB of code; the entire pipeline up to anti-aliased pixels is about 170 KB.
Demos
Touch navigation: the top row of the screen switches between the six demos (left half = back, right half = forward), the middle row cycles examples within a demo, and the bottom row is a swipe / slider area.
Hardware
You need a Cheap Yellow Display (ESP32-2432S028R): 320 × 240 ILI9341 LCD, XPT2046 resistive touchscreen, 4 MB flash, ~320 KB internal SRAM, no PSRAM. Variants without the resistive touch panel will most likely not work. The demo drives the LCD over SPI at 80 MHz with DMA and the touchscreen at 2 MHz.
Install the demo
Web flasher (recommended): open https://waruyama.github.io/flattype-cyd-demo-install/ in Chrome or Edge, plug your CYD into USB, and click Connect. Flashing takes about 30 seconds.
Manual install: dist/firmware.bin is a merged 4 MB image (bootloader + partition table + app) that flashes at
offset 0x0, with esptool.py or
espflash:
esptool.py --chip esp32 --port /dev/ttyUSB0 write_flash 0x0 firmware.bin
# or
espflash write-bin 0x0 firmware.binPerformance
Per-stage timings on the CYD, measured at the smallest font size with the maximum number of visible glyphs (13 lines, roughly 200-800 glyphs depending on font and script). Shaping runs once per text change; all other stages run on every font-size change. So the refresh subtotal is the steady-state cost of a size change, and the total is the cost of the first frame after a text change.
| Stage | Single glyph | Latin | Arabic | Devanagari | Hebrew | Emoji |
|---|---|---|---|---|---|---|
| Number of glyphs | 1 | 360-548 | 809 | 618 | 623 | 208 |
| Shaping | 0 ms | 12-46 ms | 79 ms | 156 ms | 34 ms | 9 ms |
| Line-breaking | 1 ms | 7-10 ms | 14 ms | 14 ms | 13 ms | 0 ms |
| Rendering | 0 ms | 25-97 ms | 47 ms | 30 ms | 18 ms | 95 ms |
| Flattening | 0 ms | 13-65 ms | 35 ms | 21 ms | 14 ms | 56 ms |
| Rasterizing | 1-2 ms | 64-187 ms | 121 ms | 111 ms | 77 ms | 199 ms |
| Pushing | 18 ms | 5 ms | 5 ms | 5 ms | 4 ms | 5 ms |
| Other | 0 ms | 1 ms | 1 ms | 1 ms | 1 ms | 1 ms |
| Refresh subtotal | 21 ms | 114-314 ms | 224 ms | 183 ms | 130 ms | 360 ms |
| Total | 21 ms | 135-350 ms | 303 ms | 339 ms | 164 ms | 369 ms |
What's in the binary
The flashed image is ~2.6 MB: ~1.93 MB of embedded fonts and ~610 KB of code and data. Sizes are from
xtensa-esp32-elf-size and a per-symbol breakdown via xtensa-esp32-elf-nm on a release build.
| Component | Size |
|---|---|
| Flattype code and data (bidi, shaper, renderer) | ~135 KB |
| Rasterizer path (flattener, rasterizer, LCD pushing) | ~35 KB |
| Text shaping + rendering + rasterizing subtotal | ~170 KB |
Demo logic (layout, touch, display, intro, the 6 demos, main) |
~30 KB |
| ESP-IDF C runtime (FreeRTOS, drivers, ROM glue) | ~145 KB |
Rust (core, std + alloc) |
~155 KB |
Backtrace machinery (gimli, addr2line, rustc_demangle, etc) |
~85 KB |
compiler_builtins, esp-idf-hal, mipidsi, other small crates |
~8 KB |
| Misc rodata not attributable above (vtables, format strings, etc.) | ~20 KB |
| Code + data subtotal | ~610 KB |
| Fonts (16 .ttf files embedded via include_bytes!) | ~1.93 MB |
| Grand total | ~2.6 MB |
The headline number: the entire OpenType pipeline, from shaping to anti-aliased rasterizing, compiles to about
170 KB. The largest non-font cost, the ESP-IDF C runtime at ~145 KB, is fixed overhead for any Rust ESP32 program
with std support.
The 16 fonts are static monochrome vector fonts from Google Fonts, embedded via include_bytes! and not subsetted
(except for emoji); for a real product they would be slimmed down. The largest are Noto Nastaliq Urdu (517 KB), Hind
(285 KB), Noto Sans Arabic (188 KB), subsetted Noto Emoji (186 KB), and Roboto Regular (167 KB).
Credits
- Flattype: the OpenType shaper and renderer, ported to Rust from the JavaScript original.
- mipidsi: ILI9341 driver.
- esp-idf-hal: ESP-IDF Rust bindings.
- Fonts: Roboto, the Noto families, Hind, Almendra, Assistant, and the display faces, from Google Fonts under the SIL Open Font License; Droid Serif under the Apache License 2.0.












