How font resolution works
EggPdf resolves fonts in this order:
@font-facedeclarations in CSS — fromhttp(s)URLs (Google Fonts and other CDNs),data:URIs, or files (relative paths resolve againstbasePath)- System-installed TrueType/OpenType fonts matched by name (including
.otffonts with CFF/CFF2 outlines, which are converted to TrueType outlines, and variable fonts of either flavour) - Built-in PDF standard fonts: Helvetica, Times, Courier
Remote <link rel="stylesheet"> tags are fetched too, so a plain Google Fonts
<link> works out of the box — every declared weight (300–900) selects its own
face, and characters missing from the chosen font (Hangul in Arial, symbols like ⚠, ...) fall
back to another installed font automatically — see Per-character font fallback.
Any matched TrueType font is automatically subset — only the glyphs actually used in the document are embedded, keeping PDF size small.
Per-character font fallback
No single font covers every script, so a browser draws each character it cannot find in your
font-family from some other installed font. EggPdf does the same, for every
script: when the chosen font has no glyph for a character, the glyph comes from an installed
font that actually maps it, so font-family: Arial with Korean, Japanese, Chinese,
Hebrew, Arabic, Thai, Indic, Armenian, Georgian, symbol or math text no longer shows empty
boxes. The fallback is chosen by each installed font's real character coverage, never by
language, so any script works with whatever fonts the host has.
- A per-platform, per-script candidate list is tried in order (macOS system fonts, Windows fonts, and on Linux the Noto, Droid, WenQuanYi, Nanum, Takao, Lohit and TLWG families).
- Then every installed font is considered, broadest coverage first. This covers
.ttccollections (every face is checked) and CFF/OpenType fonts, and finds fonts installed under names no list mentions. - Bitmap-only colour fonts (such as Apple Color Emoji or Noto Color Emoji) are skipped because they have no vector outlines to embed; colour emoji need a COLR font (Segoe UI Emoji, Twemoji).
Only the glyphs actually used are embedded (several fonts per document are fine), and layout
measures the fallback glyphs' real widths, so wrapping and box sizes match the painted text.
Ideographs follow the regional variant of the root lang attribute
(ja, ko, zh-Hans, zh-Hant, ...) and otherwise
the text (kana implies Japanese, Hangul implies Korean).
When no installed font covers a character it is still drawn as the missing-glyph box,
but it is no longer silent: a FONT_GLYPH_MISSING warning names the script, the
characters and a font to install (for example Noto Sans CJK). Collect warnings with
PdfRenderOptions.Warnings, the Fluent CollectWarnings(...), the CLI (printed
to stderr) or the REST X-EggPdf-Warnings response header. In a container, install a
font family for the scripts you render (the Docker images include Noto and Noto CJK) or load one
with @font-face.
To prefer your own fonts for a script, register them ahead of the built-in candidates:
FontFallbackCatalog.Default.Register(UnicodeScript.Hangul, "Corporate Sans KR");
CSS font-family
Use any installed font name in CSS:
body { font-family: Arial, sans-serif; }
h1 { font-family: 'Georgia', serif; }
code { font-family: 'Courier New', monospace; }If the named font is installed on the machine running EggPdf, it will be found, subset, and embedded. If not found, EggPdf falls back to the next font in the stack, and ultimately to Helvetica.
Built-in PDF fonts
These 14 fonts are always available without embedding — zero overhead:
Helvetica,Helvetica-Bold,Helvetica-Oblique,Helvetica-BoldObliqueTimes-Roman,Times-Bold,Times-Italic,Times-BoldItalicCourier,Courier-Bold,Courier-Oblique,Courier-BoldObliqueSymbol,ZapfDingbats
These are the 14 standard PDF fonts defined in the PDF specification. Every PDF viewer renders them without needing a font file embedded in the document.
Webfonts — Google Fonts and remote URLs
Reference a hosted stylesheet exactly like on the web — EggPdf fetches the CSS, downloads the declared faces, and embeds subsets with the document's real glyphs (full Unicode, including Vietnamese and other extended Latin):
<link href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@300;400;600;700;800" rel="stylesheet">
<style>
body { font-family: 'Be Vietnam Pro', Arial, sans-serif; }
</style>
Intermediate weights (font-weight: 500/600/800…) embed the matching variant
instead of snapping to regular/bold, and text is measured with the real font metrics so line
breaks match the browser's.
@font-face — load from file
Load fonts that aren't installed system-wide by providing a file path in @font-face
and setting basePath when calling the API:
<style>
@font-face {
font-family: 'Inter';
src: url('fonts/Inter-Regular.ttf');
font-weight: 400;
}
@font-face {
font-family: 'Inter';
src: url('fonts/Inter-Bold.ttf');
font-weight: 700;
}
body { font-family: 'Inter', Helvetica, sans-serif; }
</style>
Then render with basePath:
byte[] pdf = HtmlToPdf.Render(html, basePath: "./assets/");
The url('fonts/Inter-Regular.ttf') path resolves relative to basePath.
Use absolute paths in src: url(...) if you prefer not to set basePath.
Weight and style
Standard CSS values are supported: font-weight: 400, font-weight: 700,
font-weight: bold, font-style: italic, font-style: normal.
EggPdf selects the best matching font file from the system — for example,
Arial-Bold.ttf for font-family: Arial; font-weight: bold.
When an exact weight/style variant is not found, EggPdf applies the CSS font-weight matching algorithm to pick the closest available variant rather than substituting a different typeface.
Arabic text
Arabic letters are shaped into their contextual isolated / initial / medial / final forms,
including lam-alef ligatures, and laid out right-to-left. Supported letters are the standard
Arabic block plus Persian peh, tcheh, jeh, keheh, gaf and yeh; other Arabic-script letters
(for example Urdu tteh, ddal, rreh) are not shaped. The dir attribute and
direction: rtl both apply, and default alignment follows the direction.
Complex scripts
Text in Thai, Lao, Tibetan, Khmer, Myanmar, Sinhala and the Indic scripts (Devanagari,
Bengali, Gujarati, Gurmukhi, Oriya, Tamil, Telugu, Kannada, Malayalam), and all Arabic, is
shaped through the font's own OpenType tables. Mark attachment places stacked Thai vowels and
tone marks and Arabic diacritics; syllables are reordered (pre-base vowel signs move in front
of their consonant, a reph is placed after the base as each script requires, Khmer coeng-ro
and Myanmar kinzi are positioned) and the font's half forms, conjuncts and subscripts (such as
क्ष) are applied. For Arabic, fonts that lack the Arabic Presentation Forms
glyphs are shaped through their own init/medi/fina
features and cursive attachment.
This needs a font that contains the script. EggPdf tries your font-family list
and @font-face fonts first, then well-known script fonts (Nirmala UI, Leelawadee
UI, Myanmar Text, Noto Sans …), preferring a font that also has Latin letters so a mixed
run such as "Invoice नमस्ते" is not half boxes; if none of those is installed, any installed font
covering the script is used. That text is embedded in its own font, so Latin text elsewhere
keeps the typeface you asked for. Output was checked against Chrome's rendering for each
script listed above.
Long Thai, Lao, Khmer and Myanmar paragraphs wrap at syllable boundaries; this is a
heuristic, since real word breaking needs a dictionary (lines may end mid-word).
line-height: normal uses the shaping font's ascent, descent and line gap, so
tall fonts such as Myanmar Text don't collide. Text is reordered with the full Unicode
Bidirectional Algorithm (UAX #9: explicit embeddings, overrides and isolates, weak-type and
neutral resolution, paired brackets, mirroring) using the CSS direction as the
paragraph direction. Scripts beyond those listed are not shaped.