← dompdf.js Studio

Web Fonts and Google Fonts in dompdf.js: The Integration Guide

Web Fonts have become the default way to bring typography to the web. Services like Google Fonts deliver a font with a single link tag, freeing pages from the limits of locally installed system fonts. When the same page needs a PDF export, a new question appears: can the PDF generator, running entirely in the browser, reuse the Web Fonts the page has already loaded? The answer determines whether the exported document matches the page visually or quietly degrades to system fonts. dompdf.js renders the real DOM, so Web Fonts introduced through link tags or @font-face in both the page and the template are recognized and reused, and pairing that with document.fonts.ready turns the page's carefully chosen type into the PDF's type, glyph for glyph. This guide covers the practical side of that integration end to end: how Web Fonts differ from system fonts, the two ways to pull in Google Fonts, a complete code example, the loading timeline and the correct use of document.fonts.ready, the trade-offs between self-hosting and CDNs with particular attention to reliability in different regions, and a realistic strategy for CJK Web Fonts, whose file sizes change every rule of thumb. The goal is an integration that works the first time and stays maintainable, not an export that depends on network luck. Along the way you will see why the same integration that takes an afternoon to build can take a week to debug when the timing is wrong, and how a few deliberate decisions about where fonts live turn a fragile setup into a durable part of your infrastructure.

Web Fonts vs. System Fonts

System fonts depend on what is installed on the user's device, so the same HTML renders differently across machines, and PDF exports drift along with it. Web Fonts ship the font file as a resource with the page, so every user sees identical glyphs and exports are naturally consistent. For brands and document standardization, that determinism is the core value of Web Fonts, and it is why they became the standard mechanism for web typography rather than a progressive enhancement.

Web Fonts load on demand. The browser downloads a font file only when the page actually uses that family, so unused weights never consume bandwidth, and the same behavior applies to PDF generation: a template that references only the weights it needs keeps payload small. The flip side is that downloads are asynchronous. Generation must wait until fonts are ready, or the PDF silently falls back to system fonts, producing a document that looks fine on the developer's machine and wrong everywhere else.

The file formats have evolved from EOT through WOFF to WOFF2, which is the modern standard with the best compression. dompdf.js loads fonts through the web platform, so declaring multiple formats in src lets the browser pick the best one it supports, balancing compatibility and size without any PDF-specific adaptation. Writing the standard multi-format declaration is enough; there is no separate configuration step for the PDF path.

A practical consequence of on-demand loading is that template weight discipline pays off: declaring a dozen Google Fonts weights when the template uses two wastes load time on every export. Audit the families and weights actually referenced in the template and trim the declarations to match. The same discipline applies to the families themselves: if a template references a family only in a rarely used section, consider loading it lazily or dropping it, so the export path stays as light as the page path.

Two Ways to Pull In Google Fonts

Google Fonts offers two integration methods: a link tag pointing at the font CSS, or an @import inside a stylesheet. The link approach benefits from preconnect optimization and parses faster, which is why Google recommends it; @import keeps everything in the stylesheet, which suits projects that centralize all styling in CSS files. Both are ultimately loading the same thing — a CSS document containing @font-face declarations split by unicode-range — and the browser downloads only the font files the page actually uses.

With the link approach, the URL looks like fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700, and the browser receives font declarations tailored to its user agent. For PDF generation, once that CSS is loaded by the page, a template that uses the same font-family names reuses the same font files — no second declaration needed, and the PDF glyphs match the page automatically. That reuse is what makes Google Fonts integration with dompdf.js a small amount of code rather than a font-pipeline project.

The important detail is that Google Fonts CSS is split into many unicode-range subsets, so the browser downloads only the slices covering characters the page uses. That keeps the actual download small for templates that use a few characters, but it also means fonts may still be loading on the first export attempt. Awaiting document.fonts.ready before generating is not optional here; it is the step that turns a mostly-working integration into a reliable one.

For projects that must keep working without external requests — intranet deployments, offline demo environments, or regions with unreliable access to Google infrastructure — plan for self-hosting from the start, because retrofitting a CDN-dependent integration later is more work than choosing the delivery model up front. If you do use the hosted service, pin the CSS URL with a specific family list, because font services occasionally change how families are served, and an unpinned URL can silently shift the glyphs your exports depend on.

Code Example: Integrating Google Fonts and Generating a PDF

The template carries both the link tag and the style declarations. dompdf.js resolves the page's already-loaded font resources when rendering; if the template is an independent HTML string rather than a live DOM node, the linked font CSS is fetched and parsed the same way, and the @font-face declarations behave identically to page-level ones. What unifies both cases is the wait: document.fonts.ready before addPage is the shared prerequisite of every Web Font integration, and skipping it reintroduces the exact race condition the pattern exists to remove.

Design the fallback order deliberately: Latin font first, CJK font second, generic family last. Inter renders English and digits, Noto Sans SC renders Chinese, and sans-serif catches anything else. This three-layer order gives every character its intended glyph rather than letting scripts fight over one family, and mixed-language paragraphs keep both scripts looking designed instead of accidental.

The display=swap parameter makes text visible during font loading by showing fallback glyphs, which improves page experience; for PDF output the final result is based on the ready fonts regardless. Before shipping, run the full flow once under real network conditions — confirm the font URLs are reachable, no CORS interception occurs, and the export is stable and repeatable — so the integration is verified where it will actually run.

If the page and the template both load fonts, prefer reusing the page's loaded fonts over re-declaring them in the template. Reuse means one download, one ready wait, and guaranteed consistency between the on-screen preview and the exported PDF.

import { DomPDF } from 'dompdf.js';

