← Files Hype Design ProductionARCHIVED FILE

skills/hype-design-production/scripts/generate.py

9.88 KB · Oct 2, 2026 · 00:31 UTC

↓ Download file

#!/usr/bin/env python3
"""Deterministically render Hype Events PDFs from the bundled HTML templates."""

from __future__ import annotations

import argparse
import html
import os
from pathlib import Path
import re
import shutil
import subprocess
import sys
import tempfile


SKILL_DIR = Path(__file__).resolve().parents[1]
ASSETS_DIR = SKILL_DIR / "assets"
LOGO_FILES = ("ctfs.png", "stc.jpg", "hype.png")


def escaped(value: str) -> str:
    return html.escape(value.strip(), quote=True)


def replace_one(source: str, pattern: str, replacement: str, label: str) -> str:
    updated, count = re.subn(pattern, replacement, source, count=1, flags=re.DOTALL)
    if count != 1:
        raise RuntimeError(f"Template field not found exactly once: {label}")
    return updated


def replace_element_text(source: str, tag: str, class_value: str, value: str) -> str:
    pattern = rf'(<{tag}\s+class="{re.escape(class_value)}"[^>]*>).*?(</{tag}>)'
    return replace_one(
        source,
        pattern,
        lambda match: f"{match.group(1)}{escaped(value)}{match.group(2)}",
        class_value,
    )


def inject_css(source: str, css: str) -> str:
    marker = "</style>"
    if marker not in source:
        raise RuntimeError("Template style block is missing")
    return source.replace(marker, f"\n{css}\n{marker}", 1)


def build_record_html(args: argparse.Namespace) -> str:
    source = (ASSETS_DIR / "canvas.html").read_text(encoding="utf-8")
    old_print = ".poster { transform: scale(.75556); transform-origin: top left; }"
    new_print = ".poster { zoom: .75556; transform: none; overflow: hidden; }"
    if old_print not in source:
        raise RuntimeError("Record template print scaling marker is missing")
    source = source.replace(old_print, new_print, 1)

    brand = args.brand if args.mode == "record" else "hype"
    layout = "featured" if args.mode == "featured" else "record"
    main_pattern = r'(<main\s+class="poster")\s+data-brand="[^"]*"([^>]*>)'
    source = replace_one(
        source,
        main_pattern,
        lambda match: (
            f'{match.group(1)} data-brand="{brand}" data-layout="{layout}"{match.group(2)}'
        ),
        "poster attributes",
    )

    if args.mode == "featured":
        title = args.title
        subtitle = ""
        value = args.name
        lower_name = ""
        source = inject_css(
            source,
            """
    .poster[data-layout="featured"] .subtitle-row,
    .poster[data-layout="featured"] .name-box { display: none; }
    .poster[data-layout="featured"] .title-box { height: 185px; }
    .poster[data-layout="featured"] .value-box {
      top: 350px;
      height: 690px;
    }
    .poster[data-layout="featured"] .value {
      letter-spacing: 2px;
    }
    .poster[data-layout="featured"] .winner-row { top: 1082px; }
    .poster[data-layout="featured"] .date-row { top: 1212px; }
            """.strip(),
        )
    else:
        title = args.title
        subtitle = args.subtitle
        value = args.value
        lower_name = args.name or ""
        if not lower_name:
            source = inject_css(
                source,
                """
    .poster[data-layout="record"] .name-box { display: none; }
    .poster[data-layout="record"] .winner-row { top: 1070px; }
    .poster[data-layout="record"] .date-row { top: 1185px; }
                """.strip(),
            )

    source = replace_element_text(source, "div", "fit display title", title)
    source = replace_element_text(source, "div", "fit subtitle", subtitle)
    source = replace_element_text(source, "div", "fit display value", value)
    source = replace_element_text(source, "div", "fit display name", lower_name)
    source = replace_element_text(source, "div", "fit date", args.date)
    source = inject_css(
        source,
        """
    .logo-stc {
      background: #fff;
      filter: contrast(1.08) brightness(1.04) saturate(1.02);
    }
        """.strip(),
    )
    return source


def build_promotion_html(args: argparse.Namespace) -> str:
    source = (ASSETS_DIR / "promotion-certificate.html").read_text(encoding="utf-8")
    source = replace_element_text(source, "span", "recipient", args.name)
    source = replace_element_text(source, "p", "role", args.role)
    source = replace_element_text(source, "div", "award-date", f"Awarded {args.date}")
    return source


def clean_component(value: str) -> str:
    value = re.sub(r"[\x00-\x1f/\\:*?\"<>|]", "-", value.strip())
    value = re.sub(r"\s+", " ", value).strip(" .-")
    return value or "Untitled"


