Skip to content

Font fallbacks

When EPPlus measures, shapes or renders text, the fonts used in the workbook are not always available on the machine running the code, and an available font does not always contain every character in the text. EPPlus handles both cases with built-in fallbacks at three levels:

Level Applies when Handled by
Font The requested font cannot be found Fallback chains per font name
Script The font is found, but lacks the characters of a writing system, for example Japanese Fallback chains per Unicode script
Glyph A single character is missing from the font The script chains, and the bundled Noto Emoji and Noto Math fonts

In addition, the HTML and SVG exports substitute Office cloud fonts with fonts that browsers have, and text measurement can use serialized font metrics when a font is missing.

The defaults are designed so that you don't need to configure anything. To change them, see Configure fonts.

Font-level fallback

When text uses a font, EPPlus resolves it in this order:

  1. The requested font. The font is searched for in the configured font directories and, unless SearchSystemDirectories is turned off, the operating system's font directories. The match must be exact: the same family and the same style. A request for Calibri Bold is not satisfied by Calibri Regular.
  2. Your own fallback chain for the font name, if you have configured one in FontFallbacks.
  3. The built-in fallback chain for the font name, if there is one (see the table below).
  4. Archivo Narrow, a last-resort font that is embedded in EPPlus and is always available.

Every font in a chain must also be an exact match, family and style, to be used. If no font in a chain matches, resolution continues with the next step. Your own chain is tried before the built-in one, so your choices always win.

If you supply your own FontResolver, it replaces this whole process.

Built-in fallback chains

The built-in chains cover common Office and system fonts. They prefer metric-compatible fonts: open-source fonts designed with the same character widths as the original, such as Carlito for Calibri, Caladea for Cambria and Arimo for Arial. With a metric-compatible substitute, text takes up the same space as with the original font, so column widths, line breaks and page layout stay the same.

These fonts are not bundled with EPPlus. They are used only if they are installed or available in one of your font directories. On Linux servers and in containers, installing the Carlito, Caladea and Liberation font packages gives you substitutes for the most common Office fonts.

Requested font Fallbacks, in order
Aptos Carlito, Liberation Sans, Arial, Helvetica
Aptos Display Carlito, Liberation Sans, Arial, Helvetica
Aptos Light Carlito, Liberation Sans, Arial
Aptos Mono Cascadia Mono, Cascadia Code, Consolas, Liberation Mono, Courier New
Aptos Narrow Liberation Sans Narrow, Arial Narrow, Carlito, Liberation Sans, Arial
Aptos SemiBold Carlito, Liberation Sans, Arial
Arabic Transparent Arial, Arimo
Arabic Transparent Bold Arial Bold
Arial Arimo, Liberation Sans, Helvetica
Arial Black Liberation Sans, Arial
Arial Narrow Liberation Sans Narrow, Liberation Sans
Book Antiqua Palatino Linotype, Palatino, Liberation Serif, Times New Roman
Bookman Old Style Liberation Serif, Times New Roman, Times
Caladea Liberation Serif, Times New Roman, Times
Calibri Carlito, Liberation Sans, Arial, Helvetica
Calibri Light Carlito, Liberation Sans, Arial, Helvetica
Cambria Caladea, Liberation Serif, Times New Roman, Times
Cambria Math Latin Modern Math, STIX Two Math, Caladea, Liberation Serif
Candara Carlito, Liberation Sans, Arial
Carlito Liberation Sans, Arial, Helvetica
Cascadia Code Cascadia Mono, Consolas, Liberation Mono, Courier New
Cascadia Mono Cascadia Code, Consolas, Liberation Mono, Courier New
Century Gothic Liberation Sans, Arial, Helvetica
Century Schoolbook Liberation Serif, Times New Roman, Times
Comic Sans MS Liberation Sans, Arial
Consolas Cascadia Mono, Cascadia Code, Liberation Mono, Courier New
Constantia Caladea, Liberation Serif, Georgia, Times New Roman
Corbel Carlito, Liberation Sans, Arial
Courier Courier New, Cousine, Liberation Mono
Courier New Cousine, Liberation Mono, Courier
DejaVu Sans Liberation Sans, Arial, Helvetica
DejaVu Sans Condensed DejaVu Sans, Liberation Sans, Arial
DejaVu Sans Mono Liberation Mono, Courier New, Courier
Garamond Liberation Serif, Times New Roman, Times
Georgia Gelasio, Liberation Serif, Times New Roman, Times
Helvetica Arial, Arimo, Liberation Sans
Helvetica Neue Helvetica, Arial, Arimo, Liberation Sans
Impact Liberation Sans Narrow, Arial Narrow, Arial Black, Arial
Liberation Mono Courier New, Cousine, Courier
Liberation Sans Arial, Arimo, Helvetica
Liberation Sans Narrow Arial Narrow, Liberation Sans, Arial
Liberation Serif Times New Roman, Tinos, Times
Lucida Console DejaVu Sans Mono, Liberation Mono, Consolas, Courier New
Lucida Sans Unicode DejaVu Sans, Liberation Sans, Arial
MS Sans Serif Arial, Arimo, Liberation Sans, Helvetica
MS Serif Times New Roman, Tinos, Liberation Serif, Times
Palatino Palatino Linotype, Book Antiqua, Liberation Serif, Times New Roman
Palatino Linotype Palatino, Book Antiqua, Liberation Serif, Times New Roman
Segoe Print Comic Sans MS, Liberation Sans, Arial
Segoe Script Comic Sans MS, Liberation Sans, Arial
Segoe UI Liberation Sans, Arial, Helvetica
Tahoma DejaVu Sans Condensed, DejaVu Sans, Liberation Sans, Arial
Times Times New Roman, Tinos, Liberation Serif
Times New Roman Tinos, Liberation Serif, Times
Trebuchet MS Liberation Sans, Arial, Helvetica
Verdana DejaVu Sans, Liberation Sans, Arial

