Table of Contents

Fallback Fonts and Multilingual Text

Real text rarely stays inside one script or one font. User names, emoji, CJK text, math, and symbols all show up in the same application, so fallback is what turns a nice Latin-only demo into a text stack that survives real-world input.

Fonts handles that through TextOptions.FallbackFontFamilies.

When the primary Font does not contain a glyph for part of the text, the layout engine searches the fallback families in order and uses the first family that can supply the missing glyphs.

Use families, not fonts

Fallback is configured with FontFamily instances, not Font instances.

using SixLabors.Fonts;

FontCollection collection = new();
FontFamily latin = collection.Add("fonts/NotoSans-Regular.ttf");
FontFamily arabic = collection.Add("fonts/NotoSansArabic-Regular.ttf");
FontFamily emoji = collection.Add("fonts/NotoColorEmoji-Regular.ttf");

TextOptions options = new(latin.CreateFont(16))
{
    FallbackFontFamilies = [arabic, emoji]
};

The primary font still controls the default point size and layout options. When a fallback family is selected, Fonts creates the matching font instance for that run automatically.

Order matters

Fallback families are searched in the order you provide them.

  • Put script-specific fonts before more general fallback fonts.
  • Put emoji fonts after your normal text families unless you explicitly want them to win earlier.
  • Keep the fallback list as small and intentional as possible so the selection stays predictable.

Resolver-based fallback

FallbackFontFamilies is a fixed list. TextOptions.FontFallbackResolver adds a dynamic last step behind it. Shaping consults the resolver only for code points that neither the primary font nor any fallback family can shape. It asks at most once per distinct unresolved code point per shaping operation. Leave the value null to render such code points as the missing-glyph outline.

SystemFonts.FallbackResolver is a built-in resolver that selects from the fonts installed on the current machine:

using SixLabors.Fonts;

Font font = SystemFonts.CreateFont("Segoe UI", 18);
TextOptions options = new(font)
{
    // Ask the operating system for a family when no configured font
    // can shape a code point.
    FontFallbackResolver = SystemFonts.FallbackResolver
};

Installed fonts differ between machines, so output rendered with the system resolver can differ too. Pin FallbackFontFamilies for output that must stay identical everywhere, and use the resolver as a safety net behind them.

Implement IFontFallbackResolver for a custom policy. The resolver receives the code point, the requested family, the style, and the culture. It returns the family that contains a glyph for the code point.

Mixed-script example

This pattern works well for text that mixes Latin, Arabic, and emoji:

using SixLabors.Fonts;

FontCollection collection = new();
FontFamily latin = collection.Add("fonts/NotoSans-Regular.ttf");
FontFamily arabic = collection.Add("fonts/NotoSansArabic-Regular.ttf");
FontFamily emoji = collection.Add("fonts/NotoColorEmoji-Regular.ttf");

string text = "Status: ready 😀 مرحبا";

TextOptions options = new(latin.CreateFont(18))
{
    FallbackFontFamilies = [arabic, emoji],
    TextDirection = TextDirection.Auto,
    ColorFontSupport = ColorFontSupport.ColrV1 | ColorFontSupport.Svg
};

TextDirection.Auto lets the layout engine determine whether a run should flow left-to-right or right-to-left. ColorFontSupport matters when one of your fallback families is a color emoji font.

Fallback is not the same as explicit styling

Use fallback fonts when the goal is "use another family if the current one cannot render this text".

Use TextRuns when the goal is "this specific range should use a different font even if the base font could render it".

using SixLabors.Fonts;

const string text = "Latin title العربية";

FontCollection collection = new();
FontFamily latin = collection.Add("fonts/NotoSans-Regular.ttf");
FontFamily arabic = collection.Add("fonts/NotoSansArabic-Regular.ttf");

TextOptions options = new(latin.CreateFont(18))
{
    FallbackFontFamilies = [arabic],
    TextRuns =
    [
        new TextRun
        {
            Start = 12,
            End = 19,
            Font = arabic.CreateFont(18)
        }
    ]
};

The fallback list helps with missing glyphs. TextRuns gives you deliberate control over which grapheme ranges use which fonts.

Wrapping and script behavior

Multilingual text often benefits from layout settings beyond just fallback families:

If a script needs shaping support, make sure the selected font actually supports that script. Fallback can only help if one of the supplied families contains the needed glyphs and shaping data.

Common pitfalls

  • A fallback family will not be used if the primary font already has a glyph for that Unicode scalar value, even if you would prefer the fallback font's design.
  • TextRuns use grapheme indices, not UTF-16 code-unit indices.
  • Emoji color layers are only used if ColorFontSupport allows them.
  • Mixing many broad-coverage fonts can make fallback order hard to reason about.

If layout still looks wrong after fallback is configured, see Troubleshooting.

Practical guidance

  • Put the preferred design family first, then add fallbacks in the order you want missing glyphs to be searched.
  • Use TextRuns when a specific grapheme range must use a specific font rather than normal fallback.
  • Validate fallback with real content, especially emoji, RTL text, CJK text, and combining marks.
  • Remember that fallback solves missing glyphs; it does not guarantee matching style, metrics, or shaping behavior.