Domotion

Fonts & non-Latin scripts

Picking fonts the path renderer can find — and what to expect for Arabic, Devanagari, CJK, emoji.

Domotion's path renderer needs to find a real font file on disk to extract glyph outlines from. This page covers how the lookup works, how to handle web fonts, and what to expect for non-Latin scripts.

Where Domotion looks for fonts

Path mode reads outlines using fontkit. The lookup table mapping font-family values to font files on disk is tuned for macOS system fonts: SF Pro, SF Mono, New York, Helvetica Neue, etc.

  • macOS: the bundled mapping covers the standard system fonts. Author your demos in those families and the path renderer will "just work" with no font config.
  • Linux / Windows: the mapping table is much sparser. The capture still works, but path-mode glyphs whose font isn't found fall back to native SVG <text> with the captured CSS font properties, which means the result depends on whatever the rendering browser has installed.

If you need cross-platform path rendering, do your captures on macOS (or in a CI runner with macOS images) and ship the resulting SVGs. The glyph outlines are baked in — Linux and Windows viewers will see them correctly even though they lack the source fonts.

Picking a font that captures cleanly

Three rules of thumb:

  1. Stay in the system stack when you can. -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif — the resolved system font is one Domotion knows.
  2. If you use a web font, wait for it. Always await page.evaluate(() => document.fonts.ready) before capturing — otherwise you'll capture the fallback typeface and the layout will be subtly wrong.
  3. Avoid rare display faces. A handful of decorative fonts use OpenType features Domotion doesn't shape (variable-axis interpolation along axes other than weight, certain SVG-in-OpenType color tables); those fall back to raster.

Non-Latin scripts

Domotion handles complex scripts by detecting fallback-font runs (Chromium has already chosen a different font for them) and shaping each run as a unit with font.layout(runText). That means contextual joining, ligature clusters, and mark-on-base positioning all survive — Latin, Cyrillic, Greek, Arabic (initial / medial / final / isolated forms; bidi via bidi-js), Hebrew, Devanagari (cluster reordering, ligatures), Thai (mark-on-base), and CJK (Han, Hiragana, Katakana, Hangul; GPOS positioning) all round-trip. Other complex scripts (Tibetan, Khmer, Burmese) flow through the same pipeline.

Mixed-script lines like "Hello مرحبا नमस्ते" work — each contiguous fallback run is shaped as a unit, and the per-character anchor offsets keep the runs aligned with what Chromium painted.

Emoji and color-bitmap glyphs are the exception: each unique glyph is screenshotted once and embedded as a base64 PNG, then referenced via <use> for dedup.

Verified shaping accuracy

fontkit's shaping is within ~1% of HarfBuzz on Arabic and Thai, and pixel-identical on Devanagari and CJK ideographs at typical body sizes. The test suite includes one fixture per script that diffs the SVG render against the Chromium PNG — the path renderer is within Domotion's overall 3% pixel-diff threshold for all of them.

Web fonts

If you load a web font via <link rel="stylesheet" href="..."> or @import url(...):

  1. Wait for document.fonts.ready before capturing.
  2. If the web font isn't in Domotion's bundled mapping table (most aren't), the path renderer will fall back to native <text> with the captured CSS font-family. The text will still render, just not as embedded outlines.
  3. For self-contained output with a custom font, you can pre-process the SVG and inject a base64-embedded @font-face. That's outside Domotion's current public surface; file an issue if this matters for your use case.

Decoration handling

Underlines and strike-throughs are positioned using the font's own post.underlinePosition / OS/2.yStrikeoutPosition tables, not as a fraction of font size. This matches Chromium and avoids the slight "underline too low" effect you sometimes see in rasterized SVG output.

See also