feat: PDF 导出脚本支持三页合一
逐页渲染 page1/page2/page3 并用 pypdf 合并为单个 PDF,默认输出 dist/三页demo.pdf; --page 改为可多值的 --pages(保留旧名),每页各自校验四边 1em 与 Zpix 字体。 README 同步导出用法与各页说明。
This commit is contained in:
1 parent
7a39a8fe2c
commit
f1888fbaf8
4 files changed
+96
-34
No files matched your search
+59
-26
@@ -3,24 +3,30 @@
|
||||
页面为 28 × 44 格文字画,1em 即一个汉字格。导出页尺寸为 30em × 46em,
|
||||
场景四周各留 1em;底色铺满整页。
|
||||
|
||||
默认把 page1、page2、page3 依次合成一份三页 PDF。
|
||||
|
||||
用法:
|
||||
uv run python scripts/export_pdf.py
|
||||
uv run python scripts/export_pdf.py # 三页 → dist/三页demo.pdf
|
||||
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
|
||||
uv run python scripts/export_pdf.py --pages page1.html page3.html -o dist/选页.pdf
|
||||
uv run python scripts/export_pdf.py --em 16 -o dist/三页demo-16.pdf
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import pathlib
|
||||
|
||||
from playwright.sync_api import sync_playwright
|
||||
from pypdf import PdfWriter
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||||
|
||||
COLS = 28
|
||||
ROWS = 44
|
||||
MARGIN_EM = 1
|
||||
DEFAULT_PAGES = ("page1.html", "page2.html", "page3.html")
|
||||
|
||||
|
||||
def export_css(em: float) -> tuple[str, float, float]:
|
||||
@@ -71,19 +77,17 @@ def launch_browser(pw):
|
||||
)
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
def render_pdf(browser, html, css, em, page_w, page_h):
|
||||
"""渲染单页并返回 PDF 字节;导出前校验画面四边恰好留 1em。"""
|
||||
page = browser.new_page(
|
||||
viewport={"width": round(page_w), "height": round(page_h)},
|
||||
device_scale_factor=1,
|
||||
)
|
||||
try:
|
||||
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 字体未加载,无法导出。")
|
||||
raise SystemExit(f"{html.name}:Zpix 字体未加载,无法导出。")
|
||||
|
||||
page.add_style_tag(content=css)
|
||||
page.wait_for_timeout(200)
|
||||
@@ -106,32 +110,56 @@ def export(em: float, out: pathlib.Path, html: pathlib.Path) -> None:
|
||||
or abs(metrics["h"] - ROWS * em) > 0.5
|
||||
or any(v != expected_pad for v in metrics["pad"])
|
||||
):
|
||||
raise SystemExit(f"导出布局校验失败:{metrics}")
|
||||
raise SystemExit(f"{html.name}:导出布局校验失败:{metrics}")
|
||||
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
page.pdf(
|
||||
path=str(out),
|
||||
return page.pdf(
|
||||
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,
|
||||
)
|
||||
finally:
|
||||
page.close()
|
||||
|
||||
|
||||
def export(htmls, out, em):
|
||||
css, page_w, page_h = export_css(em)
|
||||
|
||||
with sync_playwright() as pw:
|
||||
browser, browser_name = launch_browser(pw)
|
||||
blobs = [render_pdf(browser, html, css, em, page_w, page_h) for html in htmls]
|
||||
browser.close()
|
||||
|
||||
writer = PdfWriter()
|
||||
for blob in blobs:
|
||||
writer.append(io.BytesIO(blob))
|
||||
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
with out.open("wb") as fh:
|
||||
writer.write(fh)
|
||||
writer.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)}")
|
||||
for index, html in enumerate(htmls, start=1):
|
||||
print(f" 第 {index} 页:{html.name}")
|
||||
print(f"已导出 {len(htmls)} 页:{out.relative_to(ROOT)}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="将文字画页面导出为 PDF(四周各留 1em)")
|
||||
parser = argparse.ArgumentParser(
|
||||
description="将文字画页面导出为 PDF(四周各留 1em,可多页合一)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pages",
|
||||
"--page",
|
||||
default="page1.html",
|
||||
help="要导出的 HTML 文件,默认 page1.html",
|
||||
dest="pages",
|
||||
nargs="+",
|
||||
default=list(DEFAULT_PAGES),
|
||||
help="要导出的 HTML,按顺序合成多页;默认 page1 page2 page3",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--em",
|
||||
@@ -143,16 +171,21 @@ def main() -> None:
|
||||
"-o",
|
||||
"--out",
|
||||
type=pathlib.Path,
|
||||
default=ROOT / "dist" / "封面demo.pdf",
|
||||
help="输出文件路径,默认 dist/封面demo.pdf",
|
||||
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)
|
||||
|
||||
htmls = []
|
||||
for name in args.pages:
|
||||
html = (ROOT / name).resolve()
|
||||
if not html.is_file():
|
||||
parser.error(f"页面不存在:{name}")
|
||||
htmls.append(html)
|
||||
|
||||
export(htmls, args.out.resolve(), args.em)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user