Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Fonts

A PDF embeds its fonts in the document. Unlike a browser, it cannot fall back to whatever the reader happens to have installed, so which font is used is decided at conversion time*

The order in which fonts are chosen

  1. The CLI options --font, and --gothic-font, --serif-font, --mono-font
  2. @font-face in the CSS
  3. A system font search using the names in font-family
  4. A system search for a font that can draw the characters in the document

Only if none of these finds anything does a system sans-serif candidate become the default font.

The fourth step is the safety net for documents where the names give no clue, such as Japanese text with no font-family anywhere. If the document contains characters that none of the fonts gathered in steps 1 to 3 can draw, a system font that has them, for example Noto Sans CJK JP for Japanese, is found and added. The search runs per weight and style, so a document that mixes bold and regular text gets both faces added, which keeps regular text from being drawn with the bold face. If characters still cannot be drawn, a warning is printed before they turn into empty boxes.

警告: 文字 "ไ" を描画できるフォントがありません(豆腐になります)。
  --font/--gothic-font か @font-face でフォントを明示してください

On a server or in CI, name the fonts with --font. Without it the output depends on the fonts installed on that machine, and that is how the same HTML ends up looking different on a developer’s laptop and in production.

Generic family names

serif, sans-serif, and monospace are resolved against the system fonts. cursive and fantasy are not resolved.

For Japanese, leaving this to the environment changes the typeface of the body text, so the CLI lets you pin it explicitly.

sghtmltopdf invoice.html \
  --gothic-font NotoSansJP-Regular.ttf \   # font-family: sans-serif の実体
  --serif-font  NotoSerifJP-Regular.ttf \  # font-family: serif の実体
  --mono-font   NotoSansMono-Regular.ttf   # font-family: monospace の実体

For a TrueType Collection (.ttc), give the face index with --font-index, which applies to the --font option just before it.

@font-face

@font-face {
  font-family: "MyFont";
  src: url("fonts/MyFont-Regular.ttf");
  font-weight: 400;
  font-style: normal;
}

body { font-family: "MyFont", sans-serif; }

The descriptors that are honoured are font-family, src, unicode-range, font-weight, and font-style. Within src, both local() and url() with format() or tech() are accepted. Other descriptors, such as font-display, are ignored.

Only TTF and OTF files are supported. WOFF and WOFF2 are not, so pointing at a webfont as served on the web is an error. Use the original TTF or OTF.

There is nothing to wait for. The document.fonts.ready dance that headless Chrome needs is unnecessary here, and a PDF is never produced with fonts still unresolved.

unicode-range

Ranges

@font-face {
  font-family: "Mixed";
  src: url("fonts/Latin.ttf");
  unicode-range: U+0-24F, U+1E00-1EFF;
}
@font-face {
  font-family: "Mixed";
  src: url("fonts/JP.ttf");            Ranges
}
  • Ranges
  • Ranges
  • A font declared without unicode-range, including those from local(), --font, and the system search, covers the whole range
  • Ranges

Bold and italic

ValueBehaviour
font-weightnormal, bold, or 100 to 900. Numbers are reduced to two states, with 600 and above counting as bold. Without a bold face, synthetic bold is drawn by adding a stroke around the fill
font-stylenormal, italic, or oblique, with oblique treated as italic. Without italic shapes, synthetic italic is produced by shearing the text matrix

The font shorthand is not supported. Write the longhands, such as font-size and font-family, individually.

Subsetting

Only the glyphs that are actually used get embedded. Even if you name a complete Japanese font, the PDF only grows by the characters that appear in the document.

A note on streaming mode

In streaming mode the whole document is never held at once, so the system font searches in steps 3 and 4 above do not happen; a warning is printed and the default font is used. Naming the fonts with the --font options or @font-face gives you the fonts you intended even when streaming.

As an exception, when no font at all is given, one font that can draw CJK is loaded up front alongside the Latin default. That is why a Japanese document converted in streaming mode with no options does not come out as empty boxes. Scripts other than CJK still produce a warning.