Part Two — Typography Chapter Eleven

Web Fonts in a Print Context

A typeface that loads beautifully in a browser may behave differently on its way to a PDF. Understanding the pipeline prevents surprises.

Typography in a browser and typography in a PDF share the same CSS, the same HTML, and the same fonts — but the path from source to output is different enough that it warrants its own chapter. When you design for screen, the font loads once, the browser renders it continuously, and any loading failures are visible and correctable in real time. When you design for print via Paged.js, the font must be present and fully loaded before pagination begins, must embed correctly in the PDF output, and must survive any licensing restrictions that govern its redistribution. Each of these requirements has a specific technical response, and knowing them in advance saves considerable frustration.

This chapter covers the full lifecycle of a web font in a Paged.js document — from the loading strategy that prevents pagination errors, through the question of when system fonts are actually the better choice, to the licensing considerations that govern what you can and cannot do with a font file once it is embedded in a distributed PDF.

· · ·

The font loading problem Why timing matters for pagination

Paged.js works by running a JavaScript polyfill that reads your HTML and CSS, applies the CSS Paged Media specification, and renders the content as a sequence of fixed-size pages in the browser. This pagination process — calculating where lines break, where pages break, how much content fits on each page — depends on the font metrics of the typefaces being used. The width of each character, the height of each line, the space occupied by each word — all of this is determined by the font. If the font is not loaded when Paged.js runs, it uses the browser's fallback font metrics instead, and the pagination is calculated against a typeface that may be significantly different from the one you intended.

The consequences are visible and consistent: text that overflows pages, page breaks in the wrong places, line counts that differ from the final rendered version, and a document that looks correct in the Paged.js preview but produces a different layout when the fonts eventually load. This is the font loading problem in Paged.js, and it has a reliable solution: ensure the fonts are fully loaded before Paged.js begins its work.

The most robust approach is the document.fonts.ready promise, which resolves when all font faces referenced in the document's CSS have finished loading. Paged.js provides a mechanism to delay its automatic preview until this promise resolves. In the simplest setup, where Paged.js runs automatically on page load, you can use the previewer API to trigger pagination manually after fonts are ready:

/* 1. Disable auto-preview BEFORE loading the polyfill */
<script>
  window.PagedConfig = { auto: false };
</script>

/* 2. Load the polyfill                              */
<script src="paged.polyfill.js"></script>

/* 3. Trigger preview only after fonts are ready     */
<script>
  document.fonts.ready.then(function () {
    // All fonts loaded — safe to paginate
    new window.Paged.Previewer().preview();
  });
</script>

An alternative, and often simpler, approach is to use the font-display: block descriptor in your @font-face declarations. This instructs the browser to block rendering entirely until the font loads, rather than falling back to a system font. While font-display: block is generally discouraged for performance-sensitive web pages — it can cause a visible flash of invisible text — it is entirely appropriate for a print document where visual correctness matters more than loading speed. When Paged.js runs, the fonts will already be in place, because the browser has been waiting for them.

/* Self-hosted font with font-display: block  */
@font-face {
  font-family:   'EB Garamond';
  font-style:    normal;
  font-weight:   400;
  font-display:  block; /* Wait — don't fall back */
  src: url('fonts/eb-garamond-400.woff2')
       format('woff2');
}

The display=swap problem Why the default Google Fonts setting breaks pagination

When using Google Fonts, the default font-display value is swap. This is sensible for a website: the browser renders immediately using a fallback system font, then swaps in the web font once it loads — giving users something to read quickly. For a Paged.js document it is the wrong behaviour, and understanding exactly why matters.

Paged.js runs when its script executes — usually on DOMContentLoaded or shortly after. With font-display: swap, the web fonts have not arrived yet at that moment; the browser is displaying your text in the fallback font. Paged.js reads the current rendering, calculates where lines break and where pages break, and builds its page structure — all against the fallback font's metrics. When the web font loads moments later (the "swap"), the browser updates the text rendering, but Paged.js does not re-paginate. The pagination was calculated for, say, Georgia; the text is now rendered in EB Garamond, which has different character widths and a different line rhythm. The result is text that overflows pages, page breaks in the wrong places, and a document that looks visually correct but is typographically broken.

Google Fonts supports a URL parameter that overrides this default. Appending &display=block to the API request URL applies font-display: block to every font in the request — instructing the browser to wait, invisibly, until all fonts are loaded before rendering anything:

