From cce9d62bd1aae0ef737c5323a6e93ab7a7775067 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Mon, 2 Mar 2026 10:07:31 +0900 Subject: [PATCH] fix: diacritics not stacking --- OTFbuild/calligra_font_tests.odt | Bin 15662 -> 15683 bytes OTFbuild/opentype_features.py | 47 ++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/OTFbuild/calligra_font_tests.odt b/OTFbuild/calligra_font_tests.odt index b8aa2ea6b26fc8e9536fa62fdd39ccdd7d2f2e83..a834aefcb70523ddfdf01b8b9bce99bd919062a6 100644 GIT binary patch delta 6280 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 diff --git a/OTFbuild/opentype_features.py b/OTFbuild/opentype_features.py index 1e1bdbc..edec666 100644 --- a/OTFbuild/opentype_features.py +++ b/OTFbuild/opentype_features.py @@ -1485,6 +1485,7 @@ def _generate_mark(glyphs, has): return "" lines = [] + mark_anchors = {} # cp -> mark_x, for MarkToMark mark2 anchor computation _align_suffix = { SC.ALIGN_LEFT: 'l', @@ -1529,6 +1530,7 @@ def _generate_mark(glyphs, has): # ALIGN_LEFT / ALIGN_BEFORE: mark sits at base origin. mark_x = 0 mark_y = SC.ASCENT + mark_anchors[cp] = mark_x lines.append( f"markClass {glyph_name(cp)} {class_name};" ) @@ -1608,12 +1610,53 @@ def _generate_mark(glyphs, has): lines.append("") lookup_names.append(lookup_name) - # Register mark lookups under DFLT (for Latin, etc.) + # --- MarkToMark lookups for diacritics stacking --- + # When multiple marks of the same type stack on a base, MarkToMark + # positions each successive mark relative to the previous one, + # shifted by H_DIACRITICS pixels in the stacking direction. + mkmk_lookup_names = [] + for (mark_type, align, is_dia), mark_list in sorted(mark_groups.items()): + stacking_marks = [(cp, g) for cp, g in mark_list + if g.props.stack_where in (SC.STACK_UP, + SC.STACK_DOWN, + SC.STACK_UP_N_DOWN)] + if not stacking_marks: + continue + + suffix = _align_suffix.get(align, 'x') + class_name = f"@mark_t{mark_type}_{suffix}" + ("_dia" if is_dia else "") + mkmk_name = f"mkmk_t{mark_type}_{suffix}" + ("_dia" if is_dia else "") + lines.append(f"lookup {mkmk_name} {{") + + for cp, g in stacking_marks: + mx = mark_anchors.get(cp, 0) + if g.props.stack_where in (SC.STACK_UP, SC.STACK_UP_N_DOWN): + m2y = SC.ASCENT + SC.H_DIACRITICS * SC.SCALE + else: # STACK_DOWN + m2y = SC.ASCENT - SC.H_DIACRITICS * SC.SCALE + lines.append( + f" pos mark {glyph_name(cp)}" + f" mark {class_name};" + ) + + lines.append(f"}} {mkmk_name};") + lines.append("") + mkmk_lookup_names.append(mkmk_name) + + # Register MarkToBase lookups under DFLT (for Latin, etc.) lines.append("feature mark {") for ln in lookup_names: lines.append(f" lookup {ln};") lines.append("} mark;") + # Register MarkToMark lookups under mkmk + if mkmk_lookup_names: + lines.append("") + lines.append("feature mkmk {") + for ln in mkmk_lookup_names: + lines.append(f" lookup {ln};") + lines.append("} mkmk;") + # For Devanagari, HarfBuzz's Indic v2 shaper uses abvm/blwm # features for mark positioning, not the generic 'mark' feature. # Register the same lookups under abvm for dev2 script. @@ -1622,6 +1665,8 @@ def _generate_mark(glyphs, has): lines.append(" script dev2;") for ln in lookup_names: lines.append(f" lookup {ln};") + for ln in mkmk_lookup_names: + lines.append(f" lookup {ln};") lines.append("} abvm;") return '\n'.join(lines)