mirror of
https://github.com/curioustorvald/Terrarum-sans-bitmap.git
synced 2026-03-07 11:51:50 +09:00
otf wip2
This commit is contained in:
@@ -77,15 +77,21 @@ def trace_bitmap(bitmap, glyph_width_px):
|
||||
return contours
|
||||
|
||||
|
||||
def draw_glyph_to_pen(contours, pen):
|
||||
def draw_glyph_to_pen(contours, pen, x_offset=0, y_offset=0):
|
||||
"""
|
||||
Draw rectangle contours to a TTGlyphPen or similar pen.
|
||||
Each rectangle is drawn as a clockwise closed contour (4 on-curve points).
|
||||
|
||||
x_offset/y_offset shift all contours (used for alignment positioning).
|
||||
"""
|
||||
for x0, y0, x1, y1 in contours:
|
||||
ax0 = x0 + x_offset
|
||||
ax1 = x1 + x_offset
|
||||
ay0 = y0 + y_offset
|
||||
ay1 = y1 + y_offset
|
||||
# Clockwise: bottom-left -> top-left -> top-right -> bottom-right
|
||||
pen.moveTo((x0, y0))
|
||||
pen.lineTo((x0, y1))
|
||||
pen.lineTo((x1, y1))
|
||||
pen.lineTo((x1, y0))
|
||||
pen.moveTo((ax0, ay0))
|
||||
pen.lineTo((ax0, ay1))
|
||||
pen.lineTo((ax1, ay1))
|
||||
pen.lineTo((ax1, ay0))
|
||||
pen.closePath()
|
||||
|
||||
@@ -168,11 +168,28 @@ def build_font(assets_dir, output_path, no_bitmap=False, no_features=False):
|
||||
continue
|
||||
|
||||
advance = g.props.width * SCALE
|
||||
|
||||
# Compute alignment offset (lsb shift).
|
||||
# The Kotlin code draws the full cell at an offset position:
|
||||
# ALIGN_LEFT: offset = 0
|
||||
# ALIGN_RIGHT: offset = width - W_VAR_INIT (negative)
|
||||
# ALIGN_CENTRE: offset = ceil((width - W_VAR_INIT) / 2) (negative)
|
||||
# ALIGN_BEFORE: offset = 0
|
||||
# The bitmap cell width depends on the sheet type.
|
||||
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:
|
||||
x_offset = (g.props.width - bm_cols) * SCALE
|
||||
elif g.props.align_where == SC.ALIGN_CENTRE:
|
||||
x_offset = math.ceil((g.props.width - bm_cols) / 2) * SCALE
|
||||
else:
|
||||
x_offset = 0
|
||||
|
||||
contours = trace_bitmap(g.bitmap, g.props.width)
|
||||
|
||||
pen = T2CharStringPen(advance, None)
|
||||
if contours:
|
||||
draw_glyph_to_pen(contours, pen)
|
||||
draw_glyph_to_pen(contours, pen, x_offset=x_offset)
|
||||
traced_count += 1
|
||||
charstrings[name] = pen.getCharString()
|
||||
|
||||
|
||||
@@ -191,17 +191,30 @@ def parse_variable_sheet(image, sheet_index, cell_w, cell_h, cols, is_xy_swapped
|
||||
info |= (1 << y)
|
||||
ext_info[x] = info
|
||||
|
||||
# Extract glyph bitmap: only pixels within the glyph's declared width.
|
||||
# The tag column and any padding beyond width must be stripped.
|
||||
bitmap_w = min(width, cell_w - 1) if width > 0 else 0
|
||||
# Extract glyph bitmap: full cell minus the tag column.
|
||||
# The Kotlin code draws the ENTIRE cell at a computed position;
|
||||
# the tag column is the only thing excluded.
|
||||
# Alignment and width only affect advance/positioning, not the bitmap.
|
||||
max_w = cell_w - 1 # exclude tag column
|
||||
|
||||
bitmap = []
|
||||
for row in range(cell_h):
|
||||
row_data = []
|
||||
for col in range(bitmap_w):
|
||||
for col in range(max_w):
|
||||
px = image.get_pixel(cell_x + col, cell_y + row)
|
||||
row_data.append(1 if (px & 0xFF) != 0 else 0)
|
||||
bitmap.append(row_data)
|
||||
|
||||
# Now strip the tag column pixels that may have leaked into
|
||||
# the glyph area. Tag data lives at column (cell_w - 1) which
|
||||
# we already excluded, but extInfo columns 0..6 at the LEFT
|
||||
# edge of the cell also contain tag data for replacewith glyphs.
|
||||
# Clean those columns if they were used for extInfo.
|
||||
if ext_count > 0:
|
||||
for col_idx in range(min(ext_count, max_w)):
|
||||
for row in range(cell_h):
|
||||
bitmap[row][col_idx] = 0
|
||||
|
||||
result[code] = ExtractedGlyph(code, props, bitmap)
|
||||
|
||||
return result
|
||||
|
||||
@@ -398,59 +398,103 @@ def _generate_locl(glyphs, has):
|
||||
|
||||
|
||||
def _generate_devanagari(glyphs, has):
|
||||
"""Generate Devanagari GSUB features: nukt, akhn, half, vatu, pres, blws, rphf."""
|
||||
"""Generate Devanagari GSUB features: ccmp (consonant mapping), nukt, akhn, half, vatu, pres, blws, rphf."""
|
||||
features = []
|
||||
|
||||
# --- ccmp: Map Unicode consonants to internal PUA presentation forms ---
|
||||
# This is the critical first step: U+0915-0939 have width=0 in the sheet,
|
||||
# the actual glyph bitmaps live at their PUA forms (0xF0140+).
|
||||
# This mirrors the Kotlin normalise() pass 0.
|
||||
ccmp_subs = []
|
||||
for uni_cp in range(0x0915, 0x093A):
|
||||
internal = SC.to_deva_internal(uni_cp)
|
||||
if has(uni_cp) and has(internal):
|
||||
ccmp_subs.append(
|
||||
f" sub {glyph_name(uni_cp)} by {glyph_name(internal)};"
|
||||
)
|
||||
# Also map nukta-forms U+0958-095F to their PUA equivalents
|
||||
for uni_cp in range(0x0958, 0x0960):
|
||||
try:
|
||||
internal = SC.to_deva_internal(uni_cp)
|
||||
if has(uni_cp) and has(internal):
|
||||
ccmp_subs.append(
|
||||
f" sub {glyph_name(uni_cp)} by {glyph_name(internal)};"
|
||||
)
|
||||
except ValueError:
|
||||
pass
|
||||
if ccmp_subs:
|
||||
features.append(
|
||||
"feature ccmp {\n script dev2;\n"
|
||||
" lookup DevaConsonantMap {\n"
|
||||
+ '\n'.join(" " + s for s in ccmp_subs)
|
||||
+ "\n } DevaConsonantMap;\n} ccmp;"
|
||||
)
|
||||
|
||||
# --- nukt: consonant + nukta -> nukta form ---
|
||||
# Now operates on PUA forms (after ccmp)
|
||||
nukt_subs = []
|
||||
for uni_cp in range(0x0915, 0x093A):
|
||||
internal = SC.to_deva_internal(uni_cp)
|
||||
nukta_form = internal + 48
|
||||
if has(uni_cp) and has(0x093C) and has(nukta_form):
|
||||
if has(internal) and has(0x093C) and has(nukta_form):
|
||||
nukt_subs.append(
|
||||
f" sub {glyph_name(uni_cp)} {glyph_name(0x093C)} by {glyph_name(nukta_form)};"
|
||||
f" sub {glyph_name(internal)} {glyph_name(0x093C)} by {glyph_name(nukta_form)};"
|
||||
)
|
||||
if nukt_subs:
|
||||
features.append("feature nukt {\n script dev2;\n" + '\n'.join(nukt_subs) + "\n} nukt;")
|
||||
|
||||
# --- akhn: akhand ligatures ---
|
||||
# Must reference PUA forms after ccmp
|
||||
akhn_subs = []
|
||||
if has(0x0915) and has(SC.DEVANAGARI_VIRAMA) and has(0x0937) and has(SC.DEVANAGARI_LIG_K_SS):
|
||||
ka_int = SC.to_deva_internal(0x0915)
|
||||
ssa_int = SC.to_deva_internal(0x0937)
|
||||
ja_int = SC.to_deva_internal(0x091C)
|
||||
nya_int = SC.to_deva_internal(0x091E)
|
||||
if has(ka_int) and has(SC.DEVANAGARI_VIRAMA) and has(ssa_int) and has(SC.DEVANAGARI_LIG_K_SS):
|
||||
akhn_subs.append(
|
||||
f" sub {glyph_name(0x0915)} {glyph_name(SC.DEVANAGARI_VIRAMA)} {glyph_name(0x0937)} by {glyph_name(SC.DEVANAGARI_LIG_K_SS)};"
|
||||
f" sub {glyph_name(ka_int)} {glyph_name(SC.DEVANAGARI_VIRAMA)} {glyph_name(ssa_int)} by {glyph_name(SC.DEVANAGARI_LIG_K_SS)};"
|
||||
)
|
||||
if has(0x091C) and has(SC.DEVANAGARI_VIRAMA) and has(0x091E) and has(SC.DEVANAGARI_LIG_J_NY):
|
||||
if has(ja_int) and has(SC.DEVANAGARI_VIRAMA) and has(nya_int) and has(SC.DEVANAGARI_LIG_J_NY):
|
||||
akhn_subs.append(
|
||||
f" sub {glyph_name(0x091C)} {glyph_name(SC.DEVANAGARI_VIRAMA)} {glyph_name(0x091E)} by {glyph_name(SC.DEVANAGARI_LIG_J_NY)};"
|
||||
f" sub {glyph_name(ja_int)} {glyph_name(SC.DEVANAGARI_VIRAMA)} {glyph_name(nya_int)} by {glyph_name(SC.DEVANAGARI_LIG_J_NY)};"
|
||||
)
|
||||
if akhn_subs:
|
||||
features.append("feature akhn {\n script dev2;\n" + '\n'.join(akhn_subs) + "\n} akhn;")
|
||||
|
||||
# --- half: consonant + virama -> half form ---
|
||||
# --- half: consonant (PUA) + virama -> half form ---
|
||||
# After ccmp, consonants are in PUA form, so reference PUA here
|
||||
half_subs = []
|
||||
for uni_cp in range(0x0915, 0x093A):
|
||||
internal = SC.to_deva_internal(uni_cp)
|
||||
half_form = internal + 240
|
||||
if has(uni_cp) and has(SC.DEVANAGARI_VIRAMA) and has(half_form):
|
||||
if has(internal) and has(SC.DEVANAGARI_VIRAMA) and has(half_form):
|
||||
half_subs.append(
|
||||
f" sub {glyph_name(uni_cp)} {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)};"
|
||||
)
|
||||
if half_subs:
|
||||
features.append("feature half {\n script dev2;\n" + '\n'.join(half_subs) + "\n} half;")
|
||||
|
||||
# --- vatu: consonant + virama + RA -> RA-appended form ---
|
||||
# --- vatu: consonant (PUA) + virama + RA (PUA) -> RA-appended form ---
|
||||
ra_int = SC.to_deva_internal(0x0930)
|
||||
vatu_subs = []
|
||||
for uni_cp in range(0x0915, 0x093A):
|
||||
internal = SC.to_deva_internal(uni_cp)
|
||||
ra_form = internal + 480
|
||||
if has(uni_cp) and has(SC.DEVANAGARI_VIRAMA) and has(0x0930) and has(ra_form):
|
||||
if has(internal) and has(SC.DEVANAGARI_VIRAMA) and has(ra_int) and has(ra_form):
|
||||
vatu_subs.append(
|
||||
f" sub {glyph_name(uni_cp)} {glyph_name(SC.DEVANAGARI_VIRAMA)} {glyph_name(0x0930)} by {glyph_name(ra_form)};"
|
||||
f" sub {glyph_name(internal)} {glyph_name(SC.DEVANAGARI_VIRAMA)} {glyph_name(ra_int)} by {glyph_name(ra_form)};"
|
||||
)
|
||||
if vatu_subs:
|
||||
features.append("feature vatu {\n script dev2;\n" + '\n'.join(vatu_subs) + "\n} vatu;")
|
||||
|
||||
# --- pres: named conjunct ligatures ---
|
||||
# --- pres: named conjunct ligatures (using PUA forms) ---
|
||||
def _di(u):
|
||||
"""Convert Unicode Devanagari consonant to internal PUA form."""
|
||||
try:
|
||||
return SC.to_deva_internal(u)
|
||||
except ValueError:
|
||||
return u # already PUA or non-consonant
|
||||
|
||||
pres_subs = []
|
||||
_conjuncts = [
|
||||
(0x0915, 0x0924, SC.DEVANAGARI_LIG_K_T, "K.T"),
|
||||
@@ -494,7 +538,9 @@ def _generate_devanagari(glyphs, has):
|
||||
(0x0939, 0x0932, 0xF01CA, "H.L"),
|
||||
(0x0939, 0x0935, 0xF01CB, "H.V"),
|
||||
]
|
||||
for c1, c2, result, name in _conjuncts:
|
||||
for c1_uni, c2_uni, result, name in _conjuncts:
|
||||
c1 = _di(c1_uni)
|
||||
c2 = _di(c2_uni)
|
||||
if has(c1) and has(SC.DEVANAGARI_VIRAMA) and has(c2) and has(result):
|
||||
pres_subs.append(
|
||||
f" sub {glyph_name(c1)} {glyph_name(SC.DEVANAGARI_VIRAMA)} {glyph_name(c2)} by {glyph_name(result)}; # {name}"
|
||||
@@ -502,15 +548,15 @@ def _generate_devanagari(glyphs, has):
|
||||
if pres_subs:
|
||||
features.append("feature pres {\n script dev2;\n" + '\n'.join(pres_subs) + "\n} pres;")
|
||||
|
||||
# --- blws: RA/RRA/HA + U/UU -> special syllables ---
|
||||
# --- blws: RA/RRA/HA (PUA) + U/UU -> special syllables ---
|
||||
blws_subs = []
|
||||
_blws_rules = [
|
||||
(0x0930, SC.DEVANAGARI_U, SC.DEVANAGARI_SYLL_RU, "Ru"),
|
||||
(0x0930, SC.DEVANAGARI_UU, SC.DEVANAGARI_SYLL_RUU, "Ruu"),
|
||||
(0x0931, SC.DEVANAGARI_U, SC.DEVANAGARI_SYLL_RRU, "RRu"),
|
||||
(0x0931, SC.DEVANAGARI_UU, SC.DEVANAGARI_SYLL_RRUU, "RRuu"),
|
||||
(0x0939, SC.DEVANAGARI_U, SC.DEVANAGARI_SYLL_HU, "Hu"),
|
||||
(0x0939, SC.DEVANAGARI_UU, SC.DEVANAGARI_SYLL_HUU, "Huu"),
|
||||
(_di(0x0930), SC.DEVANAGARI_U, SC.DEVANAGARI_SYLL_RU, "Ru"),
|
||||
(_di(0x0930), SC.DEVANAGARI_UU, SC.DEVANAGARI_SYLL_RUU, "Ruu"),
|
||||
(_di(0x0931), SC.DEVANAGARI_U, SC.DEVANAGARI_SYLL_RRU, "RRu"),
|
||||
(_di(0x0931), SC.DEVANAGARI_UU, SC.DEVANAGARI_SYLL_RRUU, "RRuu"),
|
||||
(_di(0x0939), SC.DEVANAGARI_U, SC.DEVANAGARI_SYLL_HU, "Hu"),
|
||||
(_di(0x0939), SC.DEVANAGARI_UU, SC.DEVANAGARI_SYLL_HUU, "Huu"),
|
||||
]
|
||||
for c1, c2, result, name in _blws_rules:
|
||||
if has(c1) and has(c2) and has(result):
|
||||
@@ -520,12 +566,12 @@ def _generate_devanagari(glyphs, has):
|
||||
if blws_subs:
|
||||
features.append("feature blws {\n script dev2;\n" + '\n'.join(blws_subs) + "\n} blws;")
|
||||
|
||||
# --- rphf: RA + virama -> reph ---
|
||||
if has(0x0930) and has(SC.DEVANAGARI_VIRAMA) and has(SC.DEVANAGARI_RA_SUPER):
|
||||
# --- rphf: RA (PUA) + virama -> reph ---
|
||||
if has(ra_int) and has(SC.DEVANAGARI_VIRAMA) and has(SC.DEVANAGARI_RA_SUPER):
|
||||
rphf_code = (
|
||||
f"feature rphf {{\n"
|
||||
f" script dev2;\n"
|
||||
f" sub {glyph_name(0x0930)} {glyph_name(SC.DEVANAGARI_VIRAMA)} by {glyph_name(SC.DEVANAGARI_RA_SUPER)};\n"
|
||||
f" sub {glyph_name(ra_int)} {glyph_name(SC.DEVANAGARI_VIRAMA)} by {glyph_name(SC.DEVANAGARI_RA_SUPER)};\n"
|
||||
f"}} rphf;"
|
||||
)
|
||||
features.append(rphf_code)
|
||||
|
||||
Reference in New Issue
Block a user