"""render_bg.py: 用无头 Chrome/Edge 把主题化 HTML 背景渲成高清 PNG。 混合方案专用 —— 封面/章节页:先用本脚本渲一张杂志级背景图,build_deck 里 `P.add_picture_bg(slide, png)` 整页铺,再叠原生可编辑文字。背景不可改但文字能改, 是 editable 前提下能拿到的最高观感(DrawingML 渐变做不出 mesh 渐变 + 模糊光晕)。 用法: python render_bg.py --out cover.png --kind cover --primary C00000 python render_bg.py --out sec.png --kind section --primary C00000 --accent FFC107 python render_bg.py --out x.png --html mybg.html # 渲任意 HTML 依赖:本机装了 Chrome 或 Edge(无需 pip 包)。两者都没有则报错退出。 产物默认 2560x1440(16:9 高清,2x 超采样),嵌进 13.33in 画布够清晰。 """ from __future__ import annotations import argparse import subprocess import sys import tempfile from pathlib import Path _CHROME_CANDIDATES = [ r"C:\Program Files\Google\Chrome\Application\chrome.exe", r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe", r"C:\Program Files\Microsoft\Edge\Application\msedge.exe", r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe", ] def find_browser() -> str: for c in _CHROME_CANDIDATES: if Path(c).exists(): return c # PATH 兜底 import shutil for name in ("chrome", "chrome.exe", "msedge", "msedge.exe"): p = shutil.which(name) if p: return p raise SystemExit("[fatal] 未找到 Chrome / Edge,无法渲染背景图。改用 DrawingML 渐变背景(apply_brand)。") def _hex(h: str) -> tuple[int, int, int]: h = h.lstrip("#") return int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16) def _mix(c, d, t): return tuple(round(a + (b - a) * t) for a, b in zip(c, d)) def _css(c) -> str: return f"rgb({c[0]},{c[1]},{c[2]})" def build_html(kind: str, primary: str, accent: str) -> str: p = _hex(primary) a = _hex(accent) dark = _mix(p, (0, 0, 0), 0.55) # 深端 deep = _mix(p, (0, 0, 0), 0.30) glow = _mix(p, (255, 255, 255), 0.25) # 亮红光晕 pc, dc, dpc, gc, ac = _css(p), _css(dark), _css(deep), _css(glow), _css(a) # 公共:mesh 渐变(多点径向叠加)+ 模糊光斑 + 细点纹理。文字由 build_deck 叠。 # cover:左侧加暗罩,让左置白字更稳;section:整页深,中心略亮。 overlay = ( "radial-gradient(1200px 900px at 18% 50%, rgba(0,0,0,.34), transparent 60%)," if kind == "cover" else "radial-gradient(1000px 800px at 50% 42%, rgba(255,255,255,.06), transparent 60%)," ) return f"""
""" def render(html: str, out: Path, w: int, h: int) -> None: browser = find_browser() with tempfile.TemporaryDirectory() as td: hp = Path(td) / "bg.html" hp.write_text(html, encoding="utf-8") url = hp.resolve().as_uri() # 用 1/2 窗口 + 2x 缩放 = 超采样,边缘/模糊更干净 cmd = [ browser, "--headless", "--disable-gpu", "--hide-scrollbars", "--default-background-color=00000000", f"--force-device-scale-factor=2", f"--window-size={w // 2},{h // 2}", f"--screenshot={out}", url, ] subprocess.run(cmd, check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) if not out.exists(): raise SystemExit(f"[fatal] 渲染失败,未生成 {out}(浏览器: {browser})") print(f"[ok] {out} ({out.stat().st_size // 1024} KB)") def main(): ap = argparse.ArgumentParser() ap.add_argument("--out", type=Path, required=True) ap.add_argument("--kind", choices=["cover", "section"], default="cover") ap.add_argument("--primary", default="C00000") ap.add_argument("--accent", default="FFC107") ap.add_argument("--html", type=Path, default=None, help="渲任意 HTML 文件(忽略 kind)") ap.add_argument("--w", type=int, default=2560) ap.add_argument("--h", type=int, default=1440) args = ap.parse_args() args.out.parent.mkdir(parents=True, exist_ok=True) html = (args.html.read_text(encoding="utf-8") if args.html else build_html(args.kind, args.primary, args.accent)) render(html, args.out, args.w, args.h) if __name__ == "__main__": main()