measure_template.py
Language Python4.1 KB104 lines
#!/usr/bin/env python3"""measure_template.py - Measure a vendor template's real geometry so the rebuildreproduces the SAME safe-line inset the vendor drew, template by template.This is the source of truth for dimensions. It does NOT read stated text labels("Visual Area: X") and it does NOT guess a default. It finds the dashed safe-linerectangle the vendor actually drew and reports the bleed size + visual size ininches, plus the exact flags to hand to build_template.py.Usage:python measure_template.py /path/to/vendor.pdfStrategy:- Page MediaBox (in inches) = the bleed / outer boundary.- Collect every DASHED rectangle on the page (the safe line is always dashed).When colors are recoverable, a green or red dashed rect is the safe line; theinnermost dashed rect wins when there are several (e.g. a red cut + green safe).- visual_w/visual_h = that rect's width/height in inches.- Insets are reported per side. If left!=right or top!=bottom beyond tolerance,it warns: build_template.py centers the visual area, so a non-centered safe linecan't be reproduced exactly without the per-side path.If NO dashed rect is found, it says so plainly and emits no dimensions - the agentmust then fall back (stated text, or a documented default) and TELL the user it did."""import sysimport pdfplumberTOL_IN = 0.05 # inches; inset asymmetry below this is treated as centered/roundingdef _in(pts):return pts / 72.0def measure(path):pdf = pdfplumber.open(path)page = pdf.pages[0]page_w_in, page_h_in = _in(float(page.width)), _in(float(page.height))# gather dashed rectangles (pdfplumber stores dash as ([pattern], phase) or None)dashed = []for r in page.rects:dash = r.get("dash")is_dashed = bool(dash and dash[0])if not is_dashed:continuex0, x1 = float(r["x0"]), float(r["x1"])top, bottom = float(r["top"]), float(r["bottom"])w_in = _in(abs(x1 - x0))h_in = _in(abs(bottom - top))# insets from page edge, in inches (top/bottom from pdfplumber are top-origin)ins_left = _in(x0)ins_right = page_w_in - _in(x1)ins_top = _in(top)ins_bottom = page_h_in - _in(bottom)dashed.append({"w_in": w_in, "h_in": h_in,"left": ins_left, "right": ins_right,"top": ins_top, "bottom": ins_bottom,"color": r.get("stroke_color"),"area": w_in * h_in,})print(f"FILE: {path}")print(f"BLEED (page MediaBox): {page_w_in:.2f} W x {page_h_in:.2f} H in")print(f"dashed rects found: {len(dashed)}")if not dashed:print("\nNO DASHED SAFE LINE FOUND.")print("=> Do NOT guess silently. Fall back to a stated visual size if the file")print(" gives one, else a documented default, and TELL THE USER what you used.")return# innermost dashed rect = safe line (smallest area). Color, when present, is a tiebreak.dashed.sort(key=lambda d: d["area"])safe = dashed[0]asym_x = abs(safe["left"] - safe["right"]) > TOL_INasym_y = abs(safe["top"] - safe["bottom"]) > TOL_INprint("\nSAFE LINE (innermost dashed rect):")print(f" visual size : {safe['w_in']:.2f} W x {safe['h_in']:.2f} H in")print(f" insets (in) : left {safe['left']:.2f} right {safe['right']:.2f} "f"top {safe['top']:.2f} bottom {safe['bottom']:.2f}")if safe["color"] is not None:print(f" stroke color: {safe['color']}")if asym_x or asym_y:print(" WARNING: safe line is not centered on the page "f"({'L!=R ' if asym_x else ''}{'T!=B' if asym_y else ''}). ""build_template.py centers the visual area, so this won't reproduce exactly. ""Flag it to the user.")print("\nPASS TO build_template.py:")print(f" --bleed-w {page_w_in:.2f} --bleed-h {page_h_in:.2f} "f"--visual-w {safe['w_in']:.2f} --visual-h {safe['h_in']:.2f}")if __name__ == "__main__":if len(sys.argv) != 2:print("usage: python measure_template.py /path/to/vendor.pdf")sys.exit(1)measure(sys.argv[1])