<!-- Google Fonts with display=block
     Prevents swap; browser waits for fonts  -->
<link rel="stylesheet"
      href="https://fonts.googleapis.com/css2?
  family=EB+Garamond:ital,wght@0,400..500;1,400..500
  &family=Outfit:wght@400;500
  &display=block">

With display=block in effect, Paged.js runs against the correct fonts — the same result as using font-display: block in a self-hosted @font-face declaration. This is the approach used in The Compositor's Garden project files. The document.fonts.ready guard adds a second layer of protection and remains useful in environments where script execution order is less predictable — both techniques together are belt-and-suspenders, but display=block alone is sufficient for typical development workflows.

Best for most projects Self-hosted + font-display: block Use when: offline capability needed, CI/CD pipeline, production documents

Font files live alongside the HTML document. No network dependency. font-display: block guarantees the browser waits for the correct font before rendering, so Paged.js always paginates against the right metrics. Requires downloading font files and managing them in the project folder.

@font-face {
  font-family: 'EB Garamond';
  font-display: block;
  src: url('fonts/eb-garamond.woff2');
}
Good for development Google Fonts + fonts.ready promise Use when: iterating quickly, network access reliable, license is SIL OFL

Fast to set up and covers a wide range of quality typefaces under open licenses. Requires the document.fonts.ready guard to prevent premature pagination. Requires an internet connection when the document is opened for preview.

document.fonts.ready.then(() => {
  window.PagedPolyfill.preview();
});
Consider carefully Commercial CDN (Adobe Fonts, etc.) Use when: specific commercial typeface required, subscription active

Gives access to professional typefaces not available on Google Fonts. Requires an active subscription, network access, and the fonts.ready guard. Check the license carefully — many commercial web font licenses do not permit PDF embedding, which is what Paged.js does when generating a downloadable document.

Figure 11.1 — Font loading strategies for Paged.js. Three approaches in order of reliability for print output. Self-hosting with font-display: block is the most robust for production documents. Google Fonts with the fonts.ready guard is the fastest to set up for development. Commercial CDN fonts require careful license checking before use in distributed PDFs.

· · ·

System fonts as a deliberate choice When the best font is already on the machine

There is a reflexive assumption in web typography that web fonts are always preferable to system fonts — that loading a font from a server is inherently better than using what is already installed on the device. For screen work, this assumption is broadly defensible: web fonts give you control over the typeface regardless of what the reader has installed, and that consistency is valuable. For print work via Paged.js, the assumption deserves more scrutiny.

When a document is intended to be generated locally — by you, on your own machine, in a controlled environment — the system fonts available on that machine are fully available to the CSS without any loading consideration. There are no network requests, no loading delays, no fonts.ready timing issues, and no PDF embedding questions. The font renders in the browser, Paged.js paginates against its metrics, and the PDF is generated with the font embedded from the local font cache. This is, in some respects, the cleanest possible setup.

The practical limitation is that system font availability varies between operating systems and even between machines running the same operating system. A font that ships with macOS — Georgia, for instance, or Charter — may not be available on Windows or Linux. This matters if the document will be generated on multiple machines, by multiple people, or in a CI/CD environment where the operating system is unpredictable.

The solution is the system font stack — a fallback chain that specifies multiple fonts in order of preference, so that whichever is available on the current system is used. For a document that needs to work well on any machine without network access, a carefully constructed system font stack can produce surprisingly good results:

Serif system font stack — for body text
1 'Charter' macOS / iOS — excellent text face, generous x-height, clean at small sizes
2 'Bitstream Charter' Linux (some distributions) — the open-source ancestor of Charter
3 'Georgia' Windows, macOS, most systems — designed for screens, reads well at text sizes
4 'Cambria' Windows — a well-made transitional serif with good screen and print performance
5 serif Browser default — Times New Roman on most systems; last resort only
Sans-serif system font stack — for UI elements and labels
1 -apple-system macOS / iOS — San Francisco; the system UI font, well-hinted, clean at any size
2 BlinkMacSystemFont Chrome on macOS — same San Francisco, different identifier
3 'Segoe UI' Windows — the Windows system UI font; readable, professional
4 'Helvetica Neue' macOS (older) — reliable neutral sans, well-spaced
5 Arial Universal — ubiquitous if not distinguished; better than system default
6 sans-serif Browser default — last resort

