Repository navigation
[p5.js 2.0+ Bug Report]: loadFont() loses all glyph data for variable fonts with short gvar offsets #9208
Description
Activity
I have a small fix ready and I'm happy to open a PR if a maintainer approves this issue.
The fix reads the gvar offsets according to the flags bit: 4-byte Offset32 values when it's set, and 2-byte uint16 values (× 2) when it's clear. Nothing else in the parser changes. The tests include a real short-offset variable font as a fixture. The existing BricolageGrotesque-Variable.ttf fixture uses long offsets, so it never exercised this path.
For quick reference, results on current main, using the repro above:
Font Type gvaroffsetsBefore fix ( main/ 2.3.3)After fix Outfit [wght]variable short ( flags = 0)no glyph data ( font.dataisundefined)glyph data present Bricolage Grotesque variable long ( flags = 1)glyph data present glyph data present Jersey 10 static no gvartableglyph data present glyph data present With the fix, all three have glyph data.
So Typr is not actually p5 code, it comes from https://github.com/photopea/Typr.js/, so we should see if this needs to be reported in their repo first, or if there are upstream changes we need to pull in. Have you checked to see if anything is different there?
Note as well that we currently have patched Typr a bit here: ca95f59 If we do need to make further changes (e.g. to get the benefits of a fix now while waiting for PR review in the Typr repo) we should also maybe update the structure of our code a bit to make it clearer how patches are applied so that we can more easily update the base Typr build without stepping on our patches.
Hi @davepagurek now that you have mentioned it I checked the upstream
- Upstream Typr already fixed this in photopea/Typr.js
41ec4b5(2025-08-28). It readsglyphVariationDataOffsetsasuint16 * 2whenflags & 1 == 0, and asOffset32otherwise (lines 1733–1736). That is the same logic as the change I had prepared, so there is nothing to report upstream. - p5's copy looks like a Dec 2024 snapshot (
1aec9a2,4c4dac0). It reads the offsets the way upstream did before that commit, always as 4 bytes, and p5 still does.
Below are my suggestions
- Now, minimal: apply just the upstream
gvarhunk tosrc/type/lib/Typr.jswith a// PATCH:comment pointing at41ec4b5, matching the existing patch markers. I'd keep a unit test with a real short-offset variable font (Outfit), since the current fixture uses long offsets and never exercised this path. - Then, as a separate PR: restructure how Typr is vendored. Upstream
Typr.jsandTypr.U.jswould stay untouched, with the upstream commit noted at the top. p5's changes (pako, compressed data for woff,globalThis) would live in a small separate patch or overlay. Updating Typr would then mean replacing the vendored files and re-applying the patch, and I can test that sync against variable fonts.
If you'd rather do the restructure first, I'm happy to start there instead.
- Upstream Typr already fixed this in photopea/Typr.js
If you're up for it, it would be great to do (1) and add some tests for the change + our other patches, and then try (2) in the PR by experimenting with importing the real upstream Typr library and seeing that the tests added before still pass. If I recall correctly there were some issues importing it as a regular node module, @limzykenneth do you remember what those were? If we try again and it works fine, we could use https://www.npmjs.com/package/patch-package to apply our changes potentially. Otherwise we can talk here about other approaches
Sure @davepagurek
Option 1 I have already implemented in #9203 So I can recreate this against this issue.As per Option 2 since it will have a bigger blast radius it will take me a day or two to understand the overall code first rather than a localized understanding so will take a look at it in the weekend and maybe create a workable adoption by next weekend depending on the issues that surface.
Sounds good, no rush! and feel free to ask any questions here!
Metadata
Metadata
Assignees
Type
Projects
- StatusShow more project fieldsNo status
Most appropriate sub-area of p5.js?
p5.js version
2.3.3
Web browser and version
Chrome: 153.0.8010.53
Operating system
Windows
Steps to reproduce this
Steps
repro.htmland open it in a browser. No server is needed.NO glyph dataline.glyph data presentwith no warning:gvaroffsets):https://raw.githubusercontent.com/google/fonts/main/ofl/bricolagegrotesque/BricolageGrotesque%5Bopsz%2Cwdth%2Cwght%5D.ttfhttps://fonts.gstatic.com/s/jersey10/v4/GftH7vZKsggXMf9n_J5X-A.ttfSnippet
Actual result
Console output:
font.dataisundefined, soloadFontfalls back to a plainFontFace. Text still renders, but glyph-outline features such astextToPoints()have no glyph data to work with.Expected result
Glyph data is parsed (
glyph data present), as it is for static fonts and for variable fonts that use longgvaroffsets.Why
Typr's
gvarparser always readsglyphVariationDataOffsetsas 4-byteOffset32values:https://github.com/processing/p5.js/blob/main/src/type/lib/Typr.js#L2211-L2214
Per the OpenType spec, the offsets are 4-byte only when
flagsbit 0 is set. Otherwise they are 2-byteuint16values, and the real offset is the value × 2. Outfit'sgvarhasflags = 0, so every offset after the first is misaligned and parsing fails.