SuperAPI AI Directory

html-to-pdf

Frontend & websites

View repository

Updated 10-15 20:29

About this skill

HTML to PDF — Screen Fidelity/Vector Text Dual Routes

When converting HTML to PDF, first determine what the user really wants:

  • Highest visual fidelity: The PDF should look as close as possible to the rendered browser screen.
  • Selectable text: Text in the PDF can be copied, searched, and embedded as a text layer.
  • Formal print document: Accepts print CSS semantics, pursuing stable pagination/long pages and smaller file size.

Must first give the user selection advice

By default, do not directly execute the conversion. First read the HTML, determine the page type, then give the user brief selection advice and wait for the user to choose. Only when the user has clearly specified a strategy in the same sentence and asked to "execute directly/without asking" may you skip the selection confirmation.

Suggested wording:

我看这个 HTML 更像【翻页演示/连续长页】。如果目标是尽量还原浏览器里的原始画面,我建议选 1。
  1. Screenshot-level fidelity (recommended): Most like the browser screen, preserves canvas/WebGL/backdrop-filter/complex CSS; text is not selectable, file is larger.
  2. Screen vector attempt: Text is selectable, uses screen media to print PDF; visuals are usually close to the browser, but complex filters/pagination may still differ.
  3. Print vector long pages: Text is selectable, smaller size, suitable for reports/articles; enters the print pipeline, not guaranteed to fully match the browser screen.

Please reply 1/2/3 or the strategy name, and I will execute.


如果用户的目标已经明确,也要先把它转化为推荐项给用户确认:

- “尽量还原浏览器渲染后的样子 / 原样 / 截图级 / 视觉保真” -> 推荐 **策略 1**。
- “文字可复制 / 可检索 / 文本层 / 不要截图” -> 推荐 **策略 2**,并说明失败或差异明显时可退到 **策略 3**。
- “长图 PDF / 不分页 / 正式报告 PDF / 体积小” -> 推荐 **策略 3**;若同时强调视觉原样,推荐 **策略 1B**。

## Step 1: 读取 HTML,判断页面类型

读取 HTML 文件并检查:

**翻页演示信号**(命中任一即可认为是 slides):
- `scroll-snap-type` 存在。
- ≥2 个 `.slide` / `[class*="slide"]` / `[data-slide]` / `<section>` / `.page`。
- 大量元素尺寸接近 `100vw` x `100vh`。
- `<title>` / `<meta>` 含 slide、PPT、演示、presentation 等关键词。
- Reveal.js、Impress.js 等演示框架标记。

**连续长页信号**:
- 没有明确 slide 容器。
- `<article>` / `<main>` / `.content` / 文档流内容连续向下。
- 页面高度显著大于视口高度。

识别后先给用户推荐策略,不要擅自执行。

## 策略 1: CDP 屏幕截图级保真

这是“尽量还原浏览器里看到的原始样子”的默认推荐路线。它不走 print media,而是在 Chrome 屏幕模式下等字体/图片加载完成后截图,再拼成 PDF。翻页演示会临时隔离当前 slide 到视口左上角,避免横向 deck、scroll-snap 或 transform 状态导致重复截第一页。

优点:
- 最接近浏览器渲染结果。
- 保留 canvas、WebGL、backdrop-filter、CSS filter、复杂背景、真实屏幕布局。
- 不需要为 print media 重写大量 CSS。

缺点:
- 文字不可选中。
- 文件通常更大。
- 超长页面可能超过旧 Acrobat 的 14400pt 兼容限制。

### 1A. 翻页演示 -> 多页截图 PDF

