Skip to content
Merged
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ build
.history/
/.venv
*.local*
*.cpfont
38 changes: 23 additions & 15 deletions lib/EpdFont/EpdFont.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,24 +148,32 @@ uint32_t EpdFont::applyLigatures(uint32_t cp, const char*& text) const {

const EpdGlyph* EpdFont::getGlyph(const uint32_t cp) const {
const int count = data->intervalCount;
if (count == 0) return nullptr;

const EpdUnicodeInterval* intervals = data->intervals;
const auto* end = intervals + count;

// upper_bound: range lookup. Finds the first interval with first > cp, so the
// interval just before it is the last one with first <= cp. That's the only
// candidate that could contain cp. Then we verify cp <= candidate.last.
const auto it = std::upper_bound(
intervals, end, cp, [](uint32_t value, const EpdUnicodeInterval& interval) { return value < interval.first; });

if (it != intervals) {
const auto& interval = *(it - 1);
if (cp <= interval.last) {
return &data->glyph[interval.offset + (cp - interval.first)];
if (count == 0 && !data->glyphMissHandler) return nullptr;

if (count > 0) {
const EpdUnicodeInterval* intervals = data->intervals;
const auto* end = intervals + count;

// upper_bound: range lookup. Finds the first interval with first > cp, so the
// interval just before it is the last one with first <= cp. That's the only
// candidate that could contain cp. Then we verify cp <= candidate.last.
const auto it = std::upper_bound(
intervals, end, cp, [](uint32_t value, const EpdUnicodeInterval& interval) { return value < interval.first; });

if (it != intervals) {
const auto& interval = *(it - 1);
if (cp <= interval.last) {
return &data->glyph[interval.offset + (cp - interval.first)];
}
}
}

// Codepoint not in interval table — try on-demand loading (SD card fonts).
if (data->glyphMissHandler) {
const EpdGlyph* loaded = data->glyphMissHandler(data->glyphMissCtx, cp);
if (loaded) return loaded;
}

if (cp != REPLACEMENT_GLYPH) {
return getGlyph(REPLACEMENT_GLYPH);
}
Expand Down
12 changes: 12 additions & 0 deletions lib/EpdFont/EpdFontData.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,16 @@ typedef struct {
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

/// On-demand glyph loading for fonts that don't keep all glyphs in RAM (e.g. SD card fonts).
/// Called by getGlyph() when a codepoint is not found in the interval table.
/// Returns a valid EpdGlyph* with correct metadata, or nullptr to fall back to the
/// replacement glyph. The returned pointer is valid until the next glyphMissHandler
/// call that causes a ring-buffer eviction — callers must consume it (measure or draw)
/// before requesting another missed glyph.
const EpdGlyph* (*glyphMissHandler)(void* ctx, uint32_t codepoint);

/// Context pointer for glyphMissHandler (typically SdCardFont*). Also used by
/// GfxRenderer::getGlyphBitmap() to retrieve overflow bitmaps via SdCardFont.
void* glyphMissCtx;
} EpdFontData;
37 changes: 37 additions & 0 deletions lib/EpdFont/FontDecompressor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,43 @@ int FontDecompressor::prewarmCache(const EpdFontData* fontData, const char* utf8
}
}

// Add ligature output glyphs: if both input codepoints of a ligature pair are
// in the needed set, the output glyph will be queried during rendering.
// Must run BEFORE the neededGlyphGroups[] parallel-array loop below so appended
// glyphs receive a group index — otherwise hot-group lookup misses them.
if (fontData->ligaturePairs && fontData->ligaturePairCount > 0) {
for (uint32_t li = 0; li < fontData->ligaturePairCount && glyphCount < MAX_PAGE_GLYPHS; li++) {
uint32_t leftCp = fontData->ligaturePairs[li].pair >> 16;
uint32_t rightCp = fontData->ligaturePairs[li].pair & 0xFFFF;

int32_t leftIdx = findGlyphIndex(fontData, leftCp);
int32_t rightIdx = findGlyphIndex(fontData, rightCp);
if (leftIdx < 0 || rightIdx < 0) continue;

bool hasLeft = false, hasRight = false;
for (uint16_t i = 0; i < glyphCount; i++) {
if (neededGlyphs[i] == static_cast<uint32_t>(leftIdx)) hasLeft = true;
if (neededGlyphs[i] == static_cast<uint32_t>(rightIdx)) hasRight = true;
if (hasLeft && hasRight) break;
}
if (!hasLeft || !hasRight) continue;

int32_t outIdx = findGlyphIndex(fontData, fontData->ligaturePairs[li].ligatureCp);
if (outIdx < 0) continue;

bool found = false;
for (uint16_t i = 0; i < glyphCount; i++) {
if (neededGlyphs[i] == static_cast<uint32_t>(outIdx)) {
found = true;
break;
}
}
if (!found) {
neededGlyphs[glyphCount++] = static_cast<uint32_t>(outIdx);
}
}
}

if (glyphCount == 0) return 0;

// Step 2: Compute total buffer size and collect unique groups
Expand Down
Loading
Loading