pdf-to-html
Converts a PDF into one self-contained, readable HTML file that preserves images, tables, charts and reading order — optionally translating it into another language while keeping every figure. Uses structured extraction (PyMuPDF), font-size-driven layout, compressed base64-inlined images (a single portable file), and mandatory headless-Chrome visual verification. Use whenever someone wants to READ a PDF as a web page or clean document, turn a PDF into HTML, or translate a PDF into another language while keeping its images/tables/charts intact — e.g. "PDF 转 HTML", "把这个 PDF 转成中文网页版", "make this report readable", "translate this PDF but don't lose the charts", "I just want to read this PDF on my phone". Distinct from doc-to-markdown (plain Markdown text) and pdf-creator (Markdown→PDF) — this one produces a styled, image-faithful HTML reading experience.
1How It Works & Mechanism
The pipeline is extract → look → (translate) → build → verify, where intermediate 'inspecting' and final 'verifying' are defined as the true foundation of fidelity—since a PDF is a layout, not a plain text stream.
The pipeline is **extract → look → (translate) → build → verify**. The middle "look" and final "verify" steps are where faithfulness actually comes from: a PDF is a layout, not just a text stream, so you read the rendered pages before building and the rendered HTML before delivering.
extract_pdf.py produces reviewable intermediate artifacts: structure.json (per-page text blocks with bbox + max font size, image blocks with decorative flags) + images/ + rendered PNG of each page, preserving reading order via PyMuPDF's get_text('dict') block order.
The point of a separate extraction step is a *verifiable intermediate output*: structure.json is the plan. Inspect it (and the rendered page PNGs) before building, instead of going PDF -> HTML in one opaque jump.
build_html.py is a data-driven general-purpose builder: it infers heading levels from font size distribution (most frequent size = body, larger sizes sequentially elevated to h3/h2/h1), strips decorative images, and compresses content images into base64-inlined assets—resulting in a single file openable with a double click.
`build_html.py` is **data-driven**: it infers heading levels from font size (most common size = body; larger steps up to h3/h2/h1), drops decorative images, and inlines content images as compressed base64 → one portable file. It is not hand-tuned to any document.
The translation branch bypasses 'full-text machine translation'; instead, the agent follows references/translation_workflow.md using a Dynamic Workflow to translate pages in parallel, handle figures/captions separately, and unify terminology, producing two overlay files (units.json/caps.json) for build to consume.
It produces two overlay files (`units.json`, `caps.json`) that step 4 consumes. **Do not** hand-translate inline for anything longer than a page — the workflow keeps terminology consistent and is far faster.
Mandatory visual verification: verify_render.py takes screenshots using headless Chrome and slices them at ~2600px intervals (first probing real content height at 1x, then choosing the maximum device-scale-factor to avoid the 16384px screenshot limit); the agent must inspect each seg-*.png via Read.
```bash uv run --with Pillow --with numpy python scripts/verify_render.py output.html ``` Then **Read every `seg-*.png`** and check: fonts render (no tofu boxes), no clipped tables/figures, headings/lists look right, all expected images present.
Ironclad fidelity rules are documented in references/failure_cases.md: never guess translations for personal or proper names, copy every number and proper noun verbatim (failure_cases #6), and read before translating.
The one that bites hardest: **never give a real person an inferred translated name, and copy every number/proper-noun verbatim** (failure_cases #6). Read that file before any translation run; skim it before any run.
2Core Capabilities
3External Dependencies
| Kind | Dependency / Endpoint |
|---|---|
| package | PyMuPDF (fitz)(extract_pdf.py,uv run --with pymupdf) |
| cli | Google Chrome / Chromium(headless 视觉验证) |
| package | Pillow / numpy(build 与 verify,uv --with) |
4Recommended Governance & Evidence
- scripts/: Execute local scripts or commands: python extract_pdf.py / build_html.py / verify_render.py (local extraction/building)
- 外部端点: Exfiltrate local content or conversation to third-party cloud storage
- 本地工作目录: File I/O and artifact output: input-build/ (structure.json, images/, pages/ per-page PNGs)
5Second-Pass Verification
- [ok] extract→look→build→verify 管线 — SKILL 三步命令与三个脚本一一对应;structure.json 字段(bbox/字号/decorative)在 extract_pdf.py 实现。
- [ok] 字号推断标题层级 — build_html.py 从 font size 推断 h3/h2/h1 的逻辑在 docstring 与代码中均有('larger sizes step up to h3/h2/h1')。
- [ok] base64 内联单文件 — build_html.py data_uri():压缩→JPEG/PNG→base64;'single portable .html you can double-click' 相符。
- [ok] decorative 判定启发式 — extract_pdf.py:DECORATIVE_MAX_BYTES=3000 + bbox 重复 > 半数页面(max(2, npages//2))。
- [ok] 强制视觉验证 headless Chrome 分片 — verify_render.py:1x 探高 + device-scale-factor 选择 + SEGMENT_PHYSICAL 分片;SKILL 要求 Read every seg-*.png。
- [ok] 翻译 overlay 机制 — translation_workflow.md 存在且描述 units.json/caps.json 产出;build_html.py 接受 --translation/--captions 参数。
- [ok] 无网络外发/转义防注入 — 三脚本无 requests/urllib/http;build_html.py md() 对段落文本 html.escape;--no-proxy-server 防代理泄漏。
- [ok] 元数据 — GitHub API:MIT / 1385 stars / pushed 2026-09-09T12:33:29Z;本地 HEAD==pin d5c4678。
6Verdict & Scenarios
a8f3ffec2f8cdcaa…