```bash
python3 ~/.claude/skills/html-to-pdf/scripts/render_slides_cdp.py "{input.html}" \
  -o "{output.pdf}" \
  --strategy slides-raster \
  --width 1280 --height 720 \
  --dpr 2 \
  --format png

Optional parameters:

Parameter Default Description
--width 1280 Browser viewport width px
--height 720 Browser viewport height px
--dpr 2 Screenshot device pixel ratio; increase to 3 when text details are not sharp enough
--format png Screenshot format; use png for lots of text/lines, jpeg for lots of photos when file size must be controlled
--quality 92 JPEG quality, ignored for PNG
--selector Auto Specify slide selector, for example .slide / [data-slide]
--ready-expr — Custom JS ready expression, for example window.__PDF_READY === true

Notes:

  • slides-raster will temporarily set the current slide to active/fixed for each page and hide the other slides.
  • This isolation only occurs in a temporary Chrome session and will not modify the source HTML file.
  • If the page relies on a global background canvas, it will usually still be preserved; if the background exists only within other slides, you need to put the background into each slide or switch to the page's own export state.

1B. Continuous long page -> single-page screenshot PDF

python3 ~/.claude/skills/html-to-pdf/scripts/render_slides_cdp.py "{input.html}" \
  -o "{output.pdf}" \
  --strategy long-raster \
  --width 1280 --height 900 \
  --dpr 2 \
  --format png \
  --tile-height 4096

Notes:

  • The script measures according to the browser screen layout scrollWidth/scrollHeight。
  • Very long pages are captured in chunks, then inserted into the same PDF long page, avoiding failure from a single oversized PNG.
  • If the file is too large, you can switch to --format jpeg --quality 92。

Strategy 2: CDP screen-media vector PDF

Used for scenarios where "text must be selectable, but you also want to stay as close as possible to screen rendering." The script will set Emulation.setEmulatedMedia(media="screen"), measure the page size according to the screen layout, then call Page.printToPDF。

python3 ~/.claude/skills/html-to-pdf/scripts/render_slides_cdp.py "{input.html}" \
  -o "{output.pdf}" \
  --strategy screen-vector \
  --width 1280 --height 900

Note:

  • This is a "vector attempt", not screenshot-level fidelity.
  • Text is usually selectable.
  • It still goes through the Chrome PDF output pipeline; complex filters, 3D transforms, and some canvas/WebGL may differ from the screen.
  • Not as stable as slides-raster for horizontal transform-based slide decks.

Strategy 3: print vector continuous long-page PDF

Suitable for reports, articles, formal documents, and outputs that need a text layer, smaller file size, or stable long pages. This route uses onepage-pdf's "oversized page rendering -> measured cropping" method and enters print media; it is not guaranteed to match the browser screen exactly.

3.1 Check for common print differences in the HTML

Read the HTML and check:

  1. Responsive breakpoints: whether there is @media (max-width: N) and N >= 741. Print MQ triggers at about 741px, which may break the desktop layout.
  2. Frosted glass: whether there is backdrop-filter. It may be silently lost in Chrome PDF output and needs to be downgraded to an opaque background.
  3. vh/vw: whether there is 100vh / 100vw. During printing, vh is parsed as an oversized page, which may blow up the height.
  4. Scroll entrance animations:.fade* / .reveal* / .animate* / AOS etc. may remain in an invisible state.

3.2 Generate fix CSS

Write the discovered fix rules to a temporary file, for example /tmp/onepage-fixes.css. See references/css-fixes.md。

3.3 Run the conversion

python3 ~/.claude/skills/html-to-pdf/scripts/onepage_pdf.py "{input.html}" \
  -o "{output.pdf}" \
  --width 1280 \
  --extra-css /tmp/onepage-fixes.css

Common parameters:

Parameter Default Description
--width 1280 Page design width in px, automatically aligned to 8px
--bedrock 18000 Initial oversized page height; automatically doubles on overflow
--padding 32 Bottom whitespace of content in px
--crop vector Content detection: vector or pixel
--virtual-time 10000 Virtual time budget in ms for JS-driven pages
--replace — Text replacement JSON [["旧","新"],...]
--forbid — Leak detection word list; abort if still present in the PDF

Verification requirements

After the conversion completes, you must verify; do not just look at whether the command succeeded.

import pymupdf
doc = pymupdf.open("{output.pdf}")
print(f"页数: {doc.page_count}")
for i in range(min(3, doc.page_count)):
    r = doc[i].rect
    print(f"第{i+1}页: {r.width:.0f}x{r.height:.0f}pt")
    doc[i].get_pixmap(dpi=72).save(f"/tmp/pdf-check-{i+1}.png")
doc.close()

Key checks:

  • Whether the page count matches expectations.
  • Whether the first screen/first 3 pages match the browser screenshot.
  • No bottom truncation, blank pages, text misalignment, or missing images.
  • Under the screenshot strategy, unselectable text is the expected result.
  • Under the vector strategy, if visual differences are obvious, ask the user whether to switch to screenshot-level fidelity.

Troubleshooting quick reference

Symptom Common strategy Cause Solution
Visual inconsistency under the requirement that the browser be reproduced as-is screen-vector / print-vector The PDF pipeline is not equal to screen rendering Switch to slides-raster or long-raster
Text edges look soft raster DPR too low or JPEG compression --dpr 2/3 --format png
File too large raster PNG + high DPR Switch photo-heavy pages to --format jpeg --quality 92
Not all slides recognized slides-raster Automatic selector misjudgment Add --selector ".slide" or [data-slide]
Async charts/fonts missing raster/vector Screenshot taken too early Add --ready-expr or increase --delay
Horizontal deck captures the wrong page slides-raster The selector mistakenly selects the container, or slides are not independent pages Add an explicit --selector; if necessary, make each page use an independent .slide
Layout collapse/grid becomes a single column print-vector Print MQ triggers at about 741px extra CSS locks desktop layout
Frosted glass lost print-vector / screen-vector Chrome PDF output limitation Screenshot strategy; or print fallback background
Bottom truncated onepage bedrock insufficient or cropping misjudged Increase --bedrock, or --crop pixel

For more mechanism explanations, see references/mechanics.md。

File structure

~/.claude/skills/html-to-pdf/
├── SKILL.md
├── README.md
├── LICENSE
├── scripts/
│   ├── onepage_pdf.py          # print 向量长页
│   └── render_slides_cdp.py    # CDP 屏幕截图/屏幕向量
└── references/
    ├── css-fixes.md
    ├── mechanics.md
    └── examples.md