From b82dbecc308faa256eaa93d19fc20ce33f72e7c9 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Sat, 7 Mar 2026 22:47:01 +0900 Subject: [PATCH] keming machine dot removal directive for OTF --- OTFbuild/calligra_font_tests.odt | Bin 17312 -> 17328 bytes OTFbuild/glyph_parser.py | 6 ++-- OTFbuild/opentype_features.py | 53 +++++++++++++++++++++++++++++++ keming_machine.txt | 2 +- 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/OTFbuild/calligra_font_tests.odt b/OTFbuild/calligra_font_tests.odt index f4ccd72d697451cd30ae7f524197f6f2060d4aba..498a840282cae83b35e7e44b95a70911bebb2206 100644 GIT binary patch delta 10269 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 delta 10235 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 8) is_kern_y_type = (kerning_bit1 & 0x80000000) != 0 kerning_mask = (kerning_bit1 >> 8) & 0xFFFFFF has_kern_data = (kerning_bit1 & 0xFF) != 0 @@ -188,7 +190,7 @@ def parse_variable_sheet(image, sheet_index, cell_w, cell_h, cols, is_xy_swapped align_where=align_where, write_on_top=write_on_top, stack_where=stack_where, ext_info=ext_info, has_kern_data=has_kern_data, is_kern_y_type=is_kern_y_type, - kerning_mask=kerning_mask, + kerning_mask=kerning_mask, dot_removal=dot_removal, directive_opcode=directive_opcode, directive_arg1=directive_arg1, directive_arg2=directive_arg2, ) diff --git a/OTFbuild/opentype_features.py b/OTFbuild/opentype_features.py index 6677e65..1b27797 100644 --- a/OTFbuild/opentype_features.py +++ b/OTFbuild/opentype_features.py @@ -70,6 +70,11 @@ languagesystem sund dflt; if ccmp_code: parts.append(ccmp_code) + # ccmp: dot removal (e.g. i→ı, j→ȷ when followed by STACK_UP marks) + dot_removal_code = _generate_dot_removal(glyphs, has) + if dot_removal_code: + parts.append(dot_removal_code) + # Hangul jamo GSUB assembly hangul_code = _generate_hangul_gsub(glyphs, has, jamo_data) if hangul_code: @@ -162,6 +167,54 @@ def _generate_ccmp(replacewith_subs, has): return '\n'.join(lines) +def _generate_dot_removal(glyphs, has): + """Generate ccmp contextual substitution for dot removal. + + When a base glyph tagged with dot_removal (kerning bit 2, pixel Y+7) is + followed by a STACK_UP mark, substitute the base with its dotless form. + Matches the Kotlin engine's dotRemoval logic. + """ + # Collect all STACK_UP marks + stack_up_marks = [] + for cp, g in glyphs.items(): + if g.props.write_on_top >= 0 and g.props.stack_where == SC.STACK_UP and has(cp): + stack_up_marks.append(cp) + + if not stack_up_marks: + return "" + + # Collect all base glyphs with dot_removal + dot_removal_subs = [] + for cp, g in glyphs.items(): + if g.props.dot_removal is not None and has(cp) and has(g.props.dot_removal): + dot_removal_subs.append((cp, g.props.dot_removal)) + + if not dot_removal_subs: + return "" + + lines = [] + + # Define the STACK_UP marks class + mark_names = ' '.join(glyph_name(cp) for cp in sorted(stack_up_marks)) + lines.append(f"@stackUpMarks = [{mark_names}];") + lines.append("") + + # Single substitution lookup for the replacements + lines.append("lookup DotRemoval {") + for src_cp, dst_cp in sorted(dot_removal_subs): + lines.append(f" sub {glyph_name(src_cp)} by {glyph_name(dst_cp)};") + lines.append("} DotRemoval;") + lines.append("") + + # Contextual rules in ccmp + lines.append("feature ccmp {") + for src_cp, _ in sorted(dot_removal_subs): + lines.append(f" sub {glyph_name(src_cp)}' lookup DotRemoval @stackUpMarks;") + lines.append("} ccmp;") + + return '\n'.join(lines) + + def _generate_hangul_gsub(glyphs, has, jamo_data): """ Generate Hangul jamo GSUB lookups for syllable assembly. diff --git a/keming_machine.txt b/keming_machine.txt index d404073..6a2c00e 100644 --- a/keming_machine.txt +++ b/keming_machine.txt @@ -1,7 +1,7 @@ --- Pixel 0 - Lowheight bit - encoding: has pixel - it's low height -- used by the diacritics system to quickly look up if the character is low height without parsing the Pixel 1 +- bit must be set if above-diacritics should be lowered (e.g. lowercase b, which has 'A' shape bit but considered lowheight) ### Legends #