Skip to content
Merged
91 changes: 0 additions & 91 deletions lib/EpdFont/CjkUiFallback.h

This file was deleted.

44 changes: 26 additions & 18 deletions lib/EpdFont/EpdFont.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

#include <algorithm>

#include "CjkUiFallback.h"
// Safe in a .cpp translation unit: EpdFontFamily.h includes EpdFont.h, but this is
// not a header, so there is no circular include. Needed for resolveGlyph + the Style cast.
#include "EpdFontFamily.h"

void EpdFont::getTextBounds(const char* string, const int startX, const int startY, int* minX, int* minY, int* maxX,
int* maxY) const {
void EpdFont::getTextBoundsImpl(const char* string, const int startX, const int startY, int* minX, int* minY, int* maxX,
int* maxY, const EpdFontFamily* family, const int style) const {
*minX = startX;
*minY = startY;
*maxX = startX;
Expand All @@ -24,14 +26,22 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star
int32_t prevAdvanceFP = 0; // 12.4 fixed-point: prev glyph's advance + next kern for snap
uint32_t cp;
uint32_t prevCp = 0;
const EpdFontData* prevData = nullptr; // EpdFontData that owned the previous base glyph
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&string)))) {
const bool isCombining = utf8IsCombiningMark(cp);

if (!isCombining) {
// Ligature substitution uses THIS font's ligaturePairs (no-op when null, as for
// the CJK fallback font), so it is unconditional and matches the single-font path.
cp = applyLigatures(cp, string);
}

const EpdGlyph* glyph = getGlyph(cp);
// Family-aware resolution: with a family, glyph+data come from the primary or fallback
// chain (source-aligned). Without a family, fall back to this single font's getGlyph.
const ResolvedGlyph r =
family ? family->resolveGlyph(cp, static_cast<EpdFontFamily::Style>(style)) : ResolvedGlyph{getGlyph(cp), data};
const EpdGlyph* glyph = r.glyph;
const EpdFontData* glyphData = r.data; // owner data, used for the cross-font kern guard
if (!glyph) {
// Keep cursor movement stable when a base glyph is missing, but don't attach subsequent
// combining marks to stale base metrics.
Expand All @@ -49,7 +59,14 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star
const int raiseBy = isCombining ? combiningMark::raiseAboveBase(glyph->top, glyph->height, lastBaseTop) : 0;

if (!isCombining && prevCp != 0) {
const auto kernFP = getKerning(prevCp, cp); // 4.4 fixed-point kern
// `prevData == glyphData` means the previous and current base glyphs came from the
// SAME EpdFontData (both primary, or both fallback). getKerning queries THIS font's
// (the primary's) kern table, so cross-font pairs (Latin-CJK, CJK-Latin) must get 0 —
// the primary's table has no entries for them anyway, but the guard makes the intent
// explicit and avoids a wrong-table lookup if a fallback ever carries its own table.
// Style-aware fallback kerning is out of scope for P5 (documented known limitation).
const auto kernFP = (prevData == glyphData) ? getKerning(prevCp, cp) // same EpdFontData — kern applies
: int8_t{0}; // cross-font boundary — kern = 0
lastBaseX += fp4::toPixel(prevAdvanceFP + kernFP);
}

Expand All @@ -69,6 +86,7 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star
lastBaseTop = glyph->top;
prevAdvanceFP = glyph->advanceX; // 12.4 fixed-point
prevCp = cp;
prevData = glyphData; // remember the owner so the next iteration's kern guard works
}
}
}
Expand Down Expand Up @@ -181,19 +199,9 @@ const EpdGlyph* EpdFont::getGlyph(const uint32_t cp) const {
if (loaded) return loaded;
}

// UI fonts: fall back to the built-in 20px CJK UI font for true-CJK codepoints
// before showing the replacement box. Gated so ASCII/Latin never routes here.
//
// Precedence contract: when both cjkUiFallback_ and glyphMissHandler are set on the
// same font, the SD handler runs first and the built-in CJK font is consulted only
// when the SD handler returns nullptr. UI fonts in this firmware do not carry a
// glyphMissHandler today; if a future custom SD UI font sets both, an SD I/O failure
// would be silently shadowed by the built-in CJK glyph for any CJK codepoint. Keep
// the two in sync.
if (cjkUiFallback_ && CjkUiFallback::shouldUse(cp)) {
return CjkUiFallback::makeGlyph(cp);
}

// CJK fallback is no longer dispatched here. EpdFontFamily::resolveGlyph owns the
// primary -> fallback chain; this single-font lookup just reports a miss via the
// replacement glyph below.
if (cp != REPLACEMENT_GLYPH) {
return getGlyph(REPLACEMENT_GLYPH);
}
Expand Down
23 changes: 14 additions & 9 deletions lib/EpdFont/EpdFont.h
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
#pragma once
#include "EpdFontData.h"

