Missing Font Issues During File Export

Exporting a page, dashboard, report, contract, or invoice can turn into a frustrating experience. The source file looks fine on screen. The exported PDF, image, or Office document does not. Text may appear as empty rectangles, boxes, or question marks. Some glyphs disappear completely. Numbers and Latin characters might survive while Asian scripts fail. Spacing can shift, line height can change, and print output can become unreadable.

This is not simply a display bug. It usually points to a deeper issue: the export pipeline may not have access to the fonts used by the source, or the target format may not preserve those fonts correctly.

Why Missing Fonts Happen

The root causes are often grouped into a few patterns.

  • The runtime lacks the font. Docker, Linux servers, and CI containers frequently ship only basic fonts, without CJK or symbol fonts.
  • The font is not embedded. A PDF may record only a font name, then substitute when the reader cannot find it.
  • The subset is incomplete. Embedding only selected characters can save space, but new or dynamic content may introduce missing glyphs.
  • The font name differs. CSS may reference Microsoft YaHei, while the actual family on Linux may be Noto Sans CJK SC.
  • Web fonts have not loaded. Headless browsers may export before font requests finish.
  • Cross-origin or licensing limits interfere. @font-face rules can be blocked by CORS, or licenses may forbid embedding.
  • Legacy tools struggle. Libraries such as wkhtmltopdf may have limited support for web fonts or variable fonts.

How to Diagnose the Problem

A clear diagnosis saves time. Start by identifying which characters are missing and which fonts the export actually uses.

  1. Inspect the output font list. Commands such as pdffonts output.pdf can show font names and embedding status. Look for whether the embedding flag is enabled.
  2. Check server fonts. On Linux, run a font listing command for the target language. If nothing appears, the required font is likely absent.
  3. Check browser font readiness. In a page, use document.fonts.check and document.fonts.ready before exporting.
  4. Create a minimal reproduction. Use fixed HTML and a fixed font to rule out dynamic content.
  5. Review logs. Headless browsers, PDF libraries, and conversion tools often warn about font substitution or missing fonts.

Practical Fixes

Install Fonts in the Export Environment

For Linux or Docker setups, install an open-source CJK font such as Noto Sans CJK or Source Han Sans. Then refresh the font cache.

RUN apt-get update && apt-get install -y \
    fonts-noto-cjk \
    fonts-noto-color-emoji \
 && fc-cache -f -v

After installation, refresh the cache. If using PDF libraries such as PDFKit or ReportLab, register the font file explicitly.

doc.registerFont('cn', '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc', 'NotoSansCJKsc-Regular');
doc.font('cn').text('Export content');

Embed Fonts Explicitly

For PDF export, prefer libraries that support embedding. Enable embedding or subsetting. In CSS, use @font-face to declare an accessible font.

@font-face {
  font-family: 'ExportFont';
  src: url('/fonts/NotoSansSC-Regular.woff2') format('woff2');
  font-display: block;
}
body {
  font-family: 'ExportFont', 'Noto Sans CJK SC', 'Microsoft YaHei', sans-serif;
}

font-display: block can reduce early fallback behavior in headless browsers, but waiting for document.fonts.ready is more reliable.

Wait for Web Fonts Before Exporting

When using Puppeteer or Playwright, wait for network idle and font readiness before generating the PDF.

await page.setContent(html, { waitUntil: 'networkidle0' });
await page.evaluate(() => document.fonts.ready);
await page.pdf({
  path: 'output.pdf',
  format: 'A4',
  printBackground: true,
  preferCSSPageSize: true
});

If fonts load through a URL, ensure the headless browser can access it and that responses allow cross-origin requests.

Handle Subsetting and Fallbacks

Subsetting reduces file size, but it must include every character in the exported content. User input, dynamic numbers, rare glyphs, and emoji require special testing. Keep a fallback stack, such as 'ExportFont', 'Noto Sans CJK SC', sans-serif, so a missing font does not break an entire paragraph.

For LibreOffice headless conversion, configure font substitution or replacement tables to map missing fonts to installed ones.

Best Practices

  • Distribute font files with the application and pin exact versions.
  • Install CJK fonts in the Docker image and run fc-cache -f -v.
  • Wait for document.fonts.ready and check document.fonts.check before export.
  • Verify PDF output with pdffonts and confirm embedding is enabled.
  • Regression test multilingual, rare-glyph, emoji, and dynamic-field content.
  • Respect font licenses and confirm embedding and commercial use are allowed.
  • Migrate from older tools when support is limited.

Summary

Missing fonts in exported files come from gaps between the export environment, font loading, and font embedding. The fix usually involves installing fonts, registering them, waiting for load, enabling embedding, and preserving fallbacks. By treating fonts as part of the export dependency and validating them, teams can reduce