## Purpose This PR includes some preparatory changes that are needed for an upcoming performant CJK font feature. The changes have no impact on render time and heap allocation for latin text. **Despite this, I think these changes stand on their own as a better font compression/decompression implementation.** ## Summary - Font decompressor rewrite: Replaced the 4-slot LRU group cache with a two-tier system — a page buffer (glyphs prewarmed before rendering begins) and a hot-group fallback (last decompressed group retained for non-prewarmed glyphs). - Byte-aligned compressed bitmap format: Glyph bitmaps within compressed groups are now stored row-padded rather than tightly packed before DEFLATE compression, improving compression ratios by making identical pixel rows produce identical byte patterns. Glyphs are compacted back to packed format on demand at render time. Reduces flash size by 155 KB. - Page prewarm system: Added `Page::collectText` and `Page::getDominantStyle` to extract per-style glyph requirements before rendering, and `GfxRenderer::prewarmFontCache` to pre-decompress only the groups needed for the dominant style — eliminating mid-render decompression for the common case. - UTF-8 robustness fixes: `utf8NextCodepoint` now validates continuation bytes and returns a replacement glyph on malformed input; `ChapterHtmlSlimParser` correctly preserves incomplete multi-byte sequences across word-buffer flush boundaries rather than splitting them. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**YES**_ Architecture and design was done by me, refined a bit by Claude. Code mostly by Claude, but not entirely.
100 lines
5.2 KiB
C++
100 lines
5.2 KiB
C++
// From
|
|
// https://github.com/vroland/epdiy/blob/c61e9e923ce2418150d54f88cea5d196cdc40c54/src/epd_internals.h
|
|
|
|
#pragma once
|
|
#include <cstdint>
|
|
|
|
/// Font metrics use "fixed-point 4" (4 fractional bits, i.e. 1/16-pixel
|
|
/// resolution). Both the 12.4 glyph advances (uint16_t) and the 4.4 kern
|
|
/// values (int8_t) share the same 4 fractional bits, so they can be freely
|
|
/// added into a single int32_t accumulator during text layout. The
|
|
/// accumulator is snapped to the nearest whole pixel only at render time,
|
|
/// which avoids the per-character rounding errors that plagued integer-only
|
|
/// layout.
|
|
///
|
|
/// The helpers below eliminate the raw bit-shifts that would otherwise be
|
|
/// scattered across every layout / measurement call site.
|
|
namespace fp4 {
|
|
constexpr int FRAC_BITS = 4;
|
|
constexpr int32_t HALF = 1 << (FRAC_BITS - 1); // 8, added before shift for round-to-nearest
|
|
|
|
/// Convert an integer pixel value to 12.4 fixed-point.
|
|
constexpr int32_t fromPixel(int px) { return static_cast<int32_t>(px) << FRAC_BITS; }
|
|
|
|
/// Snap a fixed-point value to the nearest integer pixel.
|
|
constexpr int toPixel(int32_t fp) { return static_cast<int>((fp + HALF) >> FRAC_BITS); }
|
|
|
|
/// Convert a fixed-point value to float (mainly useful for debug logging).
|
|
constexpr float toFloat(int32_t fp) { return fp / static_cast<float>(1 << FRAC_BITS); }
|
|
} // namespace fp4
|
|
|
|
/// Fixed-point conventions used by EpdGlyph and EpdFontData:
|
|
/// advanceX: 12.4 unsigned fixed-point in uint16_t (use fp4::toPixel)
|
|
/// kernMatrix: 4.4 signed fixed-point in int8_t (use fp4::toPixel)
|
|
/// Both share 4 fractional bits so they combine directly in an accumulator.
|
|
|
|
/// Font data stored PER GLYPH
|
|
typedef struct {
|
|
uint8_t width; ///< Bitmap dimensions in pixels
|
|
uint8_t height; ///< Bitmap dimensions in pixels
|
|
uint16_t advanceX; ///< Distance to advance cursor (x axis), 12.4 fixed-point in pixels
|
|
int16_t left; ///< X dist from cursor pos to UL corner
|
|
int16_t top; ///< Y dist from cursor pos to UL corner
|
|
uint16_t dataLength; ///< Size of the font data.
|
|
uint32_t dataOffset; ///< Pointer into EpdFont->bitmap (or within-group offset for compressed fonts)
|
|
} EpdGlyph;
|
|
|
|
/// Compressed font group: a DEFLATE-compressed block of glyph bitmaps
|
|
typedef struct {
|
|
uint32_t compressedOffset; ///< Byte offset into compressed data array
|
|
uint32_t compressedSize; ///< Compressed DEFLATE stream size
|
|
uint32_t uncompressedSize; ///< Decompressed size
|
|
uint16_t glyphCount; ///< Number of glyphs in this group
|
|
uint32_t firstGlyphIndex; ///< First glyph index in the global glyph array
|
|
} EpdFontGroup;
|
|
|
|
/// Glyph interval structure
|
|
typedef struct {
|
|
uint32_t first; ///< The first unicode code point of the interval
|
|
uint32_t last; ///< The last unicode code point of the interval
|
|
uint32_t offset; ///< Index of the first code point into the glyph array
|
|
} EpdUnicodeInterval;
|
|
|
|
/// Maps a codepoint to a kerning class ID, sorted by codepoint for binary search.
|
|
/// Class IDs are 1-based; codepoints not in the table have implicit class 0 (no kerning).
|
|
typedef struct {
|
|
uint16_t codepoint; ///< Unicode codepoint
|
|
uint8_t classId; ///< 1-based kerning class ID
|
|
} __attribute__((packed)) EpdKernClassEntry;
|
|
|
|
/// Ligature substitution for a specific glyph pair, sorted by `pair` for binary search.
|
|
/// `pair` encodes (leftCodepoint << 16 | rightCodepoint) for single-key lookup.
|
|
typedef struct {
|
|
uint32_t pair; ///< Packed codepoint pair (left << 16 | right)
|
|
uint32_t ligatureCp; ///< Codepoint of the replacement ligature glyph
|
|
} __attribute__((packed)) EpdLigaturePair;
|
|
|
|
/// Data stored for FONT AS A WHOLE
|
|
typedef struct {
|
|
const uint8_t* bitmap; ///< Glyph bitmaps, concatenated
|
|
const EpdGlyph* glyph; ///< Glyph array
|
|
const EpdUnicodeInterval* intervals; ///< Valid unicode intervals for this font
|
|
uint32_t intervalCount; ///< Number of unicode intervals.
|
|
uint8_t advanceY; ///< Newline distance (y axis)
|
|
int ascender; ///< Maximal height of a glyph above the base line
|
|
int descender; ///< Maximal height of a glyph below the base line
|
|
bool is2Bit;
|
|
const EpdFontGroup* groups; ///< NULL for uncompressed fonts
|
|
uint16_t groupCount; ///< 0 for uncompressed fonts
|
|
const uint16_t* glyphToGroup; ///< Per-glyph group ID (nullptr for contiguous-group fonts)
|
|
const EpdKernClassEntry* kernLeftClasses; ///< Sorted left-side class map (nullptr if none)
|
|
const EpdKernClassEntry* kernRightClasses; ///< Sorted right-side class map (nullptr if none)
|
|
const int8_t* kernMatrix; ///< Flat leftClassCount x rightClassCount matrix, 4.4 fixed-point in pixels
|
|
uint16_t kernLeftEntryCount; ///< Entries in kernLeftClasses
|
|
uint16_t kernRightEntryCount; ///< Entries in kernRightClasses
|
|
uint8_t kernLeftClassCount; ///< Number of distinct left classes (matrix rows)
|
|
uint8_t kernRightClassCount; ///< Number of distinct right classes (matrix cols)
|
|
const EpdLigaturePair* ligaturePairs; ///< Sorted ligature pair table (nullptr if none)
|
|
uint32_t ligaturePairCount; ///< Number of entries in ligaturePairs
|
|
} EpdFontData;
|