open-design/skills/pptx-html-fidelity-audit/scripts/verify_layout.py
marco 5dd70b5016
Some checks failed
ci / Validate workspace (push) Successful in 12m32s
landing-page-ci / Validate landing page (push) Successful in 9m41s
landing-page-deploy / Deploy landing page (push) Failing after 5m23s
github-metrics / Generate repository metrics SVG (push) Failing after 2m3s
refresh-contributors-wall / Refresh contributors wall cache bust (push) Failing after 11s
Initial import: open-design source for helix-mind.ai distribution
This repository contains the open-design daemon CLI source code, built
and packaged at https://helix-mind.ai/cli/open-design/latest.tgz for use
by the HelixMind /design slash command.

Licenses: Apache-2.0 (root) + MIT (skills/*)
2026-05-06 20:50:24 +02:00

145 lines
5.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Verify a re-exported .pptx against footer-rail + canvas-bound invariants.
Usage:
python verify_layout.py <path/to/deck.pptx>
python verify_layout.py <path/to/deck.pptx> --content-max-y 6.70 --canvas-h 7.5
Exits 0 on no violations, 1 on any violation. Prints a single block of
violations sorted by slide index, one per line:
slide 5 shape 'desc-row-B-1' bottom 7.214" crosses footer rail 6.70"
slide 11 shape 'note-paragraph' bottom 7.342" exceeds canvas 7.50"
Use this as the gate for "this re-export is shippable". Don't claim the audit
is fixed without running this script — the human eye misses 12 mm overflow
at zoom-out, the script doesn't.
Footer / chrome shapes are exempt from the content rail. Two heuristics
identify them, in this order:
1. **By name** — any shape whose name contains "footer", "foot", "chrome",
"page", or "pagination" (case-insensitive). Use semantic names in your
export script if you can.
2. **By position** — any shape whose `top` is at or below the footer-zone
threshold (default `--footer-zone-top 6.80`). This catches python-pptx's
auto-generated names like "TextBox 3" when the export script didn't name
them. The threshold sits ~0.10" above FOOTER_TOP so chrome rows pinned
exactly at FOOTER_TOP are still recognized.
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
try:
from pptx import Presentation
except ImportError:
sys.stderr.write(
"python-pptx is required. Install with: pip install python-pptx\n"
)
sys.exit(2)
FOOTER_NAME_HINTS = ("footer", "foot", "chrome", "page", "pagination")
EPS_IN = 0.005 # ignore sub-pixel overflows (~0.13mm)
def is_footer_by_name(name: str) -> bool:
n = (name or "").lower()
return any(hint in n for hint in FOOTER_NAME_HINTS)
def emu_to_in(emu: int | None) -> float:
return (emu or 0) / 914400
def verify(path: Path, content_max_y: float, canvas_w: float, canvas_h: float,
footer_zone_top: float) -> list[str]:
prs = Presentation(str(path))
violations: list[str] = []
actual_w = emu_to_in(prs.slide_width)
actual_h = emu_to_in(prs.slide_height)
if abs(actual_w - canvas_w) > EPS_IN or abs(actual_h - canvas_h) > EPS_IN:
violations.append(
f"canvas mismatch: file is {actual_w:.3f}\" x {actual_h:.3f}\", "
f"expected {canvas_w}\" x {canvas_h}\""
)
for i, slide in enumerate(prs.slides, 1):
for shape in slide.shapes:
if shape.top is None or shape.height is None:
continue
top = emu_to_in(shape.top)
left = emu_to_in(shape.left)
bottom = top + emu_to_in(shape.height)
right = left + emu_to_in(shape.width)
name = shape.name or "<unnamed>"
# Off-canvas (hard fail for any shape).
if bottom > canvas_h + EPS_IN:
violations.append(
f"slide {i:<2} shape '{name}' bottom {bottom:.3f}\" "
f"exceeds canvas {canvas_h}\""
)
if right > canvas_w + EPS_IN:
violations.append(
f"slide {i:<2} shape '{name}' right {right:.3f}\" "
f"exceeds canvas width {canvas_w}\""
)
if top < -EPS_IN:
violations.append(
f"slide {i:<2} shape '{name}' top {top:.3f}\" is negative"
)
if left < -EPS_IN:
violations.append(
f"slide {i:<2} shape '{name}' left {left:.3f}\" is negative"
)
# Footer rail (only enforced on content shapes).
# Shape is exempt if (a) named like a footer, or
# (b) pinned at-or-below the footer zone threshold.
if is_footer_by_name(name) or top >= footer_zone_top - EPS_IN:
continue
if bottom > content_max_y + EPS_IN:
violations.append(
f"slide {i:<2} shape '{name}' bottom {bottom:.3f}\" "
f"crosses footer rail {content_max_y}\""
)
return violations
def main() -> int:
ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
ap.add_argument("path", type=Path, help=".pptx file to verify")
ap.add_argument("--content-max-y", type=float, default=6.70,
help="content rail in inches; nothing in content area may cross (default 6.70)")
ap.add_argument("--canvas-w", type=float, default=13.333,
help="expected canvas width in inches (default 13.333 = 16:9)")
ap.add_argument("--canvas-h", type=float, default=7.5,
help="expected canvas height in inches (default 7.5 = 16:9)")
ap.add_argument("--footer-zone-top", type=float, default=6.80,
help="any shape with top >= this is treated as footer/chrome "
"(default 6.80; sits 0.10\" above the typical FOOTER_TOP=6.85\")")
args = ap.parse_args()
if not args.path.exists():
ap.error(f"file not found: {args.path}")
violations = verify(args.path, args.content_max_y, args.canvas_w, args.canvas_h,
args.footer_zone_top)
if violations:
sys.stderr.write("\n".join(violations) + "\n")
sys.stderr.write(f"\n{len(violations)} violation(s) found in {args.path}\n")
return 1
sys.stderr.write(f"OK: 0 violations across all slides in {args.path}\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())