Xteink-X4-crosspoint-reader/CUSTOM_FONTS.md

81 lines
4.0 KiB
Markdown

# Custom Font Implementation Walkthrough
This document outlines the custom font implementation in the CrossPoint Reader codebase. The system allows users to load custom TrueType/OpenType fonts (converted to a binary format) from an SD card and select them via the settings UI.
## System Overview
The custom font system consists of four main components:
1. **Font Converter (`fontconvert.py`)**: A Python script that pre-processes standard fonts into a custom optimized binary format (`.epdfont`).
2. **Font Manager (`FontManager`)**: Scans the SD card for valid font files and manages loaded font families.
3. **Font Loader (`CustomEpdFont`)**: Handles the low-level reading of the binary format, including on-demand caching of glyph bitmaps to save RAM.
4. **UI & Integration**: A settings activity to select fonts and integration into the main rendering loop.
## 1. Font Conversion & Format
To optimize for the limited RAM of the ESP32 and the specific requirements of E-Ink displays, fonts are not loaded directly as TTF/OTF files. Instead, they are pre-processed.
* **Script**: `lib/EpdFont/scripts/fontconvert.py`
* **Input**: TTF/OTF files.
* **Output**: `.epdfont` binary file.
* **Format Details**:
* **Header**: Contains metadata (magic "EPDF", version, metrics, offsets).
* **Intervals**: Unicode ranges supported by the font.
* **Glyphs**: Metrics for each character (width, height, advance, offsets).
* **Bitmaps**: 1-bit or 2-bit (antialiased) pixel data for glyphs.
## 2. Storage & Discovery
Fonts are stored on the SD card in the `/fonts` directory.
* **Location**: `/fonts`
* **Naming Convention**: `Family-Style-Size.epdfont`
* Example: `Literata-Regular-14.epdfont`
* Example: `Literata-BoldItalic-14.epdfont`
* **Manager**: `src/managers/FontManager.cpp`
* **Scans** the `/fonts` directory on startup/demand.
* **Groups** files into `Family -> Size -> Styles (Regular, Bold, Italic, BoldItalic)`.
* Exposes available families to the UI.
## 3. Low-Level Implementation (RAM Optimization)
The core logic resides in `lib/EpdFont/CustomEpdFont.cpp`.
* **Inheritance**: `CustomEpdFont` inherits from `EpdFont`.
* **Metadata in RAM**: When a font is loaded, only the *header* and *glyph metrics* (width, height, etc.) are loaded into RAM.
* **Bitmaps on Disk**: Pixel data remains on the SD card.
* **LRU Cache**: A small Least Recently Used (LRU) cache (`MAX_CACHE_SIZE = 30`) holds frequently used glyph bitmaps in RAM.
* **Hit**: Returns cached bitmap.
* **Miss**: Reads the bitmap from the SD card at the specific offset, caches it, and returns it.
* **Benefit**: Allows using large fonts with extensive character sets (e.g., CJK) without exhausting the ESP32's heap.
## 4. User Interface & Selection
The user selects a font through a dedicated Settings activity.
* **File**: `src/activities/settings/CustomFontSelectionActivity.cpp`
* **Flow**:
1. Lists available font families retrieved from `FontManager`.
2. User selects a family.
3. Selection is saved to `SETTINGS.customFontFamilyName`.
## 5. Main Integration
The selected font is applied during the system startup or when settings change.
* **File**: `src/main.cpp`
* **Function**: `setupDisplayAndFonts()`
* **Logic**:
1. Checks if `SETTINGS.fontFamily` is set to `FONT_CUSTOM`.
2. Calls `FontManager::getInstance().getCustomFontFamily(...)` with the saved name and current font size.
3. If found, the font is dynamically inserted into the global `renderer` with a generated ID.
4. The renderer then uses this font for standard text rendering.
## Code Path Summary
1. **SD Card**: `SD:/fonts/MyFont-Regular-14.epdfont`
2. **Wait**: `FontManager::scanFonts()` finds the file.
3. **Select**: User picks "MyFont" in `CustomFontSelectionActivity`.
4. **Load**: `main.cpp` calls `renderer.insertFont(..., FontManager.getCustomFontFamily("MyFont", 14))`
5. **Render**: `CustomEpdFont::getGlyphBitmap()` fetches pixels from SD -> Cache -> Screen.