131 lines
3.5 KiB
Python
131 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Generate compact Chinese 16x16 bitmap font assets for ESP32 firmware.
|
|
|
|
Output:
|
|
- main/domain/assets/fonts/cn16_index.bin (entry: uint32 codepoint + uint16 glyph_id, little-endian)
|
|
- main/domain/assets/fonts/cn16_glyphs.bin (glyph bytes, 32 bytes per glyph, row-major, MSB-left)
|
|
|
|
Glyph set:
|
|
All valid single-character GB2312 code points.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import struct
|
|
from pathlib import Path
|
|
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
|
|
def default_out_dir() -> Path:
|
|
repo_root = Path(__file__).resolve().parent.parent
|
|
return repo_root / "main" / "domain" / "assets" / "fonts"
|
|
|
|
|
|
def iter_gb2312_codepoints():
|
|
seen = set()
|
|
for hi in range(0xA1, 0xFF):
|
|
for lo in range(0xA1, 0xFF):
|
|
bs = bytes((hi, lo))
|
|
try:
|
|
ch = bs.decode("gb2312")
|
|
except UnicodeDecodeError:
|
|
continue
|
|
if len(ch) != 1:
|
|
continue
|
|
cp = ord(ch)
|
|
if cp in seen:
|
|
continue
|
|
seen.add(cp)
|
|
yield cp
|
|
|
|
|
|
def render_glyph_16(font: ImageFont.FreeTypeFont, cp: int) -> bytes:
|
|
ch = chr(cp)
|
|
img = Image.new("L", (16, 16), color=255)
|
|
draw = ImageDraw.Draw(img)
|
|
|
|
# Center glyph in 16x16 cell.
|
|
bbox = draw.textbbox((0, 0), ch, font=font)
|
|
if bbox is None:
|
|
bbox = (0, 0, 0, 0)
|
|
x0, y0, x1, y1 = bbox
|
|
w = x1 - x0
|
|
h = y1 - y0
|
|
|
|
draw_x = (16 - w) // 2 - x0
|
|
draw_y = (16 - h) // 2 - y0
|
|
draw.text((draw_x, draw_y), ch, font=font, fill=0)
|
|
|
|
# Convert to 1bpp packed rows, MSB-left.
|
|
px = img.load()
|
|
out = bytearray()
|
|
for y in range(16):
|
|
row_bits = 0
|
|
for x in range(16):
|
|
bit = 1 if px[x, y] < 128 else 0
|
|
row_bits = (row_bits << 1) | bit
|
|
out.append((row_bits >> 8) & 0xFF)
|
|
out.append(row_bits & 0xFF)
|
|
return bytes(out)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"--font",
|
|
default="app/src/main/assets/fonts/msyh.ttc",
|
|
help="Source CJK font path (TTF/TTC/OTF)",
|
|
)
|
|
parser.add_argument(
|
|
"--font-index",
|
|
type=int,
|
|
default=0,
|
|
help="TTC face index",
|
|
)
|
|
parser.add_argument(
|
|
"--out-dir",
|
|
default=str(default_out_dir()),
|
|
help="Output directory (default: <repo>/main/domain/assets/fonts)",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
font_path = Path(args.font).resolve()
|
|
if not font_path.exists():
|
|
raise FileNotFoundError(f"font not found: {font_path}")
|
|
|
|
out_dir = Path(args.out_dir).resolve()
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
index_path = out_dir / "cn16_index.bin"
|
|
glyph_path = out_dir / "cn16_glyphs.bin"
|
|
|
|
font = ImageFont.truetype(str(font_path), 16, index=args.font_index)
|
|
|
|
codepoints = sorted(iter_gb2312_codepoints())
|
|
glyphs = []
|
|
index_entries = []
|
|
|
|
for gid, cp in enumerate(codepoints):
|
|
glyph = render_glyph_16(font, cp)
|
|
glyphs.append(glyph)
|
|
index_entries.append((cp, gid))
|
|
|
|
with open(index_path, "wb") as f:
|
|
for cp, gid in index_entries:
|
|
f.write(struct.pack("<IH", cp, gid))
|
|
|
|
with open(glyph_path, "wb") as f:
|
|
for g in glyphs:
|
|
f.write(g)
|
|
|
|
print(f"font={font_path}")
|
|
print(f"glyphs={len(glyphs)}")
|
|
print(f"index={index_path} size={index_path.stat().st_size}")
|
|
print(f"glyph={glyph_path} size={glyph_path.stat().st_size}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|