Files
AI_Printer/tools/gen_cn16_font.py
admin 8df2095acf chore(repo): keep only current main snapshot
History squashed to one root commit to permanently drop old branch history and objects.
2026-02-28 16:41:12 +08:00

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()