"""Generate offline inspection sheets from the actual text-layer renderer.""" from __future__ import annotations import argparse from collections import defaultdict import hashlib import json from pathlib import Path import sys from PIL import Image, ImageDraw ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) from app.display.text_renderer import TextOptions, render_text_layer from app.fonts.store import FontCatalog from build_bus_font import gb2312_codepoints def proof(output: Path, reference: Path | None = None, height: int = 16) -> None: output.mkdir(parents=True, exist_ok=True) catalog = FontCatalog(output / "isolated-data") width = height // 2 font_id = f"builtin:walnut-bus-{width}x{height}" cps = sorted(gb2312_codepoints()) fingerprints = defaultdict(list) grey, blank, boundary = [], [], [] densities = [] for cp in cps: layer = render_text_layer(TextOptions(text=chr(cp), font=font_id, size=height), size=(width, height), font_resolver=catalog.resolve) alpha = layer.getchannel("A") values = set(alpha.tobytes()) if values - {0, 255}: grey.append(cp) if not alpha.getbbox() and cp not in (32, 0x3000): blank.append(cp) if alpha.crop((width - 1, 0, width, height)).getbbox(): boundary.append(cp) fingerprints[hashlib.sha256(alpha.tobytes()).hexdigest()].append(cp) densities.append((sum(value != 0 for value in alpha.tobytes()), f"{cp:04X}")) per_page = 256 if height == 32 else 512 scale = 2 if height == 32 else 3 cell_y = 84 if height == 32 else 64 for page, start in enumerate(range(0, len(cps), per_page)): sheet = Image.new("RGB", (16 * 48, (per_page // 16) * cell_y), "#111111") draw = ImageDraw.Draw(sheet) for index, cp in enumerate(cps[start:start + per_page]): x, y = (index % 16) * 48, (index // 16) * cell_y draw.text((x + 1, y), f"{cp:04X}", fill="#aaaaaa") layer = render_text_layer(TextOptions(text=chr(cp), font=font_id, size=height), size=(width, height), font_resolver=catalog.resolve) scaled = layer.resize((width * scale, height * scale), Image.Resampling.NEAREST) sheet.paste(scaled, (x + 8, y + 14), scaled.getchannel("A")) sheet.save(output / f"glyphs-{page + 1:02}.png") samples = ["高铁北站21路", "花鸟市场公交", "开往火车站", "鹤警藏赢鬻", "口日目田由甲", "未末土士人入", "0O1Il5S8B", "ABCxyz0123", ",。!?:;"] comparison = Image.new("RGB", (768, len(samples) * 160), "#111111") draw = ImageDraw.Draw(comparison) for row, text in enumerate(samples): for column, font in enumerate([font_id] + ([str(reference)] if reference else [])): layer = render_text_layer(TextOptions(text=text, font=font, size=height), size=(64, height), font_resolver=catalog.resolve) draw.text((column * 384 + 4, row * 160 + 2), f"BUS {width}x{height}" if column == 0 else f"REFERENCE {height}px", fill="white") scaled = layer.resize((256, height * 4), Image.Resampling.NEAREST) comparison.paste(scaled, (column * 384 + 4, row * 160 + 22), scaled.getchannel("A")) comparison.save(output / "comparison.png") result = {"glyph_count": len(cps), "grey": grey, "blank": blank, "right_edge_lit": boundary, "duplicates": [[f"{cp:04X}" for cp in group] for group in fingerprints.values() if len(group) > 1]} result["densest_32"] = [{"codepoint": cp, "lit_pixels": count, "density": count / 128} for count, cp in sorted(densities, reverse=True)[:32]] for item in result["densest_32"]: item["density"] = item["lit_pixels"] / (width * height) (output / "audit.json").write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") print(json.dumps({"grey": len(grey), "blank": len(blank), "right_edge_lit": len(boundary), "duplicate_groups": len(result["duplicates"])})) if __name__ == "__main__": parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--output", required=True, type=Path) parser.add_argument("--reference", type=Path) parser.add_argument("--height", type=int, choices=(16, 32), default=16) args = parser.parse_args() proof(args.output, args.reference, args.height)