Skip to content

fix(studio): font faces a host saves keep their own weight and style - #4753

Merged
miguel-heygen merged 3 commits into
mainfrom
dfilm/font-face-weight-style
Sep 30, 2026
Merged

miguel-heygen merged 3 commits into
mainfrom
dfilm/font-face-weight-style

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

What changed

Studio writes an @font-face for each font file a text is set in, but it never says which weight or style that file draws. When a film holds several files of one family (a Bold for the title, a Regular for the subtitle, an Italic for a caption), every face claims the same default weight and style. The browser then draws every text of that family from the last file saved, in the editor and in the render.

This lets a host that knows each file's weight and style say so; Studio then writes it into the face.

  • ImportedFontAsset takes an optional weight ("700" for a static file, "100 900" for a variable one) and style ("normal" or "italic").
  • importedFontFaceCss writes them as font-weight and font-style, so each file draws only the texts that ask for its weight and style. A weight a browser would drop (0, 5000, bold, anything but one or two numbers from 1 to 1000) is left out.
  • An asset with neither is written byte for byte as before, so faces already in a film still match and are not written twice.
  • injectPreviewImportedFont keys its preview <style> by file path instead of by family. A second file of a family now reaches the preview, and the preview shows the same file as the render. Before, it kept showing the family's first file.
  • Where the values come from: the assets a host passes to useDomEditSession as importedFontAssetsRef. resolveImportedFontAsset returns the first asset of the family in that list, so a host puts the file a save should write first in the list before that save. It returns them ahead of the file tree, and commitDomStyles and the text-field commit save them through ensureImportedFontFace. Studio's own font import passes no weight or style yet, so its behaviour is unchanged; reading them from the file name there is a separate change.
  • Two fixes to the same save path, both already on main:
    • ensureImportedFontFace passed the new CSS to String.replace as a string. A file name holding $' or $& pasted part of the page into the saved HTML. It now passes a function.
    • cssString now escapes <, so a family decoded from a file name such as </style><b>x cannot close the fonts <style> block.

What I measured

  • fontAssets.test.ts, 5 tests:
    • A face with no weight or style is written as before.
    • The descriptors are written, including a variable weight range; values a browser would drop are left out; a family cannot close the style block.
    • Two weights of one family both stay in the saved HTML (a repeat is not written twice) and both reach the preview.
    • A $' or $& in a file name is saved as written.
  • domStyleCommit.fonts.test.ts, 1 test: two commitDomStyles saves, whose resolver hands over Poppins SemiBold (600) and then Regular (400), leave two faces in the saved HTML with font-weight: 600 and font-weight: 400.
  • Mutations, each caught:
    • Dropping the descriptors fails 4 tests.
    • Keying the preview by family again fails 1.
    • Removing the < escape fails 1.
    • Loosening the weight check fails 1.
    • A string replacer fails 1.
  • useDomEditCommits, useDomEditTextCommits and useDomEditSession tests: 64 pass. Studio tsc --noEmit, oxlint, oxfmt and the comment ratchet are clean.
  • A host run against this change with the diff applied to its installed Studio: after a Poppins pick for a 600-weight title and one for a 400 subtitle, with the Google font hosts blocked, the saved page held two faces (600 and 400). Chrome loaded both from the project's files, and each text drew its own weight. At 0.8.96 the same run saved two faces with no descriptors.
  • The pages behind the captures below use the real importedFontFaceCss from main and from this branch in a hand-built page, not the Studio UI: Poppins Bold, Regular and Italic from fonts.gstatic.com, with made-up text. At main, Chrome loads only the last face; with this change it loads all three.

What I did NOT exercise

  • Studio's own font import: it still passes no weight or style, so it still writes descriptor-less faces.
  • A film that already has descriptor-less faces for a family keeps them. They claim weight 400 and style normal, so they can still win for texts that match those values after a newer face with descriptors is saved.
  • Firefox and Safari.

Before

At main, three Poppins files for three texts: every text draws from the last file, so the title and the subtitle come out italic.

before

After

Each face says its weight and style: the title draws Bold, the subtitle Regular, the caption Italic.

after

@miguel-heygen
miguel-heygen force-pushed the dfilm/font-face-weight-style branch from 44ee151 to 975e19b Compare September 30, 2026 08:12
@miguel-heygen miguel-heygen changed the title fix(studio): a family's saved font faces keep their own weight and style fix(studio): font faces a host saves keep their own weight and style Sep 30, 2026
@miguel-heygen
miguel-heygen marked this pull request as ready for review September 30, 2026 08:38
@miguel-heygen
miguel-heygen added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit be5beae Sep 30, 2026
135 checks passed
@miguel-heygen
miguel-heygen deleted the dfilm/font-face-weight-style branch September 30, 2026 09:06
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