From c299aa8d502811734bdd918a53ab58cb9f1729a3 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Mon, 2 Mar 2026 13:53:37 +0900 Subject: [PATCH] fix: mixed direction diacritics stacking up wrongly --- OTFbuild/calligra_font_tests.odt | Bin 15951 -> 17283 bytes OTFbuild/opentype_features.py | 54 ++++++++++++++++++------------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/OTFbuild/calligra_font_tests.odt b/OTFbuild/calligra_font_tests.odt index c280fb5d7a443e81615347f79ca17df532f4abc6..1e7fae617469d3c9faa948c53fedbc4dc46aab7a 100644 GIT binary patch delta 16102 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 Z@oy{+Xc0=v<;6}lTM-^ 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 oUV%ZUhZPbIuA$f<&*B3v<6-(P&pzZeI|=aD%eJ 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 diff --git a/OTFbuild/opentype_features.py b/OTFbuild/opentype_features.py index edec666..3fe17ba 100644 --- a/OTFbuild/opentype_features.py +++ b/OTFbuild/opentype_features.py @@ -1494,20 +1494,31 @@ def _generate_mark(glyphs, has): SC.ALIGN_BEFORE: 'b', } - # Group marks by (writeOnTop, alignment, isDiacriticalMark). + # Group marks by (writeOnTop, alignment, isDiacriticalMark, stackCat). # Diacritical marks (U+0300-036F) need separate classes because their # base anchors are adjusted for lowheight bases (e.g. lowercase 'e'). # Type-0 (above): shift down 4px; Type-2 (overlay): shift down 2px. - mark_groups = {} # (mark_type, align, is_dia) -> [(cp, g), ...] + # Stack category splits stacking marks from non-stacking ones so that + # MarkToMark only chains marks that actually stack together. + def _stack_cat(sw): + if sw in (SC.STACK_UP, SC.STACK_UP_N_DOWN): + return 'up' + elif sw == SC.STACK_DOWN: + return 'dn' + else: + return 'ns' + + mark_groups = {} # (mark_type, align, is_dia, stack_cat) -> [(cp, g), ...] for cp, g in marks.items(): is_dia = (0x0300 <= cp <= 0x036F) - key = (g.props.write_on_top, g.props.align_where, is_dia) + sc = _stack_cat(g.props.stack_where) + key = (g.props.write_on_top, g.props.align_where, is_dia, sc) mark_groups.setdefault(key, []).append((cp, g)) # Emit markClass definitions - for (mark_type, align, is_dia), mark_list in sorted(mark_groups.items()): + for (mark_type, align, is_dia, scat), mark_list in sorted(mark_groups.items()): suffix = _align_suffix.get(align, 'x') - class_name = f"@mark_t{mark_type}_{suffix}" + ("_dia" if is_dia else "") + class_name = f"@mark_t{mark_type}_{suffix}" + ("_dia" if is_dia else "") + f"_{scat}" for cp, g in mark_list: if align == SC.ALIGN_CENTRE: # Match Kotlin: anchorPoint - HALF_VAR_INIT centres the @@ -1535,12 +1546,12 @@ def _generate_mark(glyphs, has): f"markClass {glyph_name(cp)} {class_name};" ) - # Generate one lookup per (mark_type, align, is_dia) group. + # Generate one lookup per (mark_type, align, is_dia, stack_cat) group. lookup_names = [] - for (mark_type, align, is_dia), mark_list in sorted(mark_groups.items()): + for (mark_type, align, is_dia, scat), mark_list in sorted(mark_groups.items()): suffix = _align_suffix.get(align, 'x') - class_name = f"@mark_t{mark_type}_{suffix}" + ("_dia" if is_dia else "") - lookup_name = f"mark_t{mark_type}_{suffix}" + ("_dia" if is_dia else "") + class_name = f"@mark_t{mark_type}_{suffix}" + ("_dia" if is_dia else "") + f"_{scat}" + lookup_name = f"mark_t{mark_type}_{suffix}" + ("_dia" if is_dia else "") + f"_{scat}" lines.append(f"lookup {lookup_name} {{") for cp, g in sorted(all_bases.items()): @@ -1614,26 +1625,25 @@ def _generate_mark(glyphs, has): # 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. + # Only 'up' and 'dn' groups participate; 'ns' (non-stacking) marks + # are excluded so they don't get repositioned by MarkToMark. 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: + for (mark_type, align, is_dia, scat), mark_list in sorted(mark_groups.items()): + if scat == 'ns': 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 "") + class_name = f"@mark_t{mark_type}_{suffix}" + ("_dia" if is_dia else "") + f"_{scat}" + mkmk_name = f"mkmk_t{mark_type}_{suffix}" + ("_dia" if is_dia else "") + f"_{scat}" lines.append(f"lookup {mkmk_name} {{") - for cp, g in stacking_marks: + if scat == 'up': + m2y = SC.ASCENT + SC.H_DIACRITICS * SC.SCALE + else: # 'dn' + m2y = SC.ASCENT - SC.H_DIACRITICS * SC.SCALE + + for cp, g in mark_list: 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};"