Skip to content

[p5.js 2.0+ Bug Report]: loadFont() loses all glyph data for variable fonts with short gvar offsets #9208

Description

@nexus-hash

Most appropriate sub-area of p5.js?

  • Accessibility
  • Color
  • Core/Environment/Rendering
  • Data
  • DOM
  • Events
  • Image
  • IO
  • Math
  • Typography
  • Utilities
  • WebGL
  • WebGPU
  • p5.strands
  • Build process
  • Unit testing
  • Internationalization
  • Friendly errors
  • Other (specify if possible)

p5.js version

2.3.3

Web browser and version

Chrome: 153.0.8010.53

Operating system

Windows

Steps to reproduce this

Steps

  1. Save the snippet below as repro.html and open it in a browser. No server is needed.
  2. Open DevTools (F12) and go to the Console tab.
  3. Note the warning and the NO glyph data line.
  4. Replace the URL with each font below, reload, and confirm both log glyph data present with no warning:
    • Bricolage Grotesque (variable, long gvar offsets):
      https://raw.githubusercontent.com/google/fonts/main/ofl/bricolagegrotesque/BricolageGrotesque%5Bopsz%2Cwdth%2Cwght%5D.ttf
    • Jersey 10 (static):
      https://fonts.gstatic.com/s/jersey10/v4/GftH7vZKsggXMf9n_J5X-A.ttf

Snippet

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>p5 variable font repro</title>
  <script src="https://cdn.jsdelivr.net/npm/p5@2.3.3/lib/p5.min.js"></script>
</head>
<body>
<script>
async function setup() {
  createCanvas(400, 200);
  const font = await loadFont(
    'https://raw.githubusercontent.com/google/fonts/main/ofl/outfit/Outfit%5Bwght%5D.ttf'
  );
  console.log(font.data?.glyf ? 'glyph data present' : 'NO glyph data');
}
</script>
</body>
</html>

Actual result

Console output:

WARN: No glyph data for 'Outfit%5Bwght%5D', retrying as FontFace
NO glyph data

font.data is undefined, so loadFont falls back to a plain FontFace. Text still renders, but glyph-outline features such as textToPoints() 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 long gvar offsets.

Why

Typr's gvar parser always reads glyphVariationDataOffsets as 4-byte Offset32 values:
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 flags bit 0 is set. Otherwise they are 2-byte uint16 values, and the real offset is the value × 2. Outfit's gvar has flags = 0, so every offset after the first is misaligned and parsing fails.

Activity

  1. nexus-hash commented on Sep 24, 2026

    @nexus-hash
    Author

    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 gvar offsets Before fix (main / 2.3.3) After fix
    Outfit [wght] variable short (flags = 0) no glyph data (font.data is undefined) glyph data present
    Bricolage Grotesque variable long (flags = 1) glyph data present glyph data present
    Jersey 10 static no gvar table glyph data present glyph data present

    With the fix, all three have glyph data.

  2. davepagurek commented on Sep 24, 2026

    @davepagurek
    Contributor

    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.

  3. nexus-hash commented on Sep 24, 2026

    @nexus-hash
    Author

    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 reads glyphVariationDataOffsets as uint16 * 2 when flags & 1 == 0, and as Offset32 otherwise (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

    1. Now, minimal: apply just the upstream gvar hunk to src/type/lib/Typr.js with a // PATCH: comment pointing at 41ec4b5, 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.
    2. Then, as a separate PR: restructure how Typr is vendored. Upstream Typr.js and Typr.U.js would 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.

  4. davepagurek commented on Sep 24, 2026

    @davepagurek
    Contributor

    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

  5. nexus-hash commented on Sep 24, 2026

    @nexus-hash
    Author

    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.

  6. davepagurek commented on Sep 24, 2026

    @davepagurek
    Contributor

    Sounds good, no rush! and feel free to ask any questions here!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions