Skip to content

Fontique: 3-tier font fallback - #689

Open
nicoburns wants to merge 1 commit into
linebender:mainfrom
nicoburns:rigorous-font-fallback
Open

Fontique: 3-tier font fallback#689
nicoburns wants to merge 1 commit into
linebender:mainfrom
nicoburns:rigorous-font-fallback

Conversation

@nicoburns

@nicoburns nicoburns commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Generated with Fable High.

Motivation

Font fallback completely fails to find a matching font for some glyphs.

This is because font fallback was keyed on (script, locale), and resolved by asking the OS for a single font family which matches a per-script sample string. If that one font lacked the actual character (for example, CJK extensions, symbols, punctuation shared between scripts, scripts without samples) the Fontique gave up, even if there was actually an installed font that had the required glyph.

Changes made

Implement 3-tier fallback:

Tier A (all backends) - Ordered list of fonts per script (script, locale)

This is the existing fallback mechanism, but modified to work with a list of fonts rather a single font per (script, locale).

  • CoreText uses CTFontCopyDefaultCascadeListForLanguages
  • fontconfig uses font_sort with coverage trimming instead of font_match
  • Android uses all matching locale/script chain entries instead of the first

Tier B (skipped on Android) - per-character system fallback

  • Added Query::set_fallback_chars() where Parley passes the specific characters we are falling back for
  • When script/locale lists (Tier A) lack coverage, the backend is asked for a font covering the actual text (CTFontCreateForString / IDWriteFontFallback::MapCharacters / FC_CHARSET match)
  • This is cached per (character-set, locale) in the collection.

Tier C (all backends) - exhaustive coverage scan

As a last resort, all families are checked for real cmap coverage (guaranteeing a glyph is found if any font has one)

  • Fonts are sorted by name, with hidden/.-fonts last, and LastResort excluded)
  • Positive and negative results are cached, so the scan happens once per unique character set.
  • This scan is expensive, so there is an opt-out via a new exhaustive_fallback: bool runtime setting on Collection

This PR also:

Verification Performed

The AI has run temporary integration tests (not included in this PR) on macOS and Ubuntu.

Tested in Blitz on macOS, and it definitely seems to work and find glyphs where main doesn't.

@nicoburns nicoburns added the C-fontique Relates to the fontique crate label Jul 14, 2026

#[derive(Clone, PartialEq, Eq, Hash)]
struct TextFallbackKey {
chars: SmallVec<[char; 8]>,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 8 chars a sensible size here?

Comment on lines +129 to +130
let chars: SmallVec<[char; 8]> = chars.into_iter().collect();
if self.state.fallback_chars != chars {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can eliminate this allocation.

@nicoburns nicoburns changed the title Fontique: rigorous 3-tier font fallback Fontique: 3-tier font fallback Jul 14, 2026
Signed-off-by: Nico Burns <nico@nicoburns.com>
@nicoburns
nicoburns force-pushed the rigorous-font-fallback branch from 927b895 to 05bdd60 Compare July 14, 2026 12:52
@dfrg

dfrg commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

I don’t have time for a deep dive on this right now but, at a glance, I’m generally supportive of this with a couple caveats:

  1. The char lookup and cache is iffy to me. I’m not opposed in principle but I think we might be able to get by with appending a list of known large coverage fonts to the fallback chain for every query.
  2. I’d like to avoid adding an exhaustive search, even as an option. This is a perf killer and pretty much always produces the wrong result.

Big +1 to returning multiple fallback fonts from the system. We should probably also extend the script sample array to support multiple samples per script. This would help, for example, with selecting the -ExtB CJK fonts on Windows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-fontique Relates to the fontique crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fontique fails to find font for ❯ char (U+276F) on macOS Font Fallback not working for at least on Windows

2 participants