This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/web/features/custom-fonts.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Embed custom fonts in JavaScript PDF

PDF files should render consistently across different PDF viewers. This consistency is possible because a PDF file can embed the fonts required for rendering.

However, in some cases — due to file size or other considerations — PDFs don’t embed fonts. When this happens, the PDF viewer relies on system fonts, which may cause rendering issues if the required fonts are unavailable.

Embedding fonts in PDFs is the best way to ensure accurate rendering, but this isn’t always possible, especially when working with third-party PDFs. Custom font path support addresses this issue.

Ensure you have appropriate licenses for any fonts you use. Some fonts may require commercial licenses for distribution or embedding.

Nutrient Web SDK

You can specify an array of NutrientViewer.Font instances when initializing Nutrient Web SDK under the NutrientViewer.Configuration#customFonts property:

const fetcher = (fontFileName) =>
fetch(`https://example.com/${fontFileName}`).then((r) => {
if (r.status === 200) {
return r.blob();
} else {
throw new Error();
}
});
const customFonts = ["arial.ttf", "helvetica.ttf", "tahoma.ttf"].map(
(font) => new NutrientViewer.Font({ name: font, callback: fetcher }),
);
NutrientViewer.load({
customFonts,
// ...your additional options.
});

Consider that the array specified must remain constant between different instances of Nutrient Web SDK, so avoid specifying NutrientViewer.Configuration#customFonts as a literal array expression. Instead, pass an identifier you can reuse between Web SDK instances, as shown in the previous example. This is because the SDK checks referential equality on the array at runtime, confirming the same fonts are specified on every load. If you specify a different array between loading attempts, the instance won’t load.

When constructing NutrientViewer.Font instances, set a name property with the font asset’s file name and extension. Also set a callback that receives that name as an argument, where you define the logic for retrieving the font. The callback must return a Promise that resolves to a Blob(opens in a new tab). The SDK uses name only as an internal identifier passed to the callback; it doesn’t control the label shown in the font picker UI. The annotation toolbar reads the display name directly from the font file’s embedded metadata. There’s currently no supported way to override that display name.

For optimizing subsequent loads of the fonts, you can make use of different caching strategies. For example, rely on HTTP caching(opens in a new tab) headers returned from the server the font is fetched from. Use a service worker(opens in a new tab) in combination with the Cache API(opens in a new tab), or use IndexedDB(opens in a new tab) to store the font blobs on the client side. Any strategy that better suits your use case also works.

If the callback rejects or returns a different value than the expected blob, the Web SDK continues rendering the document using its built-in fallback fonts. It also logs an error to the browser console.

Nutrient Web SDK invokes the specified callbacks before it begins rendering your document, which is why a good loading strategy matters for user experience. The SDK also retrieves custom fonts when using NutrientViewer.preloadWorker. As a result, once you reach the point of creating an instance, both the fonts and WebAssembly artifacts might already be available upfront.

Handling different fonts per document

It’s a known limitation that you can’t dynamically change the customFonts array between loading different documents in the same session. The SDK performs a referential equality check on the customFonts array when loading an instance. If you pass a new array when switching documents, it’ll cause the instance to fail to load. The same check applies to NutrientViewer.Configuration#contentEditingFontMatcher, so pass the same function reference on every load. The check compares object identity rather than contents. A new array holding the same Font instances fails, and so does a new function that wraps the original matcher.

To support different custom fonts for different documents, you can use one of the following strategies:

  • Preload all fonts — Include every font any document might need in the initial customFonts configuration at SDK load time.
  • Full application reload — Switching font sets requires a full application reload, reinitializing the SDK with the new customFonts array. Calling unload() and loading again in the same page doesn’t reset the stored configuration.
  • Use dynamic font loading — Consider the dynamic font loading feature, which enables the SDK to fetch fonts on demand. Use this approach if you can’t predict all the required fonts in advance. The content editor doesn’t use dynamic fonts, so supply content editing fonts through customFonts.

Nutrient Web SDK with Document Engine

You can expose a directory of fonts from the host machine to the Docker container by adding the following to your docker-compose.yml file:

pspdfkit:
volumes:
- /font-directory-path-on-the-host:/custom-fonts

The font directory can be any directory that’s accessible to your app. The SDK adds all .ttf, .ttc, and .otf files there to the Nutrient font list.

These fonts are available for rendering and export. The text annotation font picker runs on the client and lists them only once you add them yourself, as described in headless text annotations.

If Document Engine later processes annotations created with custom fonts in Nutrient Web SDK standalone, refer to the preferred annotation fonts guide.

Microsoft core fonts

Microsoft core fonts are widely used on the web and in PDFs. Adding them as custom fonts improves document conversion and rendering accuracy. Nutrient doesn’t include these fonts because Microsoft no longer provides them directly, and redistribution is prohibited by license(opens in a new tab). To use these fonts, download them from SourceForge(opens in a new tab) and add them as custom fonts.

Using custom fonts with font substitution

When a PDF references non-embedded fonts by name, combine customFonts with fontSubstitutions to remap those names to your custom font faces. This is useful for Chinese, Japanese, and Korean (CJK) documents that reference system fonts not available to the SDK. See the font substitution guide for details and examples. That guide also explains the distinction between a custom font asset name and the face name to use in fontSubstitutions.target.

Troubleshooting

If custom fonts aren’t rendering as expected, refer to our font rendering troubleshooting guide for help with Identity-H encoding, non-embedded fonts, and font substitution issues.

Using emojis

To use emojis in your project, import the Windows-compatible Noto Color Emoji font(opens in a new tab). Currently, this is the only supported font for displaying emojis.