more devanagari obscurities

This commit is contained in:
minjaesong
2026-02-26 10:15:23 +09:00
parent be1c8e2f79
commit 982fb94828
2 changed files with 91 additions and 11 deletions

Binary file not shown.

View File

@@ -542,6 +542,16 @@ def _generate_devanagari(glyphs, has, replacewith_subs=None):
return u # already PUA or non-consonant return u # already PUA or non-consonant
akhn_subs = [] akhn_subs = []
# ISCII: RA + ZWJ + virama + YA -> RYA (uF0106)
ra_int = SC.to_deva_internal(0x0930)
ya_int = SC.to_deva_internal(0x092F)
RYA = 0xF0106
if has(ra_int) and has(0x200D) and has(SC.DEVANAGARI_VIRAMA) and has(ya_int) and has(RYA):
akhn_subs.append(
f" sub {glyph_name(ra_int)} {glyph_name(0x200D)} {glyph_name(SC.DEVANAGARI_VIRAMA)} {glyph_name(ya_int)} by {glyph_name(RYA)}; # RYA"
)
ka_int = SC.to_deva_internal(0x0915) ka_int = SC.to_deva_internal(0x0915)
ssa_int = SC.to_deva_internal(0x0937) ssa_int = SC.to_deva_internal(0x0937)
ja_int = SC.to_deva_internal(0x091C) ja_int = SC.to_deva_internal(0x091C)
@@ -639,6 +649,12 @@ def _generate_devanagari(glyphs, has, replacewith_subs=None):
half_subs.append( half_subs.append(
f" sub {glyph_name(internal)} {glyph_name(SC.DEVANAGARI_VIRAMA)} by {glyph_name(half_form)};" f" sub {glyph_name(internal)} {glyph_name(SC.DEVANAGARI_VIRAMA)} by {glyph_name(half_form)};"
) )
# RYA (uF0106) has a special half form (uF0107), not at +240
HALF_RYA = 0xF0107
if has(RYA) and has(SC.DEVANAGARI_VIRAMA) and has(HALF_RYA):
half_subs.append(
f" sub {glyph_name(RYA)} {glyph_name(SC.DEVANAGARI_VIRAMA)} by {glyph_name(HALF_RYA)};"
)
if half_subs: if half_subs:
features.append("feature half {\n script dev2;\n" + '\n'.join(half_subs) + "\n} half;") features.append("feature half {\n script dev2;\n" + '\n'.join(half_subs) + "\n} half;")
@@ -787,7 +803,7 @@ def _generate_devanagari(glyphs, has, replacewith_subs=None):
abvs_lines.append("} abvs;") abvs_lines.append("} abvs;")
features.append('\n'.join(abvs_lines)) features.append('\n'.join(abvs_lines))
# --- psts: I-matra and II-matra length variants --- # --- psts: I-matra/II-matra length variants + open Ya ---
# Must run AFTER abvs because abvs uses uni093F as context for complex # Must run AFTER abvs because abvs uses uni093F as context for complex
# reph substitution. If I-matra were substituted before abvs, those # reph substitution. If I-matra were substituted before abvs, those
# contextual rules would break. # contextual rules would break.
@@ -795,9 +811,15 @@ def _generate_devanagari(glyphs, has, replacewith_subs=None):
# HarfBuzz dev2 feature order: init → pres → abvs → blws → psts → haln # HarfBuzz dev2 feature order: init → pres → abvs → blws → psts → haln
# psts has F_GLOBAL_MANUAL_JOINERS masking → applied to ALL glyphs in the # psts has F_GLOBAL_MANUAL_JOINERS masking → applied to ALL glyphs in the
# syllable, so it works for both pre-base I-matra and post-base II-matra. # syllable, so it works for both pre-base I-matra and post-base II-matra.
psts_code = _generate_psts_matra_variants(glyphs, has, _conjuncts) matra_lookups, matra_body = _generate_psts_matra_variants(glyphs, has, _conjuncts)
if psts_code: ya_lookups, ya_body = _generate_psts_open_ya(glyphs, has)
features.append(psts_code) all_lookups = matra_lookups + ya_lookups
all_body = matra_body + ya_body
if all_body:
feat = ["feature psts {", " script dev2;"]
feat.extend(all_body)
feat.append("} psts;")
features.append('\n'.join(all_lookups + [''] + feat))
if not features: if not features:
return "" return ""
@@ -1012,15 +1034,73 @@ def _generate_psts_matra_variants(glyphs, has, conjuncts):
psts_ii_lines = [] psts_ii_lines = []
if not psts_i_lines and not psts_ii_lines: if not psts_i_lines and not psts_ii_lines:
return "" return [], []
# Assemble the feature block return lines, psts_i_lines + psts_ii_lines
feat = ["feature psts {", " script dev2;"]
feat.extend(psts_i_lines)
feat.extend(psts_ii_lines)
feat.append("} psts;")
return '\n'.join(lines + [''] + feat)
def _generate_psts_open_ya(glyphs, has):
"""Generate psts rules for open Ya substitution.
In the bitmap font, Ya (uF015A) uses an "open" variant (uF0108) when it
follows certain half-form consonants. Half-Ya (uF024A) similarly becomes
open-half-Ya (uF0109).
Returns (lookup_lines, feature_body_lines) like _generate_psts_matra_variants.
"""
OPEN_YA = 0xF0108
OPEN_HALF_YA = 0xF0109
YA_INT = 0xF015A # 0x092F.toDevaInternal()
HALF_YA = YA_INT + 240 # 0xF024A
if not has(OPEN_YA) or not has(YA_INT):
return [], []
# Consonants whose half forms trigger open Ya (from Kotlin ligateIndicConsonants):
# 1. Basic consonants: KA, NGA, CHA, TTA, TTH, DD, DDH
open_ya_full = {
SC.to_deva_internal(cp)
for cp in [0x0915, 0x0919, 0x091B, 0x091F, 0x0920, 0x0921, 0x0922]
}
# 2. D.RA
open_ya_full.add(0xF0331)
# 3. Conjuncts in 0xF01B0..0xF01DF
for cp in range(0xF01B0, 0xF01E0):
if has(cp):
open_ya_full.add(cp)
# 4. RA-appended conjuncts in 0xF0390..0xF03BF
for cp in range(0xF0390, 0xF03C0):
if has(cp):
open_ya_full.add(cp)
# Collect the HALF forms of all these consonants
open_ya_halfs = set()
for cp in open_ya_full:
half = cp + 240
if has(half):
open_ya_halfs.add(half)
if not open_ya_halfs:
return [], []
lookups = []
lookups.append(f"lookup OpenYa {{")
lookups.append(f" sub {glyph_name(YA_INT)} by {glyph_name(OPEN_YA)};")
lookups.append(f"}} OpenYa;")
if has(OPEN_HALF_YA) and has(HALF_YA):
lookups.append(f"lookup OpenHalfYa {{")
lookups.append(f" sub {glyph_name(HALF_YA)} by {glyph_name(OPEN_HALF_YA)};")
lookups.append(f"}} OpenHalfYa;")
body = []
half_names = ' '.join(glyph_name(cp) for cp in sorted(open_ya_halfs))
body.append(f" @openYaHalfs = [{half_names}];")
body.append(f" sub @openYaHalfs {glyph_name(YA_INT)}' lookup OpenYa;")
if has(OPEN_HALF_YA) and has(HALF_YA):
body.append(f" sub @openYaHalfs {glyph_name(HALF_YA)}' lookup OpenHalfYa;")
return lookups, body
def _generate_tamil(glyphs, has): def _generate_tamil(glyphs, has):