chore(repo): keep only current main snapshot
History squashed to one root commit to permanently drop old branch history and objects.
This commit is contained in:
130
tools/gen_cn16_font.py
Normal file
130
tools/gen_cn16_font.py
Normal file
@@ -0,0 +1,130 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user