Skip to content

Configure fonts

When EPPlus renders text to the HTML/SVG and PDF exports, it measures and shapes the text using real font data. EPPlus ships with sensible defaults, including built-in fallbacks for missing fonts and characters, so in most cases you don't need to configure anything. When you do, Workbook.ConfigureFonts lets you control where fonts are loaded from, how missing fonts and characters are handled, and how fonts are embedded in PDF files.

Font configuration is applied per workbook and must be set before you export. It does not affect any other workbook or any global state.

Default behaviour

If you never call ConfigureFonts:

  • Fonts are searched for in the operating system's standard font directories.
  • A missing font is replaced using EPPlus's built-in fallback chains, which prefer metric-compatible substitutes such as Carlito for Calibri. If none is available, the embedded Archivo Narrow font is used, so a missing font never causes an export to fail.
  • Characters that the font lacks, for example Japanese text in a Latin font, are taken from per-script fallback fonts when these are installed. Emoji and mathematical symbols always come from the bundled Noto Emoji and Noto Math fonts.

The export always succeeds, but the result is not always the intended one. Text in a writing system for which no font is available is drawn with missing-glyph boxes. To render, for example, Chinese, Japanese or Korean text on a server, make sure a font for the script is installed or supplied through FontDirectories.

How the fallbacks work, and the complete list of built-in chains, is described in Font fallbacks.

Configuring fonts

Call ConfigureFonts on the workbook and change the configuration inside the callback. The example below adds a font directory and turns off the system font search, so that only fonts from your directory are used:

using var package = new ExcelPackage();
package.Workbook.ConfigureFonts(cfg =>
{
    cfg.FontDirectories.Add(@"C:\MyFonts");
    cfg.SearchSystemDirectories = false;
});

// ...add drawings/charts, then export
Using package As New ExcelPackage()
    package.Workbook.ConfigureFonts(Sub(cfg)
                                        cfg.FontDirectories.Add("C:\MyFonts")
                                        cfg.SearchSystemDirectories = False
                                    End Sub)

    ' ...add drawings/charts, then export
End Using

The changes you make inside the callback are applied as a single transaction: when the callback returns, the font resolver is rebuilt and the workbook's font caches are cleared.

These are the members you can use inside the callback:

Member Data type Description
FontDirectories IList<string> Additional directories to search for font files, beyond the system font directories.
SearchSystemDirectories bool Whether the operating system's standard font directories are searched. Defaults to true. Set to false to use only FontDirectories.
FontFallbacks IDictionary<string, string[]> Your own fallback chains, mapping a font name to the fonts to try when it is missing. Tried before the built-in chains. See Font-level fallback.
SetScriptFallback(...) method Replaces the fallback chain for a Unicode script, used for characters the font lacks. See Script-level fallback.
WebFontSubstitutions IDictionary<string, string> Fonts substituted in the HTML and SVG exports, for fonts that browsers can't use, such as Aptos. See Web font substitution.
MetricsFallback MetricsFallbackMode When text is measured with serialized font metrics instead of a font file. See Measurement without the font.
OnFontEmbedding(...) method Decides how fonts are embedded in PDF files. See Embedding restrictions.
Logger IFontLogger Receives reports about font resolution, fallbacks and embedding. See Finding out which fonts were used.
FontResolver IFontResolver Replaces the font resolver entirely. See Custom font resolution.
Reset() method Restores the default configuration, including the built-in script chains and web font substitutions.

Common configurations

Fonts on a server or in a container

Servers and containers often have few fonts installed. Either install the fonts the workbooks use, or ship them with your application and add their directory:

package.Workbook.ConfigureFonts(cfg =>
{
    cfg.FontDirectories.Add(Path.Combine(AppContext.BaseDirectory, "Fonts"));
});
package.Workbook.ConfigureFonts(Sub(cfg)
                                    cfg.FontDirectories.Add(Path.Combine(AppContext.BaseDirectory, "Fonts"))
                                End Sub)

If you can't distribute the original fonts, the metric-compatible open fonts Carlito, Caladea and the Liberation family are used automatically by the built-in fallback chains when they are available. They keep column widths and line breaks close to the original.

A fallback for your own font

package.Workbook.ConfigureFonts(cfg =>
{
    // If "Corporate Sans" is not available, try these in order.
    cfg.FontFallbacks["Corporate Sans"] = new[] { "Arial", "Open Sans" };
});
package.Workbook.ConfigureFonts(Sub(cfg)
                                    ' If "Corporate Sans" is not available, try these in order.
                                    cfg.FontFallbacks("Corporate Sans") = New String() {"Arial", "Open Sans"}
                                End Sub)

A font for a writing system

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

    // Use "Noto Sans JP" for Chinese/Japanese ideographs the cell's font lacks.
    cfg.SetScriptFallback(UnicodeScript.Han, "Noto Sans JP");
});
package.Workbook.ConfigureFonts(Sub(cfg)
                                    cfg.FontDirectories.Add("C:\MyFonts")

                                    ' Use "Noto Sans JP" for Chinese/Japanese ideographs the cell's font lacks.
                                    cfg.SetScriptFallback(UnicodeScript.Han, "Noto Sans JP")
                                End Sub)

Setting a chain replaces the built-in chain for that script completely. Pass no font names to turn off fallback for a script.

Embedding restrictions

