#!/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: /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("