class EpdFont {
void getTextBounds(const char* string, int startX, int startY, int* minX, int* minY, int* maxX, int* maxY) const;
class EpdFontFamily; // forward declaration — no #include (would be circular)

class EpdFont {
public:
const EpdFontData* data;
explicit EpdFont(const EpdFontData* data) : data(data) {}
~EpdFont() = default;

// When enabled (UI fonts only), getGlyph() falls back to the built-in 20px CJK
// UI font for true-CJK codepoints the primary font lacks. Reader fonts leave this off.
void enableCjkUiFallback() { cjkUiFallback_ = true; }
bool cjkUiFallbackEnabled() const { return cjkUiFallback_; }
// Public surface unchanged for single-font callers: measures with this font only.
void getTextBounds(const char* string, int startX, int startY, int* minX, int* minY, int* maxX, int* maxY) const {
getTextBoundsImpl(string, startX, startY, minX, minY, maxX, maxY, nullptr, /*style=*/0); // 0 = REGULAR
}

// Family-aware variant used by EpdFontFamily::getTextBounds. When `family` is non-null,
// glyph lookup goes through family->resolveGlyph (primary + fallback chain) so CJK
// codepoints measure with the fallback font instead of the replacement box.
// `style` is passed as int to avoid pulling EpdFontFamily.h into this header;
// EpdFont.cpp casts it back to EpdFontFamily::Style at the resolveGlyph call site.
void getTextBoundsImpl(const char* string, int startX, int startY, int* minX, int* minY, int* maxX, int* maxY,
const EpdFontFamily* family, int style) const;

void getTextDimensions(const char* string, int* w, int* h) const;

Expand All @@ -29,7 +37,4 @@ class EpdFont {
/// as many following codepoints from text as possible. Returns the
/// (possibly substituted) codepoint; advances text past consumed chars.
uint32_t applyLigatures(uint32_t cp, const char*& text) const;

private:
bool cjkUiFallback_ = false;
};
56 changes: 52 additions & 4 deletions lib/EpdFont/EpdFontFamily.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#include "EpdFontFamily.h"
// REPLACEMENT_GLYPH (0xFFFD) is provided by Utf8.h as a macro — do NOT redeclare it here.
#include <Utf8.h>

#include <algorithm>

const EpdFont* EpdFontFamily::getFont(const Style style) const {
// Extract font style bits (ignore UNDERLINE bit for font selection)
Expand All @@ -18,10 +22,6 @@ const EpdFont* EpdFontFamily::getFont(const Style style) const {
return regular;
}

void EpdFontFamily::getTextDimensions(const char* string, int* w, int* h, const Style style) const {
getFont(style)->getTextDimensions(string, w, h);
}

const EpdFontData* EpdFontFamily::getData(const Style style) const { return getFont(style)->data; }

const EpdGlyph* EpdFontFamily::getGlyph(const uint32_t cp, const Style style) const {
Expand All @@ -35,3 +35,51 @@ int8_t EpdFontFamily::getKerning(const uint32_t leftCp, const uint32_t rightCp,
uint32_t EpdFontFamily::applyLigatures(const uint32_t cp, const char*& text, const Style style) const {
return getFont(style)->applyLigatures(cp, text);
}

ResolvedGlyph EpdFontFamily::resolveGlyph(const uint32_t cp, const Style style) const {
const EpdFont* primary = getFont(style);

// Fast path: no fallback registered (reader body-text fonts, SD card fonts).
// Skip the pointer-identity miss detection entirely — these paths may use
// glyphMissHandler with ring-buffer-backed glyph storage where calling
// getGlyph(REPLACEMENT_GLYPH) before getGlyph(cp) would invalidate state.
if (fallback_ == nullptr) {
return {primary->getGlyph(cp), primary->data};
}

// Family with fallback: pointer-identity miss detection is safe HERE because
// fallback is only registered on UI fonts (built-in static tables, no
// glyphMissHandler), so getGlyph(REPLACEMENT_GLYPH) returns a stable pointer
// that the subsequent getGlyph(cp) cannot invalidate. setFallback() asserts
// this precondition (glyphMissHandler == nullptr) for both fonts.
const EpdGlyph* replacement = primary->getGlyph(REPLACEMENT_GLYPH);
const EpdGlyph* g = primary->getGlyph(cp);

// A primary hit: non-null AND (not the replacement, OR cp IS the replacement itself)
const bool primaryHit = g && (g != replacement || cp == REPLACEMENT_GLYPH);
if (primaryHit) {
return {g, primary->data};
}

const EpdGlyph* fallbackReplacement = fallback_->getGlyph(REPLACEMENT_GLYPH);
const EpdGlyph* fg = fallback_->getGlyph(cp);
const bool fallbackHit = fg && (fg != fallbackReplacement || cp == REPLACEMENT_GLYPH);
if (fallbackHit) {
return {fg, fallback_->data};
}

// Both missed — return primary's replacement glyph with primary's data
return {replacement ? replacement : g, primary->data};
}

int EpdFontFamily::getMaxAscender(const Style style) const {
const int primary = getFont(style)->data->ascender;
if (fallback_) return std::max(primary, fallback_->data->ascender);
return primary;
}

int EpdFontFamily::getMaxAdvanceY(const Style style) const {
const int primary = static_cast<int>(getFont(style)->data->advanceY);
if (fallback_) return std::max(primary, static_cast<int>(fallback_->data->advanceY));
return primary;
}
Loading
Loading