Files
Crosspoint/lib/EpdFont/FontDecompressor.h
T
jpirnayandClaude Sonnet 4.6 80722118a2 FontDecompressor: heap-allocate hot group buffer sized to active font
Replace the 50 KB static BSS _hotGroupBuf array with a heap pointer
allocated once per font (ensureHotGroupBuf). The buffer is sized to the
largest group in the active font — 6 KB for small fonts, ~50 KB for 18pt
— so no memory is wasted when rendering body text at typical sizes.

The buffer persists across pages (freed only in freeHotGroup/deinit),
so there is exactly one malloc per font session rather than one per page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 08:14:04 +02:00

90 lines
3.5 KiB
C++

#pragma once
#include <InflateReader.h>
#include "EpdFontData.h"
class FontDecompressor {
public:
static constexpr uint16_t MAX_PAGE_GLYPHS = 512;
static constexpr uint8_t MAX_PAGE_SLOTS = 4; // One per font style (R/B/I/BI)
FontDecompressor() = default;
~FontDecompressor();
bool init();
void deinit();
// Returns pointer to decompressed bitmap data for the given glyph.
// Checks the page buffer (from prewarm) first, then falls back to the hot group slot.
const uint8_t* getBitmap(const EpdFontData* fontData, const EpdGlyph* glyph, uint32_t glyphIndex);
// Free all cached data (page buffer + hot group).
void clearCache();
// Pre-scan UTF-8 text and extract needed glyph bitmaps into a flat page buffer.
// Each group is decompressed once into a temp buffer; only needed glyphs are kept.
// Returns the number of glyphs that couldn't be loaded (0 on full success).
int prewarmCache(const EpdFontData* fontData, const char* utf8Text);
struct Stats {
uint32_t cacheHits = 0;
uint32_t cacheMisses = 0;
uint32_t decompressTimeMs = 0;
uint16_t uniqueGroupsAccessed = 0;
uint32_t pageBufferBytes = 0; // pageBuffer allocation
uint32_t pageGlyphsBytes = 0; // pageGlyphs lookup table allocation
uint32_t hotGroupBytes = 0; // current hot group allocation
uint32_t peakTempBytes = 0; // largest temp buffer in prewarm
uint32_t getBitmapTimeUs = 0; // cumulative getBitmap time (micros)
uint32_t getBitmapCalls = 0; // number of getBitmap calls
};
void logStats(const char* label = "FDC");
void resetStats();
const Stats& getStats() const { return stats; }
private:
Stats stats;
InflateReader inflateReader;
// Page buffer slots: each style gets its own flat glyph buffer with sorted lookup.
// Up to MAX_PAGE_SLOTS (4) styles can be prewarmed simultaneously.
struct PageGlyphEntry {
uint32_t glyphIndex;
uint32_t bufferOffset;
uint32_t alignedOffset; // byte-aligned offset within its decompressed group (set during prewarm pre-scan)
};
struct PageSlot {
uint8_t* buffer = nullptr;
const EpdFontData* fontData = nullptr;
PageGlyphEntry* glyphs = nullptr;
uint16_t glyphCount = 0;
};
PageSlot pageSlots[MAX_PAGE_SLOTS] = {};
uint8_t pageSlotCount = 0;
static constexpr uint16_t HOT_GLYPH_BUF_SIZE = 512; // largest packed single glyph
// Hot group: single heap buffer sized to the largest group of the active font.
// Allocated once per font (lazily in ensureHotGroupBuf) and freed in freeHotGroup().
// Single malloc per font session — no repeated alloc/free per page.
const EpdFontData* hotGroupFont = nullptr;
uint16_t hotGroupIndex = UINT16_MAX;
uint32_t _hotGroupBufUsed = 0;
uint8_t* _hotGroupBuf = nullptr;
uint32_t _hotGroupBufSize = 0;
// Scratch buffer for compacting a single glyph out of the byte-aligned hot group.
// Valid until the next getBitmap() call.
uint8_t _hotGlyphBuf[HOT_GLYPH_BUF_SIZE];
void freePageBuffer();
void freeHotGroup();
bool ensureHotGroupBuf(const EpdFontData* fontData);
uint16_t getGroupIndex(const EpdFontData* fontData, uint32_t glyphIndex);
uint32_t getAlignedOffset(const EpdFontData* fontData, uint16_t groupIndex, uint32_t glyphIndex);
bool decompressGroup(const EpdFontData* fontData, uint16_t groupIndex, uint8_t* outBuf, uint32_t outSize);
static void compactSingleGlyph(const uint8_t* alignedSrc, uint8_t* packedDst, uint8_t width, uint8_t height);
static int32_t findGlyphIndex(const EpdFontData* fontData, uint32_t codepoint);
};