Skip to content

Harfbuzz 14.5 and Unicode 18 - #300

Open
benoitkugler wants to merge 35 commits into
mainfrom
harfbuzz-15
Open

benoitkugler wants to merge 35 commits into
mainfrom
harfbuzz-15

Conversation

@benoitkugler

@benoitkugler benoitkugler commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Hello,

This PR update our Harfbuzz and Unicode implementation to follow Harfbuzz v14.5 and Unicode 18.

I've tried to split it in small commits, so that the change set is reviewable...

As an aside, the CI seems to have an issue with staticcheck, and I'm not sure what the proper fix is. Perhaps @andydotxyz has an idea ? Perhaps we have to bump staticcheck version ?

Edit : that seems to be dominikh/go-tools#1832

A zero-valued PairPos record can select a different, non-zero record
if the second glyph changes, so keep the pair unsafe to concat even
when no positioning was applied.
Correct the ArabicPUATraditional.txt entry for 0xF295 which should be
isolated not final form. Also enable 0xFC43 ligature so the ligature is
actually used.
Insertion state machines may emit glyphs during the end-of-text transition, when the buffer has no current input glyph. Emit the glyph first in that case and apply AAT metadata to the new output glyph instead of buffer->cur().
Contextual and non-contextual AAT subtables replace glyphs in place.
When such a replacement produced the AAT deleted glyph, we changed only
the glyph ID and did not set the AAT-deleted metadata that later cleanup
uses to remove deleted glyphs from the buffer.

Make replace_glyph_inplace() match the other AAT replacement helpers by
setting the deleted scratch flag and glyph-info marker for glyph 0xFFFF.
move_to()'s two memmoves - the forward copy into out_info and the
rewind copy-back - were not charged against max_ops, while
shift_forward() already charges its moved tail. A morx insertion
subtable that keeps its mark far behind the insertion point can force
an O(distance) rewind plus re-copy per insertion action, for O(N^2)
total work that the max_ops budget never sees.

Charge count in both branches, mirroring shift_forward()'s failure
pattern. Charging only the rewind branch would also bound the
quadratic, but both memmoves are attacker-drivable work, and
symmetric accounting is simpler to reason about.
When the skipping iterator fails to find another kernable glyph,
resume at the position it reports instead of retrying from the next
glyph. This prevents a suffix of ignored marks from being scanned
repeatedly while preserving later feature ranges.
Non-first pieces of a MultipleSubst now add no extra component, so a
decomposed glyph absorbed into a ligature counts as the single component
it started as, and its marks attach where MarkBasePos would.

Part of harfbuzz/harfbuzz#4969
Unbounded syllables can make the USE and Myanmar reordering passes
perform quadratic work. Process syllables in configurable chunks of at
most 64 glyphs to bound the work performed by each reordering pass.
Cluster merges serve two distinct purposes that require different level
gates:

- Grapheme grouping: combining marks merged into their base cluster.
  Correct gate: IS_GRAPHEMES (levels 0 and 3).
  New functions: merge_grapheme_clusters / merge_out_grapheme_clusters.

- Monotonicity maintenance: clusters merged to prevent non-monotone
  output after ligature formation, reordering, etc.
  Correct gate: IS_MONOTONE (levels 0 and 1).
  Existing functions: merge_clusters / merge_out_clusters.

Previously both paths shared merge_clusters_impl's single IS_MONOTONE
gate, so GRAPHEMES (level 3) was incorrectly blocked from merging
grapheme clusters.  This also caused a regression in the Thai and
Hangul shapers for MONOTONE_CHARACTERS (level 1): when their explicit
== MONOTONE_GRAPHEMES call-site guards were removed in #5069, the
impls' IS_MONOTONE gate no longer protected them.
The buffer digest is only consumed by the OT lookup-apply loop. After
morx substitution it exists solely for a following GPOS pass; when
positioning goes through kerx/kern instead, rebuilding it is wasted
work. Speeds up the Menlo/en-thelittleprince benchmark by 2%; shaping
outputs are unchanged on the AAT benchmark fonts.
Cap each stch run to at most 256 output glyphs.

This keeps pathological stretch runs from expanding to unbounded
size.
U+180F MONGOLIAN FREE VARIATION SELECTOR FOUR was added in Unicode
14.0 and has Default_Ignorable_Code_Point=Yes, but
is_default_ignorable() only covered U+180B..180E, so FVS4 was never
hidden and leaked a .notdef into the output.
@andydotxyz

Copy link
Copy Markdown
Contributor

I expect applying the equivalent to this fixes it: go-gl/glfw@0e60a4a

Static check does not yet support 1.27

@benoitkugler

Copy link
Copy Markdown
Contributor Author

Thanks @andydotxyz, the CI is fixed !

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