mirror of
https://github.com/curioustorvald/Terrarum-sans-bitmap.git
synced 2026-03-17 00:16:17 +09:00
otf wip2
This commit is contained in:
@@ -77,15 +77,21 @@ def trace_bitmap(bitmap, glyph_width_px):
|
|||||||
return contours
|
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.
|
Draw rectangle contours to a TTGlyphPen or similar pen.
|
||||||
Each rectangle is drawn as a clockwise closed contour (4 on-curve points).
|
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:
|
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
|
# Clockwise: bottom-left -> top-left -> top-right -> bottom-right
|
||||||
pen.moveTo((x0, y0))
|
pen.moveTo((ax0, ay0))
|
||||||
pen.lineTo((x0, y1))
|
pen.lineTo((ax0, ay1))
|
||||||
pen.lineTo((x1, y1))
|
pen.lineTo((ax1, ay1))
|
||||||
pen.lineTo((x1, y0))
|
pen.lineTo((ax1, ay0))
|
||||||
pen.closePath()
|
pen.closePath()
|
||||||
|
|||||||
@@ -168,11 +168,28 @@ def build_font(assets_dir, output_path, no_bitmap=False, no_features=False):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
advance = g.props.width * SCALE
|
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)
|
contours = trace_bitmap(g.bitmap, g.props.width)
|
||||||
|
|
||||||
pen = T2CharStringPen(advance, None)
|
pen = T2CharStringPen(advance, None)
|
||||||
if contours:
|
if contours:
|
||||||
draw_glyph_to_pen(contours, pen)
|
draw_glyph_to_pen(contours, pen, x_offset=x_offset)
|
||||||
traced_count += 1
|
traced_count += 1
|
||||||
charstrings[name] = pen.getCharString()
|
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)
|
info |= (1 << y)
|
||||||
ext_info[x] = info
|
ext_info[x] = info
|
||||||
|
|
||||||
# Extract glyph bitmap: only pixels within the glyph's declared width.
|
# Extract glyph bitmap: full cell minus the tag column.
|
||||||
# The tag column and any padding beyond width must be stripped.
|
# The Kotlin code draws the ENTIRE cell at a computed position;
|
||||||
bitmap_w = min(width, cell_w - 1) if width > 0 else 0
|
# 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 = []
|
bitmap = []
|
||||||
for row in range(cell_h):
|
for row in range(cell_h):
|
||||||
row_data = []
|
row_data = []
|
||||||
for col in range(bitmap_w):
|
for col in range(max_w):
|
||||||
px = image.get_pixel(cell_x + col, cell_y + row)
|
px = image.get_pixel(cell_x + col, cell_y + row)
|
||||||
row_data.append(1 if (px & 0xFF) != 0 else 0)
|
row_data.append(1 if (px & 0xFF) != 0 else 0)
|
||||||
bitmap.append(row_data)
|
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)
|
result[code] = ExtractedGlyph(code, props, bitmap)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -398,59 +398,103 @@ def _generate_locl(glyphs, has):
|
|||||||
|
|
||||||
|
|
||||||
def _generate_devanagari(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 = []
|
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 ---
|
# --- nukt: consonant + nukta -> nukta form ---
|
||||||
|
# Now operates on PUA forms (after ccmp)
|
||||||
nukt_subs = []
|
nukt_subs = []
|
||||||
for uni_cp in range(0x0915, 0x093A):
|
for uni_cp in range(0x0915, 0x093A):
|
||||||
internal = SC.to_deva_internal(uni_cp)
|
internal = SC.to_deva_internal(uni_cp)
|
||||||
nukta_form = internal + 48
|
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(
|
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:
|
if nukt_subs:
|
||||||
features.append("feature nukt {\n script dev2;\n" + '\n'.join(nukt_subs) + "\n} nukt;")
|
features.append("feature nukt {\n script dev2;\n" + '\n'.join(nukt_subs) + "\n} nukt;")
|
||||||
|
|
||||||
# --- akhn: akhand ligatures ---
|
# --- akhn: akhand ligatures ---
|
||||||
|
# Must reference PUA forms after ccmp
|
||||||
akhn_subs = []
|
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(
|
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(
|
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:
|
if akhn_subs:
|
||||||
features.append("feature akhn {\n script dev2;\n" + '\n'.join(akhn_subs) + "\n} akhn;")
|
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 = []
|
half_subs = []
|
||||||
for uni_cp in range(0x0915, 0x093A):
|
for uni_cp in range(0x0915, 0x093A):
|
||||||
internal = SC.to_deva_internal(uni_cp)
|
internal = SC.to_deva_internal(uni_cp)
|
||||||
half_form = internal + 240
|
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(
|
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:
|
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;")
|
||||||
|
|
||||||
# --- 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 = []
|
vatu_subs = []
|
||||||
for uni_cp in range(0x0915, 0x093A):
|
for uni_cp in range(0x0915, 0x093A):
|
||||||
internal = SC.to_deva_internal(uni_cp)
|
internal = SC.to_deva_internal(uni_cp)
|
||||||
ra_form = internal + 480
|
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(
|
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:
|
if vatu_subs:
|
||||||
features.append("feature vatu {\n script dev2;\n" + '\n'.join(vatu_subs) + "\n} vatu;")
|
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 = []
|
pres_subs = []
|
||||||
_conjuncts = [
|
_conjuncts = [
|
||||||
(0x0915, 0x0924, SC.DEVANAGARI_LIG_K_T, "K.T"),
|
(0x0915, 0x0924, SC.DEVANAGARI_LIG_K_T, "K.T"),
|
||||||
@@ -494,7 +538,9 @@ def _generate_devanagari(glyphs, has):
|
|||||||
(0x0939, 0x0932, 0xF01CA, "H.L"),
|
(0x0939, 0x0932, 0xF01CA, "H.L"),
|
||||||
(0x0939, 0x0935, 0xF01CB, "H.V"),
|
(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):
|
if has(c1) and has(SC.DEVANAGARI_VIRAMA) and has(c2) and has(result):
|
||||||
pres_subs.append(
|
pres_subs.append(
|
||||||
f" sub {glyph_name(c1)} {glyph_name(SC.DEVANAGARI_VIRAMA)} {glyph_name(c2)} by {glyph_name(result)}; # {name}"
|
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:
|
if pres_subs:
|
||||||
features.append("feature pres {\n script dev2;\n" + '\n'.join(pres_subs) + "\n} pres;")
|
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_subs = []
|
||||||
_blws_rules = [
|
_blws_rules = [
|
||||||
(0x0930, SC.DEVANAGARI_U, SC.DEVANAGARI_SYLL_RU, "Ru"),
|
(_di(0x0930), SC.DEVANAGARI_U, SC.DEVANAGARI_SYLL_RU, "Ru"),
|
||||||
(0x0930, SC.DEVANAGARI_UU, SC.DEVANAGARI_SYLL_RUU, "Ruu"),
|
(_di(0x0930), SC.DEVANAGARI_UU, SC.DEVANAGARI_SYLL_RUU, "Ruu"),
|
||||||
(0x0931, SC.DEVANAGARI_U, SC.DEVANAGARI_SYLL_RRU, "RRu"),
|
(_di(0x0931), SC.DEVANAGARI_U, SC.DEVANAGARI_SYLL_RRU, "RRu"),
|
||||||
(0x0931, SC.DEVANAGARI_UU, SC.DEVANAGARI_SYLL_RRUU, "RRuu"),
|
(_di(0x0931), SC.DEVANAGARI_UU, SC.DEVANAGARI_SYLL_RRUU, "RRuu"),
|
||||||
(0x0939, SC.DEVANAGARI_U, SC.DEVANAGARI_SYLL_HU, "Hu"),
|
(_di(0x0939), SC.DEVANAGARI_U, SC.DEVANAGARI_SYLL_HU, "Hu"),
|
||||||
(0x0939, SC.DEVANAGARI_UU, SC.DEVANAGARI_SYLL_HUU, "Huu"),
|
(_di(0x0939), SC.DEVANAGARI_UU, SC.DEVANAGARI_SYLL_HUU, "Huu"),
|
||||||
]
|
]
|
||||||
for c1, c2, result, name in _blws_rules:
|
for c1, c2, result, name in _blws_rules:
|
||||||
if has(c1) and has(c2) and has(result):
|
if has(c1) and has(c2) and has(result):
|
||||||
@@ -520,12 +566,12 @@ def _generate_devanagari(glyphs, has):
|
|||||||
if blws_subs:
|
if blws_subs:
|
||||||
features.append("feature blws {\n script dev2;\n" + '\n'.join(blws_subs) + "\n} blws;")
|
features.append("feature blws {\n script dev2;\n" + '\n'.join(blws_subs) + "\n} blws;")
|
||||||
|
|
||||||
# --- rphf: RA + virama -> reph ---
|
# --- rphf: RA (PUA) + virama -> reph ---
|
||||||
if has(0x0930) and has(SC.DEVANAGARI_VIRAMA) and has(SC.DEVANAGARI_RA_SUPER):
|
if has(ra_int) and has(SC.DEVANAGARI_VIRAMA) and has(SC.DEVANAGARI_RA_SUPER):
|
||||||
rphf_code = (
|
rphf_code = (
|
||||||
f"feature rphf {{\n"
|
f"feature rphf {{\n"
|
||||||
f" script dev2;\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;"
|
f"}} rphf;"
|
||||||
)
|
)
|
||||||
features.append(rphf_code)
|
features.append(rphf_code)
|
||||||
|
|||||||
Reference in New Issue
Block a user