Add Chinese text rendering support

Extend the bitmap font pipeline with the Chinese Joker glyph subset and encode supported UTF-8 text for Tonc TTE. Move the affine map clear of the expanded font, add a compile-time VRAM overlap guard, and cover every custom glyph with host tests.
This commit is contained in:
2026-07-19 11:35:14 +08:00
parent 7a5a4a1276
commit c7ef3c0047
13 changed files with 583 additions and 15 deletions
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env python3
"""Append the Chinese Joker-description glyph subset to the project's 8x8 ASCII font."""
from argparse import ArgumentParser
from pathlib import Path
import re
from PIL import Image, ImageDraw, ImageFont
ASCII_FONT_HEIGHT = 48
CELL_SIZE = 8
GLYPHS_PER_ROW = 16
def read_codepoints(table_path: Path) -> list[int]:
table = table_path.read_text(encoding="utf-8")
return [int(value, 16) for value in re.findall(r"CHINESE_GLYPH\(0x([0-9A-Fa-f]+)\)", table)]
def main() -> None:
parser = ArgumentParser()
parser.add_argument("--font", required=True, help="Fusion Pixel Font 8px zh_hans TTF")
parser.add_argument("--table", default="include/def_chinese_glyph_table.h")
parser.add_argument("--image", default="font/gbalatro_sys8.png")
args = parser.parse_args()
image_path = Path(args.image)
base = Image.open(image_path).convert("RGB").crop((0, 0, 128, ASCII_FONT_HEIGHT))
codepoints = read_codepoints(Path(args.table))
extra_rows = (len(codepoints) + GLYPHS_PER_ROW - 1) // GLYPHS_PER_ROW
output = Image.new("RGB", (128, ASCII_FONT_HEIGHT + extra_rows * CELL_SIZE), "white")
output.paste(base, (0, 0))
draw = ImageDraw.Draw(output)
font = ImageFont.truetype(args.font, CELL_SIZE)
for index, codepoint in enumerate(codepoints):
x = (index % GLYPHS_PER_ROW) * CELL_SIZE
y = ASCII_FONT_HEIGHT + (index // GLYPHS_PER_ROW) * CELL_SIZE
draw.text((x, y - 1), chr(codepoint), font=font, fill="black")
output.save(image_path, optimize=True)
print(f"Appended {len(codepoints)} Chinese glyphs to {image_path}")
if __name__ == "__main__":
main()
+10 -5
View File
@@ -5,7 +5,6 @@ import argparse
CHAR_WIDTH = 8
CHAR_HEIGHT = 8
NUM_VERT_CHARS = 6
NUM_HORZ_CHARS = 16
WORD_SIZE = 32
@@ -48,8 +47,14 @@ img = img.convert("P")
img = img.convert('1')
img = ImageOps.invert(img)
if img.width != NUM_HORZ_CHARS * CHAR_WIDTH or img.height % CHAR_HEIGHT != 0:
raise ValueError("font image must be 128px wide and a multiple of 8px high")
num_vert_chars = img.height // CHAR_HEIGHT
num_chars = num_vert_chars * NUM_HORZ_CHARS
glyphs = []
for char_y in range(NUM_VERT_CHARS):
for char_y in range(num_vert_chars):
for char_x in range(NUM_HORZ_CHARS):
# Get the 8x8 crop area at our current character in the image
pix_x = char_x * CHAR_WIDTH
@@ -86,7 +91,7 @@ with open(out_path, "w") as out:
.global gbalatro_sys8Font
gbalatro_sys8Font:
.word gbalatro_sys8Glyphs, 0, 0
.hword 32, 96
.hword 32, %d
.byte 8, 8
.byte 8, 8
.hword 8
@@ -94,9 +99,9 @@ gbalatro_sys8Font:
.section .rodata
.align 2
.global gbalatro_sys8Glyphs @ 768 bytes (192 unsigned ints)
.global gbalatro_sys8Glyphs
gbalatro_sys8Glyphs:
""")
""" % num_chars)
for i, glyph in enumerate(glyphs):
if first: