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>
58 lines
1.6 KiB
C++
58 lines
1.6 KiB
C++
#pragma once
|
|
|
|
#include <EpdFontFamily.h>
|
|
|
|
#include <cstdint>
|
|
#include <map>
|
|
#include <string>
|
|
|
|
class FontDecompressor;
|
|
class SdCardFont;
|
|
|
|
class FontCacheManager {
|
|
public:
|
|
FontCacheManager(const std::map<int, EpdFontFamily>& fontMap, const std::map<int, SdCardFont*>& sdCardFonts);
|
|
|
|
void setFontDecompressor(FontDecompressor* d);
|
|
|
|
void clearCache();
|
|
void prewarmCache(int fontId, const char* utf8Text, uint8_t styleMask = 0x0F);
|
|
void logStats(const char* label = "render");
|
|
void resetStats();
|
|
|
|
// Scan-mode API: called by GfxRenderer::drawText() during scan pass
|
|
bool isScanning() const;
|
|
void recordText(const char* text, int fontId, EpdFontFamily::Style style);
|
|
|
|
// The FontDecompressor pointer, needed by GfxRenderer::getGlyphBitmap()
|
|
FontDecompressor* getDecompressor() const { return fontDecompressor_; }
|
|
|
|
// RAII scope for two-pass prewarm pattern
|
|
class PrewarmScope {
|
|
public:
|
|
explicit PrewarmScope(FontCacheManager& manager);
|
|
~PrewarmScope();
|
|
void endScanAndPrewarm();
|
|
PrewarmScope(PrewarmScope&& other) noexcept;
|
|
PrewarmScope& operator=(PrewarmScope&&) = delete;
|
|
PrewarmScope(const PrewarmScope&) = delete;
|
|
PrewarmScope& operator=(const PrewarmScope&) = delete;
|
|
|
|
private:
|
|
FontCacheManager* manager_;
|
|
bool active_ = true;
|
|
};
|
|
PrewarmScope createPrewarmScope();
|
|
|
|
private:
|
|
const std::map<int, EpdFontFamily>& fontMap_;
|
|
const std::map<int, SdCardFont*>& sdCardFonts_;
|
|
FontDecompressor* fontDecompressor_ = nullptr;
|
|
|
|
enum class ScanMode : uint8_t { None, Scanning };
|
|
ScanMode scanMode_ = ScanMode::None;
|
|
std::string scanText_;
|
|
uint32_t scanStyleCounts_[4] = {};
|
|
int scanFontId_ = -1;
|
|
};
|