onepage-pdf

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

onepage-pdf

onepage-pdf

Vendored from github.com/xntj-ai/onepage-pdf by 张拼拼 · XNTJ, under the MIT License. See LICENSE.
Render HTML to one tall PDF page via headless Chrome, then crop the page to the real content height with PyMuPDF. Height is never predicted (print layout is not screen layout); it is measured after rendering, which is exact.
Requires: Python with
pymupdf
, plus a local Chrome or Edge. No API token.
源自 github.com/xntj-ai/onepage-pdf 由张拼拼 · XNTJ开发,基于MIT许可证。详见LICENSE
通过无头Chrome将HTML渲染为一个长PDF页面,然后使用PyMuPDF将页面裁剪至实际内容高度。高度不会提前预测(打印布局并非屏幕布局),而是在渲染后精确测量得出。
依赖要求: 安装有
pymupdf
的Python环境,以及本地Chrome或Edge浏览器。无需API令牌。

Workflow

工作流程

1. Inspect the source HTML first

1. 先检查源HTML

Read the HTML and check four things; they decide whether
--extra-css
is needed:
  1. Responsive breakpoints. Print media queries evaluate against the default paper width (~741px), NOT the
    @page
    size. Any
    @media (max-width: N)
    with N ≥ 741 will fire during print and collapse the desktop layout. For each such rule, write an override locking the desktop value with
    !important
    (e.g.
    .grid{grid-template-columns:repeat(3,1fr)!important}
    ).
  2. Glassmorphism.
    backdrop-filter
    blur is silently dropped in PDF output. If glass elements sit on busy backgrounds, add a print fallback:
    .glass{backdrop-filter:none!important;background:rgba(255,255,255,.88)!important}
    .
  3. Scroll-reveal animations. Common class patterns (
    fade*
    ,
    reveal*
    ,
    animate*
    ,
    aos
    ) are forced visible automatically. Anything else that starts at
    opacity:0
    needs an explicit
    opacity:1!important
    override.
  4. vh/vw sizing. Viewport units resolve against the page area in print and drift ~1%; a
    min-height:100vh
    hero becomes ~187in tall on the bedrock page. Override such rules with fixed px values.
Put all overrides in one CSS file and pass it via
--extra-css
.
读取HTML并检查四项内容,它们决定是否需要使用
--extra-css
参数:
  1. 响应式断点:打印媒体查询会针对默认纸张宽度(约741px)进行评估,而非
    @page
    尺寸。任何
    @media (max-width: N)
    且N≥741的规则会在打印时触发,导致桌面布局失效。对于此类规则,需编写覆盖样式,用
    !important
    锁定桌面端值(例如:
    .grid{grid-template-columns:repeat(3,1fr)!important}
    )。
  2. 毛玻璃效果
    backdrop-filter
    模糊效果在PDF输出中会被自动忽略。如果毛玻璃元素位于复杂背景之上,需添加打印回退样式:
    .glass{backdrop-filter:none!important;background:rgba(255,255,255,.88)!important}
  3. 滚动显示动画:常见的类模式(
    fade*
    reveal*
    animate*
    aos
    )会被自动强制显示。其他任何初始为
    opacity:0
    的元素,需要显式添加
    opacity:1!important
    覆盖样式。
  4. 视口单位尺寸:视口单位在打印时会基于页面区域解析,存在约1%的偏差;设置
    min-height:100vh
    的头部区域在基础页面上会变成约187英寸高。需用固定px值覆盖此类规则。
将所有覆盖样式放入一个CSS文件,并通过
--extra-css
参数传入。

2. Convert

2. 转换

