Skip to content

Fix repha, pre-base vowel and dotted circle placement in USE - #400

Merged
blikblum merged 2 commits into
foliojs:masterfrom
Likheet:fix/use-dotted-circle
Oct 18, 2026
Merged

blikblum merged 2 commits into
foliojs:masterfrom
Likheet:fix/use-dotted-circle

Conversation

@Likheet

@Likheet Likheet commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

reorder() in the Universal Shaping Engine differs from HarfBuzz in three ways that depend on each other: once the dotted circle of a broken cluster is a base, broken clusters with a repha or a split vowel go through the other two.

Dotted circle. It's inserted after the first non-repha glyph instead of before it, so a lone vowel sign comes out as the sign followed by the circle:

const font = fontkit.openSync('test/data/NotoSans/NotoSansBalinese-Regular.ttf');
font.layout('ᬶ').glyphs.map((g) => g.id); // [58, 128], HarfBuzz gives [128, 58]

The circle also shared the first glyph's shaperInfo, so it never counted as a base. It now gets its own (GB, the category of U+25CC), like in the Indic shaper, and goes before the first non-repha glyph, like in HarfBuzz's hb_syllabic_insert_dotted_circles.

Repha. glyphs.splice(start, 0, ...glyphs.splice(start + 1, i - start), glyphs[i]) reads glyphs[i] after the inner splice has shortened the array, so it inserts a duplicate glyph or undefined. Any repha followed by a base hits it: RA + virama + KA in Modi or Tirhuta throws Cannot read properties of undefined (reading 'shaperInfo'), and with a vowel sign after the base that sign is duplicated. This moves the repha before the first post-base glyph, or to the end, as HarfBuzz's reorder_syllable_use does. Fixing only the splice and keeping the old "after the first base" rule still puts the repha in the wrong place when a nukta or a conjunct follows the base.

Split pre-base vowels. When a font splits a pre-base vowel sign with a multiple substitution, every part is moved before the base, so in Noto Sans Siddham KA + vowel sign AI starts with the above-base part. HarfBuzz only moves the first component, and the Indic shaper already checks isMultiplied for this.

Tests, with HarfBuzz's output as the expected results: dotted circles with the Balinese font in test/data, repha cases with Noto Sans Tirhuta and the split vowel with Noto Sans Siddham (both OFL, from google/fonts, with their licenses next to them). Five of the six fail on master. The sixth, a lone pre-base vowel sign, already passes and is there to keep it working.

I also compared about 16,000 strings with 196 fonts against HarfBuzz with and without this change, including every combining mark of each font on its own:

  • 573 strings that differed from HarfBuzz now match it exactly, one that crashed now lays out, and none that matched before differ now.
  • Of the other 125 strings whose output changed, 48 are closer to HarfBuzz and 22 just as close. In 54, fontkit's older syllable machine sees a broken cluster where HarfBuzz doesn't (a mark after punctuation, for example), so there is a dotted circle HarfBuzz doesn't add, and it now comes before the mark as in every other broken cluster. The last one is a cluster the two machines split differently.

One interaction: in Noto Sans Siddham the dotted circle has NULL anchors for some mark classes. Now that lone vowel signs attach to the circle, 21 of those strings reach the NULL anchor crash from #367. With #374 applied as well nothing crashes, so it's best to merge #374 first.

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

Likheet and others added 2 commits October 13, 2026 15:52
reorder() in the Universal Shaping Engine differed from HarfBuzz in
three places:

- The dotted circle of a broken cluster was inserted after the first
  non-repha glyph instead of before it, and shared that glyph's
  shaperInfo, so it was never treated as a base.
- Moving a repha read glyphs[i] after splicing the array, which
  duplicated a glyph or inserted undefined, so a repha followed by a
  base (e.g. RA + virama + KA in Modi or Tirhuta) crashed or gave
  extra glyphs. Move it before the first post-base glyph, as HarfBuzz
  does.
- Every part of a pre-base vowel split by a multiple substitution was
  moved before the base. Only move the first, like HarfBuzz and the
  Indic shaper.
@blikblum
blikblum merged commit d14faea into foliojs:master Oct 18, 2026
3 checks passed
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