Skip to content

Continue after the glyphs a lookup matched or inserted - #398

Merged
blikblum merged 2 commits into
foliojs:masterfrom
Likheet:fix/383-continue-after-substitution
Oct 18, 2026
Merged

blikblum merged 2 commits into
foliojs:masterfrom
Likheet:fix/383-continue-after-substitution

Conversation

@Likheet

@Likheet Likheet commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #383.

After a lookup applies, applyLookups moves on to the glyph after the one it started at. HarfBuzz continues after the glyphs the lookup consumed or produced instead, and the difference shows up in three places:

  • Multiple substitution: the inserted glyphs are visited next, so the lookup applies to its own output. sub a by b a; never finishes.
  • Contextual lookups (GSUB 5/6, GPOS 7/8): applyLookupList resets to the start of the match, so the lookup is tried again on glyphs inside its input sequence and on glyphs its nested lookups inserted. In Libre Barcode EAN13 Text, lookup 82 rewrites a digit D followed by an EAN-8 tail as lt.below.quiet guard.normal.ean8 D, then matches that D again, until the heap runs out. Before that, lookup 15 also matches an EAN-8 rule inside the EAN-13 sequence it just matched.
  • Deletion (multiple substitution with an empty sequence): the glyph that moves into the deleted slot is skipped.

This continues after the inserted glyphs, at the glyph that followed a deleted one, and after the input sequence of a contextual lookup, moved by the glyphs its nested lookups insert or delete but never before the glyph a nested lookup applied to (as in HarfBuzz's apply_lookup). Every step now either moves forward or removes glyphs, so a lookup always finishes.

Tests:

  • test/shaping.js: a 1.3 KB font built with fontTools (the feature code is in the test) covers each case, with HarfBuzz's output as the expected results. On master, two of the five run out of memory and the other three give different glyphs or advances.
  • test/issues.js: the font from the issue (OFL, from google/fonts) with the codes from the report. EAN-8 matches HarfBuzz. For EAN-13, five glyphs still differ because of the backtrack order bug that Fix chaining context backtrack matching order #372 fixes; with Fix chaining context backtrack matching order #372 applied too, it matches HarfBuzz exactly.

Other fonts run into this as well: with Noto Sans Kaithi (it ships with macOS), any consonant followed by U+110B1 KAITHI VOWEL SIGN I ran out of memory.

To make sure nothing else changes, I shaped about 11,400 strings (a multilingual sample plus random strings from each font's cmap) with 196 fonts that have GSUB or GPOS: the fonts in test/data, the macOS system fonts, and Google Fonts that lean on contextual lookups (Fira Code, JetBrains Mono, Recursive, Noto Nastaliq Urdu, Scheherazade New, Noto Sans Myanmar, the other Libre Barcode families and more). Compared with master, the output changed for 6 strings, all of which ran out of memory before. One now matches HarfBuzz exactly and the other five are closer to it; what's left comes from #372's bug and from the USE shaper's reordering. No string that matched HarfBuzz before differs now.

npm test passes and the changed files are Prettier-clean.

After a lookup applied, fontkit always moved on to the glyph after the
one it started at. A multiple substitution was then applied to its own
output, and a contextual lookup to glyphs inside its input sequence or
inserted by its nested lookups, so fonts like Libre Barcode EAN13 Text
ran out of memory (foliojs#383). After a deletion, the following glyph was
skipped.

Like HarfBuzz, continue after the inserted glyphs, at the glyph that
followed a deleted one, and after the input sequence of a contextual
lookup, adjusted for the glyphs its nested lookups inserted or deleted.
@Likheet
Likheet force-pushed the fix/383-continue-after-substitution branch from e1e3506 to fa1cfa1 Compare October 13, 2026 03:32
@blikblum
blikblum merged commit 5f3c7a9 into foliojs:master Oct 18, 2026
3 checks passed
@blikblum

Copy link
Copy Markdown
Member

Many thanks. Just did a small change

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.

OOM when using Libre Barcode EAN13 Text from Google Fonts

2 participants