Files
jiazi-gala-poster-demo/scripts/export_pdf.py
T
rogerwang2008 319ba1890a feat: 新增 page3 演职员表并拆分多页资源
- 新增 page3.html:无「天」背景的演职员表与火山场景
- 重构为页面专属 CSS/JS 与共用的 text-scene 渲染引擎
- PDF 导出支持 --page 参数
2026-11-11 00:13:14 +08:00

160 lines
4.7 KiB
Python
Raw 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.
"""将文字画页面导出为 PDF,画面四边各留恰好 1em。
页面为 28 × 44 格文字画,1em 即一个汉字格。导出页尺寸为 30em × 46em,
场景四周各留 1em;底色铺满整页。
用法:
uv run python scripts/export_pdf.py
uv run python scripts/export_pdf.py --page page2.html -o dist/page2.pdf
uv run python scripts/export_pdf.py --em 16 -o dist/封面demo-16.pdf
"""
from __future__ import annotations
import argparse
import pathlib
from playwright.sync_api import sync_playwright
ROOT = pathlib.Path(__file__).resolve().parents[1]
COLS = 28
ROWS = 44
MARGIN_EM = 1
def export_css(em: float) -> tuple[str, float, float]:
page_w = (COLS + MARGIN_EM * 2) * em
page_h = (ROWS + MARGIN_EM * 2) * em
css = f"""
@page {{ size: {page_w:g}px {page_h:g}px; margin: 0; }}
html, body {{
width: {page_w:g}px !important;
height: {page_h:g}px !important;
min-height: 0 !important;
margin: 0 !important;
display: block !important;
overflow: hidden !important;
background: #000 !important;
}}
body {{ padding: {em:g}px !important; }}
.scene {{
font-size: {em:g}px !important;
width: {COLS}em !important;
height: {ROWS}em !important;
margin: 0 !important;
}}
* {{
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}}
"""
return css, page_w, page_h
def launch_browser(pw):
for name, kwargs in (
("Microsoft Edge", {"channel": "msedge"}),
("Google Chrome", {"channel": "chrome"}),
("Playwright Chromium", {}),
):
try:
return pw.chromium.launch(**kwargs), name
except Exception:
continue
raise SystemExit(
"未找到可用浏览器。请先运行:uv run playwright install chromium"
)
def export(em: float, out: pathlib.Path, html: pathlib.Path) -> None:
css, page_w, page_h = export_css(em)
with sync_playwright() as pw:
browser, browser_name = launch_browser(pw)
page = browser.new_page(
viewport={"width": round(page_w), "height": round(page_h)},
device_scale_factor=1,
)
page.goto(html.as_uri(), wait_until="load")
page.evaluate("() => document.fonts.ready")
if not page.evaluate("document.fonts.check('16px Zpix')"):
raise SystemExit("Zpix 字体未加载,无法导出。")
page.add_style_tag(content=css)
page.wait_for_timeout(200)
metrics = page.evaluate(
"""() => {
const scene = document.querySelector('.scene').getBoundingClientRect();
const body = getComputedStyle(document.body);
return {
x: scene.x, y: scene.y, w: scene.width, h: scene.height,
pad: [body.paddingTop, body.paddingRight, body.paddingBottom, body.paddingLeft],
};
}"""
)
expected_pad = f"{em:g}px"
if (
abs(metrics["x"] - em) > 0.5
or abs(metrics["y"] - em) > 0.5
or abs(metrics["w"] - COLS * em) > 0.5
or abs(metrics["h"] - ROWS * em) > 0.5
or any(v != expected_pad for v in metrics["pad"])
):
raise SystemExit(f"导出布局校验失败:{metrics}")
out.parent.mkdir(parents=True, exist_ok=True)
page.pdf(
path=str(out),
width=f"{page_w:g}px",
height=f"{page_h:g}px",
margin={"top": "0", "right": "0", "bottom": "0", "left": "0"},
print_background=True,
prefer_css_page_size=True,
)
browser.close()
mm_w = page_w / 96 * 25.4
mm_h = page_h / 96 * 25.4
print(f"浏览器:{browser_name}")
print(f"页面:{page_w:g} × {page_h:g}px(约 {mm_w:.0f} × {mm_h:.0f} mm),四边各 {em:g}px(1em)")
print(f"已导出:{out.relative_to(ROOT)}")
def main() -> None:
parser = argparse.ArgumentParser(description="将文字画页面导出为 PDF(四周各留 1em)")
parser.add_argument(
"--page",
default="page1.html",
help="要导出的 HTML 文件,默认 page1.html",
)
parser.add_argument(
"--em",
type=float,
default=24,
help="导出时 1em(一个汉字格)的像素大小,默认 24",
)
parser.add_argument(
"-o",
"--out",
type=pathlib.Path,
default=ROOT / "dist" / "封面demo.pdf",
help="输出文件路径,默认 dist/封面demo.pdf",
)
args = parser.parse_args()
if args.em <= 0:
parser.error("--em 必须大于 0")
html = (ROOT / args.page).resolve()
if not html.is_file():
parser.error(f"页面不存在:{args.page}")
export(args.em, args.out.resolve(), html)
if __name__ == "__main__":
main()