Configure PDF fonts

Choose font sources, control substitutions, and inspect font evidence for PDF conversion.

Fonts set line breaks, page counts, and the glyphs embedded in the PDF. By default, the converter uses installed fonts and packaged substitutes.

Supply font files

The following example validates a licensed font with createFontSource and converts with system fonts off:

import { readFile, writeFile } from 'node:fs/promises';
import { createFontSource, exportPdf } from '@docx-editor.dev/docx-to-pdf';

const fontBytes = new Uint8Array(await readFile('ApplicationSans.ttf'));
const admitted = createFontSource(fontBytes, {
  family: 'Application Sans',
  weight: 400,
  style: 'normal',
});
if ('failure' in admitted) {
  throw new Error(admitted.failure.diagnostic ?? admitted.failure.reason);
}

const result = await exportPdf(await readFile('document.docx'), {
  useSystemFonts: false,
  fonts: { sources: [admitted.source] },
});
await writeFile('document.pdf', result.bytes);

Register bold, italic, and bold-italic files separately, with weights 400 and 700 and styles 'normal' and 'italic'. Use defineFontResolver to read the families that the document requests. The PdfFontOrigin and PdfFontsSource types describe font inputs.

Choose source precedence

Earlier sources win. Each option takes one source or an ordered array.

OrderSourceNotes
1fontsOverrides installed and packaged faces.
2Installed fontsOn by default. Set useSystemFonts: false for the same output on every host. Skips installed copies of faces that the packages supply.
3Packaged substitutesMetric substitutes for common DOCX fonts.
4fallbackFontsFaces absent from earlier sources.
5Supplemental packaged facesIncludes @docx-editor.dev/fonts-cjk when installed.
6Document-embedded fontsFaces stored in the DOCX.
7lastResortFontsFinal caller fallback, after embedded fonts.
8Generic substitutesUnresolved families. Reports font-substitution.

Default sources need no network access. A resolver that you supply can make network requests.

Add Chinese, Japanese, and Korean text

The converter has no CJK font. If the server has no suitable font, install the optional peer:

npm install @docx-editor.dev/fonts-cjk
  • The converter finds the package at runtime, with no configuration. It works with useSystemFonts: false.
  • Add it to production dependencies, keep it external to the server bundle, and keep its dist/ and assets/ directories in place.
  • It supplies Noto Sans CJK JP Regular (weight 400, style 'normal'), the Japanese regional face. Supply your own font through fonts for other regional faces or designed styles.
  • If you set glyphFallbacks, your list replaces the default. Add { family: NOTO_SANS_CJK_JP_FAMILY, weight: 400, style: 'normal' } to keep this fallback.
  • Without coverage, export reports missing-glyph and strict export fails.

The package runs on Node.js ^20.16.0 || >=22.3.0, reads its font from disk, and has these exports:

ExportValue
NOTO_SANS_CJK_JP_FAMILY'Noto Sans CJK JP'
NOTO_SANS_CJK_JP_URLfile: URL for assets/NotoSansCJKjp-Regular.otf

For browser loading, see Packaged browser fallback.

Choose a font policy

Font policy and PDF fidelity policy are separate checks:

PolicyRejectsAllows
fontPolicy: 'best-effort' (default)Nothing for fontsRecovered source failures and incomplete coverage
fontPolicy: 'strict'Required source failures and incomplete family coverage, even when a later source supplies the fontSubstitutes that complete coverage; a shaping-limit rejection of an optional glyph fallback alone
fidelityPolicy: 'strict' (default)Generic substitutions, missing glyphs, and unsupported or approximate outputPackaged metric substitutes
fidelityPolicy: 'best-effort'Nothing; reports diagnosticsAll available output

An optional glyph fallback is a glyphFallbacks family that the document does not request and that is not the default family. fidelityPolicy: 'strict' does not turn on strict font policy. The following example sets both:

const result = await exportPdf(source, {
  fontPolicy: 'strict',
  fidelityPolicy: 'strict',
  useSystemFonts: false,
  onFontResolution(report) {
    console.table(report.families);
  },
});

The callback runs before a strict font refusal. Conversion does not await returned promises.

Read font evidence

Field or codeMeaning
fontResolution.familiesCoverage and selected faces. Inspect sourceFamily, via, and substitution on each face.
fontResolution.originFailuresSource failures and their causes.
fontResolution.droppedEmbeddedFontsEmbedded faces rejected during admission.
font-origin-failedSource index, optional name, and guarded cause for one failed source.
incomplete-fontPartial coverage or substituted variants. Informational when fontPolicy allows recovery.

Troubleshoot fonts

SymptomAction
Pages differ across hostsSet useSystemFonts: false, and use the same font bytes and package versions.
font-substitution diagnosticSupply the family through fonts, or review best-effort output.
Strict font policy rejects conversionRead the onFontResolution report for failed sources and missing faces.
Missing font assets after bundlingKeep converter packages external and deploy their assets. See Deploy with Next.js.
missing-glyph diagnosticSupply a face with the characters. For CJK text, install @docx-editor.dev/fonts-cjk.
font-origin-failed for the CJK packageIts assets are missing or unreadable. Read originFailures.

Limits

  • Check your font license before embedding. The writer rejects prohibited embedding, prohibited subsetting, variable fonts, and unsupported containers.
  • Faces over the shaping byte limit are rejected; later sources can supply the face. The report records the rejection.
  • Installed fonts resolve by their internal family name. Machines with different fonts can produce different PDFs.
  • The converter builds its installed-font index once per process. It caches up to 128 MiB of font files per process and reloads a file that changes.
  • A semicolon stays part of a font family name, so Georgia;Verdana requests one family.
  • Missing bold or italic faces draw with synthesized styles. Supply all four files for designed variants.
  • Complete coverage can include substitutes. It does not prove original font selection.
  • The CJK package code uses Apache 2.0, and its font uses the SIL Open Font License 1.1. Include its license files when you distribute it. The converter uses the EigenPal Pro License.

Next steps