Choosing fonts
The built-in families
Without any font setup, three families are available under their generic CSS names:
| CSS name | Font | Character |
|---|---|---|
serif |
Crimson Pro | The default for body text: an old-style serif with a small x-height. |
sans |
TeX Gyre Heros | A Helvetica descendant. |
monospace |
CamingoCode | The default inside code and pre. |
glu deliberately does not pick up fonts installed on the system.
A document that renders on one machine renders identically on every
other machine, including CI, because the complete font set is either
embedded in the binary or referenced explicitly by the document. If
you want a system font, point an @font-face rule at its file.
Loading your own fonts
@font-face works as in the browser. Relative paths resolve against
the document (in Lua’s htmlbag.render, against base_dir):
@font-face {
font-family: "Source Serif";
src: url("fonts/SourceSerif4-Regular.ttf");
}
@font-face {
font-family: "Source Serif";
font-weight: bold;
src: url("fonts/SourceSerif4-Bold.ttf");
}
body { font-family: "Source Serif", serif; }Declare one @font-face rule per style you use: the regular, bold,
italic and bold-italic cuts each map to a font-weight /
font-style combination. <strong> and <em> then pick the right
cut automatically. When a requested weight has no exact match, the
closest registered weight is used.
Variable fonts
For a variable font you do not declare one rule per weight. Declare
the file once with its supported range, and every weight inside the
range pins the font’s wght axis to exactly that value:
@font-face {
font-family: "Source Code Pro";
font-weight: 200 900;
src: url("fonts/SourceCodePro-VariableFont_wght.ttf");
}
code { font-family: "Source Code Pro"; font-weight: 450; }
All seven rows above come from a single font file. Weights outside
the declared range clamp to its nearest end. Other axes are set with
font-variation-settings:
h1 { font-variation-settings: "wdth" 75, "opsz" 32; }Matching the x-height: size-adjust
Mixing families in running text usually exposes their different
x-heights: code spans in a serif paragraph look too large or too
small. The size-adjust descriptor scales a face relative to its
declared font-size so the proportions match:
@font-face {
font-family: "CamingoCode";
src: url("fonts/CamingoCode-Regular.ttf");
size-adjust: 89%;
}
Fallback happens per glyph
A font-family list is not a single choice but a priority-ordered
stack. Coverage is decided per glyph cluster: each character is set in
the first font of the stack that has a glyph for it, and the rest of
the text stays in the primary font.
This is also how color emoji work: put an emoji font at the end of the stack and only the emoji clusters use it. See the color emoji example for a complete document.
When a glyph is missing everywhere
If no font in the stack covers a character, the PDF shows a blank of
the missing glyph’s width (with the original character attached as
ActualText, so copy and paste and screen readers still see it), and
the log carries one warning per font and character:
WARN Font has no glyph for character, shown as .notdef char=쎾 codepoint=U+C3BE font=CrimsonPro-RegularAn unexpectedly blank character in the output is therefore always worth a look into the log file. See Debugging.