Skip to content

slides: an embedded font is declared with the weights it has (#850) - #851

Merged
nyblnet merged 1 commit into
mainfrom
slides-font-weight
Oct 24, 2026
Merged

nyblnet merged 1 commit into
mainfrom
slides-font-weight

Conversation

@nyblnet

@nyblnet nyblnet commented Oct 24, 2026

Copy link
Copy Markdown
Owner

Fixes #850.

"+ Embed font file…" declared every embedded face font-weight: normal. A variable font covering 100–900 then matched only weight 400, so Bold drew the 400 instance thickened by the browser (faux bold) instead of the font's own bold. A static bold file got faux bold on top of its bold.

What it does

When a font is embedded, embedFont now writes the weight and style the file states into doc.fonts (new slides/src/fontmeta.ts):

  • TTF/OTF: the fvar wght range, e.g. 100 900. A font with no wght axis gets its OS/2 usWeightClass. The OS/2 italic bit gives style: italic.
  • WOFF: the same tables, inflated with DecompressionStream('deflate').
  • WOFF2: see below.

WOFF2: measured by rendering

All of a WOFF2's tables sit in one brotli stream, and no browser accepts brotli in DecompressionStream (Chrome 149 and 156 both refuse it). Only the table directory is readable, and it says whether fvar is present.

For a variable WOFF2, the range is measured instead:

  • the face is loaded with a 1 1000 descriptor and drawn on a canvas at weights 1, 100, 200 … 1000;
  • the font clamps any weight outside its own axis, so renders outside the axis are pixel-identical to the clamped end;
  • the low bound is the highest weight that still renders exactly like weight 1, and the high bound the lowest that renders exactly like 1000.

An axis end that falls between two hundreds reads as the outer one. The range can come out wider than the font, never narrower. Wider costs nothing, since the font clamps; narrower would bring faux bold back. The shell's own Instrument Sans measures 400 700, which matches its fvar.

Known gap

A static WOFF2's weight lives inside the brotli stream, so it can't be read. It stays unstated, as before.

Evidence

Test fonts: our own design, MIT, 4 KB in total, in scripts/fixtures/fonts/ with the generator that rebuilds them. A single bar glyph whose stem and advance grow with wght, so a rendered weight can be measured. Variable TTF/WOFF/WOFF2 with a wght axis of 100–900, plus a static TTF/WOFF2 with weight 700 and the italic bit set.

test-slides-font-weight.ts (validate-slides): 12/12.

  • Each container gives the expected result.
  • The static WOFF2 gives nothing.
  • Instrument Sans reads as variable.
  • Truncated or foreign bytes give {}, never a throw.
  • One row checks that the WOFF fixture really stores fvar compressed, so the inflate path runs.

test-slides-font-weight-browser.mjs (ci-slides): 10/10 on bundled Chromium 149 and on Chrome 156.

  • It embeds each file through the real picker and checks doc.fonts.
  • Rendering, declared 100 900: weight 700 is 1.55x the width of 400, which is the font's own bold.
  • The control, the same bytes declared normal: weight 700 is 1.00x the width of 400 (faux bold).

On main's embedFont: 5/10. Every embed row is red, with no weight written.

Mutants, each confirmed applied and each build exited 0:

Mutant Rows that go red
WOFF2 probe returns nothing both WOFF2 rows
fvar reader off TTF and WOFF (they read OS/2's 400)
inflate skipped the WOFF row

The existing font rigs still pass: test-slides-fonts 25/25, and font-lifecycle.

Shell: +1.7 KB compressed.

Decks that embedded a font before this change keep their entry. Re-embedding the font writes the weights.

@github-actions

Copy link
Copy Markdown

Build size

main (dfda71f) → slides-font-weight (37cc3a2)

app base PR change
bento/slides 700.0 KiB 701.7 KiB +1.6 KiB (+0.24%)
bento/spaces 408.2 KiB 408.2 KiB 0.0 KiB (0.00%)
bento/dash 514.5 KiB 514.5 KiB 0.0 KiB (0.00%)

Updated: 2026-10-24T14:32:52Z

@nyblnet
nyblnet merged commit 931bc13 into main Oct 24, 2026
20 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.

Embedded TrueType variable fonts lose weights on import

1 participant