For the project document, we use web fonts — EB Garamond and Outfit — because the document is intended to be used by readers on any machine, including those without access to the best system serif fonts. But for a document you are producing yourself, on your own machine, for your own use or for a small number of specific recipients, a system font stack is a perfectly legitimate and technically simpler choice.

· · ·

Font licensing for PDF distribution What the licence actually permits

Font licensing is one of the least understood and most important aspects of professional typography. Every font file is software, and every font file is distributed under a licence that governs what you can and cannot do with it. The licence applies whether you paid for the font or downloaded it for free, and "I didn't know" is not a defence that holds in any professional context.

For a document produced with Paged.js, there are two distinct uses of the font that must be permitted by the licence. The first is web use: serving the font from a server (or a CDN like Google Fonts) and rendering it in a browser. The second is PDF embedding: including the font data inside the PDF file so that readers who do not have the font installed can still see the correct typeface. Both uses must be permitted. A licence that permits only web use — common in some commercial web font licences — does not permit the PDF embedding that Paged.js performs.1

The simplest and most reliable approach is to use typefaces licensed under the SIL Open Font License, which explicitly permits both web use and embedding in documents. All Google Fonts are SIL OFL licensed. Many high-quality typefaces from independent designers are also SIL OFL — Source Serif, EB Garamond, Lora, Libre Baskerville, and many others that are entirely suitable for professional document work.

For commercial typefaces — those requiring a purchase or subscription — the licence must be read carefully. The terms vary considerably between foundries. Some commercial licences permit PDF embedding as part of their standard web font licence; others require a separate desktop or embedding licence; others prohibit embedding entirely for files intended for wide distribution. When in doubt, contact the foundry directly. Most are willing to clarify what their licence permits, and many offer specific embedding licences at reasonable cost.

Licence type Web use PDF embedding Notes
SIL Open Font License ✓ Yes ✓ Yes All Google Fonts. Explicitly permits both uses. Redistribution of the font file itself in modified form requires the same licence.
Web font licence (commercial) ✓ Yes ⚠ Check terms Varies by foundry. Many commercial web font licences permit embedding for documents not primarily intended to distribute the font itself — but read the specific terms.
Desktop licence (commercial) ✗ No ✓ Yes Desktop licences permit installation and embedding in static documents but not web serving. Cannot be used with Google Fonts or any CDN delivery.
Adobe Fonts (subscription) ✓ Yes ⚠ Limited Adobe Fonts permits embedding in PDFs for personal and internal use. Distribution of PDFs containing Adobe Fonts to external parties may require additional licensing — check the current terms.
System fonts (pre-installed) ✗ No ✓ Usually yes System fonts like Georgia and Charter are licensed for use on the system they are installed on, which includes embedding in documents created on that system. Not for web serving.

One practical point worth emphasising: when Paged.js generates a PDF via the browser's print function, font embedding is handled by the browser's PDF engine, not by Paged.js itself. The browser will attempt to embed font data in the PDF according to the font's embedding permissions flags — a field set in the font file itself by the foundry. Fonts with the embedding permissions set to "no embedding" will not be embedded, and the PDF will fall back to a substitute font for readers who do not have the typeface installed. Fonts with the SIL Open Font License, and most web fonts, have their embedding permissions set to allow embedding.

· · ·

Performance vs. typographic richness Making the trade-off consciously

For a document that will be opened in a browser, paginated by Paged.js, and exported as a PDF, loading performance is a consideration — but it is a different consideration than for a public-facing website. A website visited by thousands of users must load in under two seconds or risk losing them. A document opened by one person, or a handful of people, on a machine with a reliable internet connection, can afford to take five or ten seconds to load its fonts without any meaningful consequence.

This shifts the performance calculation considerably. For a web page, you might choose a system font stack or a single-weight web font to minimise load time. For a Paged.js document, you can afford to load four or five font files — roman and italic in two weights, plus a companion sans-serif — without causing problems for any realistic use case. The trade-off is real but the stakes are lower than they first appear.

Where performance does matter in this context is in automated pipelines — situations where the document is generated programmatically, perhaps hundreds of times, in a headless browser environment. In this case, network requests add up, and self-hosted fonts become essential. They also improve consistency: a headless browser with access to local font files will produce identical output every time, regardless of network conditions or CDN availability.

