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>
60 lines
2.1 KiB
C++
60 lines
2.1 KiB
C++
#pragma once
|
|
|
|
#include <SdCardFontRegistry.h>
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
|
|
/// Shared utility for font installation (device download + browser upload).
|
|
/// Handles directory creation, file validation, deletion, and registry refresh.
|
|
class FontInstaller {
|
|
public:
|
|
enum class Error {
|
|
OK,
|
|
INVALID_FAMILY_NAME,
|
|
INVALID_FILE,
|
|
SD_WRITE_ERROR,
|
|
MAX_FAMILIES_REACHED,
|
|
};
|
|
|
|
explicit FontInstaller(SdCardFontRegistry& registry);
|
|
|
|
/// Validate a family name: alphanumeric + hyphen + underscore only, no path traversal.
|
|
static bool isValidFamilyName(const char* name);
|
|
|
|
/// Validate a .cpfont filename: ends with ".cpfont", no path separators or
|
|
/// traversal sequences, basename uses only alphanumeric + hyphen + underscore
|
|
/// + dot (only as the extension separator). Rejects "../foo.cpfont" and
|
|
/// "evil/foo.cpfont".
|
|
static bool isValidCpfontFilename(const char* name);
|
|
|
|
/// Ensure /<root>/<family>/ exists, where <root> is /.fonts (preferred) or /fonts.
|
|
/// Re-uses the existing root if the family is already installed; otherwise
|
|
/// creates it under SdCardFontRegistry::defaultWriteRoot().
|
|
bool ensureFamilyDir(const char* familyName);
|
|
|
|
/// Validate a .cpfont file on disk (check magic bytes).
|
|
bool validateCpfontFile(const char* path);
|
|
|
|
/// Build the full SD path for a font file.
|
|
/// Writes "/<root>/<family>/<filename>" to outBuf, choosing <root> the same
|
|
/// way ensureFamilyDir does (existing install dir, else default-write root).
|
|
static void buildFontPath(const char* family, const char* filename, char* outBuf, size_t outBufSize);
|
|
|
|
/// Delete a family directory and all .cpfont files in it.
|
|
/// If the deleted family is the active reader font, clears the setting.
|
|
Error deleteFamily(const char* familyName);
|
|
|
|
/// Re-run registry discovery to pick up new/removed fonts.
|
|
void refreshRegistry();
|
|
|
|
/// Check whether a family name already exists in the registry.
|
|
bool isFamilyInstalled(const char* familyName) const;
|
|
|
|
private:
|
|
SdCardFontRegistry& registry_;
|
|
|
|
static constexpr const char* CPFONT_MAGIC = "CPFONT\0";
|
|
static constexpr size_t CPFONT_MAGIC_LEN = 8;
|
|
};
|