From b73aa76285aa40ce5734b1265e2aab0543afd4d5 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Thu, 26 Feb 2026 04:18:27 +0900 Subject: [PATCH] nudge-x --- OTFbuild/calligra_font_tests.odt | Bin 13564 -> 13817 bytes OTFbuild/font_builder.py | 8 ++++++++ OTFbuild/opentype_features.py | 21 +++++++++++++++++++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/OTFbuild/calligra_font_tests.odt b/OTFbuild/calligra_font_tests.odt index 5d1f13096c98cacfb784c799d9e48e6806467e28..652cf324d78977ef6079e7d7d4b9d9959c56a0cb 100644 GIT binary patch delta 12635 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 diff --git a/OTFbuild/font_builder.py b/OTFbuild/font_builder.py index 6cb4301..327ac43 100644 --- a/OTFbuild/font_builder.py +++ b/OTFbuild/font_builder.py @@ -264,6 +264,12 @@ def build_font(assets_dir, output_path, no_bitmap=False, no_features=False): # ALIGN_CENTRE: offset = ceil((width - W_VAR_INIT) / 2) (negative) # ALIGN_BEFORE: offset = 0 # The bitmap cell width depends on the sheet type. + # nudge_x shifts the glyph left by that many pixels in the + # bitmap engine. For zero-advance glyphs (marks and width-0 + # non-marks like U+0361) this is a pure visual shift that must + # be baked into the contours. For positive-advance glyphs the + # bitmap engine's nudge/extraWidth mechanism already maps to + # the OTF advance, so we must NOT shift contours. import math bm_cols = len(g.bitmap[0]) if g.bitmap and g.bitmap[0] else 0 if g.props.align_where == SC.ALIGN_RIGHT: @@ -272,6 +278,8 @@ def build_font(assets_dir, output_path, no_bitmap=False, no_features=False): x_offset = math.ceil((g.props.width - bm_cols) / 2) * SCALE else: x_offset = 0 + if advance == 0: + x_offset -= g.props.nudge_x * SCALE contours = trace_bitmap(g.bitmap, g.props.width) diff --git a/OTFbuild/opentype_features.py b/OTFbuild/opentype_features.py index 4ca9703..be9c532 100644 --- a/OTFbuild/opentype_features.py +++ b/OTFbuild/opentype_features.py @@ -11,6 +11,7 @@ Features implemented: - mark: GPOS mark-to-base positioning (diacritics anchors) """ +import math from typing import Dict, List, Set, Tuple from glyph_parser import ExtractedGlyph @@ -1097,7 +1098,22 @@ def _generate_mark(glyphs, has): for mark_type, mark_list in sorted(mark_classes.items()): class_name = f"@mark_type{mark_type}" for cp, g in mark_list: - mark_x = ((g.props.width + 1) // 2) * SC.SCALE + if g.props.align_where == SC.ALIGN_CENTRE: + # Match Kotlin: anchorPoint - HALF_VAR_INIT centres the + # cell on the anchor. For U+0900-0902 the Kotlin engine + # uses (W_VAR_INIT + 1) / 2 instead (1 px nudge left). + # mark_x must match font_builder's total x_offset + # (alignment + nudge) so column `half` sits on the anchor. + bm_cols = len(g.bitmap[0]) if g.bitmap and g.bitmap[0] else 0 + if 0x0900 <= cp <= 0x0902: + half = (SC.W_VAR_INIT + 1) // 2 + else: + half = (SC.W_VAR_INIT - 1) // 2 + x_offset = math.ceil((g.props.width - bm_cols) / 2) * SC.SCALE + x_offset -= g.props.nudge_x * SC.SCALE + mark_x = x_offset + half * SC.SCALE + else: + mark_x = ((g.props.width + 1) // 2) * SC.SCALE mark_y = SC.ASCENT lines.append( f"markClass {glyph_name(cp)} {class_name};" @@ -1114,7 +1130,8 @@ def _generate_mark(glyphs, has): for cp, g in sorted(bases_with_anchors.items()): anchor = g.props.diacritics_anchors[mark_type] if mark_type < 6 else None if anchor and (anchor.x_used or anchor.y_used): - ax = anchor.x * SC.SCALE + # Match Kotlin: when x_used is false, default to width / 2 + ax = (anchor.x if anchor.x_used else g.props.width // 2) * SC.SCALE ay = (SC.ASCENT // SC.SCALE - anchor.y) * SC.SCALE lines.append(f" pos base {glyph_name(cp)} mark {class_name};")