Add a complete SD card font subsystem that enables users to install and use custom fonts beyond the three built-in families. This combines the back-end firmware support (#1327) with the font configuration, build pipeline, CI distribution, and user-facing management UI (#1392). Core font system: - Custom .cpfont binary format (v4) with multi-style support (regular, bold, italic, bold-italic) packed into a single file per size - On-demand glyph loading from SD card with two-pass prewarm rendering to bulk-read glyphs per page, achieving near-flash performance for Latin text (~697ms vs ~681ms) and viable CJK rendering (~32% slower) - Persistent advance cache for layout measurement without SD I/O - Overflow ring buffer for glyph cache misses during rendering - Memory-conscious design: only advance tables kept in RAM; glyph bitmaps, kern tables, and ligatures loaded on demand from SD Font management: - On-device WiFi download from GitHub Releases with manifest-based discovery, install/update detection, and progress UI - Web interface font upload, listing, and deletion via /fonts page - Manual SD card copy to /fonts/ or /.fonts/ directories - Font selection integrated into Settings > Reader > Font Family Build pipeline: - Declarative YAML config (sd-fonts.yaml) as single source of truth for the 17-family font library (serif, sans, mono, accessibility) - Python converter (fontconvert_sdcard.py) for TTF/OTF to .cpfont with FreeType rasterization, class-based kerning, and ligature extraction - Parallel build orchestrator with variable font instance extraction - CI workflow publishing versioned + stable releases to a dedicated crosspoint-fonts repository with auto-incrementing revision tags - Centralized version constants (cpfont_version.py) shared across build tooling and CI, with firmware headers as manual sync points Additional fixes: - CJK characters no longer get hyphens inserted at line breaks - Advance table eliminates 30+ second stalls during CJK section indexing for paragraphs with >512 unique codepoints Closes #930 Co-authored-by: Zach Nelson <zach@zdnelson.com> Co-authored-by: Justin <itsthisjustin@users.noreply.github.com> Co-authored-by: jpirnay <jens@pirnay.com> Co-authored-by: mcrosson <kemonine@kemonine.info>
43 lines
2.2 KiB
C++
43 lines
2.2 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <string>
|
|
#define REPLACEMENT_GLYPH 0xFFFD
|
|
|
|
uint32_t utf8NextCodepoint(const unsigned char** string);
|
|
// Remove the last UTF-8 codepoint from a std::string and return the new size.
|
|
size_t utf8RemoveLastChar(std::string& str);
|
|
// Truncate string by removing N UTF-8 codepoints from the end.
|
|
void utf8TruncateChars(std::string& str, size_t numChars);
|
|
|
|
// Truncate a raw char buffer to the last complete UTF-8 codepoint boundary.
|
|
// Returns the new length (<= len). If the buffer ends mid-sequence, the
|
|
// incomplete trailing bytes are excluded.
|
|
int utf8SafeTruncateBuffer(const char* buf, int len);
|
|
|
|
// Returns true for CJK characters that allow line breaks on either side without hyphenation.
|
|
// Covers CJK Unified Ideographs, Hiragana, Katakana, Hangul Syllables, CJK punctuation,
|
|
// and fullwidth forms — the ranges where word boundaries are implicit per character.
|
|
inline bool utf8IsCjkBreakable(const uint32_t cp) {
|
|
return (cp >= 0x3000 && cp <= 0x303F) // CJK Symbols and Punctuation
|
|
|| (cp >= 0x3040 && cp <= 0x309F) // Hiragana
|
|
|| (cp >= 0x30A0 && cp <= 0x30FF) // Katakana
|
|
|| (cp >= 0x3400 && cp <= 0x4DBF) // CJK Extension A
|
|
|| (cp >= 0x4E00 && cp <= 0x9FFF) // CJK Unified Ideographs
|
|
|| (cp >= 0xAC00 && cp <= 0xD7AF) // Hangul Syllables
|
|
|| (cp >= 0xF900 && cp <= 0xFAFF) // CJK Compatibility Ideographs
|
|
|| (cp >= 0xFE30 && cp <= 0xFE4F) // CJK Compatibility Forms
|
|
|| (cp >= 0xFF01 && cp <= 0xFF60) // Fullwidth Latin / Punctuation
|
|
|| (cp >= 0xFF65 && cp <= 0xFFEF) // Halfwidth Katakana / Hangul
|
|
|| (cp >= 0x20000 && cp <= 0x2A6DF) // CJK Extension B
|
|
|| (cp >= 0x2A700 && cp <= 0x2B73F); // CJK Extension C
|
|
}
|
|
|
|
// Returns true for Unicode combining diacritical marks that should not advance the cursor.
|
|
inline bool utf8IsCombiningMark(const uint32_t cp) {
|
|
return (cp >= 0x0300 && cp <= 0x036F) // Combining Diacritical Marks
|
|
|| (cp >= 0x1DC0 && cp <= 0x1DFF) // Combining Diacritical Marks Supplement
|
|
|| (cp >= 0x20D0 && cp <= 0x20FF) // Combining Diacritical Marks for Symbols
|
|
|| (cp >= 0xFE20 && cp <= 0xFE2F); // Combining Half Marks
|
|
}
|