Repository navigation
fix(text): a variable font is instantiated out of a WOFF or a WOFF2, where the first weight off the file's default threw - #479
Merged
Conversation
…where the first weight off the file's default threw
sidorares
pushed a commit
that referenced
this pull request
Sep 30, 2026
🤖 I have created a release *beep* *boop* --- ## [8.17.2](v8.17.1...v8.17.2) (2026-09-30) ### Bug Fixes * **svg:** a gradient takes its stops, and what it does not set, from the gradient its href names ([#481](#481)) ([9db8108](9db8108)) * **text:** a font weight between two of the hundreds goes to fontconfig on its own scale, so 450 in Arial is the regular and not the bold ([#480](#480)) ([3973b42](3973b42)) * **text:** a variable font is instantiated out of a WOFF or a WOFF2, where the first weight off the file's default threw ([#479](#479)) ([b009cdd](b009cdd)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
sidorares
added a commit
that referenced
this pull request
Sep 30, 2026
…F or a WOFF2 is read as the sfnt inside it (#485) 8.17.2 never reached npm, and nothing merged since 8.17.1 is published. #479 named fontkit by the URL of a fork's release tarball, and npm 12 fetches no URL or git dependency unless told to: `allow-remote` and `allow-git` default to `none`. The publish job upgrades npm and failed in `npm ci` with `EALLOWREMOTE`. The test jobs install with the npm their Node ships, so CI was green. An application on npm 12 could not have installed that version either, which I reproduced with npm 12.1.0 against a packed master. That was my recommendation in #479 and it was wrong: I tried the tarball with npm 11 and Bun, not with the npm the publish job uses. ## What this does **`fontkit` is `^2.0.4` from npm again**, and the lockfile is 8.17.1's for it. **What the fork was for is done here instead.** fontkit cuts an instance of a variable font by reading the file again as a plain sfnt, which a WOFF or a WOFF2 is not. So a variable face that arrives in a container is handed to fontkit as the sfnt inside it, built by the new `lib/text/sfnt.js`: a WOFF's tables as fontkit inflates them, a WOFF2's as it decompresses them, with `glyf` and `loca` written back from the transformed streams. It is done once, when the face is opened, 10 ms for Geist's 70 KB WOFF2. A static face stays in its container, and a container that cannot be taken apart stays too and refuses at `variation`, as before #479. **CI has a `publishable` job** that does what the publish job does up to the upload, with the newest npm, and then installs the packed tarball into an empty project. The `loca` guard from #479 stays: it is about static WOFF2 faces and does not depend on which fontkit is installed. ## Verification - The tests #479 added pass unchanged but for one assertion, against fontkit 2.0.4: every glyph of an instance out of the `.woff` and the `.woff2` fixture equals the sfnt's, at three weights. - `test/sfnt.test.js`: the sfnt made of a WOFF is the original, table for table. The one made of a WOFF2 has every table but `glyf`, `loca` and `head` byte for byte, and every glyph reads the same. The two faces used have composite, hinted and empty glyphs between them, and a test says so. - Outside the suite, 16 containers were rebuilt and compared with fontTools' own reading of the same file, glyph by glyph: coordinates, on-curve flags, contour ends, instructions, component records, boxes, the overlap flag, and every other table's bytes. Geist with 975 glyphs, Mada with 292 composites of 529, Source Sans Pro with 1974 hinted glyphs, CFF and CFF2 flavours, and a WOFF2 with an untransformed `glyf`. - Through react-x11-components' `<Html>` with this `lib/` and fontkit 2.0.4: all 728 characters of Geist's variable WOFF2 at eight weights, each matched to an instance of the rebuilt face. - With npm 12.1.0: `npm ci`, `npm publish --dry-run`, and an install of the packed tarball into an empty project all pass. `npm test`: 1449 pass, 10 skipped, none fail. ## After this 8.17.2 cannot be published from its tag, whose commit has the URL. Merging this and then the release PR, #484, publishes 8.17.3 with everything since 8.17.1.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A variable font registered from a
.woff2, which is how a web font nearly always arrives, threw frommatchat any weight but the file's default:ntk: cannot instantiate a variation of Geist-Regular (WOFF2). In react-x11-components'<Html>that was a whole document left blank, found on nextjs.org/blog. A.wofffailed the same way.Cause
fontkit 2.0.4's
getVariationrebuilds the face as a plain sfnt from the container's bytes. For a WOFF that reads the wrong directory, and for a WOFF2 the bytes are still Brotli-compressed.Fix
fontkit is now windowkit's fork, as a release tarball. The fix is windowkit/fontkit#1, offered upstream as foliojs/fontkit#389. Upstream's last release is from 2024, so
dependenciesnames the URL of the tarball attached to the fork'sv2.0.4-windowkit.1release, which is whatnpm publishwould have uploaded, and the lockfile holds its integrity. Not agithub:spec: a git dependency has nodist/, npm builds one on every install with parcel and the devDependencies, 18 s measured, and Bun installs it unbuilt. The tarball was installed with npm and with Bun.AGENTS.mdrecords the decision and when to go back to a range on npm.One change in ntk itself.
mendEmptyGlyphBoxesasked thelocatable which glyphs are empty. A WOFF2 whoseglyftable is transformed has nolocain the file, and fontkit reads the table after it in its place: it is wrong about 7 of the fixture's 76 glyphs, 24 of Geist's 975, 17 of KaTeX Main's 286. Those glyphs had their box read from a header that is not there, in static faces too, and in an instance of a variable face, whose box is needed to make the outline, the read went round until the stack ran out. A transformedglyfis now left alone.The
cannot instantiateerror stays for an application that resolvesfontkitto npm's over ntk's, and says that.Tests
test/variable-fonts.test.js, with the fixture as a.woffand a.woff2, made from the.ttfwith fontTools:loadand matched at 700 lay text out as wide as the sfnt doeslocachangeAll three fail on master, and the first two fail with a stack overflow when only the fontkit is changed.
npm test: 1399 pass, 46 skipped, and one failure inpresent-clock.test.js, a frame-timing test that passed three times out of three alone, on a machine at a load average of 40.Every character of Geist's variable WOFF2, 728 of them, was also set at five weights through
<Html>with this ntk.