Spaces:
Sleeping
Sleeping
File size: 5,635 Bytes
58e6885 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | """
Scan all final-infographic SVGs from a quality-check run and look for
inconsistent shrink ratios inside the title block.
For each <g data-type="title"> we collect every <text> element. If a text
carries both font-size="<N>px" (the original attribute) and inline
style="font-size: <M>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'<g[^>]*data-type="title"[^>]*>(?P<inner>.*?)</g>',
re.DOTALL,
)
TEXT_RE = re.compile(r"<text\b[^>]*>", re.IGNORECASE)
ATTR_FS_RE = re.compile(r'font-size="(?P<v>[\d.]+)px"')
STYLE_FS_RE = re.compile(r'style="[^"]*font-size:\s*(?P<v>[\d.]+)\s*px[^"]*"')
def extract_title_texts(svg: str) -> List[Tuple[float, float]]:
"""Return [(attr_font_px, inline_font_px), ...] for every <text> 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 <g data-type='title'> 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()
|