The guiding principle is: load what you need, no more. For the project document, this means loading EB Garamond in two weights (400 and 500), both roman and italic, plus Outfit in two weights (400 and 500). That is five font files — or fewer, if using a variable font — which is a comfortable load for the browser and a complete typographic toolkit for the document. Request only the weights and styles you actually use in the CSS; loading a full font family "just in case" adds weight without adding benefit.

/* ── Optimised Google Fonts request ─────────
   Only the weights and styles actually used
   in the project stylesheet                  */

<!-- Preconnect for performance -->
<link rel="preconnect"
      href="https://fonts.googleapis.com">
<link rel="preconnect"
      href="https://fonts.gstatic.com"
      crossorigin>

<!-- EB Garamond: 400 and 500, roman + italic
     Outfit: 400 and 500, roman only
     Variable font syntax for EB Garamond      -->
<link rel="stylesheet"
      href="https://fonts.googleapis.com/css2?
  family=EB+Garamond:ital,wght@
    0,400..500;1,400..500
  &family=Outfit:wght@400;500
  &display=block">

The display=block parameter applies the behaviour described in the previous section. Note also the variable font syntax for EB Garamond: wght@0,400..500;1,400..500 requests a continuous weight range from 400 to 500 in both roman and italic, rather than requesting two discrete weights as separate font files. This reduces the number of network requests while giving the CSS full access to any intermediate weight in that range.2

· · ·

Putting it all together The complete font setup for the project

The following is the complete, production-ready font loading setup for The Compositor's Garden, combining everything from this chapter: the optimised Google Fonts request, the fonts.ready guard for Paged.js, and a graceful fallback for offline or restricted environments.

<!-- ════════════════════════════════════════
     Font loading — The Compositor's Garden
     ════════════════════════════════════════ -->

<!-- 1. Preconnect hints -->
<link rel="preconnect"
      href="https://fonts.googleapis.com">
<link rel="preconnect"
      href="https://fonts.gstatic.com"
      crossorigin>

<!-- 2. Font request — only used weights
     Variable weight range: 400–500         -->
<link rel="stylesheet"
      href="https://fonts.googleapis.com/css2?
  family=EB+Garamond:ital,wght@
    0,400..500;1,400..500
  &family=Outfit:wght@400;500
  &display=block">

<!-- 3. Disable auto-preview BEFORE loading Paged.js -->
<script>
  window.PagedConfig = { auto: false };
</script>
<script src="paged.polyfill.js"></script>

<!-- 4. Font-ready guard — belt-and-suspenders     -->
<script>
  document.fonts.ready.then(function () {
    new window.Paged.Previewer().preview();
  });
</script>

/* 5. CSS fallback stack — in stylesheet     */
:root {
  /* Web font first, quality system fonts as
     fallback for offline/restricted envs    */
  --font-body: 'EB Garamond',
               Charter,
               'Bitstream Charter',
               Georgia,
               serif;

  --font-ui:   'Outfit',
               -apple-system,
               BlinkMacSystemFont,
               'Segoe UI',
               sans-serif;
}

A note on offline use

If you will be working on the project document in environments without reliable internet access — on a plane, in a location with poor connectivity, or in a build environment without outbound network access — download the font files from Google Fonts using the google-webfonts-helper tool and switch to the self-hosted @font-face approach from earlier in this chapter. Change font-display: block in the @font-face declarations, remove the Google Fonts <link> elements, and remove the fonts.ready guard — it is no longer needed, because the fonts load from local files before the page script runs.

The compositor-garden project uses the Google Fonts approach with &display=block. To switch to self-hosted fonts, download the files from Google Fonts, add the @font-face declarations to the project's 01-tokens.css with font-display: block, and remove the <link> elements from the HTML. The fonts.ready guard can be removed in that case — self-hosted fonts load from disk before any script executes.

· · ·

Font loading, licensing, and performance are not the most glamorous topics in typography, but they are the ones most likely to cause real problems in a real project. Getting them right is invisible — the document simply works, the PDF is correct, the typefaces render as intended. Getting them wrong produces a document that breaks in ways that are difficult to diagnose if you do not know where to look.

With this chapter, Part Two is complete. The typographic foundation of The Compositor's Garden is fully in place: typefaces chosen and loaded, scale defined, spacing system established, OpenType features configured, and the font pipeline understood. In Part Three, we build the spatial structure on top of this foundation — the grid, the margins, and the column system that give the type a physical home on the page.