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
+2
-1
@@ -29,5 +29,6 @@ lib/EpdFont/scripts/output/
|
||||
/.dummy
|
||||
/CMakeLists.txt
|
||||
/dependencies.lock
|
||||
/sdkconfig.*
|
||||
/sdkconfig.default
|
||||
/sdkconfig.defaults
|
||||
sdkconfig.sticky
|
||||
@@ -153,7 +153,6 @@ To convert your own TrueType/OpenType fonts:
|
||||
| `armenian` | Armenian |
|
||||
| `ethiopic` | Ethiopic + Extended |
|
||||
| `vietnamese` | Vietnamese subset (ơ/ư and combining marks) |
|
||||
| `ipa-chars` | IPA Extensions + Spacing Modifier Letters (phonetic transcription) |
|
||||
| `punctuation` | General punctuation (U+2000–U+206F) |
|
||||
| `cjk` | CJK Unified Ideographs + Hiragana + Katakana + Fullwidth |
|
||||
| `hangul` | Korean Hangul syllables + Jamo + Compatibility Jamo |
|
||||
|
||||
+1
-1
Submodule freeink-sdk updated: 8b8337b586...a485dc46ef.
@@ -369,11 +369,6 @@ int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8
|
||||
}
|
||||
stats.pageBufferBytes += totalBytes;
|
||||
stats.pageGlyphsBytes += glyphCount * sizeof(PageGlyphEntry);
|
||||
// MEMFIX-PORT: page-slot address landmark for the heap map; portable
|
||||
// Landmark for the heap block map: page slots are the largest flash-font
|
||||
// allocations and otherwise show up as anonymous ~4-20 KB used blocks.
|
||||
LOG_DBG("FDC", "page slot buffer=%p bytes=%u glyphs=%u", static_cast<void*>(slot.buffer), (unsigned)totalBytes,
|
||||
(unsigned)glyphCount);
|
||||
|
||||
slot.fontData = fontData;
|
||||
slot.glyphCount = glyphCount;
|
||||
|
||||
+26
-83
@@ -191,10 +191,6 @@ void SdCardFont::freeStyleAll(PerStyle& s) {
|
||||
void SdCardFont::freeAll() {
|
||||
clearOverflow();
|
||||
clearPersistentCache();
|
||||
delete[] overflowEmergencyBuf_;
|
||||
overflowEmergencyBuf_ = nullptr;
|
||||
overflowEmergencyCap_ = 0;
|
||||
overflowEmergency_ = {};
|
||||
for (uint8_t i = 0; i < MAX_STYLES; i++) {
|
||||
freeStyleAll(styles_[i]);
|
||||
}
|
||||
@@ -711,12 +707,6 @@ bool SdCardFont::load(const char* path) {
|
||||
applyGlyphMissCallback(i);
|
||||
}
|
||||
|
||||
// Reserve the overflow fallback buffer now, while the heap is at its healthiest
|
||||
// (font load runs before the per-page render allocations). Best-effort: if it
|
||||
// can't allocate, onGlyphMiss() simply keeps its per-glyph malloc path.
|
||||
overflowEmergencyBuf_ = new (std::nothrow) uint8_t[OVERFLOW_EMERGENCY_BYTES];
|
||||
overflowEmergencyCap_ = overflowEmergencyBuf_ ? OVERFLOW_EMERGENCY_BYTES : 0;
|
||||
|
||||
loaded_ = true;
|
||||
|
||||
LOG_DBG("SDCF", "Loaded: %s (v%u, %u styles)", path, CPFONT_VERSION, styleCount_);
|
||||
@@ -1023,21 +1013,20 @@ int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint3
|
||||
totalBitmapSize += s.miniGlyphs[i].dataLength;
|
||||
}
|
||||
|
||||
// Read bitmap data sorted by file offset (sequential glyphs then avoid a seek).
|
||||
// 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; });
|
||||
|
||||
// Place each glyph wholly within one fixed-size chunk (padding to the next chunk
|
||||
// when it wouldn't fit the remainder), so a large page's bitmap fits a fragmented
|
||||
// heap yet the render still gets a contiguous per-glyph pointer. `span` is the
|
||||
// byte offset across the logical chunk sequence and becomes glyph->dataOffset;
|
||||
// chunks are allocated on demand and kept across pages. Any per-chunk alloc
|
||||
// failure drops to the stub (overflow path), exactly as the old single-buffer
|
||||
// allocation did.
|
||||
// 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++) {
|
||||
const uint32_t mapIdx = readOrder[i];
|
||||
uint32_t mapIdx = readOrder[i];
|
||||
EpdGlyph& glyph = s.miniGlyphs[mapIdx];
|
||||
const uint32_t len = glyph.dataLength;
|
||||
|
||||
@@ -1046,16 +1035,17 @@ int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint3
|
||||
continue;
|
||||
}
|
||||
if (len > MINI_BM_CHUNK_SIZE) {
|
||||
// A glyph bigger than one chunk can't be placed contiguously. Reading fonts
|
||||
// never hit this; bail to the stub rather than corrupt the layout.
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Avoid straddling a chunk boundary.
|
||||
// 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;
|
||||
@@ -1063,6 +1053,7 @@ int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint3
|
||||
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);
|
||||
@@ -1072,6 +1063,7 @@ int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint3
|
||||
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);
|
||||
@@ -1081,7 +1073,7 @@ int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint3
|
||||
}
|
||||
|
||||
const uint32_t off = span & (MINI_BM_CHUNK_SIZE - 1);
|
||||
const uint32_t fileOff = s.bitmapFileOffset + glyph.dataOffset;
|
||||
uint32_t fileOff = s.bitmapFileOffset + glyph.dataOffset;
|
||||
if (fileOff != lastBitmapEnd) {
|
||||
if (!file.seekSet(fileOff)) {
|
||||
LOG_ERR("SDCF", "Prewarm: failed to seek to bitmap (style %u)", styleIdx);
|
||||
@@ -1105,7 +1097,8 @@ int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint3
|
||||
glyph.dataOffset = span;
|
||||
span += len;
|
||||
}
|
||||
s.miniBitmapUsed = span; // footprint incl. padding: underuse-hysteresis signal
|
||||
// Footprint including inter-chunk padding: the underuse-hysteresis signal.
|
||||
s.miniBitmapUsed = span;
|
||||
s.miniBitmapCapacity = s.miniBitmapChunkCount * MINI_BM_CHUNK_SIZE;
|
||||
}
|
||||
|
||||
@@ -1129,9 +1122,9 @@ 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));
|
||||
// Mini bitmap is chunked (non-contiguous) so there is no single base pointer.
|
||||
// SD mini glyphs are resolved via SdCardFont::miniGlyphBitmap() in the renderer;
|
||||
// leave bitmap null so any accidental &bitmap[dataOffset] deref faults loudly.
|
||||
// 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;
|
||||
@@ -1516,45 +1509,25 @@ const EpdGlyph* SdCardFont::onGlyphMiss(void* ctx, uint32_t codepoint) {
|
||||
|
||||
// Read bitmap data into temporary (if any)
|
||||
uint8_t* tempBitmap = nullptr;
|
||||
bool usingEmergency = false;
|
||||
if (tempGlyph.dataLength > 0) {
|
||||
tempBitmap = new (std::nothrow) uint8_t[tempGlyph.dataLength];
|
||||
if (!tempBitmap) {
|
||||
// Per-glyph malloc failed under heap pressure. Fall back to the pre-reserved
|
||||
// emergency buffer so the glyph still renders instead of blanking (the whole
|
||||
// last-loaded style, in practice bold-italic, otherwise disappears while BLE
|
||||
// is resident). Read directly into it; this path bypasses the cache ring.
|
||||
if (self->overflowEmergencyBuf_ && tempGlyph.dataLength <= self->overflowEmergencyCap_) {
|
||||
tempBitmap = self->overflowEmergencyBuf_;
|
||||
usingEmergency = true;
|
||||
} else {
|
||||
LOG_ERR("SDCF", "Overflow: failed to allocate %u bytes for U+%04X bitmap", tempGlyph.dataLength, codepoint);
|
||||
return nullptr;
|
||||
}
|
||||
LOG_ERR("SDCF", "Overflow: failed to allocate %u bytes for U+%04X bitmap", tempGlyph.dataLength, codepoint);
|
||||
return nullptr;
|
||||
}
|
||||
if (!file.seekSet(s.bitmapFileOffset + tempGlyph.dataOffset)) {
|
||||
LOG_ERR("SDCF", "Overflow: failed to seek to bitmap for U+%04X", codepoint);
|
||||
if (!usingEmergency) delete[] tempBitmap;
|
||||
delete[] tempBitmap;
|
||||
file.close();
|
||||
return nullptr;
|
||||
}
|
||||
if (file.read(tempBitmap, tempGlyph.dataLength) != static_cast<int>(tempGlyph.dataLength)) {
|
||||
LOG_ERR("SDCF", "Overflow: failed to read bitmap for U+%04X", codepoint);
|
||||
if (!usingEmergency) delete[] tempBitmap;
|
||||
delete[] tempBitmap;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Emergency-buffer path: return the glyph directly, without touching the ring
|
||||
// (the buffer is a single shared slot, overwritten on the next miss).
|
||||
if (usingEmergency) {
|
||||
self->overflowEmergency_.glyph = tempGlyph;
|
||||
self->overflowEmergency_.bitmap = tempBitmap;
|
||||
self->overflowEmergency_.codepoint = codepoint;
|
||||
self->overflowEmergency_.styleIdx = styleIdx;
|
||||
return &self->overflowEmergency_.glyph;
|
||||
}
|
||||
|
||||
// All reads succeeded — commit to slot and advance ring buffer
|
||||
if (wasAtCapacity) {
|
||||
delete[] self->overflow_[slot].bitmap;
|
||||
@@ -1567,42 +1540,13 @@ const EpdGlyph* SdCardFont::onGlyphMiss(void* ctx, uint32_t codepoint) {
|
||||
self->overflow_[slot].codepoint = codepoint;
|
||||
self->overflow_[slot].styleIdx = styleIdx;
|
||||
|
||||
// No per-glyph log here: when the prewarm mini-data doesn't cover a page every
|
||||
// glyph takes this path, which floods the console. Overflow-heavy pages are
|
||||
// instead visible as slow renders.
|
||||
LOG_DBG("SDCF", "Overflow: loaded U+%04X style %u on demand (slot %u/%u)", codepoint, styleIdx, slot,
|
||||
OVERFLOW_CAPACITY);
|
||||
|
||||
return &self->overflow_[slot].glyph;
|
||||
}
|
||||
|
||||
size_t SdCardFont::reportMemory() const {
|
||||
size_t total = 0;
|
||||
for (uint8_t si = 0; si < MAX_STYLES; ++si) {
|
||||
const auto& s = styles_[si];
|
||||
if (!s.present) continue;
|
||||
size_t fixed = 0; // loaded once per family: interval/kern/lig tables
|
||||
if (s.fullIntervals) fixed += s.header.intervalCount * sizeof(EpdUnicodeInterval);
|
||||
if (s.bmpIntervals) fixed += s.header.intervalCount * sizeof(PerStyle::BmpInterval16);
|
||||
if (s.kernLeftClasses) fixed += s.header.kernLeftEntryCount * sizeof(EpdKernClassEntry);
|
||||
if (s.kernRightClasses) fixed += s.header.kernRightEntryCount * sizeof(EpdKernClassEntry);
|
||||
if (s.ligaturePairs) fixed += s.header.ligaturePairCount * sizeof(EpdLigaturePair);
|
||||
// kept-if-fits mini arenas: capacity (not count) is what stays resident
|
||||
size_t mini = s.miniIntervalCapacity * sizeof(EpdUnicodeInterval) + s.miniGlyphCapacity * sizeof(EpdGlyph) +
|
||||
s.miniBitmapCapacity + s.miniKernLeftCapacity * sizeof(EpdKernClassEntry) +
|
||||
s.miniKernRightCapacity * sizeof(EpdKernClassEntry) + s.miniKernMatrixCapacity;
|
||||
const size_t adv = advanceTableSize_[si] * sizeof(AdvanceEntry);
|
||||
LOG_DBG("SDCF", "mem style%u: fixed=%u mini=%u adv=%u", si, (unsigned)fixed, (unsigned)mini, (unsigned)adv);
|
||||
total += fixed + mini + adv;
|
||||
}
|
||||
size_t overflowBytes = 0;
|
||||
for (uint32_t i = 0; i < overflowCount_; ++i) {
|
||||
if (overflow_[i].bitmap) overflowBytes += overflow_[i].glyph.dataLength;
|
||||
}
|
||||
total += overflowBytes + overflowCount_ * sizeof(OverflowEntry);
|
||||
return total;
|
||||
}
|
||||
|
||||
bool SdCardFont::isOverflowGlyph(const EpdGlyph* glyph) const {
|
||||
if (glyph == &overflowEmergency_.glyph) return true;
|
||||
for (uint32_t i = 0; i < overflowCount_; i++) {
|
||||
if (&overflow_[i].glyph == glyph) return true;
|
||||
}
|
||||
@@ -1610,7 +1554,6 @@ bool SdCardFont::isOverflowGlyph(const EpdGlyph* glyph) const {
|
||||
}
|
||||
|
||||
const uint8_t* SdCardFont::getOverflowBitmap(const EpdGlyph* glyph) const {
|
||||
if (glyph == &overflowEmergency_.glyph) return overflowEmergency_.bitmap;
|
||||
for (uint32_t i = 0; i < overflowCount_; i++) {
|
||||
if (&overflow_[i].glyph == glyph) {
|
||||
return overflow_[i].bitmap;
|
||||
|
||||
+18
-41
@@ -94,12 +94,10 @@ class SdCardFont {
|
||||
// Returns the bitmap for an on-demand-loaded (overflow) glyph.
|
||||
const uint8_t* getOverflowBitmap(const EpdGlyph* glyph) const;
|
||||
|
||||
// Resolve a prewarmed mini glyph's bitmap. The mini bitmap is chunked (see
|
||||
// MINI_BM_* above), so glyph->dataOffset is a byte offset across the fixed-size
|
||||
// chunk sequence; each glyph lives wholly within one chunk. ctx is the glyph's
|
||||
// EpdFontData::glyphMissCtx (an OverflowContext carrying the style index). Called
|
||||
// by GfxRenderer::getGlyphBitmap for SD-card mini glyphs. Returns nullptr if the
|
||||
// chunk isn't resident.
|
||||
// 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.
|
||||
@@ -113,11 +111,6 @@ class SdCardFont {
|
||||
uint32_t uniqueGlyphs = 0;
|
||||
uint32_t bitmapBytes = 0;
|
||||
};
|
||||
// MEMFIX-PORT: SD font resident-bytes audit; portable
|
||||
// Log per-style resident heap (full tables + kept-if-fits mini arenas +
|
||||
// advance tables + overflow bitmaps) and return the total in bytes. Pure
|
||||
// accounting — no allocation, no state change.
|
||||
size_t reportMemory() const;
|
||||
void logStats(const char* label = "SDCF");
|
||||
void resetStats();
|
||||
const Stats& getStats() const { return stats_; }
|
||||
@@ -142,15 +135,17 @@ class SdCardFont {
|
||||
uint8_t ligaturePairCount = 0;
|
||||
};
|
||||
|
||||
// Per-page prewarm bitmap chunking. A page of a large 2-bpp font needs a
|
||||
// 20-40 KB bitmap; as one contiguous block it fails to allocate on a fragmented,
|
||||
// BLE-resident heap, dropping the whole style to the slow overflow path. Splitting
|
||||
// it into fixed 4 KB chunks (each glyph kept whole within one chunk) lets it fit
|
||||
// scattered free space, so prewarm succeeds and the render still gets a contiguous
|
||||
// per-glyph pointer. Mirrors the image .pxc slot's chunking.
|
||||
// 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 max per-style page bitmap
|
||||
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 {
|
||||
@@ -211,16 +206,15 @@ class SdCardFont {
|
||||
EpdFontData miniData{};
|
||||
EpdUnicodeInterval* miniIntervals = nullptr;
|
||||
EpdGlyph* miniGlyphs = nullptr;
|
||||
// Chunked mini bitmap (see MINI_BM_* above): fixed-size chunks so a large page's
|
||||
// bitmap fits a fragmented heap. Each glyph is placed wholly within one chunk;
|
||||
// EpdGlyph::dataOffset is a byte offset across the logical chunk sequence.
|
||||
// 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; // chunks currently allocated (0..count-1), kept across pages
|
||||
uint32_t miniBitmapChunkCount = 0;
|
||||
uint32_t miniIntervalCount = 0;
|
||||
uint32_t miniGlyphCount = 0;
|
||||
uint32_t miniIntervalCapacity = 0;
|
||||
uint32_t miniGlyphCapacity = 0;
|
||||
uint32_t miniBitmapCapacity = 0; // miniBitmapChunkCount * MINI_BM_CHUNK_SIZE (hysteresis/report)
|
||||
uint32_t miniBitmapCapacity = 0;
|
||||
// Bitmap bytes the current page actually used (set by prewarmStyle), the
|
||||
// underuse-hysteresis signal; 0 = no bitmap built this scope (metadata-only
|
||||
// prewarm), which leaves the hysteresis counter untouched.
|
||||
@@ -270,10 +264,7 @@ class SdCardFont {
|
||||
};
|
||||
OverflowContext overflowCtx_[MAX_STYLES] = {};
|
||||
|
||||
// Shared on-demand overflow ring (glyphs loaded via glyphMissHandler) -- the
|
||||
// fallback for glyphs the per-page prewarm didn't cover. Kept small: the chunked
|
||||
// mini-bitmap (below) lets prewarm succeed on a fragmented heap, so this ring
|
||||
// only serves the rare uncovered glyph rather than a whole page.
|
||||
// Shared on-demand overflow buffer (ring buffer of glyphs loaded via glyphMissHandler)
|
||||
static constexpr uint32_t OVERFLOW_CAPACITY = 8;
|
||||
struct OverflowEntry {
|
||||
EpdGlyph glyph;
|
||||
@@ -285,20 +276,6 @@ class SdCardFont {
|
||||
uint32_t overflowCount_ = 0;
|
||||
uint32_t overflowNext_ = 0;
|
||||
|
||||
// Single pre-reserved fallback buffer for the on-demand overflow path.
|
||||
// onGlyphMiss() normally mallocs each glyph's bitmap; under heap pressure (BLE
|
||||
// resident) that per-glyph malloc fails and the glyph -- and thus the whole
|
||||
// last-loaded style, in practice bold-italic -- renders blank. This buffer is
|
||||
// reserved once at load(), when the heap is healthy. When the per-glyph malloc
|
||||
// fails, the current glyph is read into this buffer and rendered directly. It is
|
||||
// NOT part of the cache ring above (it is overwritten on every use), which is
|
||||
// fine: getGlyphBitmap() consumes the bitmap immediately after the miss handler
|
||||
// returns, before the next glyph is fetched.
|
||||
static constexpr uint32_t OVERFLOW_EMERGENCY_BYTES = 1024;
|
||||
uint8_t* overflowEmergencyBuf_ = nullptr;
|
||||
uint32_t overflowEmergencyCap_ = 0;
|
||||
OverflowEntry overflowEmergency_ = {};
|
||||
|
||||
// Compact advance-only table for layout measurement (per-style).
|
||||
// Built by buildAdvanceTable(), queried by getAdvance().
|
||||
struct AdvanceEntry {
|
||||
|
||||
@@ -107,14 +107,6 @@ void SdCardFontManager::unloadAll(GfxRenderer& renderer) {
|
||||
loadedPointSize_ = 0;
|
||||
}
|
||||
|
||||
size_t SdCardFontManager::reportMemory() const {
|
||||
size_t total = 0;
|
||||
for (const auto& lf : loaded_) {
|
||||
if (lf.font) total += lf.font->reportMemory();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
int SdCardFontManager::getFontId(const std::string& familyName) const {
|
||||
if (familyName != loadedFamilyName_ || loaded_.empty()) return 0;
|
||||
return loaded_.front().fontId;
|
||||
|
||||
@@ -39,10 +39,6 @@ class SdCardFontManager {
|
||||
// Get name of currently loaded family (empty if none).
|
||||
const std::string& currentFamilyName() const { return loadedFamilyName_; };
|
||||
|
||||
// MEMFIX-PORT: font manager audit passthrough; portable
|
||||
// Sum of loaded fonts' resident heap (see SdCardFont::reportMemory).
|
||||
size_t reportMemory() const;
|
||||
|
||||
// Point size that was actually loaded.
|
||||
// 0 if nothing loaded.
|
||||
uint8_t currentPointSize() const { return loadedPointSize_; };
|
||||
|
||||
@@ -49,7 +49,6 @@ INTERVAL_PRESETS = {
|
||||
"armenian": [(0x0530, 0x058F)],
|
||||
"ethiopic": [(0x1200, 0x137F), (0x1380, 0x139F), (0x2D80, 0x2DDF)],
|
||||
"vietnamese": [(0x01A0, 0x01B0), (0x1EA0, 0x1EF9)],
|
||||
"ipa-chars": [(0x0250, 0x02AF), (0x02B0, 0x02FF)],
|
||||
"punctuation": [(0x2000, 0x206F)],
|
||||
"cjk": [(0x3000, 0x303F), (0x3040, 0x309F), (0x30A0, 0x30FF),
|
||||
(0x4E00, 0x9FFF), (0xF900, 0xFAFF), (0xFF00, 0xFFEF)],
|
||||
|
||||
@@ -35,7 +35,7 @@ families:
|
||||
|
||||
- name: Literata
|
||||
description: "Screen-optimized serif (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic,ipa-chars
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/googlefonts/literata/main/fonts/ttf/Literata-Regular.ttf"}
|
||||
@@ -45,7 +45,7 @@ families:
|
||||
|
||||
- name: SourceSerif4
|
||||
description: "Adobe transitional serif (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic,ipa-chars
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/adobe-fonts/source-serif/release/TTF/SourceSerif4-Regular.ttf"}
|
||||
@@ -55,7 +55,7 @@ families:
|
||||
|
||||
- name: NotoSerifExtended
|
||||
description: "Serif (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic,ipa-chars
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/notofonts/NotoSerif/main/fonts/ttf/unhinted/instance_ttf/NotoSerif-Regular.ttf"}
|
||||
@@ -65,7 +65,7 @@ families:
|
||||
|
||||
- name: Merriweather
|
||||
description: "Warm serif for long-form reading (Latin, Cyrillic)"
|
||||
intervals: latin-ext,cyrillic,ipa-chars
|
||||
intervals: latin-ext,cyrillic
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/SorkinType/Merriweather/master/fonts/ttf/Merriweather-Regular.ttf"}
|
||||
@@ -75,7 +75,7 @@ families:
|
||||
|
||||
- name: Lora
|
||||
description: "Calligraphic serif for literary reading (Latin, Cyrillic)"
|
||||
intervals: latin-ext,cyrillic,ipa-chars
|
||||
intervals: latin-ext,cyrillic
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/cyrealtype/Lora-Cyrillic/main/fonts/ttf/Lora-Regular.ttf"}
|
||||
@@ -85,7 +85,7 @@ families:
|
||||
|
||||
- name: GentiumBookPlus
|
||||
description: "Scholarly serif with wide Unicode coverage (Latin, Greek, Cyrillic, IPA)"
|
||||
intervals: latin-ext,greek,cyrillic,ipa-chars
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/gentiumbookplus/GentiumBookPlus-Regular.ttf"}
|
||||
@@ -95,7 +95,7 @@ families:
|
||||
|
||||
- name: IBMPlexSerif
|
||||
description: "Professional serif (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic,ipa-chars
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/ibmplexserif/IBMPlexSerif-Regular.ttf"}
|
||||
@@ -105,7 +105,7 @@ families:
|
||||
|
||||
- name: Bitter
|
||||
description: "Slab serif designed for e-ink (Latin, Cyrillic)"
|
||||
intervals: latin-ext,cyrillic,ipa-chars
|
||||
intervals: latin-ext,cyrillic
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/bitter/Bitter%5Bwght%5D.ttf", variable: {wght: 500}}
|
||||
@@ -115,7 +115,7 @@ families:
|
||||
|
||||
- name: Domitian
|
||||
description: "A humanist serif for literary reading (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic,ipa-chars
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://mirrors.ctan.org/fonts/domitian/opentype/Domitian-Roman.otf"}
|
||||
@@ -125,7 +125,7 @@ families:
|
||||
|
||||
- name: LibreBaskerville
|
||||
description: "A serif reimplementing the classic Baskerville (Latin)"
|
||||
intervals: latin-ext,ipa-chars
|
||||
intervals: latin-ext
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/impallari/Libre-Baskerville/master/fonts/ttf/LibreBaskerville-Regular.ttf"}
|
||||
@@ -135,7 +135,7 @@ families:
|
||||
|
||||
- name: Vollkorn
|
||||
description: "A serif for bread and butter use by Friedrich Althausen (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic,ipa-chars
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/vollkorn/Vollkorn%5Bwght%5D.ttf", variable: {wght: 400}}
|
||||
@@ -147,7 +147,7 @@ families:
|
||||
|
||||
- name: NotoSansExtended
|
||||
description: "Sans-serif (Latin, Greek, Cyrillic, Georgian, Armenian, Ethiopic)"
|
||||
intervals: latin-ext,greek,cyrillic,georgian,armenian,ethiopic,ipa-chars
|
||||
intervals: latin-ext,greek,cyrillic,georgian,armenian,ethiopic
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {path: "builtinFonts/source/NotoSans/NotoSans-Regular.ttf"}
|
||||
@@ -157,7 +157,7 @@ families:
|
||||
|
||||
- name: Inter
|
||||
description: "Modern sans-serif (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic,ipa-chars
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/inter/Inter%5Bopsz%2Cwght%5D.ttf", variable: {wght: 400, opsz: 14}}
|
||||
@@ -167,7 +167,7 @@ families:
|
||||
|
||||
- name: SourceSans3
|
||||
description: "Adobe humanist sans-serif (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic,ipa-chars
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/adobe-fonts/source-sans/release/TTF/SourceSans3-Regular.ttf"}
|
||||
@@ -177,7 +177,7 @@ families:
|
||||
|
||||
- name: IBMPlexSans
|
||||
description: "IBM corporate sans-serif (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic,ipa-chars
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/IBM/plex/master/packages/plex-sans/fonts/complete/ttf/IBMPlexSans-Regular.ttf"}
|
||||
@@ -187,7 +187,7 @@ families:
|
||||
|
||||
- name: Alegreya
|
||||
description: "Calligraphic serif/display (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic,ipa-chars
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/alegreya/Alegreya%5Bwght%5D.ttf", variable: {wght: 400}}
|
||||
@@ -199,7 +199,7 @@ families:
|
||||
|
||||
- name: IBMPlexMono
|
||||
description: "Monospace for code and technical reading (Latin, Greek, Cyrillic)"
|
||||
intervals: latin-ext,greek,cyrillic,ipa-chars
|
||||
intervals: latin-ext,greek,cyrillic
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/google/fonts/main/ofl/ibmplexmono/IBMPlexMono-Regular.ttf"}
|
||||
@@ -209,7 +209,7 @@ families:
|
||||
|
||||
- name: SourceCodePro
|
||||
description: "Adobe monospace with excellent hinting (Latin)"
|
||||
intervals: latin-ext,ipa-chars
|
||||
intervals: latin-ext
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/adobe-fonts/source-code-pro/release/TTF/SourceCodePro-Regular.ttf"}
|
||||
@@ -221,7 +221,7 @@ families:
|
||||
|
||||
- name: OpenDyslexic
|
||||
description: "Dyslexia-friendly font (Latin)"
|
||||
intervals: latin-ext,ipa-chars
|
||||
intervals: latin-ext
|
||||
sizes: [8, 10, 12, 14]
|
||||
styles:
|
||||
regular: {path: "builtinFonts/source/OpenDyslexic/OpenDyslexic-Regular.otf"}
|
||||
@@ -233,7 +233,7 @@ families:
|
||||
|
||||
- name: AtkinsonHyperlegibleNext
|
||||
description: "Accessibility font for low vision (Latin)"
|
||||
intervals: latin-ext,ipa-chars
|
||||
intervals: latin-ext
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/googlefonts/atkinson-hyperlegible-next/main/fonts/ttf/AtkinsonHyperlegibleNext-Regular.ttf"}
|
||||
@@ -243,7 +243,7 @@ families:
|
||||
|
||||
- name: LexicaUltralegible
|
||||
description: "Accessibility font for low vision / dyslexia (Latin)"
|
||||
intervals: latin-ext,ipa-chars
|
||||
intervals: latin-ext
|
||||
sizes: [12, 14, 16, 18]
|
||||
styles:
|
||||
regular: {url: "https://raw.githubusercontent.com/jacobxperez/lexica-ultralegible/main/fonts/ttf/LexicaUltralegible-Regular.ttf"}
|
||||
|
||||
@@ -44,18 +44,6 @@ class Epub {
|
||||
}
|
||||
~Epub() = default;
|
||||
std::string& getBasePath() { return contentBasePath; }
|
||||
// MEMFIX-PORT: epub resident-bytes audit accessor; portable
|
||||
// Approximate resident heap of the open book (audit): path strings, the CSS
|
||||
// file list, and the parsed stylesheet. BookMetadataCache is file-backed
|
||||
// (counts + HalFile handles) and contributes little.
|
||||
size_t residentBytes() const {
|
||||
size_t total = sizeof(Epub) + tocNcxItem.capacity() + tocNavItem.capacity() + filepath.capacity() +
|
||||
contentBasePath.capacity() + cachePath.capacity();
|
||||
for (const auto& f : cssFiles) total += sizeof(f) + (f.capacity() > 15 ? f.capacity() : 0);
|
||||
if (cssParser) total += cssParser->residentBytes();
|
||||
return total;
|
||||
}
|
||||
size_t cssRuleCount() const { return cssParser ? cssParser->ruleCount() : 0; }
|
||||
bool load(bool buildIfMissing = true, bool skipLoadingCss = false);
|
||||
bool clearCache() const;
|
||||
void setupCacheDir() const;
|
||||
|
||||
@@ -173,10 +173,6 @@ std::unique_ptr<Page> Page::deserialize(HalFile& file) {
|
||||
|
||||
uint16_t count;
|
||||
serialization::readPod(file, count);
|
||||
// Reserve up front: growth-by-doubling needs old + new capacity live at once and
|
||||
// reallocates repeatedly — a field crash (bad_alloc -> abort under -fno-exceptions)
|
||||
// hit exactly this append path on a heavily fragmented heap.
|
||||
page->elements.reserve(count);
|
||||
|
||||
// Reserve up front so a page load costs one allocation for the element vector
|
||||
// instead of a grow-copy-free cycle every doubling. `count` is untrusted (it
|
||||
|
||||
@@ -681,76 +681,6 @@ static inline bool isCjkIdeograph(uint32_t cp) {
|
||||
(cp >= 0x20000 && cp <= 0x3FFFF);
|
||||
}
|
||||
|
||||
// The first word of a line may have its ruby characters wider than the word (the base text). In that case, we need to
|
||||
// move the base text to the right a bit so that ruby text doesn't overflow the left border, and it is still centered
|
||||
// over the base text. This function calculates how much we need to move the base text to the right.
|
||||
int ParsedText::calculateRubyExtraStartOffset(const size_t wordIdx, const size_t maxWordIdx,
|
||||
const GfxRenderer& renderer, const int fontId) const {
|
||||
if (rubyTexts.empty() || wordIdx >= rubyTexts.size() || rubyTexts[wordIdx].empty() ||
|
||||
(wordStyles[wordIdx] & EpdFontFamily::RUBY_CONTINUE) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t groupWordCount = 1;
|
||||
while (wordIdx + groupWordCount < maxWordIdx &&
|
||||
(wordStyles[wordIdx + groupWordCount] & EpdFontFamily::RUBY_CONTINUE) != 0) {
|
||||
groupWordCount++;
|
||||
}
|
||||
int groupActualWidth = 0;
|
||||
for (size_t k = 0; k < groupWordCount; ++k) {
|
||||
groupActualWidth += measureWordWidth(renderer, fontId, words[wordIdx + k], wordStyles[wordIdx + k]);
|
||||
}
|
||||
const int rubyWidth = renderer.getTextAdvanceX(fontId, rubyTexts[wordIdx].c_str(), EpdFontFamily::SUP);
|
||||
if (rubyWidth <= groupActualWidth) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int leftOverlap = (rubyWidth - groupActualWidth) / 2;
|
||||
|
||||
// This function is only ever called for the first word of a line.
|
||||
// words[wordIdx - 1], if it exists, is always the last word of the *prior* line
|
||||
// and cannot absorb any left overhang on the current line.
|
||||
// The full leftOverlap must therefore be reserved as a visual indent so the
|
||||
// ruby text does not overflow the left margin.
|
||||
return leftOverlap;
|
||||
}
|
||||
|
||||
// The last ruby group on a line may have its ruby characters wider than the group's base text.
|
||||
// The right half of that overhang protrudes past the last base character. This function returns
|
||||
// the amount of right-margin space that must be reserved so the ruby does not overflow the right
|
||||
// border. It mirrors calculateRubyExtraStartOffset: words[lineBreak] is on the *next* line and
|
||||
// cannot absorb any of the right overhang on the current line, so the full rightOverlap is returned.
|
||||
int ParsedText::calculateRubyExtraEndOffset(const size_t lineStartIdx, const size_t lineBreakIdx,
|
||||
const GfxRenderer& renderer, const int fontId) const {
|
||||
if (rubyTexts.empty() || lineBreakIdx == 0 || lineStartIdx >= lineBreakIdx) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Walk backwards from the last word to find the leader of the last ruby group on the line.
|
||||
size_t leaderIdx = lineBreakIdx - 1;
|
||||
while (leaderIdx > lineStartIdx && (wordStyles[leaderIdx] & EpdFontFamily::RUBY_CONTINUE) != 0) {
|
||||
leaderIdx--;
|
||||
}
|
||||
|
||||
// leaderIdx must be a ruby group leader (non-empty ruby, no RUBY_CONTINUE flag).
|
||||
if (leaderIdx >= rubyTexts.size() || rubyTexts[leaderIdx].empty() ||
|
||||
(wordStyles[leaderIdx] & EpdFontFamily::RUBY_CONTINUE) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Measure the group.
|
||||
int groupActualWidth = 0;
|
||||
for (size_t k = leaderIdx; k < lineBreakIdx; ++k) {
|
||||
groupActualWidth += measureWordWidth(renderer, fontId, words[k], wordStyles[k]);
|
||||
}
|
||||
const int rubyWidth = renderer.getTextAdvanceX(fontId, rubyTexts[leaderIdx].c_str(), EpdFontFamily::SUP);
|
||||
if (rubyWidth <= groupActualWidth) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (rubyWidth - groupActualWidth) / 2;
|
||||
}
|
||||
|
||||
std::vector<uint16_t> ParsedText::calculateWordWidths(const GfxRenderer& renderer, const int fontId) {
|
||||
std::vector<uint16_t> wordWidths;
|
||||
wordWidths.reserve(words.size());
|
||||
@@ -761,6 +691,9 @@ std::vector<uint16_t> ParsedText::calculateWordWidths(const GfxRenderer& rendere
|
||||
|
||||
// Adjust widths for ruby groups to comply with JLReq standards
|
||||
if (!rubyTexts.empty()) {
|
||||
const int ascender = renderer.getFontAscenderSize(fontId);
|
||||
const int maxOverhang = ascender / 2; // Maximum overhang over non-ideographic characters (Kana, punctuation, etc.)
|
||||
|
||||
struct RubyGroupInfo {
|
||||
size_t start;
|
||||
size_t count;
|
||||
@@ -797,10 +730,9 @@ std::vector<uint16_t> ParsedText::calculateWordWidths(const GfxRenderer& rendere
|
||||
if (g.start > 0) {
|
||||
const uint32_t cpPrev = lastCodepoint(words[g.start - 1]);
|
||||
if (isCjkIdeograph(cpPrev)) {
|
||||
wordWidths[g.start - 1] += g.leftOverlap;
|
||||
wordWidths[g.start] += g.leftOverlap;
|
||||
} else {
|
||||
const int maxLeftOverhang = wordWidths[g.start - 1] / 2;
|
||||
wordWidths[g.start - 1] += std::max(0, g.leftOverlap - maxLeftOverhang);
|
||||
wordWidths[g.start] += std::max(0, g.leftOverlap - maxOverhang);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -820,8 +752,7 @@ std::vector<uint16_t> ParsedText::calculateWordWidths(const GfxRenderer& rendere
|
||||
if (isCjkIdeograph(cpNext)) {
|
||||
wordWidths[g.start + g.count - 1] += g.rightOverlap;
|
||||
} else {
|
||||
const int maxRightOverhang = wordWidths[nextIdx] / 2;
|
||||
wordWidths[g.start + g.count - 1] += std::max(0, g.rightOverlap - maxRightOverhang);
|
||||
wordWidths[g.start + g.count - 1] += std::max(0, g.rightOverlap - maxOverhang);
|
||||
}
|
||||
|
||||
// Check if there is another ruby group further ahead separated only by non-ideographs
|
||||
@@ -838,10 +769,8 @@ std::vector<uint16_t> ParsedText::calculateWordWidths(const GfxRenderer& rendere
|
||||
gapWidth += wordWidths[k];
|
||||
}
|
||||
if (onlyNonIdeographsInBetween) {
|
||||
const int maxRightOverhang = wordWidths[g.start + g.count - 1] / 2;
|
||||
const int maxLeftOverhang = wordWidths[nextG.start - 1] / 2;
|
||||
const int allowedRight = std::min(g.rightOverlap, maxRightOverhang);
|
||||
const int allowedLeft = std::min(nextG.leftOverlap, maxLeftOverhang);
|
||||
const int allowedRight = std::min(g.rightOverlap, maxOverhang);
|
||||
const int allowedLeft = std::min(nextG.leftOverlap, maxOverhang);
|
||||
const int touchOverlap = allowedRight + allowedLeft - gapWidth;
|
||||
if (touchOverlap > 0) {
|
||||
wordWidths[g.start + g.count - 1] += touchOverlap;
|
||||
@@ -908,7 +837,36 @@ std::vector<size_t> ParsedText::computeLineBreaks(const GfxRenderer& renderer, c
|
||||
}
|
||||
|
||||
// Calculate extraStartOffset for the first word on the line (i) (protect left margin)
|
||||
const int extraStartOffset = (j == i) ? calculateRubyExtraStartOffset(i, totalWordCount, renderer, fontId) : 0;
|
||||
int extraStartOffset = 0;
|
||||
if (j == i && !rubyTexts.empty() && i < rubyTexts.size() && !rubyTexts[i].empty() &&
|
||||
(wordStyles[i] & EpdFontFamily::RUBY_CONTINUE) == 0) {
|
||||
int groupWordCount = 1;
|
||||
while (i + groupWordCount < totalWordCount &&
|
||||
(wordStyles[i + groupWordCount] & EpdFontFamily::RUBY_CONTINUE) != 0) {
|
||||
groupWordCount++;
|
||||
}
|
||||
int groupActualWidth = 0;
|
||||
for (int k = 0; k < groupWordCount; ++k) {
|
||||
groupActualWidth += measureWordWidth(renderer, fontId, words[i + k], wordStyles[i + k]);
|
||||
}
|
||||
const int rubyWidth = renderer.getTextAdvanceX(fontId, rubyTexts[i].c_str(), EpdFontFamily::SUP);
|
||||
if (rubyWidth > groupActualWidth) {
|
||||
const int leftOverlap = (rubyWidth - groupActualWidth) / 2;
|
||||
const int ascender = renderer.getFontAscenderSize(fontId);
|
||||
const int maxOverhang = ascender / 2;
|
||||
|
||||
int expansion_added = 0;
|
||||
if (i > 0) {
|
||||
const uint32_t cpPrev = lastCodepoint(words[i - 1]);
|
||||
if (isCjkIdeograph(cpPrev)) {
|
||||
expansion_added = leftOverlap;
|
||||
} else {
|
||||
expansion_added = std::max(0, leftOverlap - maxOverhang);
|
||||
}
|
||||
}
|
||||
extraStartOffset = leftOverlap - expansion_added;
|
||||
}
|
||||
}
|
||||
|
||||
currlen += wordWidths[j] + gap + (j == i ? extraStartOffset : 0);
|
||||
|
||||
@@ -921,7 +879,46 @@ std::vector<size_t> ParsedText::computeLineBreaks(const GfxRenderer& renderer, c
|
||||
continue;
|
||||
}
|
||||
|
||||
const int extraEndOffset = calculateRubyExtraEndOffset(i, j + 1, renderer, fontId);
|
||||
// Calculate extraEndOffset if we break after word j (protect right margin)
|
||||
int extraEndOffset = 0;
|
||||
if (!rubyTexts.empty() && j < rubyTexts.size()) {
|
||||
bool isLastOfGroup = false;
|
||||
size_t leaderIdx = j;
|
||||
while (true) {
|
||||
if (!rubyTexts[leaderIdx].empty() && (wordStyles[leaderIdx] & EpdFontFamily::RUBY_CONTINUE) == 0) {
|
||||
isLastOfGroup = true;
|
||||
break;
|
||||
}
|
||||
if (leaderIdx == 0 || (wordStyles[leaderIdx] & EpdFontFamily::RUBY_CONTINUE) == 0) {
|
||||
break;
|
||||
}
|
||||
leaderIdx--;
|
||||
}
|
||||
|
||||
if (isLastOfGroup && (j + 1 == totalWordCount || (wordStyles[j + 1] & EpdFontFamily::RUBY_CONTINUE) == 0)) {
|
||||
int groupActualWidth = 0;
|
||||
for (size_t k = leaderIdx; k <= j; ++k) {
|
||||
groupActualWidth += measureWordWidth(renderer, fontId, words[k], wordStyles[k]);
|
||||
}
|
||||
const int rubyWidth = renderer.getTextAdvanceX(fontId, rubyTexts[leaderIdx].c_str(), EpdFontFamily::SUP);
|
||||
if (rubyWidth > groupActualWidth) {
|
||||
const int rightOverlap = (rubyWidth - groupActualWidth) / 2;
|
||||
const int ascender = renderer.getFontAscenderSize(fontId);
|
||||
const int maxOverhang = ascender / 2;
|
||||
|
||||
int expansion_added = 0;
|
||||
if (j + 1 < totalWordCount) {
|
||||
const uint32_t cpNext = firstCodepoint(words[j + 1]);
|
||||
if (isCjkIdeograph(cpNext)) {
|
||||
expansion_added = rightOverlap;
|
||||
} else {
|
||||
expansion_added = std::max(0, rightOverlap - maxOverhang);
|
||||
}
|
||||
}
|
||||
extraEndOffset = rightOverlap - expansion_added;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currlen + extraEndOffset > effectivePageWidth) {
|
||||
continue; // Cannot split here as it would overflow the right margin
|
||||
@@ -942,9 +939,7 @@ std::vector<size_t> ParsedText::computeLineBreaks(const GfxRenderer& renderer, c
|
||||
}
|
||||
}
|
||||
|
||||
// Favor longer lines when line-breaking costs are equal, to avoid unnecessary short lines in Chinese and Japanese
|
||||
// text.
|
||||
if (cost <= dp[i]) {
|
||||
if (cost < dp[i]) {
|
||||
dp[i] = cost;
|
||||
ans[i] = j; // j is the index of the last word in this optimal line
|
||||
}
|
||||
@@ -1176,9 +1171,6 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const
|
||||
std::copy(rubyTexts.begin() + lastBreakAt, rubyTexts.begin() + lastBreakAt + copyCount, lineRubyTexts.begin());
|
||||
}
|
||||
|
||||
const int extraStartOffset = calculateRubyExtraStartOffset(lastBreakAt, lineBreak, renderer, fontId);
|
||||
const int extraEndOffset = calculateRubyExtraEndOffset(lastBreakAt, lineBreak, renderer, fontId);
|
||||
|
||||
std::vector<std::string> lineWords;
|
||||
lineWords.reserve(lineWordCount);
|
||||
std::vector<EpdFontFamily::Style> lineWordStyles;
|
||||
@@ -1233,9 +1225,8 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const
|
||||
? CssTextAlign::Right
|
||||
: blockStyle.alignment;
|
||||
|
||||
// For justified text, compute per-gap extra to distribute remaining space evenly.
|
||||
// extraEndOffset reserves space for any ruby group at the right edge of the line.
|
||||
const int spareSpace = effectivePageWidth - extraStartOffset - extraEndOffset - lineWordWidthSum - totalNaturalGaps;
|
||||
// For justified text, compute per-gap extra to distribute remaining space evenly
|
||||
const int spareSpace = effectivePageWidth - lineWordWidthSum - totalNaturalGaps;
|
||||
const int justifyExtra = (effectiveAlignment == CssTextAlign::Justify && !isLastLine)
|
||||
? computeJustifyExtra(spareSpace, actualGapCount)
|
||||
: 0;
|
||||
@@ -1316,8 +1307,7 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const
|
||||
}
|
||||
}
|
||||
|
||||
const int reorderedSpare =
|
||||
effectivePageWidth - extraStartOffset - extraEndOffset - reorderedWordWidthSum - reorderedNaturalGaps;
|
||||
const int reorderedSpare = effectivePageWidth - reorderedWordWidthSum - reorderedNaturalGaps;
|
||||
const int reorderedJustifyExtra = (effectiveAlignment == CssTextAlign::Justify && !isLastLine)
|
||||
? computeJustifyExtra(reorderedSpare, reorderedGapCount)
|
||||
: 0;
|
||||
@@ -1422,7 +1412,7 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const
|
||||
}
|
||||
} else {
|
||||
// LTR: position words from left to right
|
||||
int xpos = firstLineIndent + extraStartOffset;
|
||||
int xpos = firstLineIndent;
|
||||
if (effectiveAlignment == CssTextAlign::Right) {
|
||||
xpos = effectivePageWidth - lineWordWidthSum - totalNaturalGaps;
|
||||
} else if (effectiveAlignment == CssTextAlign::Center) {
|
||||
|
||||
@@ -59,9 +59,6 @@ class ParsedText {
|
||||
void pushVisibleOffset(uint32_t offset);
|
||||
void insertVisibleOffset(size_t wordIndex, uint32_t offset);
|
||||
void eraseVisibleOffsetPrefix(size_t count);
|
||||
int calculateRubyExtraStartOffset(size_t wordIdx, size_t maxWordIdx, const GfxRenderer& renderer, int fontId) const;
|
||||
int calculateRubyExtraEndOffset(size_t lineStartIdx, size_t lineBreakIdx, const GfxRenderer& renderer,
|
||||
int fontId) const;
|
||||
int resolveFirstLineIndent(bool isFirstLine, const GfxRenderer& renderer, int fontId) const;
|
||||
std::vector<size_t> computeLineBreaks(const GfxRenderer& renderer, int fontId, int pageWidth,
|
||||
std::vector<uint16_t>& wordWidths, std::vector<bool>& continuesVec,
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace {
|
||||
// match. Keeps <br>-per-paragraph books (common CJK formatting) from
|
||||
// re-adding container spacing at every paragraph.
|
||||
// v35: Persist a uint32_t visible-text start offset for every page.
|
||||
constexpr uint8_t SECTION_FILE_VERSION = 36;
|
||||
constexpr uint8_t SECTION_FILE_VERSION = 35;
|
||||
// Written into the version field while a build is in progress; patched to
|
||||
// SECTION_FILE_VERSION only when the build is finalized. An abandoned /
|
||||
// crash-interrupted .bin therefore carries version 0, which loadSectionFile rejects
|
||||
|
||||
@@ -72,7 +72,6 @@ class Section {
|
||||
// Builds write here and are swapped over filePath only on commit, so a prior
|
||||
// partial/finalized file stays readable while a rebuild is in progress.
|
||||
std::string binTmpPath() const { return filePath + ".part"; }
|
||||
|
||||
std::unique_ptr<Page> loadPageAt(int page) const;
|
||||
// Read a page already laid out by the in-progress build (page < build LUT size), from
|
||||
// the partially-written tmp .bin without disturbing the build's write cursor.
|
||||
@@ -125,20 +124,6 @@ class Section {
|
||||
// (covers finalized sections and partials from a previous session).
|
||||
std::optional<uint16_t> findAnchor(const std::string& anchor) const;
|
||||
|
||||
// MEMFIX-PORT: section resident-bytes audit accessor; portable
|
||||
// Approximate resident heap for the audit log. Steady state (no build) a
|
||||
// Section holds little beyond itself; during a build the page LUT and path
|
||||
// strings dominate (the parser's internal footprint is not walked here).
|
||||
size_t residentBytes() const {
|
||||
size_t total = sizeof(Section) + filePath.capacity();
|
||||
if (build_) {
|
||||
total += sizeof(BuildContext) + build_->lut.capacity() * sizeof(PageLutEntry) +
|
||||
build_->parsePath.capacity() + build_->contentBase.capacity() + build_->imageBasePath.capacity() +
|
||||
build_->htmlPath.capacity() + build_->tmpHtmlPath.capacity();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// True if this spine's unzipped HTML is already cached, so a build won't pay the (multi-second on a
|
||||
// giant spine) zip inflation. Lets the reader skip the indexing popup on a fast reopen/rebuild.
|
||||
bool hasHtmlCache() const;
|
||||
|
||||
@@ -327,25 +327,6 @@ void ImageBlock::render(GfxRenderer& renderer, const int x, const int y) {
|
||||
FontCacheManager* fcm = renderer.getFontCacheManager();
|
||||
if (fcm && fcm->isScanning()) return;
|
||||
|
||||
// Grayscale strip passes re-invoke render() once per horizontal band and once
|
||||
// per plane. When the image's pixels are resident in the RAM slot, redrawing
|
||||
// from RAM is cheap and preserves 4-level AA. When they are NOT resident -- the
|
||||
// payload was too big to cache on this heap (a full-page image is ~80 KB vs
|
||||
// ~48 KB free with BLE up), or another image on the page owns the single slot --
|
||||
// each band would re-stream the entire .pxc from SD. For a full-page image
|
||||
// that is ~13 full-file reads (BW pass + ~6 column bands x 2 planes in
|
||||
// portrait), the dominant cost of an image page (measured bw_render ~7.8 s).
|
||||
// The image already has its 1bpp rendering from the BW pass, so skip the
|
||||
// grayscale redraw instead of re-streaming: cacheable images keep full AA,
|
||||
// oversized ones stay BW (already dithered in the .pxc). Orientation-agnostic --
|
||||
// gated on slot residency, not on Portrait vs Landscape.
|
||||
const GfxRenderer::RenderMode renderMode = renderer.getRenderMode();
|
||||
if (renderMode == GfxRenderer::GRAYSCALE_LSB || renderMode == GfxRenderer::GRAYSCALE_MSB) {
|
||||
if (pxcSlotHash != imagePathHash(getCachePath(imagePath))) {
|
||||
return; // not slot-resident; leave the BW-pass pixels untouched
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DBG("IMG", "Rendering image at %d,%d: %s (%dx%d)", x, y, imagePath.c_str(), width, height);
|
||||
|
||||
const int screenWidth = renderer.getScreenWidth();
|
||||
|
||||
@@ -135,32 +135,86 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int
|
||||
const bool scanning = renderer.isFontCacheScanning();
|
||||
const int ascender = renderer.getFontAscenderSize(fontId);
|
||||
|
||||
// Resolve ruby positions. Layout (extractLine) has already reserved extraStartOffset on the
|
||||
// left and extraEndOffset on the right, so the centered rubyX is always within the page margins.
|
||||
// Resolve ruby collisions left-to-right to prevent adjacent ruby texts from overlapping
|
||||
struct RubyDrawInfo {
|
||||
int x;
|
||||
int width;
|
||||
std::string text;
|
||||
BidiUtils::BidiBaseDir baseDir;
|
||||
};
|
||||
// hasRuby() is an O(numWords) scan, so resolve it once here rather than per word.
|
||||
// Both arrays below are only ever read when the line carries ruby, so they stay
|
||||
// empty (zero allocations) for the ruby-less case, which is every line of a
|
||||
// non-CJK book. Sized lazily inside the branch.
|
||||
const bool blockHasRuby = hasRuby();
|
||||
std::vector<int> wordShiftArr;
|
||||
std::vector<RubyDrawInfo> rubies;
|
||||
if (blockHasRuby) {
|
||||
wordShiftArr.assign(numWords, 0);
|
||||
rubies.resize(numWords);
|
||||
int accumulatedShift = 0;
|
||||
int lastEnd = -9999;
|
||||
for (uint16_t i = 0; i < numWords; i++) {
|
||||
wordShiftArr[i] = accumulatedShift;
|
||||
if (i < rubyTexts.size() && !rubyTexts[i].empty() && (wordStyle(i) & EpdFontFamily::RUBY_CONTINUE) == 0) {
|
||||
// Find the group size (how many words are part of this ruby annotation)
|
||||
int groupWordCount = 1;
|
||||
while (i + groupWordCount < numWords && (wordStyle(i + groupWordCount) & EpdFontFamily::RUBY_CONTINUE) != 0) {
|
||||
groupWordCount++;
|
||||
}
|
||||
|
||||
// Compute actual width for the group
|
||||
int groupActualWidth = 0;
|
||||
for (int k = 0; k < groupWordCount; ++k) {
|
||||
groupActualWidth += renderer.getTextAdvanceX(fontId, wordText(i + k), wordStyle(i + k));
|
||||
}
|
||||
const int rubyWidth = renderer.getTextAdvanceX(fontId, rubyTexts[i].c_str(), EpdFontFamily::SUP);
|
||||
|
||||
const char* word = wordText(i);
|
||||
const int leaderWordX = xposArr[i] + x;
|
||||
const int leaderWordX_shifted = leaderWordX + accumulatedShift;
|
||||
const auto baseDir =
|
||||
static_cast<BidiUtils::BidiBaseDir>(BidiUtils::detectParagraphLevel(wordText(i), blockStyle.isRtl ? 1 : 0));
|
||||
rubies[i] = {leaderWordX - (rubyWidth - groupActualWidth) / 2, rubyTexts[i], baseDir};
|
||||
static_cast<BidiUtils::BidiBaseDir>(BidiUtils::detectParagraphLevel(word, blockStyle.isRtl ? 1 : 0));
|
||||
const int rubyWidth = renderer.getTextAdvanceX(fontId, rubyTexts[i].c_str(), EpdFontFamily::SUP);
|
||||
const int screenWidth = renderer.getScreenWidth();
|
||||
|
||||
int rubyX = 0;
|
||||
int groupDrawX = 0;
|
||||
if (rubyWidth > groupActualWidth) {
|
||||
rubyX = leaderWordX_shifted - (rubyWidth - groupActualWidth) / 2;
|
||||
if (i == 0) {
|
||||
rubyX = std::max(leaderWordX_shifted, rubyX);
|
||||
}
|
||||
if (rubyX < lastEnd) {
|
||||
rubyX = lastEnd;
|
||||
}
|
||||
groupDrawX = rubyX + (rubyWidth - groupActualWidth) / 2;
|
||||
} else {
|
||||
groupDrawX = leaderWordX_shifted;
|
||||
rubyX = groupDrawX + (groupActualWidth - rubyWidth) / 2;
|
||||
if (i == 0) {
|
||||
rubyX = std::max(leaderWordX_shifted, rubyX);
|
||||
}
|
||||
if (rubyX < lastEnd) {
|
||||
const int push = lastEnd - rubyX;
|
||||
rubyX = lastEnd;
|
||||
groupDrawX += push;
|
||||
}
|
||||
}
|
||||
rubyX = std::max(0, std::min(rubyX, screenWidth - rubyWidth));
|
||||
// Keep groupDrawX aligned if rubyX was clamped by screen edges
|
||||
if (rubyWidth > groupActualWidth) {
|
||||
groupDrawX = rubyX + (rubyWidth - groupActualWidth) / 2;
|
||||
}
|
||||
|
||||
rubies[i] = {rubyX, rubyWidth, rubyTexts[i], baseDir};
|
||||
lastEnd = rubyX + rubyWidth;
|
||||
|
||||
// Propagate shift to all words in the group and subsequent words
|
||||
const int groupShift = groupDrawX - leaderWordX;
|
||||
accumulatedShift = groupShift;
|
||||
for (int k = 0; k < groupWordCount; ++k) {
|
||||
wordShiftArr[i + k] = accumulatedShift;
|
||||
}
|
||||
i += groupWordCount - 1;
|
||||
}
|
||||
}
|
||||
@@ -221,7 +275,7 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int
|
||||
wordY += ascender / 4;
|
||||
}
|
||||
|
||||
const int drawX = wordX;
|
||||
const int drawX = wordX + (blockHasRuby ? wordShiftArr[i] : 0);
|
||||
|
||||
if (boundary > 0) {
|
||||
// Focus split: draw bold prefix, then the regular suffix at a pre-computed x offset.
|
||||
|
||||
@@ -67,20 +67,6 @@ class CssParser {
|
||||
*/
|
||||
[[nodiscard]] static CssStyle parseInlineStyle(std::string_view styleValue);
|
||||
|
||||
// MEMFIX-PORT: stylesheet resident-bytes audit accessor; portable
|
||||
// Approximate resident heap of the parsed stylesheet, for the audit log.
|
||||
// unordered_map cost model: bucket array + one node per rule (libstdc++ node
|
||||
// overhead ~= 2 pointers + hash) + key string capacity when it exceeds SSO.
|
||||
size_t residentBytes() const {
|
||||
size_t total = rulesBySelector_.bucket_count() * sizeof(void*);
|
||||
for (const auto& kv : rulesBySelector_) {
|
||||
total += sizeof(void*) * 2 + sizeof(size_t); // node overhead
|
||||
total += sizeof(kv);
|
||||
if (kv.first.capacity() > 15) total += kv.first.capacity(); // beyond SSO
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any rules have been loaded
|
||||
*/
|
||||
|
||||
@@ -62,22 +62,16 @@ void FontCacheManager::resetStats() {
|
||||
bool FontCacheManager::isScanning() const { return scanMode_ == ScanMode::Scanning; }
|
||||
|
||||
void FontCacheManager::recordText(const char* text, int fontId, EpdFontFamily::Style style) {
|
||||
if (!text) return;
|
||||
// Bucket the text under its base style so each style is prewarmed only for what
|
||||
// it actually renders (see the header comment on scanText_).
|
||||
scanText_ += text;
|
||||
if (scanFontId_ < 0) scanFontId_ = fontId;
|
||||
const uint8_t baseStyle = static_cast<uint8_t>(style) & 0x03;
|
||||
size_t& len = scanTextLen_[baseStyle];
|
||||
const size_t remaining = (len < SCAN_TEXT_CAPACITY - 1) ? (SCAN_TEXT_CAPACITY - 1 - len) : 0;
|
||||
if (remaining > 0) {
|
||||
const size_t textLen = strnlen(text, remaining);
|
||||
memcpy(scanText_[baseStyle] + len, text, textLen);
|
||||
len += textLen;
|
||||
scanText_[baseStyle][len] = '\0';
|
||||
}
|
||||
if (!scanRecorded_) {
|
||||
scanFontId_ = fontId; // capture the first drawn font; may be negative (SD font)
|
||||
scanRecorded_ = true;
|
||||
const unsigned char* p = reinterpret_cast<const unsigned char*>(text);
|
||||
uint32_t cpCount = 0;
|
||||
while (*p) {
|
||||
if ((*p & 0xC0) != 0x80) cpCount++;
|
||||
p++;
|
||||
}
|
||||
scanStyleCounts_[baseStyle] += cpCount;
|
||||
}
|
||||
|
||||
// --- PrewarmScope implementation ---
|
||||
@@ -86,37 +80,28 @@ FontCacheManager::PrewarmScope::PrewarmScope(FontCacheManager& manager) : manage
|
||||
manager_->scanMode_ = ScanMode::Scanning;
|
||||
manager_->clearCache();
|
||||
manager_->resetStats();
|
||||
for (uint8_t i = 0; i < STYLE_COUNT; i++) {
|
||||
manager_->scanTextLen_[i] = 0;
|
||||
manager_->scanText_[i][0] = '\0';
|
||||
}
|
||||
manager_->scanText_.clear();
|
||||
manager_->scanText_.reserve(2048); // Pre-allocate to avoid heap fragmentation from repeated concat
|
||||
memset(manager_->scanStyleCounts_, 0, sizeof(manager_->scanStyleCounts_));
|
||||
manager_->scanFontId_ = -1;
|
||||
manager_->scanRecorded_ = false;
|
||||
}
|
||||
|
||||
void FontCacheManager::PrewarmScope::endScanAndPrewarm() {
|
||||
manager_->scanMode_ = ScanMode::None;
|
||||
if (!manager_->scanRecorded_) return; // nothing recorded (also the "already ran" no-op)
|
||||
if (manager_->scanText_.empty()) return;
|
||||
|
||||
// Prewarm each style separately, against only the text drawn in that style, so a
|
||||
// secondary style's page-slot buffer stays as small as its actual usage. A single
|
||||
// per-style bit mask routes prewarmCache() to exactly that style on both the SD
|
||||
// and compressed-font paths.
|
||||
for (uint8_t i = 0; i < STYLE_COUNT; i++) {
|
||||
if (manager_->scanTextLen_[i] == 0) continue;
|
||||
const unsigned long tStyle = millis();
|
||||
manager_->prewarmCache(manager_->scanFontId_, manager_->scanText_[i], static_cast<uint8_t>(1u << i));
|
||||
// Per-style prewarm cost + text volume: on a heap-pressured build (BLE resident)
|
||||
// a cold chapter-opening page can touch several styles, each paying SD reads for
|
||||
// bitmaps + kern/ligature tables. This line attributes a slow page to the styles
|
||||
// and byte volume actually loaded, so the wall-clock spike is not a guess.
|
||||
LOG_DBG("FCM", "prewarm style%u: %lums (%u text bytes)", i, millis() - tStyle,
|
||||
(unsigned)manager_->scanTextLen_[i]);
|
||||
manager_->scanTextLen_[i] = 0;
|
||||
manager_->scanText_[i][0] = '\0';
|
||||
// Build style bitmask from all styles that appeared during the scan
|
||||
uint8_t styleMask = 0;
|
||||
for (uint8_t i = 0; i < 4; i++) {
|
||||
if (manager_->scanStyleCounts_[i] > 0) styleMask |= (1 << i);
|
||||
}
|
||||
manager_->scanFontId_ = -1;
|
||||
manager_->scanRecorded_ = false; // makes the dtor's second call a no-op
|
||||
if (styleMask == 0) styleMask = 1; // default to regular
|
||||
|
||||
manager_->prewarmCache(manager_->scanFontId_, manager_->scanText_.c_str(), styleMask);
|
||||
|
||||
// Free scan string memory
|
||||
manager_->scanText_.clear();
|
||||
manager_->scanText_.shrink_to_fit();
|
||||
}
|
||||
|
||||
FontCacheManager::PrewarmScope::~PrewarmScope() {
|
||||
|
||||
Loaded 20 of 103 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user