Skip to content

fix(fonts): supplement alias faces from the canonical family - #3085

Open
akzarma wants to merge 1 commit into
heygen-com:mainfrom
akzarma:fix/alias-supplement-canonical-family
Open

fix(fonts): supplement alias faces from the canonical family#3085
akzarma wants to merge 1 commit into
heygen-com:mainfrom
akzarma:fix/alias-supplement-canonical-family

Conversation

@akzarma

@akzarma akzarma commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

What breaks

A family that resolves through FONT_ALIAS_MAP to a bundled canonical can end up with two different typefaces under one font-family.

font-family: Helvetica with any italic text renders upright as Inter (the canonical bundle) and italic as real Helvetica (fetched from Google under the authored name). Same for Noto Sans, Georgia, Verdana, Garamond and the other cross-typeface aliases — including three that typography.md advertises as safe.

Details, measurements and the alias-by-alias table are in #3083.

Root cause

packages/producer/src/services/deterministicFonts.ts, in buildFontFaceCss:

const googleFaces = await fetchGoogleFont(originalCaseFamily, options, fontText);

The embedded canonical's faces are emitted under the authored family name, and then the weights/styles the bundle lacks are supplemented by querying Google — with the authored name. For a self-referencing alias (montserrat → Montserrat) that is correct. For a cross-typeface alias it fetches the very typeface the alias exists to replace.

Two properties make it bite rather than stay theoretical:

  • No CANONICAL_FONTS entry declares style: "italic", so every italic Google serves for the authored name is classified "missing from the bundle" and injected.
  • Google's css2 endpoint now serves many of these names (Helvetica, Helvetica Neue, Georgia, Verdana, Tahoma, Trebuchet MS, Garamond, Noto Sans, …), so the comment at fetchGoogleFont assuming alias names 4xx is no longer true for them.

The fix

Query CANONICAL_FONT_DISPLAY_NAMES[canonicalKey] instead of the authored name. It is already re-exported from @hyperframes/core/fonts/aliases, which this file imports.

Faces are still emitted under originalCaseFamily, so authored CSS keeps matching and nothing about the aliasing policy changes — only the source the supplementary faces come from.

Test

deterministicFonts-aliasSupplement.test.ts — hermetic, injects fetchImpl, no network:

  • Noto Sans → asserts the supplementary query asks for Inter, that the authored spelling still names the family, and that every injected src is Inter (embedded bundle or Inter fetch) with no real Noto Sans bytes present.
  • Montserrat → asserts self-referencing aliases still supplement from their own family (guards against over-correcting).

Verified the test bites: against unfixed deterministicFonts.ts it is 1 pass / 1 fail; with the fix, 2 pass / 0 fail.

Test plan

  • bun test packages/producer/src/services/deterministicFonts-aliasSupplement.test.ts → 2 pass, 0 fail
  • Same suite against unfixed source → 1 fail, confirming the regression test bites
  • oxfmt --check and oxlint on both changed files → clean, 0 warnings / 0 errors
  • Full packages/producer unit lane not run — it needs workspace packages built beyond what a fresh clone provides; relying on CI for that

`buildFontFaceCss` emits a bundled canonical's faces under the authored
family name, then fills the weights and styles the bundle lacks by
querying Google Fonts. That supplementary query used the authored name.

For a cross-typeface alias — `helvetica`, `noto sans`, `georgia` and the
other FONT_ALIAS_MAP entries that do not point at themselves — the
authored name is a different typeface from the canonical the alias
resolves to, and Google now serves many of those names. No canonical
bundle ships an italic face, so every italic Google returns for the
authored name is injected: `font-family: Helvetica` renders upright as
Inter and italic as real Helvetica, two typefaces under one family.

Query the canonical display name instead. The faces are still emitted
under the authored family, so authored CSS keeps matching, and
self-referencing aliases are unaffected.

Closes heygen-com#3083

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reproduced the bug against live Google Fonts, confirmed this branch fixes it, and found one thing I'd like addressed before it lands.

Repro (main, unfixed)

font-family: Helvetica, upright + italic text, real network:

google css2 family= queried: [ "Helvetica" ]
  Helvetica  normal 400  EMBEDDED INTER BUNDLE
  Helvetica  normal 700  EMBEDDED INTER BUNDLE
  Helvetica  normal 900  EMBEDDED INTER BUNDLE
  Helvetica  italic 400  FETCHED FROM GOOGLE
  Helvetica  italic 700  FETCHED FROM GOOGLE

Rendered headless, the upright line is Inter and the italic line is Google's Helvetica substitute. Two typefaces under one font-family, exactly as described. css2?family=Helvetica returns 200 today, so the "alias names 4xx" comment is indeed stale.

With this branch

google css2 family= queried: [ "Inter" ]
  ... italic 400 / 700 now come from Inter

Italic renders as Inter Italic. Bug is gone, and weights 100-800 that previously had no face at all now resolve to real Inter weights. The fix is at the right layer, in the one function every aliased family routes through, and reusing CANONICAL_FONT_DISPLAY_NAMES is the right call: all 18 canonical slugs have an entry and every one is a real Google family.

Blocking: the payload multiplies

Google serves Inter as a variable font, so css2 hands back the same woff2 URL for every static weight. The supplement loop embeds that identical base64 blob once per weight.

One family, Helvetica, compiled HTML:

faces unique blobs HTML
main 5 5 110 KB
this branch 11 5 302 KB

Six @font-face rules (100/200/300/500/600/800) carry byte-identical copies of one 25 KB blob; the two italics are another duplicate pair.

It compounds across aliases, because names that used to 4xx now fetch. Four families that all resolve to Inter (Helvetica, Arial, SF Pro, Verdana):

faces unique blobs HTML
main 16 7 405 KB
this branch 44 5 963 KB

963 KB carrying five distinct fonts. The duplication is pre-existing in the supplement loop, but this PR is what makes it fire on every cross-typeface alias, so I'd rather not land the amplification untouched.

Smallest fix that keeps semantics: when consecutive Google faces share a src, emit one rule with a weight range (font-weight: 100 800) instead of one rule per weight. That is the correct declaration for a variable font anyway and takes the 4-family case back under 300 KB. If you'd rather keep this PR to the one-line correctness fix, that's fine by me, but please open the follow-up and link it here.

Non-blocking

  1. ?? originalCaseFamily is unreachable today, and if it ever becomes reachable it silently restores this exact bug. Worth a line in deterministicFonts.test.ts asserting every CANONICAL_FONTS key has a CANONICAL_FONT_DISPLAY_NAMES entry, then the fallback can go.
  2. resolveAliasDisplayName() already exists in the same module and does this lookup in one call. Would drop the extra import. Pure taste, ignore if you prefer the explicit map.

Test file looks right to me. It classifies into the unit/bun lane with no manifest edit needed, and the assertions do bite: on unfixed source queriedFamilies is ["Noto Sans"], so the first test fails and the Montserrat guard passes. Matches your 1 pass / 1 fail.

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.

2 participants