""" Scan all final-infographic SVGs from a quality-check run and look for inconsistent shrink ratios inside the title block. For each we collect every element. If a text carries both font-size="px" (the original attribute) and inline style="font-size: px;" (rewritten by shrink_overlapping_text() in screenshot_utils.py), we treat M/N as that text's shrink ratio. A title block is "inconsistently shrunk" when at least two of its texts were touched by the shrinker AND their ratios differ. That's exactly the case the collaborator described: the JS picks per-text targets, so two title segments end up scaled by different factors. """ import argparse import csv import os import re from pathlib import Path from typing import List, Tuple TITLE_BLOCK_RE = re.compile( r']*data-type="title"[^>]*>(?P.*?)', re.DOTALL, ) TEXT_RE = re.compile(r"]*>", re.IGNORECASE) ATTR_FS_RE = re.compile(r'font-size="(?P[\d.]+)px"') STYLE_FS_RE = re.compile(r'style="[^"]*font-size:\s*(?P[\d.]+)\s*px[^"]*"') def extract_title_texts(svg: str) -> List[Tuple[float, float]]: """Return [(attr_font_px, inline_font_px), ...] for every in the (first) title block, omitting texts that lack either field.""" m = TITLE_BLOCK_RE.search(svg) if not m: return [] inner = m.group("inner") out = [] for tm in TEXT_RE.finditer(inner): tag = tm.group(0) a = ATTR_FS_RE.search(tag) s = STYLE_FS_RE.search(tag) if not a: continue attr = float(a.group("v")) inline = float(s.group("v")) if s else attr # not shrunk -> ratio 1.0 out.append((attr, inline)) return out def parse_args(): p = argparse.ArgumentParser() p.add_argument("--root", default="output/quality_check", help="Directory containing per-template subdirs") p.add_argument("--out", default="output/quality_check/_title_shrink_audit.csv") p.add_argument("--ratio-tol", type=float, default=0.01, help="Treat shrink ratios within this absolute tolerance as 'same'") return p.parse_args() def main(): args = parse_args() root = Path(args.root) rows = [] inconsistent = [] n_title_blocks = 0 n_with_shrink = 0 n_inconsistent = 0 n_files = 0 n_no_title = 0 for tpl_dir in sorted(p for p in root.iterdir() if p.is_dir()): chart_name = tpl_dir.name for svg_path in sorted(tpl_dir.glob("*.svg")): n_files += 1 try: svg = svg_path.read_text(encoding="utf-8", errors="ignore") except OSError: continue texts = extract_title_texts(svg) if not texts: n_no_title += 1 continue n_title_blocks += 1 ratios = [round(inl / attr, 4) for attr, inl in texts if attr > 0] # only count texts whose attr differs from inline (i.e. actually # touched by shrinker) shrunk_ratios = [r for r in ratios if abs(r - 1.0) > args.ratio_tol] if not shrunk_ratios: continue n_with_shrink += 1 # distinct shrink ratios distinct = sorted({round(r, 3) for r in ratios}) is_inconsistent = ( len([r for r in distinct if abs(r - 1.0) > args.ratio_tol]) >= 1 and len(distinct) >= 2 ) if is_inconsistent: n_inconsistent += 1 row = { "chart_name": chart_name, "svg": str(svg_path.relative_to(root.parent)), "n_texts": len(texts), "attrs": ";".join(f"{a:.1f}" for a, _ in texts), "inlines": ";".join(f"{i:.2f}" for _, i in texts), "ratios": ";".join(f"{r:.3f}" for r in ratios), "min_ratio": min(ratios) if ratios else 1.0, "max_ratio": max(ratios) if ratios else 1.0, "ratio_spread": (max(ratios) - min(ratios)) if ratios else 0.0, "inconsistent": is_inconsistent, } rows.append(row) if is_inconsistent: inconsistent.append(row) os.makedirs(os.path.dirname(args.out) or ".", exist_ok=True) rows.sort(key=lambda r: (-r["ratio_spread"], r["chart_name"])) with open(args.out, "w", newline="", encoding="utf-8") as fh: w = csv.DictWriter(fh, fieldnames=list(rows[0].keys()) if rows else [ "chart_name", "svg", "n_texts", "attrs", "inlines", "ratios", "min_ratio", "max_ratio", "ratio_spread", "inconsistent", ]) w.writeheader() for r in rows: w.writerow(r) print(f"Wrote {args.out}") print() print(f"Total final-svg files scanned: {n_files}") print(f" with a block: {n_title_blocks} (no title: {n_no_title})") print(f" in which >=1 text was shrunk: {n_with_shrink}") print(f" with INCONSISTENT shrink ratios: {n_inconsistent}" f" ({n_inconsistent/max(n_with_shrink,1)*100:.1f}% of shrunk)") print() print("=== top 15 worst (largest ratio spread inside the title block) ===") print(f"{'spread':>7s} {'min':>5s} {'max':>5s} {'n':>2s} chart_name / svg") for r in rows[:15]: print( f" {r['ratio_spread']:.3f} {r['min_ratio']:.3f} {r['max_ratio']:.3f} " f"{r['n_texts']:>2d} {r['chart_name']} {os.path.basename(r['svg'])}" ) if __name__ == "__main__": main()