Font substitution enables you to map one font name to another when a PDF references a font that isn’t embedded. The rules apply to page rendering and to text annotations and form fields that don’t have an appearance stream, so you can keep rendering consistent without modifying the original file. If you use dynamic font loading, the SDK resolves missing fonts using the substituted name.

Page-rendering substitution for non-embedded fonts is available starting with Nutrient Web SDK 1.13.0. Font substitution doesn’t modify the PDF itself. If the substituted font isn’t available, provide it with dynamic font loading or custom fonts.

When you use Office conversion, font substitution also replaces fonts used to generate the converted PDF.

Define font substitutions

Before you load a document, define fontSubstitutions as an array of objects. Each object includes:

  • pattern — The font name pattern to match.
  • target — The replacement font name.

Patterns support the following wildcards:

  • /*/ matches multiple characters.
  • ? matches a single character.

Both the pattern and target names are case-insensitive.

For target, use the exact font face name expected by the SDK, including style and weight when applicable. For example, use Noto Sans SC Regular instead of Noto Sans SC.

To substitute all fonts that start with “Helv” with “Courier”, use the following:

const customFontSubstitutions = [
{
pattern: "Helv*",
target: "Courier",
},
];

Font names sometimes match multiple patterns, so pattern order matters. Nutrient evaluates patterns in the order they appear in the array. For example, consider these substitutions:

const customFontSubstitutions = [
{
pattern: "Helv*",
target: "Courier",
},
{
pattern: "Helvetica",
target: "Arial",
},
];

In this order, the substitution matches all fonts that start with “Helv” and replaces them with “Courier”. If you swap the pattern order, the substitution matches “Helvetica” first and replaces it with “Arial”.

Providing font data for page rendering

For page-rendering substitution to work, the SDK needs access to the replacement font data. Use fontSubstitutions together with either dynamic font loading or custom fonts:

await NutrientViewer.load({
...defaultConfiguration,
fontSubstitutions: [
{ pattern: "MS Gothic*", target: "Noto Sans JP Regular" },
{ pattern: "MS 明朝", target: "Noto Serif JP Regular" },
],
dynamicFonts: "/path/to/fonts.json",
});

The target value must match the full font name in the dynamic fonts catalog (fonts.json). Include the style suffix (e.g. Regular, Bold) when the catalog uses it.

With custom fonts

const fetcher = (name) =>
fetch(`/fonts/${name}`).then((r) => {
if (r.status === 200) return r.blob();
throw new Error(`Failed to fetch font: ${name}`);
});
const customFonts = [
"NotoSansJP-Regular.otf",
"NotoSerifJP-Regular.otf",
].map((font) => new NutrientViewer.Font({ name: font, callback: fetcher }));
await NutrientViewer.load({
...defaultConfiguration,
fontSubstitutions: [
{ pattern: "MS Gothic*", target: "Noto Sans JP Regular" },
{ pattern: "MS 明朝", target: "Noto Serif JP Regular" },
],
customFonts,
});

The name you pass to NutrientViewer.Font can be the asset file name. For fontSubstitutions.target, use the font face name exposed by that file (for example, Noto Sans JP Regular), rather than the asset name.

Using fontSubstitutions without dynamicFonts or customFonts only remaps the font name — it doesn’t provide font data. If the original font isn’t embedded in the PDF, text will be missing.

Loading Nutrient with font substitutions only

You can also pass fontSubstitutions on its own for annotation and form field substitution where appearance streams need regenerating:

await NutrientViewer.load({
...defaultConfiguration,
fontSubstitutions: customFontSubstitutions,
});

Font substitution in action

During viewing, substitutions are applied at render time for non-embedded page text. When exporting a document with font substitution and viewing it in any PDF viewer, substituted fonts appear in the appearance stream of text annotations and form fields.