Skip to content

Fix #296: variable fonts list and draw every named style - #326

Merged
bflatastic merged 1 commit into
mainfrom
fix/issue-296
Oct 7, 2026
Merged

bflatastic merged 1 commit into
mainfrom
fix/issue-296

Conversation

@bflatastic

Copy link
Copy Markdown
Member

Fixes #296.

The font database made one style per font file, from its name table. A variable font such as Figtree from Google Fonts (Figtree[wght].ttf, Figtree-Italic[wght].ttf) is one file per family whose default instance is Light, so VectorCraft listed only Light and Light Italic and never read the fvar named instances (Regular to Black). Outlines, advances and shaping were also always taken at the default location.

What users see

  • Every named instance of a variable font is a style of its family in the Character panel, Properties, Find Font, the Glyphs panel and text.fontList / MCP, sorted by wght with italics after. Figtree shows Light to Black with their italics; Bahnschrift shows its 15 styles (Condensed and SemiCondensed included); Segoe UI Variable its 15 (Light to Bold in Display, Small and Text).
  • The chosen instance is shaped, measured and drawn at its own axis settings: the canvas, Create Outlines, hit testing, the Glyphs panel and line metrics (MVAR).
  • Saved documents keep the style name as before; on reopen, resolve(family, "Bold") finds the instance, including installed fonts not loaded yet. PostScript names such as Figtree-Bold resolve too (PDF and .ai import).
  • PDF export embeds the instance through krilla's Font::new_variable (instanced by subsetter, CFF2 turned into TrueType), so the text stays real, selectable text in the instance's glyphs.
  • SVG export with embedded fonts writes @font-face as a static subset of the instance (embedding the variable file would show the default instance in browsers). A licence that forbids subsetting embeds the whole instance; an instance that can't be instanced isn't embedded rather than embedding the wrong one.
  • Static fonts and the web build (no system fonts there) behave as before.
  • Damaged fonts are capped (64 axes, 512 instances, name-table size) and never panic.

Tests

  • crates/text/src/test_fonts.rs builds a variable font in code with write-fonts (no font file is committed): the bundled Source Sans 3 plus a wght axis whose gvar widens l, I and n by 25 %, with instances Normal (default), SemiBold, Bold, an unnamed one and a duplicate BOLD. Other crates' tests reach it through the dev-only test-fonts feature.
  • tests_variable.rs:
    • instances are listed without duplicates or the default instance, with the right weights;
    • resolving by name, normalized name, the default instance's own name, closest weight and PostScript name;
    • Bold advances and outlines are 25 % wider, SemiBold half that, and shaped layout uses them;
    • the embedded subset is static (no fvar/gvar) with Bold advances;
    • installed variable fonts are cataloged with their instances before loading;
    • damaged fvar tables (huge axis or instance counts, bad record sizes, truncation, 2,000 instances) and 300 rounds of random damage to fvar/gvar of a real font don't panic.
  • System fonts, skipped when absent (both ran on Windows 11): Bahnschrift (Bold l and text wider than Light) and Segoe UI Variable.
  • PDF variable_font_instances_are_real_text_in_their_own_glyphs: real text in Regular and Bold re-imports within 0.5 pt of the outlined export, and Bold is wider.
  • SVG variable_font_instances_embed_as_themselves: the @font-face fonts are static, with the instance's advances.

Not done yet

  • Arbitrary axis values (a Variable Font panel with sliders): only named instances are offered. Listed in the ROADMAP's Type advanced row.
  • SVG export without embedded fonts writes font-weight / font-style only, so a width instance such as "Bold Condensed" shows as Bold in a browser (as for static fonts today).
  • The PDF's embedded font name is the file's default PostScript name; the glyphs are the instance's.

Dependencies: subsetter's variable-fonts feature in vectorcraft-text (krilla already enabled it everywhere, wasm included); write-fonts 0.48 (already in the lock through subsetter) as a dev-dependency and behind the dev-only test-fonts feature.

Checks

🤖 Generated with Claude Code

The font database made one style per font file, so a variable font
such as Figtree[wght].ttf listed only its default instance (Light).
Faces now also yield their fvar named instances, each a style of the
family named by its subfamily name (the default instance is the face
itself; unnamed or duplicate instances are left out; axes and
instances read are capped). The system font scan reads the fvar table
beside the name table, so installed instances are listed before the
file is loaded and found by name and by PostScript name.

An instance face carries its axis settings: skrifa draws its outlines
and reads its metrics and advances at that location, harfrust shapes
with a ShaperInstance, so layout, the canvas, Create Outlines and hit
testing all use the instance. PDF export embeds it through krilla's
Font::new_variable, SVG @font-face through subsetter's
subset_with_variations: both write a static font of that instance.

Tests build a small variable font in code (test_fonts, via
write-fonts; no font file is committed) and check listing, resolving,
glyphs and advances, PDF and SVG export and damaged fvar/gvar tables;
Bahnschrift and Segoe UI Variable are checked where installed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bflatastic
bflatastic merged commit dfe7f1a into main Oct 7, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Only loads two weights of fonts

1 participant