When EPPlus embeds a font in a PDF it subsets the font by default: only the glyphs actually used are included, which keeps the file small. Fonts declare, through their OS/2 fsType field, whether and how they may be embedded. EPPlus honours those declarations:

  • Installable, Preview & Print, or Editable embedding: the font is subsetted and embedded normally.
  • No subsetting: the font may be embedded, but not as a subset. EPPlus embeds the complete, unmodified font instead. This produces a larger PDF but respects the font's terms.
  • Restricted License: the font declares that it must not be embedded at all. By default EPPlus throws an exception rather than silently embedding a font you may not have the right to embed.

The fsType field is the font vendor's declaration; it is not a technical lock. If you hold a licence from the font's owner that permits embedding, regardless of what fsType says, you can override EPPlus's default handling with OnFontEmbedding:

package.Workbook.ConfigureFonts(cfg =>
{
    cfg.OnFontEmbedding(info =>
    {
        // You have licensed "Acme Display" for embedding.
        if (info.FontName == "Acme Display")
            return FontEmbeddingDecision.Subset;
        // Everything else follows EPPlus's default handling.
        return FontEmbeddingDecision.Default;
    });
});
package.Workbook.ConfigureFonts(Sub(cfg)
                                    cfg.OnFontEmbedding(Function(info)
                                                            ' You have licensed "Acme Display" for embedding.
                                                            If info.FontName = "Acme Display" Then
                                                                Return FontEmbeddingDecision.Subset
                                                            End If
                                                            ' Everything else follows EPPlus's default handling.
                                                            Return FontEmbeddingDecision.Default
                                                        End Function)
                                End Sub)

The callback is invoked once per font about to be embedded and receives a FontEmbeddingInfo describing the font: its FontName and the Restriction it declares (a FontEmbeddingRestriction of None, NoSubsetting or NoEmbedding). You can inspect the declared restriction and decide accordingly, for example keeping restricted fonts out of the PDF entirely rather than failing the export:

package.Workbook.ConfigureFonts(cfg =>
{
    cfg.OnFontEmbedding(info =>
    {
        // We hold no licence for restricted fonts, so keep them out of the PDF
        // and let the fallback chain render their text instead.
        if (info.Restriction == FontEmbeddingRestriction.NoEmbedding)
            return FontEmbeddingDecision.Skip;
        // Everything else follows EPPlus's default handling.
        return FontEmbeddingDecision.Default;
    });
});
package.Workbook.ConfigureFonts(Sub(cfg)
                                    cfg.OnFontEmbedding(Function(info)
                                                            ' We hold no licence for restricted fonts, so keep them out of the PDF
                                                            ' and let the fallback chain render their text instead.
                                                            If info.Restriction = FontEmbeddingRestriction.NoEmbedding Then
                                                                Return FontEmbeddingDecision.Skip
                                                            End If
                                                            ' Everything else follows EPPlus's default handling.
                                                            Return FontEmbeddingDecision.Default
                                                        End Function)
                                End Sub)

Return one of:

Decision Effect
Default Follow EPPlus's standard handling for the font's declared restriction (throw for Restricted License, embed whole for no subsetting, subset otherwise).
Subset Subset and embed the font, regardless of fsType.
EmbedWhole Embed the complete font without subsetting.
Skip Do not embed the font; its text is rendered with the fallback chain instead (ultimately the last-resort font, Archivo Narrow). Lets you keep a font out of the PDF without failing the export.

You are responsible for compliance

Returning Subset or EmbedWhole for a font that restricts embedding asserts that you hold the necessary rights. EPPlus cannot verify any licence you may have obtained and does not check on your behalf.

Fonts bundled with EPPlus (Archivo Narrow, Noto Emoji and Noto Math) are always subsetted and embedded.

Custom font resolution

The font pipeline is built around interfaces, so its parts can be replaced when the built-in behaviour isn't enough. Most notably, you can supply your own font resolver: implement IFontResolver (OfficeOpenXml.Interfaces.Fonts) and assign it to FontResolver to take full responsibility for producing font data, for example to load fonts from a database or an embedded resource instead of the file system.

A custom resolver replaces the whole font-level fallback, including the built-in chains and the last-resort font. Script-level and glyph-level fallback still apply, and the fonts in the script chains are loaded through your resolver. Keep in mind:

  • Return a font for every request. Archivo Narrow, the last-resort font, is part of EPPlus's own resolver and is not used with a custom one. If your resolver can't find a font, return a default font of your own rather than null.
  • Implement IFontAvailabilityProvider as well, if your resolver substitutes fonts internally. EPPlus uses it to check whether a font in a script chain is really available. Without it, EPPlus can only check that the resolver returns some font, so every font in a chain looks available and the chain is not walked as intended.
using OfficeOpenXml.Interfaces.Fonts;

package.Workbook.ConfigureFonts(cfg =>
{
    // MyCustomFontResolver implements IFontResolver
    cfg.FontResolver = new MyCustomFontResolver();
});
Imports OfficeOpenXml.Interfaces.Fonts

package.Workbook.ConfigureFonts(Sub(cfg)
                                    ' MyCustomFontResolver implements IFontResolver
                                    cfg.FontResolver = New MyCustomFontResolver()
                                End Sub)

Which exports use the font configuration

Font configuration affects the exports that measure and shape text against real font data:

  • HTML/SVG export: charts and shapes rendered to SVG.
  • PDF export.

Other operations, such as saving the workbook as an .xlsx file, are not affected by this configuration.

See also