mirror of
https://github.com/crosspoint-reader/crosspoint-reader.git
synced 2026-08-13 14:21:57 -07:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27c4be8a34 |
No files matched your search
+74
-19
@@ -99,8 +99,11 @@ void SdCardFont::freeStyleMiniData(PerStyle& s) {
|
||||
s.miniIntervals = nullptr;
|
||||
delete[] s.miniGlyphs;
|
||||
s.miniGlyphs = nullptr;
|
||||
delete[] s.miniBitmap;
|
||||
s.miniBitmap = nullptr;
|
||||
for (auto& chunk : s.miniBitmapChunks) {
|
||||
delete[] chunk;
|
||||
chunk = nullptr;
|
||||
}
|
||||
s.miniBitmapChunkCount = 0;
|
||||
s.miniIntervalCount = 0;
|
||||
s.miniGlyphCount = 0;
|
||||
s.miniIntervalCapacity = 0;
|
||||
@@ -1010,30 +1013,66 @@ int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint3
|
||||
totalBitmapSize += s.miniGlyphs[i].dataLength;
|
||||
}
|
||||
|
||||
if (!ensureArrayCapacity(s.miniBitmap, s.miniBitmapCapacity, totalBitmapSize)) {
|
||||
LOG_ERR("SDCF", "Failed to allocate mini bitmap (%u bytes) for style %u", totalBitmapSize, styleIdx);
|
||||
delete[] readOrder;
|
||||
delete[] mappings;
|
||||
freeStyleMiniData(s);
|
||||
return static_cast<int>(cpCount);
|
||||
}
|
||||
s.miniBitmapUsed = totalBitmapSize; // underuse-hysteresis signal for resetStyleMiniData
|
||||
|
||||
// Read bitmap data sorted by file offset
|
||||
std::sort(readOrder, readOrder + validCount,
|
||||
[&](uint32_t a, uint32_t b) { return s.miniGlyphs[a].dataOffset < s.miniGlyphs[b].dataOffset; });
|
||||
|
||||
uint32_t miniBitmapOffset = 0;
|
||||
// Assemble the bitmap arena from on-demand 4 KB chunks (see MINI_BM_CHUNK_*).
|
||||
// `span` is the running virtual offset into the arena; each glyph is placed
|
||||
// wholly within one chunk, skipping past a boundary rather than straddling it.
|
||||
// glyph.dataOffset becomes the virtual offset, decoded at render time by
|
||||
// miniGlyphBitmap(). Chunks are allocated only as span reaches them, so a
|
||||
// sparse page never allocates the full ceiling.
|
||||
uint32_t span = 0;
|
||||
uint32_t lastBitmapEnd = UINT32_MAX;
|
||||
for (uint32_t i = 0; i < validCount; i++) {
|
||||
uint32_t mapIdx = readOrder[i];
|
||||
EpdGlyph& glyph = s.miniGlyphs[mapIdx];
|
||||
const uint32_t len = glyph.dataLength;
|
||||
|
||||
if (glyph.dataLength == 0) {
|
||||
glyph.dataOffset = miniBitmapOffset;
|
||||
if (len == 0) {
|
||||
glyph.dataOffset = span;
|
||||
continue;
|
||||
}
|
||||
if (len > MINI_BM_CHUNK_SIZE) {
|
||||
// A single glyph larger than one chunk cannot be placed. Real fonts never
|
||||
// hit this (a 4 KB 2bpp glyph would be enormous); bail rather than corrupt.
|
||||
LOG_ERR("SDCF", "Prewarm: glyph %u B exceeds chunk %u B (style %u)", len, MINI_BM_CHUNK_SIZE, styleIdx);
|
||||
file.close();
|
||||
delete[] readOrder;
|
||||
delete[] mappings;
|
||||
freeStyleMiniData(s);
|
||||
return static_cast<int>(cpCount);
|
||||
}
|
||||
|
||||
// Skip to the next chunk boundary if this glyph would straddle one.
|
||||
const uint32_t within = span & (MINI_BM_CHUNK_SIZE - 1);
|
||||
if (within != 0 && within + len > MINI_BM_CHUNK_SIZE) {
|
||||
span += MINI_BM_CHUNK_SIZE - within;
|
||||
}
|
||||
const uint32_t chunkIdx = span >> MINI_BM_CHUNK_SHIFT;
|
||||
if (chunkIdx >= MINI_BM_MAX_CHUNKS) {
|
||||
LOG_ERR("SDCF", "Prewarm: mini bitmap needs > %u chunks (style %u)", MINI_BM_MAX_CHUNKS, styleIdx);
|
||||
file.close();
|
||||
delete[] readOrder;
|
||||
delete[] mappings;
|
||||
freeStyleMiniData(s);
|
||||
return static_cast<int>(cpCount);
|
||||
}
|
||||
if (!s.miniBitmapChunks[chunkIdx]) {
|
||||
s.miniBitmapChunks[chunkIdx] = new (std::nothrow) uint8_t[MINI_BM_CHUNK_SIZE];
|
||||
if (!s.miniBitmapChunks[chunkIdx]) {
|
||||
LOG_ERR("SDCF", "Failed to allocate mini bitmap chunk %u (style %u)", chunkIdx, styleIdx);
|
||||
file.close();
|
||||
delete[] readOrder;
|
||||
delete[] mappings;
|
||||
freeStyleMiniData(s);
|
||||
return static_cast<int>(cpCount);
|
||||
}
|
||||
if (chunkIdx + 1 > s.miniBitmapChunkCount) s.miniBitmapChunkCount = chunkIdx + 1;
|
||||
}
|
||||
|
||||
const uint32_t off = span & (MINI_BM_CHUNK_SIZE - 1);
|
||||
uint32_t fileOff = s.bitmapFileOffset + glyph.dataOffset;
|
||||
if (fileOff != lastBitmapEnd) {
|
||||
if (!file.seekSet(fileOff)) {
|
||||
@@ -1046,18 +1085,21 @@ int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint3
|
||||
}
|
||||
seekCount++;
|
||||
}
|
||||
if (file.read(s.miniBitmap + miniBitmapOffset, glyph.dataLength) != static_cast<int>(glyph.dataLength)) {
|
||||
if (file.read(s.miniBitmapChunks[chunkIdx] + off, len) != static_cast<int>(len)) {
|
||||
LOG_ERR("SDCF", "Prewarm: short bitmap read (style %u)", styleIdx);
|
||||
delete[] readOrder;
|
||||
delete[] mappings;
|
||||
freeStyleMiniData(s);
|
||||
return static_cast<int>(cpCount);
|
||||
}
|
||||
lastBitmapEnd = fileOff + glyph.dataLength;
|
||||
lastBitmapEnd = fileOff + len;
|
||||
|
||||
glyph.dataOffset = miniBitmapOffset;
|
||||
miniBitmapOffset += glyph.dataLength;
|
||||
glyph.dataOffset = span;
|
||||
span += len;
|
||||
}
|
||||
// Footprint including inter-chunk padding: the underuse-hysteresis signal.
|
||||
s.miniBitmapUsed = span;
|
||||
s.miniBitmapCapacity = s.miniBitmapChunkCount * MINI_BM_CHUNK_SIZE;
|
||||
}
|
||||
|
||||
uint32_t sdTime = millis() - sdStart;
|
||||
@@ -1080,7 +1122,10 @@ int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint3
|
||||
s.miniMetadataOnly = metadataOnly;
|
||||
s.miniHysteresisPending = !metadataOnly; // one hysteresis evaluation per rebuild
|
||||
memset(&s.miniData, 0, sizeof(s.miniData));
|
||||
s.miniData.bitmap = s.miniBitmap;
|
||||
// The mini bitmap is chunked (non-contiguous), so there is no single base
|
||||
// pointer. Prewarmed SD glyphs are resolved via SdCardFont::miniGlyphBitmap()
|
||||
// in the renderer; leave bitmap null so any stray &bitmap[dataOffset] faults.
|
||||
s.miniData.bitmap = nullptr;
|
||||
s.miniData.glyph = s.miniGlyphs;
|
||||
s.miniData.intervals = s.miniIntervals;
|
||||
s.miniData.intervalCount = s.miniIntervalCount;
|
||||
@@ -1517,4 +1562,14 @@ const uint8_t* SdCardFont::getOverflowBitmap(const EpdGlyph* glyph) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const uint8_t* SdCardFont::miniGlyphBitmap(const void* ctx, uint32_t dataOffset) const {
|
||||
const auto* octx = static_cast<const OverflowContext*>(ctx);
|
||||
const PerStyle& s = styles_[octx->styleIdx];
|
||||
const uint32_t chunkIdx = dataOffset >> MINI_BM_CHUNK_SHIFT;
|
||||
if (chunkIdx >= s.miniBitmapChunkCount) return nullptr;
|
||||
const uint8_t* chunk = s.miniBitmapChunks[chunkIdx];
|
||||
if (!chunk) return nullptr;
|
||||
return chunk + (dataOffset & (MINI_BM_CHUNK_SIZE - 1));
|
||||
}
|
||||
|
||||
SdCardFont* SdCardFont::fromMissCtx(void* ctx) { return static_cast<OverflowContext*>(ctx)->self; }
|
||||
@@ -94,6 +94,12 @@ class SdCardFont {
|
||||
// Returns the bitmap for an on-demand-loaded (overflow) glyph.
|
||||
const uint8_t* getOverflowBitmap(const EpdGlyph* glyph) const;
|
||||
|
||||
// Resolves a prewarmed mini glyph's chunked bitmap. `ctx` is the glyphMissCtx
|
||||
// (an OverflowContext identifying the style); `dataOffset` is the glyph's
|
||||
// virtual offset into the style's chunked arena. Returns nullptr if the chunk
|
||||
// is absent or out of range. Called by GfxRenderer::getGlyphBitmap().
|
||||
const uint8_t* miniGlyphBitmap(const void* ctx, uint32_t dataOffset) const;
|
||||
|
||||
// Extract SdCardFont* from an opaque glyphMissCtx pointer.
|
||||
// Used by GfxRenderer::getGlyphBitmap() to recover the SdCardFont from EpdFontData::glyphMissCtx.
|
||||
static SdCardFont* fromMissCtx(void* ctx);
|
||||
@@ -129,6 +135,18 @@ class SdCardFont {
|
||||
uint8_t ligaturePairCount = 0;
|
||||
};
|
||||
|
||||
// The per-style mini bitmap arena is stored as a list of fixed-size chunks
|
||||
// rather than one contiguous block. A whole page's 2bpp glyph bitmaps can run
|
||||
// tens of KB; on a fragmented heap a single contiguous allocation of that size
|
||||
// fails even when the same bytes are available as several smaller free blocks.
|
||||
// Chunking lets the arena be assembled from blocks the allocator can actually
|
||||
// provide. Each glyph's bitmap is placed wholly within one chunk (never
|
||||
// straddling a boundary), so a glyph is addressed by a virtual offset that
|
||||
// maps to (chunk index, offset-in-chunk) via miniGlyphBitmap().
|
||||
static constexpr uint32_t MINI_BM_CHUNK_SHIFT = 12; // 4 KB chunks
|
||||
static constexpr uint32_t MINI_BM_CHUNK_SIZE = 1u << MINI_BM_CHUNK_SHIFT;
|
||||
static constexpr uint32_t MINI_BM_MAX_CHUNKS = 24; // 96 KB ceiling per style/page
|
||||
|
||||
// All per-style data: file offsets, intervals, kern/lig, prewarm cache, EpdFont
|
||||
struct PerStyle {
|
||||
CpFontHeader header{};
|
||||
@@ -188,7 +206,10 @@ class SdCardFont {
|
||||
EpdFontData miniData{};
|
||||
EpdUnicodeInterval* miniIntervals = nullptr;
|
||||
EpdGlyph* miniGlyphs = nullptr;
|
||||
uint8_t* miniBitmap = nullptr;
|
||||
// Chunked mini bitmap arena (see MINI_BM_CHUNK_* above). Chunks are allocated
|
||||
// on demand during prewarm; miniBitmapChunkCount is how many are live.
|
||||
uint8_t* miniBitmapChunks[MINI_BM_MAX_CHUNKS] = {};
|
||||
uint32_t miniBitmapChunkCount = 0;
|
||||
uint32_t miniIntervalCount = 0;
|
||||
uint32_t miniGlyphCount = 0;
|
||||
uint32_t miniIntervalCapacity = 0;
|
||||
|
||||
@@ -81,6 +81,9 @@ const uint8_t* GfxRenderer::getGlyphBitmap(const EpdFontData* fontData, const Ep
|
||||
if (sdFont->isOverflowGlyph(glyph)) {
|
||||
return sdFont->getOverflowBitmap(glyph); // may be nullptr for zero-width glyphs
|
||||
}
|
||||
// Prewarmed SD glyph: the mini bitmap is chunked (non-contiguous), so there is
|
||||
// no fontData->bitmap base to index. Resolve via the per-style chunk table.
|
||||
return sdFont->miniGlyphBitmap(fontData->glyphMissCtx, glyph->dataOffset);
|
||||
}
|
||||
return &fontData->bitmap[glyph->dataOffset];
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user