"""Generate golden data for the Rust lamp-label crate. Writes into ``crates/lamp-label/tests/golden/``: * ``qr_matrices.txt``: for each ID, the QR code Python ``qrcode`` 8.2 builds for ``render_label`` (ERROR_CORRECT_M, fit=True, automatic mask): a header line `` version= mask= size=`` followed by ``n`` rows of ``0``/``1`` (1 = dark), quiet zone not included. * ``label_.png``: the complete 202x202 label from Python ``render_label``. * ``text_info.txt``: per ID the font, chosen size, PIL text bbox and the draw origin used by ``render_label``. Run with: C:/dev/HPCS/.venv/Scripts/python.exe -W ignore crates/lamp-label/tools/golden.py """ import argparse import importlib.util import os import re from pathlib import Path import qrcode from PIL import Image, ImageDraw, ImageFont OUT = Path(__file__).resolve().parent.parent / "tests" / "golden" DOTS = (202, 202) TEXT_HEIGHT = 52 QR_IDS = [f"L{i:04d}" for i in range(1, 21)] + [ "L0999", "L1000", "L12345", "L123456", # mode / version coverage "12345678", # numeric mode "LAMP-7 X", # alphanumeric with space "lamp_7", # upper-cased, '_' -> byte mode "https://example.com/lamp/L0006?x=1", # alnum chunk + byte chunk "L" + "0123456789" * 13, # byte 'L' + numeric chunk, version 4 "L" + "ABCDEFGHIJ" * 20, # alphanumeric, version 8 (version info) ] TEXT_IDS = ["L0001", "L0006", "L0123", "L12345", "L9", "L123456", "L0123456789012", "LAVA-7", "WWWWWWWWWW", "lamp_7"] # ---- verbatim copy of label_printer.py (HPCS) ------------------------------- def _font(size): for name in ("arialbd.ttf", "arial.ttf", "DejaVuSans-Bold.ttf"): try: return ImageFont.truetype(name, size) except OSError: continue return ImageFont.load_default(size) def render_label(lamp_id: str) -> Image.Image: image = Image.new("1", DOTS, 1) qr = qrcode.QRCode( error_correction=qrcode.constants.ERROR_CORRECT_M, box_size=1, border=2, ) qr.add_data(lamp_id.upper()) qr.make(fit=True) modules = len(qr.get_matrix()) qr_size = DOTS[1] - TEXT_HEIGHT qr.box_size = max(1, qr_size // modules) qr_img = qr.make_image().get_image().convert("1") image.paste(qr_img, ((DOTS[0] - qr_img.width) // 2, (qr_size - qr_img.height) // 2)) draw = ImageDraw.Draw(image) size = 44 while size > 10: font = _font(size) box = draw.textbbox((0, 0), lamp_id, font=font) if box[2] - box[0] <= DOTS[0] - 8: break size -= 2 draw.text( ((DOTS[0] - (box[2] - box[0])) // 2 - box[0], DOTS[1] - TEXT_HEIGHT + (TEXT_HEIGHT - (box[3] - box[1])) // 2 - box[1]), lamp_id, font=font, fill=0, ) return image # ----------------------------------------------------------------------------- def text_layout(lamp_id): """Same loop as render_label, returning what it decided.""" draw = ImageDraw.Draw(Image.new("1", DOTS, 1)) size = 44 while size > 10: font = _font(size) box = draw.textbbox((0, 0), lamp_id, font=font) if box[2] - box[0] <= DOTS[0] - 8: break size -= 2 origin = ( (DOTS[0] - (box[2] - box[0])) // 2 - box[0], DOTS[1] - TEXT_HEIGHT + (TEXT_HEIGHT - (box[3] - box[1])) // 2 - box[1], ) return font, size, box, origin def file_safe(lamp_id): return re.sub(r"[^A-Za-z0-9_-]", "_", lamp_id)[:40] def main(): ap = argparse.ArgumentParser() ap.add_argument("--reference", default=os.environ.get("LABEL_PRINTER_PY")) args = ap.parse_args() reference = None if args.reference and Path(args.reference).exists(): spec = importlib.util.spec_from_file_location("label_printer_ref", args.reference) reference = importlib.util.module_from_spec(spec) spec.loader.exec_module(reference) OUT.mkdir(parents=True, exist_ok=True) for f in OUT.iterdir(): if f.suffix in (".png", ".txt"): f.unlink() lines = [] for lamp_id in QR_IDS: qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_M, box_size=1, border=2) qr.add_data(lamp_id.upper()) qr.make(fit=True) mask = qr.best_mask_pattern() # deterministic; same choice make() used qr.make(fit=True) n = qr.modules_count lines.append(f"{lamp_id} version={qr.version} mask={mask} size={n}") for row in qr.modules: lines.append("".join("1" if m else "0" for m in row)) (OUT / "qr_matrices.txt").write_bytes(("\n".join(lines) + "\n").encode("utf-8")) names = [] for lamp_id in dict.fromkeys(QR_IDS + TEXT_IDS): im = render_label(lamp_id) if reference is not None: assert reference.render_label(lamp_id).tobytes() == im.tobytes(), lamp_id name = f"label_{file_safe(lamp_id)}.png" im.save(OUT / name) names.append(f"{lamp_id}\t{name}") (OUT / "labels.txt").write_bytes(("\n".join(names) + "\n").encode("utf-8")) info = [] for lamp_id in TEXT_IDS: font, size, box, origin = text_layout(lamp_id) path = Path(getattr(font, "path", "builtin")).name info.append( f"{lamp_id}\tfont={path}\tsize={size}\tbbox={box[0]},{box[1]},{box[2]},{box[3]}\torigin={origin[0]},{origin[1]}" ) (OUT / "text_info.txt").write_bytes(("\n".join(info) + "\n").encode("utf-8")) print(f"{len(QR_IDS)} QR matrices, {len(names)} labels, {len(info)} text layouts -> {OUT}") print("reference render_label checked" if reference else "reference render_label NOT checked") for line in info: print(line) if __name__ == "__main__": main()