To add a chain for a font that is not in the table, or to replace a built-in chain, use FontFallbacks:

package.Workbook.ConfigureFonts(cfg =>
{
    // Tried before the built-in chain for Calibri.
    cfg.FontFallbacks["Calibri"] = new[] { "Open Sans", "Arial" };
});
package.Workbook.ConfigureFonts(Sub(cfg)
                                    ' Tried before the built-in chain for Calibri.
                                    cfg.FontFallbacks("Calibri") = New String() {"Open Sans", "Arial"}
                                End Sub)

The last-resort font

Archivo Narrow guarantees that font resolution always ends with a font, so a missing font never causes an export to fail. It covers Latin script. Text in other writing systems is still handled by glyph-level fallback, which works on top of whichever font was resolved, including Archivo Narrow.

Script-level fallback

A font can be available and still lack the characters of a writing system. A typical example is a Latin font such as Calibri used for a cell that contains Japanese text. For each writing system (Unicode script), EPPlus has a fallback chain of fonts to take the missing characters from.

The built-in chains try the fonts that ship with Windows and macOS first, then the Noto fonts that are common on Linux:

Script Fallbacks, in order
Han (Chinese ideographs) Microsoft YaHei, SimSun, Noto Sans CJK SC, PingFang SC
Hiragana Yu Gothic, MS Gothic, Meiryo, Noto Sans CJK JP
Katakana Yu Gothic, MS Gothic, Meiryo, Noto Sans CJK JP
Hangul Malgun Gothic, Gulim, Noto Sans CJK KR
Arabic Segoe UI, Tahoma, Arial, Noto Sans Arabic
Hebrew Segoe UI, Tahoma, Arial, Noto Sans Hebrew
Thai Tahoma, Leelawadee UI, Noto Sans Thai
Devanagari Mangal, Nirmala UI, Noto Sans Devanagari

A chain never crosses into another language. Japanese text does not fall back to a Chinese font, or the reverse, since the same ideograph is drawn differently in Chinese and Japanese typography.

Fonts in a script chain must be installed or available in a font directory, and are skipped if they are not. A chain is resolved the first time a character of that script is encountered.

There are no built-in chains for Latin, Cyrillic or Greek, since these are expected to be covered by the font itself. If your workbooks contain Cyrillic or Greek text in fonts that may be missing on the machine, configure a chain for those scripts.

Use SetScriptFallback to configure a chain. It replaces the built-in chain for the script completely; the two are not merged:

package.Workbook.ConfigureFonts(cfg =>
{
    cfg.FontDirectories.Add(@"C:\MyFonts");

    // Japanese text: prefer our own font, then the Windows default.
    cfg.SetScriptFallback(UnicodeScript.Hiragana, "Noto Sans JP", "Yu Gothic");
    cfg.SetScriptFallback(UnicodeScript.Katakana, "Noto Sans JP", "Yu Gothic");

    // Cyrillic has no built-in chain.
    cfg.SetScriptFallback(UnicodeScript.Cyrillic, "Noto Sans", "Arial");

    // No fallback at all for Thai: missing characters render as .notdef.
    cfg.SetScriptFallback(UnicodeScript.Thai);
});
package.Workbook.ConfigureFonts(Sub(cfg)
                                    cfg.FontDirectories.Add("C:\MyFonts")

                                    ' Japanese text: prefer our own font, then the Windows default.
                                    cfg.SetScriptFallback(UnicodeScript.Hiragana, "Noto Sans JP", "Yu Gothic")
                                    cfg.SetScriptFallback(UnicodeScript.Katakana, "Noto Sans JP", "Yu Gothic")

                                    ' Cyrillic has no built-in chain.
                                    cfg.SetScriptFallback(UnicodeScript.Cyrillic, "Noto Sans", "Arial")

                                    ' No fallback at all for Thai: missing characters render as .notdef.
                                    cfg.SetScriptFallback(UnicodeScript.Thai)
                                End Sub)

Note that Japanese text is usually a mix of Han, Hiragana and Katakana. To make all of it come from the same font, configure the same chain for all three scripts.

Glyph-level fallback

