---
title: "PDF font substitution using JavaScript | Nutrient"
canonical_url: "https://www.nutrient.io/guides/web/viewer/fonts/substitution/"
md_url: "https://www.nutrient.io/guides/web/viewer/fonts/substitution.md"
last_updated: "2026-06-08T17:11:05.621Z"
description: "Learn to implement font substitution in PDF documents using JavaScript and control how non-embedded fonts render on pages, annotations, and form fields."
---

# Substitute fonts in PDFs using JavaScript

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](https://www.nutrient.io/blog/what-are-appearance-streams/), 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](https://www.nutrient.io/guides/web/viewer/fonts/dynamic-fonts.md) or [custom fonts](https://www.nutrient.io/guides/web/features/custom-fonts.md). Starting with Nutrient Web SDK 1.15.0, that same combination also continues to work during linearized PDF loads, so progressive loading no longer falls back prematurely while the rest of the file is still downloading.

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:

```js

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:

```js

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](https://www.nutrient.io/guides/web/viewer/fonts/dynamic-fonts.md) or [custom fonts](https://www.nutrient.io/guides/web/features/custom-fonts.md):

### With dynamic font loading (recommended)

```js

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

```js

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:

```js

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.
---

## Related pages

- [Load fonts on demand in PDFs using JavaScript](/guides/web/viewer/fonts/dynamic-fonts.md)
- [Embed custom fonts in our JavaScript PDF viewer](/guides/web/features/custom-fonts.md)
- [Introduction to PDF fonts](/guides/web/viewer/fonts/introduction.md)
- [PDF viewer supported fonts](/guides/web/viewer/fonts/support.md)