Paths below are relative to this skill directory.
bash
python scripts/onepage_pdf.py input.html -o output.pdf --width 1280 \
    [--extra-css fixes.css] [--replace subs.json --forbid words.txt]
  • --width
    : match the design width of the page (snapped to 8px; non-8px page sizes hit MediaBox rounding bugs that spawn phantom pages).
  • --replace
    : JSON
    [["old","new"], ...]
    , applied in order — put longer / more specific strings first. Use for redaction before publishing.
  • --forbid
    : one word per line; the script aborts if any survives in the HTML or in the final PDF text layer. Always pair with
    --replace
    .
  • --crop pixel
    : switch to raster row-scanning if the vector crop misjudges (e.g. a decorative element painted taller than the real content).
The script self-handles: oversized-bedrock rendering with auto-retry on overflow, content cropping (MediaBox + CropBox rewritten identically for viewer compatibility), CJK-safe output paths, single-page assertion.
A bundled example lives in
examples/
— try it end to end:
bash
python scripts/onepage_pdf.py examples/demo.html -o /tmp/demo.pdf \
    --width 1280 --extra-css examples/demo-fixes.css
以下路径均相对于此工具目录。
bash
python scripts/onepage_pdf.py input.html -o output.pdf --width 1280 \
    [--extra-css fixes.css] [--replace subs.json --forbid words.txt]
  • --width
    :匹配页面的设计宽度(对齐到8px;非8px的页面尺寸会触发MediaBox舍入错误,产生空白页面)。
  • --replace
    :JSON格式的
    [["old","new"], ...]
    ,按顺序应用——请将更长/更具体的字符串放在前面。用于发布前的脱敏处理。
  • --forbid
    :每行一个词;如果HTML或最终PDF文本层中存在任何该列表中的词,脚本会终止运行。请始终与
    --replace
    配合使用。
  • --crop pixel
    :如果矢量裁剪判断错误(例如装饰元素的绘制高度超过实际内容),切换为光栅行扫描模式。
脚本会自动处理:超大基础页面渲染及溢出时的自动重试、内容裁剪(为兼容查看器,MediaBox和CropBox会被重写为相同值)、支持CJK的输出路径、单页验证。
examples/
目录中包含一个完整示例——可端到端尝试:
bash
python scripts/onepage_pdf.py examples/demo.html -o /tmp/demo.pdf \
    --width 1280 --extra-css examples/demo-fixes.css

3. Verify

3. 验证

The script prints
OK 1 page, WxHpt
. Then:
  1. Render a thumbnail and eyeball it (layout intact, no collapsed grids, backgrounds present, nothing cut at the bottom):
    python
    import pymupdf
    doc = pymupdf.open("output.pdf")
    doc[0].get_pixmap(dpi=40).save("check.png")
  2. If redaction was used, the forbid check already ran against the PDF text; still spot-check the rendered image for sensitive content in raster form.
  3. Heed the script warnings: heights above 14400pt break Acrobat (Chrome, Firefox and WeChat preview are fine); "content nearly fills the bedrock" means inspect the tail for truncation.
脚本会输出
OK 1 page, WxHpt
。之后:
  1. 生成缩略图并目视检查(布局完整,无网格塌陷,背景存在,底部无内容截断):
    python
    import pymupdf
    doc = pymupdf.open("output.pdf")
    doc[0].get_pixmap(dpi=40).save("check.png")
  2. 如果使用了脱敏功能,脚本已针对PDF文本层执行了禁用词检查;仍需抽查渲染图像,确认是否存在光栅形式的敏感内容。
  3. 注意脚本警告:高度超过14400pt会导致Acrobat无法正常显示(Chrome、Firefox和微信预览不受影响);“content nearly fills the bedrock”意味着需要检查末尾是否存在内容截断。

Troubleshooting and mechanics

故障排查与机制说明

Read references/mechanics.md when output looks wrong (collapsed layout, missing backgrounds, blank page, phantom second page, blurry or missing CJK glyphs) — it documents the Chrome print-rendering rules this tool is built around, plus the CDP-based alternative route.
当输出异常时(布局塌陷、背景缺失、空白页面、出现空白第二页、CJK字体模糊或缺失),请阅读references/mechanics.md——其中记录了此工具所基于的Chrome打印渲染规则,以及基于CDP的替代方案。