Repository navigation
v0.21.0
Font handling: cache, throw on failure, loud Core Text fallback, FreeType include fix
Four font-handling fixes that had accumulated in working trees of ge consumers (multimaze2, ge canonical) and ship together because they all sit in the ge::resolveFont and ge/src/text.cpp path.
resolveFont throws on failure
Previously returned an empty FontRef with a SPDLOG_WARN. Consumers had to remember to check path.empty() after every call — easy to forget, and a missing-font failure would silently fall through to the bgfx upload path or FreeType, producing hard-to-attribute errors downstream.
resolveFont now throws std::runtime_error on every system-font resolution failure (logical name unknown, family / PS name not installed, Core Text returned unusable URL / PS name). file: and bundle-resource paths remain pass-through — no readability check; the caller's first read will surface a missing file in its own loud way.
The cache memoizes failures too: an empty FontRef in the cache is the failure marker, and a hit re-throws without re-running the resolver. The detailed first-time warning happens once; subsequent calls for the same URI throw immediately with a "previously failed" message.
svg.cpp::registerPlatformFont catches around resolveFont because SVG default-font registration is genuinely best-effort — SVGs that reference a missing system family should still rasterize (unstyled), not abort. Apps that want a hard failure for a specific family call resolveFont + registerSvgFontFace explicitly and let the throw propagate.
resolveFont caching (Apple + Android)
Each call previously rebuilt its answer from scratch:
- Apple —
CTFontCreateWithName→ trait copy → URL copy → PS-name lookup →CTFontManagerCreateFontDescriptorsFromURLto find the TTC face index. ~1 ms per call. - Android —
access(path, R_OK)probes against the candidate list (e.g. Roboto-Regular → DroidSans).
resolveFont now wraps the per-platform body with a process-static unordered_map<string, FontRef> guarded by a mutex. Hot path: SVGs with <text> elements (lunasvg calls back into ge for every glyph during rasterization), plus any consumer that calls rasterizeText repeatedly.
Loud Core Text fallback (Apple)
CTFontCreateWithName silently substitutes Helvetica for missing families / PostScript names. resolveSystemFont now compares the returned face's family + PS name against what was asked for and throws on divergence. Also widens the input set: arbitrary system family or PostScript names work via CTFontCreateWithName directly (e.g. "Krungthep", "Helvetica Neue"), not just the six logical names.
FreeType headers via SDL_ttf (CMake)
SDL3_ttf::SDL3_ttf doesn't propagate FreeType include dirs, but ge/src/text.cpp uses the FreeType C API directly. The Android and iOS CMake branches now add the SDL_ttf vendored FreeType include path explicitly so <ft2build.h> resolves.
Migration
Most consumers don't need code changes — empty-path checks like if (font.path.empty()) ... become unreachable but harmless. Consumers that want the loud failure now get it for free; consumers calling resolveFont from a hot path that previously absorbed the cost on each call will see the latency disappear after the first cache-warm call.