Minimal EPUB reader, compatible only with Xteink X4. Not recommended for locked devices — fork of CidVonHighwind/microreader
- 4 control modes (Default, Side Inverted, Front Inverted, Inverted) replacing the 3 separate booleans from upstream, applied independently to reader and menu for a more natural and consistent navigation experience
- Long-press back/select → Contextual actions in MainMenu
- Recently opened books section with the last 5 opened books
- Delete book with long-press back
- Remove from Recents with long-press back from Recents list
- Rebuild Book Index with long-press select
- Custom Logo
- Reduced margins to show long titles
- Book title as header instead of static "Options"
- Font size default = 24px
- Font size presets adjusted (17-36px in 5 steps)
- Fonts: moved from flash to SD card (Alegreya)
- Sleep images: only bird embedded; stone and bebop moved to SD
- Auto-migration of settings (bool invert → ControlMode, Alegreya embedded → SD, sleep images embedded → SD)
- Button state cleanup post-transition (prevents ghost presses)
based on crosspoint-reader/crosspoint-reader#1786
- ESP32 image validation (magic 0xE9, segment walk, XOR, CRC32)
- Chip/revision guard + SHA256 + battery check
- OTA rollback via ESP_IMG_PENDING_VERIFY
- Force switch via otadata direct write as fallback
- Horizontal rule: centered at 1/3 width, 2px thickness
- CSS
width: XX%parsing for<hr>
- Drop caps from
<span style="float: left; font-size: ...">
--upload-dirfor batch EPUB upload via serial
- ControlMode (4 navigation modes)
- ReaderOptions: book title as header
- Toast feedback on actions
Minimal EPUB reader for ESP32-C3 + SSD1677 e-ink display (480×800 portrait). Includes a desktop SDL2 emulator for development without hardware.
| MCU | ESP32-C3 (RISC-V, 160 MHz) |
| Display | 4.26" e-ink 800×480 (SSD1677), rotated → 480×800 portrait |
| Storage | SD card (FAT32, SPI) |
| Flash | 16 MB |
| Input | ADC buttons |
lib/microreader/ shared core (platform-agnostic C++17)
content/ EPUB parsing, layout, MRB binary format
display/ Canvas, DisplayQueue, Font interfaces
screens/ UI screen implementations
platforms/desktop/ SDL2 emulator
platforms/esp32/ ESP-IDF + PlatformIO firmware
test/ Google Test suite
tools/ Python scripts
resources/ Fonts, sleep images
cmake -S platforms/desktop -B build/desktop-debug -DCMAKE_BUILD_TYPE=Debug "-DCMAKE_POLICY_VERSION_MINIMUM:STRING=3.5"
cmake --build build/desktop-debug --config Debug
.\build\desktop-debug\Debug\microreader_desktop.exe# Build + flash
$env:USERPROFILE\.platformio\penv\Scripts\pio.exe run -t upload
# Serial monitor
$env:USERPROFILE\.platformio\penv\Scripts\pio.exe device monitor --baud 115200COM4, upload baud 921600.
cd test
cmake -B build2 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_POLICY_VERSION_MINIMUM:STRING=3.5
cmake --build build2 --config Debug
.\build2\Debug\unit_tests.exe # fast (~375 tests, <1s)
.\build2\Debug\microreader_tests.exe # includes real EPUB integration testsBooks (.epub) can go anywhere on the SD card — the device scans recursively from the root. Fonts (.mfb) go in /fonts/. You can copy files directly or use tools/serial_cmd.py to transfer over serial:
# Upload an EPUB book
python tools/serial_cmd.py --port COM4 --upload path/to/book.epub
# Upload all EPUB books in a directory
python tools/serial_cmd.py --port COM4 --upload-dir path/to/books/
# Upload an SD card font (no firmware rebuild needed)
python tools/serial_cmd.py --port COM4 --upload-sd-font "resources/sd fonts/Cartisse.mfb"
# Upload all SD fonts
foreach ($f in (Get-ChildItem "resources/sd fonts/*.mfb")) {
python tools/serial_cmd.py --port COM4 --upload-sd-font $f.FullName
}
# Interactive console (status, button injection, file management, benchmarks)
python tools/serial_cmd.py --port COM4
# Delete a specific file from the device
# (interactive: rm /sdcard/books/book.epub)Reader fonts are FNTS bundles (.mfb), generated from TTF/OTF sources via tools/generate_font.py.
Two kinds:
- Built-in (
resources/fonts/) — embedded in the firmware asset blob. Require a firmware rebuild to update. - SD card (
resources/sd fonts/) — loaded from/sdcard/fonts/at runtime. No firmware rebuild needed; just copy or upload.
The generation command is the same for both:
python tools/generate_font.py "resources/sd fonts/ttf/Cartisse-Regular.ttf" `
-o "resources/sd fonts/Cartisse.mfb" --with-styles `
--bold "resources/sd fonts/ttf/Cartisse-Bold.ttf" `
--italic "resources/sd fonts/ttf/Cartisse-Italic.ttf" `
--bold-italic "resources/sd fonts/ttf/Cartisse-BoldItalic.ttf" `
--bundle --bundle-sizes 20 22 24 26 28 30 32 --font-name Cartisse
# Regenerate all SD fonts
$ttf = "resources/sd fonts/ttf"; $out = "resources/sd fonts"
foreach ($f in @("Bitter","Cartisse","NV_Bitter","NV_Charis","NV_Cooper","NV_Garamond","NV_Jost","NV_Palatium","Readerly")) {
python tools/generate_font.py "$ttf/$f-Regular.ttf" -o "$out/$f.mfb" --with-styles `
--bold "$ttf/$f-Bold.ttf" --italic "$ttf/$f-Italic.ttf" --bold-italic "$ttf/$f-BoldItalic.ttf" `
--bundle --bundle-sizes 20 22 24 26 28 30 32 --font-name $f
}
# Font preview (generates tools/font_overview.html)
python tools/font_overview.py
# UI fonts (bitmap, bw-only)
python tools/generate_font.py resources/fonts/terminus/Terminus-Bold.ttf 14 -o resources/fonts/ui-small.mbf --header lib/microreader/display/ui_font_small.h --bw-only --ranges ui
python tools/generate_font.py resources/fonts/terminus/Terminus-Bold.ttf 32 -o resources/fonts/ui-header.mbf --header lib/microreader/display/ui_font_header.h --bw-only --ranges uiFont partition limit: SD card fonts must fit within 3.375 MB. The font data + 4 KB header must not exceed
0x360000bytes.
Note: Built-in fonts and sleep images are embedded directly in the firmware image (
EMBED_FILES), sofirmware.binis a standard IDF binary that any web flasher can validate and flash without issues. The tradeoff is that the assets (~2 MB) consume DROM MMU pages.
# Backup running firmware partition
python -m esptool --port COM4 read_flash 0x10000 0x650000 app0_backup.bin
# Restore
python -m esptool --port COM4 write_flash 0x10000 app0_backup.bin
# Switch OTA boot partition
python tools/switch_partition.py app0 --port COM4 --flash
python tools/switch_partition.py app1 --port COM4 --flashSleep screen images (.mgr) go in /sleep/ on the SD card. Convert a JPEG first, then copy manually or upload via serial:
python tools/image_to_mgr.py "resources/sleep/sleep_2.jpg" --out-prefix "resources/sleep/sleep_2" --bin
python tools/serial_cmd.py --port COM4 --upload-sleep "resources/sleep/sleep_2.mgr"The reader uses the Liang hyphenation algorithm — the same algorithm used by TeX. Language-specific TeX pattern files are compiled into compact binary tries by Typst hypher and embedded as constexpr byte arrays. The language is detected automatically from the EPUB's xml:lang attribute.
Supported languages:
| Code | Language | Trie size |
|---|---|---|
en |
English | 26 KB |
de |
German | 206 KB |
fr |
French | 7 KB |
es |
Spanish | 14 KB |
it |
Italian | 2 KB |
nl |
Dutch | 64 KB |
pt |
Portuguese | 1 KB |
pl |
Polish | 16 KB |
ru |
Russian | 33 KB |
Trie data lives in lib/microreader/content/hyphenation/Liang/hyph-<lang>.trie.h as constexpr byte arrays — no heap allocation, no flash reads at runtime (data is placed in DROM on ESP32).
To add a new language:
- Download the
.binfrom typst/hypher/tries intotools/hyphenation/ - Generate the header:
python tools/generate_trie_header.py tools/hyphenation/<lang>.bin lib/microreader/content/hyphenation/Liang/hyph-<lang>.trie.h <lang> - Add the new enum value to
HyphenationLanginHyphenation.h - Add a
#include+caseinHyphenation.cpp(hyphenate_word) and anieqcheck indetect_language
# Terminal 1
python tools/run_qemu.py --with-books
# Terminal 2
python tools/test_books.py --port socket://localhost:4444 --pages 20 --delay 0.1