Fallback within a text is decided character by character. For each character, EPPlus uses the first of the following that has a glyph for it:

  1. The resolved font, that is the requested font or the font chosen by font-level fallback.
  2. Depending on the character:
    • Emoji: Noto Emoji, which is bundled with EPPlus.
    • Mathematical symbols: Noto Math, which is bundled with EPPlus.
    • Other characters: the script chain for the character's script.
  3. If no font has the glyph, the character is drawn with the font's missing glyph symbol (.notdef), usually an empty box. The export still succeeds.

A single cell can therefore be drawn with several fonts, for example Calibri for the Latin text, Yu Gothic for the Japanese characters and Noto Emoji for an emoji. In a PDF, each font that contributed glyphs is subsetted and embedded.

Emoji and mathematical symbols are always handled by the bundled fonts and cannot be configured.

Limitations

  • Text shaping applies to the resolved font only. Kerning, ligatures, contextual forms and the placement of diacritical marks are applied to glyphs from the resolved font, but not to glyphs taken from a fallback font. For most scripts this makes little visible difference. For scripts that depend on contextual forms, such as Arabic and Devanagari, use a font that covers the script as the cell's font rather than relying on fallback.
  • Fallback glyphs are always regular. Fonts in a script chain are loaded in their regular style, so bold or italic text that falls back to another font is drawn without bold or italic.
  • Variation sequences (a character followed by a variation selector, used by some emoji and CJK ideographs) are only honoured when the same font has both the character and the sequence.

Web font substitution

When a workbook is exported to HTML or SVG, the text is drawn by the browser, with the fonts the browser has access to. Office cloud fonts, such as Aptos, are delivered to Office through Microsoft's font service and are not installed on the system, so browsers can't use them, even on machines where Office has them.

For the HTML and SVG exports, EPPlus therefore substitutes these fonts with fonts that ship with Windows and have metric-compatible open alternatives. The text is then both measured on the server and drawn in the browser with the same font:

Font in the workbook Used in HTML and SVG
Aptos Arial
Aptos Display Arial
Aptos Narrow Calibri
Aptos Serif Cambria
Aptos Mono Consolas
Grandview Calibri
Seaford Segoe UI
Tenorite Segoe UI
Bierstadt Arial
Skeena Segoe UI

The substitution is a single step: the substitute is not substituted again. PDF export is not affected, since a PDF embeds the fonts it uses.

To change the substitutions, edit WebFontSubstitutions. Remove an entry to pass the original font name through to the browser, for example if your users have the font installed:

package.Workbook.ConfigureFonts(cfg =>
{
    cfg.WebFontSubstitutions["Aptos"] = "Segoe UI";
    cfg.WebFontSubstitutions.Remove("Aptos Narrow");
});
package.Workbook.ConfigureFonts(Sub(cfg)
                                    cfg.WebFontSubstitutions("Aptos") = "Segoe UI"
                                    cfg.WebFontSubstitutions.Remove("Aptos Narrow")
                                End Sub)

Measurement without the font

Measuring text, for example to autofit columns or to break lines, needs the widths of the characters, not their outlines. When font resolution ends at the last-resort font, EPPlus measures with serialized metrics of the requested font instead, if it has them. These are character widths for common fonts that are included in EPPlus. Measuring the right font from stored widths is closer to the result in Excel than measuring a different font exactly.

A metric-compatible substitute found by font-level fallback is always preferred, since it is a real font. The behaviour is controlled by MetricsFallback:

Value Behaviour
WhenFontMissing Default. Serialized metrics are used when font resolution ends at the last-resort font.
Always Serialized metrics are used whenever they exist for the font, without looking for font files. Measurements are then the same on every machine, regardless of the fonts installed.
Disabled Serialized metrics are never used.
package.Workbook.ConfigureFonts(cfg =>
{
    // Same column widths on every server, whatever fonts are installed.
    cfg.MetricsFallback = MetricsFallbackMode.Always;
});
package.Workbook.ConfigureFonts(Sub(cfg)
                                    ' Same column widths on every server, whatever fonts are installed.
                                    cfg.MetricsFallback = MetricsFallbackMode.Always
                                End Sub)

Serialized metrics are used for measurement only. Text that is rendered, for example in a PDF, always needs a real font.

Finding out which fonts were used

To see which fonts and fallbacks EPPlus actually used, assign a logger to the Logger property in ConfigureFonts. The fallback decisions are reported as events:

Event Severity Reported when
FontFallback Information A font was replaced by a font from a fallback chain.
FontLastResort Warning No font in any chain was found, and the last-resort font was used.
ScriptChainResolved Information The script chain for a script was resolved, with the status of each font in it.
ScriptFallbackUsed Information A fallback font supplied glyphs for the first time.
GlyphFallback Debug A character was taken from a fallback font (once per character).
GlyphMissing Warning No font had a glyph for a character (once per character, at most 100).
MeasurementMetricsUsed Information Text was measured with serialized metrics instead of a font file.
WebFontSubstitution Debug A font was substituted for the HTML and SVG exports.

A GlyphMissing warning includes the reason, for example that no script chain is configured for the character's script, or that none of the fonts in the chain is installed.

See also