// 1. Load Google Fonts via link tag and declare styles in the template
const html = `
  <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700&family=Inter:wght@400;600&display=swap" rel="stylesheet">
  <style>
    body { font-family: 'Inter', 'Noto Sans SC', sans-serif; font-size: 11pt; }
    .title { font-weight: 700; font-size: 20pt; }
  </style>
  <h1 class="title">Web Font Integration Demo</h1>
  <p>Inter renders Latin glyphs, Noto Sans SC renders Chinese, sans-serif is the last-resort fallback.</p>`;

// 2. Wait until every Web Font has finished loading
await document.fonts.ready;

// 3. Generate the PDF only after fonts are ready
const pdf = new DomPDF();
pdf.addPage(html, { format: 'A4' });
pdf.save('webfont-demo.pdf');

Loading Timing: Using document.fonts.ready Correctly

document.fonts.ready returns a Promise that resolves when all fonts in the document have loaded, making it the most reliable readiness signal the platform offers. For pages that export PDFs, awaiting it before addPage is the core discipline. The subtle failure mode is order: if the template uses a family the page has not declared, that font must be added to document.fonts via the FontFace API before awaiting ready, because the promise resolves based on the fonts registered at the time it is created. Register first, then await, then generate.

For finer-grained control, the FontFaceSet events give more detail: load fires as each font succeeds, loadingdone signals that everything finished, and loadingerror indicates partial failure. For a one-shot export, awaiting document.fonts.ready is sufficient; when users need progress feedback on large batches, listening to load and updating a counter per font is a small amount of code that meaningfully improves the experience.

Caching is the easy win that people overlook. On a second export with the same fonts, the browser cache satisfies the requests, document.fonts.ready resolves almost immediately, and generation speeds up noticeably. Serving font files from stable, cacheable static paths with immutable URLs turns high-frequency export workflows into progressively faster ones at zero code cost, which makes URL stability a deployment concern worth planning rather than an accident of how the resources were uploaded.

A production-grade wrapper is worth writing once: a small helper that awaits document.fonts.ready, verifies the families the template needs with check(), and only then calls addPage. Encapsulating the sequence keeps every export path — buttons, batch jobs, automated runs — on the same reliable footing, and when a font problem appears, the helper is the single place to instrument it with logging or error reporting instead of hunting through each call site.

Self-Hosting vs. CDN: Reliability and Performance Trade-offs

A CDN is fast to integrate and may offer edge acceleration, but its availability is outside your control: blocked regions, rate limits, or service changes silently break font loading, and the PDF quietly falls back to system fonts. For systems serving users in regions where Google Fonts is unreliable, that risk is not theoretical — evaluate reachability for your actual user base and either pick a font service that is reachable or self-host and remove the external dependency entirely.

Self-hosting means serving the font files from your own static assets, optionally with preload hints or inline CSS declarations. The loading path is fully under your control, offline environments still generate PDFs, and the files ride your company CDN and caching policy like every other asset. The cost is maintaining the files and the declaration CSS yourself, but that is a one-time setup burden traded for stability and predictability — precisely the properties an export feature should have, because exports are a production function, not a page decoration.

When self-hosting, serve both WOFF2 and TTF or OTF: WOFF2 for modern browsers and small payloads, the outline formats as a compatibility fallback. An @font-face src listing multiple url() values lets the browser try them in order until one works, which is the most robust declaration for mixed environments and the safest pattern for PDF generation where you may not control the embedding context.

If you do stay on a CDN, at least make the dependency visible: document which font service the templates depend on, keep the pinned CSS URL in the repository, and include a font-readiness check in staging so a service-side change is detected before it reaches production exports rather than after customers report missing type.

A middle path for teams that want CDN convenience with less risk is pinning a specific Google Fonts CSS URL and downloading the referenced font files into the repository at build time, which keeps the integration reproducible while still letting you serve everything from your own origin.

CJK Web Font Strategy and Common Questions

CJK Web Fonts differ from Latin ones in one overwhelming way: size. A complete Chinese font starts at tens of megabytes, and bundling it wholesale cripples the page. Google Fonts' Noto Sans SC mitigates this with unicode-range slicing, downloading only the character slices the page uses, which makes it a workable CJK Web Font path. dompdf.js offers an even simpler route — the built-in Source Han Sans SC renders Chinese body text with zero loading cost and fully standard results.

The recommended strategy is tiered: built-in Source Han Sans SC for Chinese body text, custom Web Fonts for headings and brand elements, and a Latin Web Font for English and digits. This arrangement guarantees Chinese coverage with no waiting, puts the brand typeface exactly where it is seen, and keeps payload and visual quality balanced — the highest-value combination for Chinese PDF projects and the easiest for a team to adopt and maintain.

Common questions have equally common answers. Fonts loaded but the PDF still shows default glyphs: verify document.fonts.ready actually ran and the family names match. Chinese font loading is painfully slow: subset or fall back to the built-in face. Offline exports fail: self-host or embed fonts as data URLs. Thinking through these three up front prevents the majority of CJK Web Font integration failures and keeps export quality stable across environments.

Finally, test with real content, not demo strings. The characters that actually appear in production documents — names, addresses, technical terms — determine which unicode-range slices download, so validate load behavior with production copy to confirm the timing assumptions hold where it counts. And keep the font list in the template in sync with the page: a template that references a family the page does not load adds a hidden network dependency that only surfaces as slow first exports.

⚡ 现场演示(点击生成 PDF)

下面的按钮用 dompdf.js 在浏览器端实时生成 PDF,无需后端:

Hello from dompdf.js!

这是由 dompdf.js 渲染的示例 PDF 内容。