mirror of
https://github.com/curioustorvald/Terrarum-sans-bitmap.git
synced 2026-03-07 20:01:52 +09:00
diacritics pos for glyphs sans explicit anchor tags
This commit is contained in:
Binary file not shown.
@@ -1244,8 +1244,22 @@ def _generate_sundanese(glyphs, has):
|
|||||||
def _generate_mark(glyphs, has):
|
def _generate_mark(glyphs, has):
|
||||||
"""
|
"""
|
||||||
Generate GPOS mark-to-base positioning using diacritics anchors from tag column.
|
Generate GPOS mark-to-base positioning using diacritics anchors from tag column.
|
||||||
|
|
||||||
|
Marks are grouped by (writeOnTop, alignment) into separate mark classes
|
||||||
|
and lookups. Different alignments need different default base anchor
|
||||||
|
positions to match the Kotlin engine's fallback behaviour:
|
||||||
|
- ALIGN_CENTRE: base anchor at width/2
|
||||||
|
- ALIGN_RIGHT: base anchor at width
|
||||||
|
- ALIGN_LEFT/BEFORE: base anchor at 0 (mark sits at base origin)
|
||||||
"""
|
"""
|
||||||
bases_with_anchors = {}
|
# Collect ALL non-mark glyphs as potential bases (excluding CJK
|
||||||
|
# ideographs and Braille which are unlikely to receive combining marks
|
||||||
|
# and would bloat the GPOS table).
|
||||||
|
_EXCLUDE_RANGES = (
|
||||||
|
range(0x3400, 0xA000), # CJK Unified Ideographs (Ext A + main)
|
||||||
|
range(0x2800, 0x2900), # Braille
|
||||||
|
)
|
||||||
|
all_bases = {}
|
||||||
marks = {}
|
marks = {}
|
||||||
|
|
||||||
for cp, g in glyphs.items():
|
for cp, g in glyphs.items():
|
||||||
@@ -1253,33 +1267,37 @@ def _generate_mark(glyphs, has):
|
|||||||
continue
|
continue
|
||||||
if g.props.write_on_top >= 0:
|
if g.props.write_on_top >= 0:
|
||||||
marks[cp] = g
|
marks[cp] = g
|
||||||
elif any(a.x_used or a.y_used for a in g.props.diacritics_anchors):
|
elif g.bitmap and g.props.width > 0:
|
||||||
bases_with_anchors[cp] = g
|
if not any(cp in r for r in _EXCLUDE_RANGES):
|
||||||
|
all_bases[cp] = g
|
||||||
|
|
||||||
if not bases_with_anchors or not marks:
|
if not all_bases or not marks:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
# Group marks by writeOnTop type
|
_align_suffix = {
|
||||||
mark_classes = {}
|
SC.ALIGN_LEFT: 'l',
|
||||||
for cp, g in marks.items():
|
SC.ALIGN_RIGHT: 'r',
|
||||||
mark_type = g.props.write_on_top
|
SC.ALIGN_CENTRE: 'c',
|
||||||
if mark_type not in mark_classes:
|
SC.ALIGN_BEFORE: 'b',
|
||||||
mark_classes[mark_type] = []
|
}
|
||||||
mark_classes[mark_type].append((cp, g))
|
|
||||||
|
|
||||||
for mark_type, mark_list in sorted(mark_classes.items()):
|
# Group marks by (writeOnTop, alignment) for separate mark classes.
|
||||||
class_name = f"@mark_type{mark_type}"
|
mark_groups = {} # (mark_type, align) -> [(cp, g), ...]
|
||||||
|
for cp, g in marks.items():
|
||||||
|
key = (g.props.write_on_top, g.props.align_where)
|
||||||
|
mark_groups.setdefault(key, []).append((cp, g))
|
||||||
|
|
||||||
|
# Emit markClass definitions
|
||||||
|
for (mark_type, align), mark_list in sorted(mark_groups.items()):
|
||||||
|
suffix = _align_suffix.get(align, 'x')
|
||||||
|
class_name = f"@mark_t{mark_type}_{suffix}"
|
||||||
for cp, g in mark_list:
|
for cp, g in mark_list:
|
||||||
if g.props.align_where == SC.ALIGN_CENTRE:
|
if align == SC.ALIGN_CENTRE:
|
||||||
# Match Kotlin: anchorPoint - HALF_VAR_INIT centres the
|
# Match Kotlin: anchorPoint - HALF_VAR_INIT centres the
|
||||||
# cell on the anchor. For U+0900-0902 the Kotlin engine
|
# cell on the anchor. For U+0900-0902 the Kotlin engine
|
||||||
# uses (W_VAR_INIT + 1) / 2 instead (1 px nudge left).
|
# 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.
|
|
||||||
# The Kotlin engine always uses W_VAR_INIT for alignment,
|
|
||||||
# even for EXTRAWIDE sheets.
|
|
||||||
bm_cols = SC.W_VAR_INIT
|
bm_cols = SC.W_VAR_INIT
|
||||||
if 0x0900 <= cp <= 0x0902:
|
if 0x0900 <= cp <= 0x0902:
|
||||||
half = (SC.W_VAR_INIT + 1) // 2
|
half = (SC.W_VAR_INIT + 1) // 2
|
||||||
@@ -1288,28 +1306,57 @@ def _generate_mark(glyphs, has):
|
|||||||
x_offset = math.ceil((g.props.width - bm_cols) / 2) * SC.SCALE
|
x_offset = math.ceil((g.props.width - bm_cols) / 2) * SC.SCALE
|
||||||
x_offset -= g.props.nudge_x * SC.SCALE
|
x_offset -= g.props.nudge_x * SC.SCALE
|
||||||
mark_x = x_offset + half * SC.SCALE
|
mark_x = x_offset + half * SC.SCALE
|
||||||
|
elif align == SC.ALIGN_RIGHT:
|
||||||
|
# Match Kotlin: mark at base anchor - W_VAR_INIT.
|
||||||
|
# The contour x_offset already includes (width - W_VAR_INIT),
|
||||||
|
# so mark_x just needs the nudge_x component.
|
||||||
|
mark_x = g.props.nudge_x * SC.SCALE
|
||||||
else:
|
else:
|
||||||
mark_x = ((g.props.width + 1) // 2) * SC.SCALE
|
# ALIGN_LEFT / ALIGN_BEFORE: mark sits at base origin.
|
||||||
|
mark_x = 0
|
||||||
mark_y = SC.ASCENT
|
mark_y = SC.ASCENT
|
||||||
lines.append(
|
lines.append(
|
||||||
f"markClass {glyph_name(cp)} <anchor {mark_x} {mark_y}> {class_name};"
|
f"markClass {glyph_name(cp)} <anchor {mark_x} {mark_y}> {class_name};"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Define lookups at top level so they can be referenced from
|
# Generate one lookup per (mark_type, align) group.
|
||||||
# multiple script registrations in the mark feature.
|
|
||||||
lookup_names = []
|
lookup_names = []
|
||||||
for mark_type, mark_list in sorted(mark_classes.items()):
|
for (mark_type, align), mark_list in sorted(mark_groups.items()):
|
||||||
class_name = f"@mark_type{mark_type}"
|
suffix = _align_suffix.get(align, 'x')
|
||||||
lookup_name = f"mark_type{mark_type}"
|
class_name = f"@mark_t{mark_type}_{suffix}"
|
||||||
|
lookup_name = f"mark_t{mark_type}_{suffix}"
|
||||||
lines.append(f"lookup {lookup_name} {{")
|
lines.append(f"lookup {lookup_name} {{")
|
||||||
|
|
||||||
for cp, g in sorted(bases_with_anchors.items()):
|
for cp, g in sorted(all_bases.items()):
|
||||||
anchor = g.props.diacritics_anchors[mark_type] if mark_type < 6 else None
|
anchor = (g.props.diacritics_anchors[mark_type]
|
||||||
if anchor and (anchor.x_used or anchor.y_used):
|
if mark_type < len(g.props.diacritics_anchors)
|
||||||
# Match Kotlin: when x_used is false, default to width / 2
|
else None)
|
||||||
ax = (anchor.x if anchor.x_used else g.props.width // 2) * SC.SCALE
|
has_explicit = anchor and (anchor.x_used or anchor.y_used)
|
||||||
ay = (SC.ASCENT // SC.SCALE - anchor.y) * SC.SCALE
|
|
||||||
lines.append(f" pos base {glyph_name(cp)} <anchor {ax} {ay}> mark {class_name};")
|
if align in (SC.ALIGN_LEFT, SC.ALIGN_BEFORE):
|
||||||
|
# Kotlin ignores base anchors for these alignments;
|
||||||
|
# the mark always sits at posX[base].
|
||||||
|
ax = 0
|
||||||
|
ay = SC.ASCENT
|
||||||
|
elif has_explicit:
|
||||||
|
ax = (anchor.x if anchor.x_used
|
||||||
|
else g.props.width // 2) * SC.SCALE
|
||||||
|
ay = ((SC.ASCENT // SC.SCALE - anchor.y) * SC.SCALE
|
||||||
|
if anchor.y_used else SC.ASCENT)
|
||||||
|
elif align == SC.ALIGN_CENTRE:
|
||||||
|
ax = (g.props.width // 2) * SC.SCALE
|
||||||
|
ay = SC.ASCENT
|
||||||
|
elif align == SC.ALIGN_RIGHT:
|
||||||
|
ax = g.props.width * SC.SCALE
|
||||||
|
ay = SC.ASCENT
|
||||||
|
else:
|
||||||
|
ax = 0
|
||||||
|
ay = SC.ASCENT
|
||||||
|
|
||||||
|
lines.append(
|
||||||
|
f" pos base {glyph_name(cp)}"
|
||||||
|
f" <anchor {ax} {ay}> mark {class_name};"
|
||||||
|
)
|
||||||
|
|
||||||
lines.append(f"}} {lookup_name};")
|
lines.append(f"}} {lookup_name};")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|||||||
Reference in New Issue
Block a user