K1Guitar/tools/gen_misans_font.py

296 lines
9.9 KiB
Python

#!/usr/bin/env python3
"""Generate MiSans bitmap font C arrays for ILI9341 LCD driver."""
import glob
import math
import os
import re
import sys
from PIL import Image, ImageDraw, ImageFont
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
HEADER_IN = os.path.join(ROOT, "device", "LCD_ILI9341", "Drv_ILI9341_Lcd_ChineseFont.h")
HEADER_OUT = HEADER_IN
ENG_HEADER_IN = os.path.join(ROOT, "device", "LCD_ILI9341", "Drv_ILI9341_Lcd_EnglishFont.h")
ENG_HEADER_OUT = ENG_HEADER_IN
MISANS_DIR = os.path.join(os.path.dirname(ROOT), "..", "Doc", "UI", "MiSans")
MISANS_DIR = os.path.normpath(MISANS_DIR)
def find_ttf(name_part):
for path in glob.glob(os.path.join(MISANS_DIR, "**", "*.ttf"), recursive=True):
if name_part.lower() in os.path.basename(path).lower():
return path
raise FileNotFoundError(f"TTF matching {name_part} not found under {MISANS_DIR}")
def parse_chs_tables(text):
tables = {}
for m in re.finditer(r"const char (CHS\d+Table)\[\]\[2\] = \{(.*?)\};", text, re.S):
name = m.group(1)
body = m.group(2)
entries = []
comments = []
for line in body.splitlines():
cm = re.search(r"\{(0x[0-9A-Fa-f]{2})\s*,\s*(0x[0-9A-Fa-f]{2})\}", line)
if cm:
entries.append((int(cm.group(1), 16), int(cm.group(2), 16)))
cmt = re.search(r"//\s*(.+)", line)
comments.append(cmt.group(1).strip() if cmt else "")
tables[name] = list(zip(entries, comments))
return tables
def gbk_char(h, l):
return bytes([h, l]).decode("gbk", errors="replace")
def render_glyph(ch, size, font, square=True):
if square:
w = h = size
else:
w = size // 2
h = size
img = Image.new("L", (w, h), 0)
draw = ImageDraw.Draw(img)
bbox = draw.textbbox((0, 0), ch, font=font)
tw = bbox[2] - bbox[0]
th = bbox[3] - bbox[1]
x = (w - tw) // 2 - bbox[0]
y = (h - th) // 2 - bbox[1]
draw.text((x, y), ch, fill=255, font=font)
pixels = []
for row in range(h):
for col in range(w):
pixels.append(1 if img.getpixel((col, row)) > 127 else 0)
return pixels, w, h
def cn_to_bytes_msb(pixels, width, height):
row_bytes = (width + 7) // 8
out = []
for row in range(height):
for bi in range(row_bytes):
val = 0
for bit in range(8):
col = bi * 8 + bit
if col < width and pixels[row * width + col]:
val |= (0x80 >> bit)
elif col >= width:
pass
out.append(val)
return out
def ascii_to_bytes_lsb(pixels, width, height):
row_bytes = (width + 7) // 8
out = []
for row in range(height):
for bi in range(row_bytes):
val = 0
for bit in range(8):
col = bi * 8 + bit
if col < width and pixels[row * width + col]:
val |= (0x01 << bit)
out.append(val)
return out
def fmt_bytes(data):
parts = []
for i, b in enumerate(data):
parts.append(f"0x{b:02X}")
lines = []
for i in range(0, len(parts), 16):
lines.append(",".join(parts[i:i + 16]))
return "{" + ",".join(lines) + "}"
def generate_chinese_array(table_name, entries, size, font):
lines = [f"const unsigned char Chinese_font_{size}[][{((size + 7)//8)*size}] = {{"]
for idx, ((h, l), comment) in enumerate(entries):
ch = gbk_char(h, l)
pixels, w, hgt = render_glyph(ch, size, font, square=True)
data = cn_to_bytes_msb(pixels, w, hgt)
cmt = comment or ch
lines.append(f"{fmt_bytes(data)},/*\"{cmt}\",{idx}*/")
lines.append("};")
return "\n".join(lines)
def generate_ascii_array(size, font, array_name=None):
sizex = size // 2
row_bytes = (sizex + 7) // 8
total = row_bytes * size
suffix = f"{size}{sizex}"
name = array_name or f"ascii_{suffix}"
lines = [f"const unsigned char {name}[][{total}]={{"]
for num in range(95):
ch = chr(num + 32)
pixels, w, hgt = render_glyph(ch, size, font, square=False)
data = ascii_to_bytes_lsb(pixels, w, hgt)
lines.append(f"{fmt_bytes(data)},/*\"{ch}\",{num}*/")
lines.append("};")
return name, "\n".join(lines)
def patch_header_chinese(text, tables, fonts_by_size):
# Replace each Chinese_font_XX array
for size, font in fonts_by_size.items():
table_key = {
8: "CHS8Table",
11: "CHS11Table",
12: "CHS12Table",
13: "CHS13Table",
15: "CHS15Table",
16: "CHS16Table",
28: "CHS24Table", # reuse 24 table chars for 28 if no CHS28
}.get(size)
if size == 28:
table_key = "CHS24Table"
if table_key not in tables:
print(f"Skip size {size}: no {table_key}")
continue
entries = tables[table_key]
new_array = generate_chinese_array(table_key, entries, size, font)
pat = rf"const unsigned char Chinese_font_{size}\[\]\[\d+\] = \{{.*?\}};"
if re.search(pat, text, re.S):
text = re.sub(pat, new_array, text, count=1, flags=re.S)
else:
# insert before Chinese_font for next size or at end of tables section
insert_after = f"const char {table_key}"
# add new table for CHS8/CHS15 if missing
pass
return text
def ensure_chs8_table(text, tables):
if "CHS8Table" in tables:
return text, tables
if "CHS12Table" not in tables:
return text, tables
# Insert CHS8Table copy of CHS12Table before CHS11Table
entries = tables["CHS12Table"]
block = "const char CHS8Table[][2] = {\n"
for (h, l), cmt in entries:
block += f" {{0x{h:02X},0x{l:02X}}}, // {cmt}\n"
block += "};\n\n"
text = text.replace("const char CHS11Table", block + "const char CHS11Table", 1)
tables["CHS8Table"] = entries
return text, tables
def ensure_chs15_table(text, tables):
if "CHS15Table" in tables:
return text, tables
if "CHS13Table" not in tables:
return text, tables
entries = tables["CHS13Table"]
block = "const char CHS15Table[][2] = {\n"
for (h, l), cmt in entries:
block += f" {{0x{h:02X},0x{l:02X}}}, // {cmt}\n"
block += "};\n\n"
text = text.replace("const char CHS16Table", block + "const char CHS16Table", 1)
tables["CHS15Table"] = entries
return text, tables
def main():
medium = find_ttf("MiSans-Medium")
semibold = find_ttf("MiSans-Semibold")
print("Medium:", medium)
print("Semibold:", semibold)
with open(HEADER_IN, "r", encoding="utf-8", errors="replace") as f:
text = f.read()
tables = parse_chs_tables(text)
print("Tables:", {k: len(v) for k, v in tables.items()})
text, tables = ensure_chs8_table(text, tables)
text, tables = ensure_chs15_table(text, tables)
# Regenerate all Chinese font arrays
cn_sizes = [8, 11, 12, 13, 15, 16, 24, 28]
for size in cn_sizes:
if size in (24, 28):
table_key = "CHS24Table"
font = ImageFont.truetype(semibold if size == 28 else medium, size - 2)
else:
table_key = {
8: "CHS8Table", 11: "CHS11Table", 12: "CHS12Table",
13: "CHS13Table", 15: "CHS15Table", 16: "CHS16Table",
}[size]
font = ImageFont.truetype(medium, size - 1)
if table_key not in tables:
continue
entries = tables[table_key]
new_array = generate_chinese_array(table_key, entries, size, font)
pat = rf"const unsigned char Chinese_font_{size}\[\]\[\d+\] =[\s\S]*?\}};"
if re.search(pat, text, re.S):
text = re.sub(pat, new_array, text, count=1, flags=re.S)
print(f"Updated Chinese_font_{size} ({len(entries)} glyphs)")
else:
# Insert new array before Chinese_font for next existing or after CHS tables
anchor = "const unsigned char Chinese_font_11"
if size == 8:
anchor = "const unsigned char Chinese_font_11"
elif size == 15:
anchor = "const unsigned char Chinese_font_16"
elif size == 28:
anchor = None
if anchor and anchor in text:
text = text.replace(anchor, new_array + "\n\n" + anchor, 1)
print(f"Inserted Chinese_font_{size}")
elif size == 28:
text = text.rstrip() + "\n\n" + new_array + "\n"
print(f"Appended Chinese_font_{size}")
with open(HEADER_OUT, "w", encoding="utf-8", newline="\n") as f:
f.write(text)
# English / ASCII fonts
with open(ENG_HEADER_IN, "r", encoding="utf-8", errors="replace") as f:
eng = f.read()
ascii_targets = [
(8, medium, None), (11, medium, None), (12, medium, None), (13, medium, "ascii_1306"),
(15, medium, None), (16, medium, "ascii_1608"), (24, medium, "ascii_2412"),
(28, semibold, None),
]
for size, ttf, legacy_name in ascii_targets:
font = ImageFont.truetype(ttf, size - 1)
name, arr = generate_ascii_array(size, font, legacy_name)
pat = rf"const unsigned char {name}\[\]\[\d+\][\s\S]*?\}};"
if re.search(pat, eng, re.S):
eng = re.sub(pat, arr, eng, count=1, flags=re.S)
print(f"Updated {name}")
else:
auto_name, _ = generate_ascii_array(size, font)
pat2 = rf"const unsigned char {auto_name}\[\]\[\d+\][\s\S]*?\}};"
if auto_name != name and re.search(pat2, eng, re.S):
eng = re.sub(pat2, arr, eng, count=1, flags=re.S)
print(f"Updated {auto_name} -> {name}")
else:
eng = eng.rstrip() + "\n\n" + arr + "\n"
print(f"Appended {name}")
with open(ENG_HEADER_OUT, "w", encoding="utf-8", newline="\n") as f:
f.write(eng)
print("Done.")
if __name__ == "__main__":
main()