def output_filename(args: argparse.Namespace) -> str:
    if args.filename:
        stem = Path(args.filename).stem
    elif args.mode == "promotion":
        stem = "-".join(("Promotion Certificate", args.name, args.date))
    elif args.mode == "featured":
        stem = "-".join((args.title, args.name, args.date))
    else:
        subtitle = args.subtitle.strip()
        full_title = args.title
        if subtitle:
            full_title = f"{args.title} {subtitle[0].lower()}{subtitle[1:]}"
        parts = [full_title]
        if args.name:
            parts.append(args.name)
        parts.extend((args.value, args.date))
        stem = "-".join(parts)
    return clean_component(stem) + ".pdf"


def default_output_dir() -> Path:
    if SKILL_DIR.parent.name == "skills" and SKILL_DIR.parent.parent.name == ".agents":
        return SKILL_DIR.parent.parent.parent / "result"
    return Path.cwd() / "result"


def locate_chrome(explicit: str | None) -> Path:
    candidates: list[str] = []
    if explicit:
        candidates.append(explicit)
    if os.environ.get("CHROME_PATH"):
        candidates.append(os.environ["CHROME_PATH"])
    candidates.extend(
        [
            "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
            "/Applications/Chromium.app/Contents/MacOS/Chromium",
        ]
    )
    for command in ("google-chrome", "google-chrome-stable", "chromium", "chromium-browser"):
        resolved = shutil.which(command)
        if resolved:
            candidates.append(resolved)
    for candidate in candidates:
        path = Path(candidate).expanduser()
        if path.is_file() and os.access(path, os.X_OK):
            return path
    raise RuntimeError(
        "Google Chrome was not found. Install Chrome or set CHROME_PATH to its executable."
    )


def pdf_page_count(path: Path) -> int:
    data = path.read_bytes()
    if not data.startswith(b"%PDF-"):
        raise RuntimeError("Chrome output is not a valid PDF")
    return len(re.findall(rb"/Type\s*/Page\b", data))


def render(args: argparse.Namespace) -> Path:
    output_dir = Path(args.output_dir).expanduser().resolve() if args.output_dir else default_output_dir()
    output_dir.mkdir(parents=True, exist_ok=True)
    chrome = locate_chrome(args.chrome)
    work_dir = Path(tempfile.mkdtemp(prefix=".hype-temp-", dir=output_dir))

    try:
        for logo in LOGO_FILES:
            shutil.copy2(ASSETS_DIR / logo, work_dir / logo)

        generated_html = (
            build_promotion_html(args) if args.mode == "promotion" else build_record_html(args)
        )
        html_path = work_dir / "design.html"
        html_path.write_text(generated_html, encoding="utf-8")

        staged_pdf = work_dir / "output.pdf"
        command = [
            str(chrome),
            "--headless",
            "--disable-gpu",
            "--no-pdf-header-footer",
            f"--print-to-pdf={staged_pdf}",
            html_path.as_uri(),
        ]
        completed = subprocess.run(command, capture_output=True, text=True, timeout=60)
        if completed.returncode != 0 or not staged_pdf.exists() or staged_pdf.stat().st_size == 0:
            detail = (completed.stderr or completed.stdout).strip()
            raise RuntimeError(f"Chrome PDF export failed: {detail or 'no output'}")

        pages = pdf_page_count(staged_pdf)
        if pages != 1:
            raise RuntimeError(f"Expected a one-page PDF, but Chrome produced {pages} pages")

        final_pdf = output_dir / output_filename(args)
        os.replace(staged_pdf, final_pdf)
        shutil.rmtree(work_dir)
        return final_pdf
    except Exception:
        print(f"Temporary HTML retained at: {work_dir}", file=sys.stderr)
        raise


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--output-dir", help="Destination result directory")
    parser.add_argument("--chrome", help="Path to the Chrome executable")
    parser.add_argument("--filename", help="Optional output filename override")
    subparsers = parser.add_subparsers(dest="mode", required=True)

    record = subparsers.add_parser("record", help="Generate a normal record poster")
    record.add_argument("--brand", choices=("ctfs", "stc", ""), required=True)
    record.add_argument("--title", required=True)
    record.add_argument("--subtitle", required=True)
    record.add_argument("--value", required=True)
    record.add_argument("--name", default="")
    record.add_argument("--date", required=True)

    featured = subparsers.add_parser("featured", help="Generate a Hype featured award poster")
    featured.add_argument(
        "--title",
        choices=("Best Leader of the Week", "New Kids on the Block This Week"),
        required=True,
    )
    featured.add_argument("--name", required=True)
    featured.add_argument("--date", required=True)
    featured.set_defaults(brand="hype", subtitle="", value="")

    promotion = subparsers.add_parser("promotion", help="Generate a promotion certificate")
    promotion.add_argument("--name", required=True)
    promotion.add_argument("--date", required=True)
    promotion.add_argument("--role", default="Corporate Trainer")

    return parser


def main() -> int:
    parser = build_parser()
    args = parser.parse_args()
    try:
        result = render(args)
    except Exception as exc:
        print(f"ERROR: {exc}", file=sys.stderr)
        return 1
    print(result)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

SHA-256: c3aa531873d9ff51b13cbf38876123b5b9a4f7182f99291a6b916a2aaf229013