How to configure custom fonts in Document Engine

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.

Available fonts

Refer to the following APIs for a list of available fonts and configured substitutions:

If you mount custom fonts and configure substitutions, you can use the same API to verify your setup.

To view a list of preloaded fonts when using Nutrient Web SDK with Document Engine, refer to the PDF viewer supported fonts guide.

Mounting custom fonts

To add custom fonts to Document Engine, mount them in the /custom-fonts directory.

If you deploy using Helm, specify additional mounts using the following values:

extraVolumes:
  - name: my-custom-fonts
    <volume specification>
extraVolumeMounts:
  - name: my-custom-fonts
    mountPath: /custom-fonts

To provide Document Engine with access to the font files, you can use an init container and download them to an emptyDir volume. Alternatively, use a dedicated image with built-in fonts or another storage option, such as a network volume.

The following example uses an init container:

extraVolumes:
  - name: my-custom-fonts
    emptyDir: {}
extraVolumeMounts:
  - name: my-custom-fonts
    mountPath: /custom-fonts
initContainers:
  - name: my-font-downloader
    image: curlimages/curl:latest
    args:
      - "-o"
      - "/tmp/data/wonderful.ttf"
      - "https://my.site.local/path/to/wonderful.ttf"
    volumeMounts:
      - name: my-custom-fonts
        mountPath: /tmp/data

With Docker Compose

If you use Docker Compose, expose a directory of fonts from the host machine to the container by adding the following to your docker-compose.yml file:

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

Caching considerations

After adding fonts, you may still notice PDFs rendered with incorrect fonts in the web viewer. Multiple layers of caching can cause this issue, displaying previously rendered pages. Follow the steps below to resolve it:

  1. Clear the browser cache — Removes cached rendering artifacts from the browser.

  2. Restart Document Engine — Clears the in-memory cache for rendered pages.

  3. If using Redis — Delete keys with the PSPDFKit-TileCache- and PSPDFKit-PageCache- prefixes to remove cached renderings. Be aware that this can impact performance in high-volume deployments, as previously cached pages will need to be rerendered. Consider applying Redis eviction policies to remove cache entries gradually.

The font directory can be any location accessible to your app. All .ttf, .ttc, and .otf files in this directory will be added to the Nutrient font list.

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. To use these fonts, download them from SourceForge and add them as custom fonts.

Using emojis

To display emojis, import the Windows-compatible Noto Color Emoji font. Currently, this is the only supported font for emojis.

Font substitutions

Document Engine may not have access to required fonts for conversions or rendering annotations if they’re missing from the default container fonts or the /custom-fonts directory.

To specify alternative fonts for unavailable fonts, create a font-substitutions.json file and mount it in the Document Engine container — refer to the mounting font substitutions section further down to learn how to do that.

The font-substitutions.json file follows this schema (in TypeScript notation):

type FontSubstitutions = {
  fontSubstitutions: FontSubstitution[];
};

type FontSubstitution = {
  // Note that font family name replacements are made based upon pattern matching,
  // allowing for a font family name to be replaced with a different name.
  // Patterns are matched using the following rules:
  //  - `*` matches multiple characters
  //  - `?` matches a single character
  pattern: string;

  // The font that should be used as a replacement
  // when any font matching the given pattern is unavailable.
  target: string;
};

Example font-substitions.json file:

{
  "fontSubstitutions": [
    {
      "pattern": "Roboto-*",
      "target": "Courier New"
    },
    {
      "pattern": "Calibri",
      "target": "Caladea"
    }
  ]
}

Notes on font substitutions

Case-insensitive — The pattern and target names are case-insensitive.

Ordering matters — Substitutions are applied in the order listed. If multiple patterns match a font, the first match takes priority.

For example, consider the following list of font substitutions:

{
  "fontSubstitutions": [
    {
      "pattern": "Roboto-*",
      "target": "Courier New"
    },
    {
      "pattern": "Roboto-Medium",
      "target": "Menlo"
    },
    {
      "pattern": "Roboto-Medium*",
      "target": "Consolas"
    }
  ]
}

If Document Engine processes a document with Roboto-MediumItalic font but the font is unavailable, it will substitute Courier New, as Roboto-* is the first match.

Document Engine applies font substitutions from font-substitutions.json in all document processing contexts, including conversions and annotation rendering.

To define font substitutions for a specific document and layer, use the API reference for the font substitutions endpoint.

Any document layer substitutions defined through the font substitutions API merge with those in font-substitutions.json. If both specify a substitution for the same pattern, the API-defined substitution takes precedence.

For example, if both sources specify a target for Roboto-Medium*, Document Engine will use the target set through the API instead of the one in font-substitutions.json.

Mounting font substitutions

The most convenient way to add a file to Kubernetes deployment is by creating a ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-default-font-substitutions
  namespace: <your namespace>
data:
  font-substitutions.json: |
    {
      "fontSubstitutions": [
        {
          "pattern": "Roboto-*",
          "target": "Courier New"
        }
      ]
    }

If you deploy using Helm, specify additional mounts for the ConfigMap resource using the following values:

extraVolumes:
  - name: my-font-substitutions
    configMap:
      name: my-default-font-substitutions
      items:
        - key: font-substitutions.json
          path: font-substitutions.json
extraVolumeMounts:
  - name: my-font-substitutions
    mountPath: /font-substitutions.json
    subPath: font-substitutions.json

With Docker Compose

If you use Docker Compose, expose the font-substitutions.json file from the host machine to the container by adding the following to your docker-compose.yml file:

document-engine:
  volumes:
    - /path-to-font-substitutions-json-on-host:/font-substitutions.json