用代码把 HTML 转成 PDF:哪些 HTML 和 CSS 会被打印出来
实测 HTML 转 PDF 能打印什么:网格、弹性盒、@page 页面尺寸、网页字体和重复表头都行,JavaScript 不会运行。附 curl 示例。
HTML 转 PDF用 WeasyPrint 打印你的 HTML 和 CSS,用的不是浏览器。测试中,网格、弹性盒、@page 的尺寸和页边距、来自公开地址的网页字体、重复的表头和页码都排出来了;JavaScript 完全没有运行。可以发一个 .html 文件,也可以发一个 ZIP,里面放 HTML 和它的样式表、图片、字体。页面在一个公开地址上时,网址转 PDF直接从地址完成同样的事。
HTML 转 PDF 到底会打印哪些 HTML 和 CSS?
调用就是一个 multipart 请求,文件作为 fileInput:
curl -X POST https://pdf123.xyz/api/v1/convert/html/pdf \
-F [email protected] \
-o page.pdf
用命令行也一样:
pdfx html-to-pdf page.html -o page.pdf
两条命令我都于 2026-10-23 在本机运行的一份服务器上执行过,测试页里写了 @page { size: A5; margin: 12mm }。curl 返回 HTTP 200,pdfx 退出码为 0,pdfinfo 报告 1 页,419.528 x 595.276 点,即 A5。
下表来自一组很小的测试页,每个都用同样的方式转换,再用 pdfinfo、pdftotext、pdffonts 和 pdfimages 检查,排版问题则看渲染出的图片。表里写的是实际转出来的结果,不是规范的承诺。
| 功能 | 我发送的内容 | 转出来的结果 |
|---|---|---|
| 用 CSS 定页面尺寸 | @page { size: A5; margin: 12mm } |
419.528 x 595.276 pt(A5) |
| 自定义页面尺寸 | @page { size: 210mm 99mm } |
595.276 x 280.63 pt |
没有 @page 规则 |
一个裸 HTML 文件 | 595.276 x 841.89 pt(A4) |
| CSS 网格 | grid-template-columns: 1fr 2fr 1fr; gap: 10mm,六个单元格 |
三列,宽度比 1:2:1,第 4 到 6 个单元格在下一行 |
| 弹性盒 | display: flex; justify-content: space-between,三个项目 |
项目从行的左边缘分布到右边缘 |
| 来自公开地址的网页字体 | @font-face 指向公共 CDN 上的 .woff 文件 |
以 @font-face 里的名字嵌入 PDF |
| 打包在 ZIP 里的字体 | @font-face 用相对 url() 指向压缩包里的文件 |
嵌入 PDF |
| 机器上安装的字体 | font-family: "Times New Roman" |
在我的测试机上嵌入了;取决于服务器有哪些字体 |
| 哪里都没有的字体 | font-family: "NoSuchFontAnywhere", monospace |
通用的 monospace 回退成机器上的某个字体 |
| 强制分页 | break-before: page 和 page-break-before: always |
3 页:第一次分页前的内容,之后每次分页一页 |
表格跨多页,表头在 <thead> 里 |
100 行的表格,A5 | 4 页,每页顶部都有表头行 |
| 表格跨多页,表头是普通行 | 同一个表格,去掉 <thead> |
4 页,只有第 1 页有表头行 |
| 页码和页眉文字 | @page { @bottom-center { content: "Page " counter(page) " of " counter(pages) } } |
Page 1 of 3、Page 2 of 3、Page 3 of 3,以及页眉文字 |
@media print |
一个 print-only 块和一个 screen-only 块 |
打印块打印了,屏幕块没有 |
| 较新的 CSS | 自定义属性、calc()、linear-gradient、border-radius、transform: rotate() |
全部画出来了 |
filter 和 position: sticky |
filter: blur(3px) 和 position: sticky |
模糊被忽略;粘性定位的块按普通块打印 |
| JavaScript | 一段设置 textContent 并调用 document.write 的脚本 |
两段文字都没有出现;静态文字出现了 |
<noscript> |
<noscript> 里的文字 |
打印了 |
| 公开地址上的图片 | <img src="https://..."> |
嵌入了 |
| 私有地址上的图片 | 地址是环回地址的 <img> |
没有加载;打印了替代文字;调用仍返回 200 |
从代码生成 HTML 时,有两行最要紧。只有表头放在 <thead> 里,每一页才会重复表头行。另外,脚本写进页面的任何内容都到不了 PDF,在浏览器里才构建内容的页面,打印出来的是它最初的空壳:单页应用发来一个空的 <div id="root">,得到的就是一份空 PDF。
怎样把 HTML 连同样式表、图片和字体一起发过去?
放进 ZIP。压缩包根目录有 index.html 就用它,否则用第一个名字以 .html 结尾的文件;压缩包里的 .htm 文件不会被选中。相对路径从 HTML 文件自己所在的文件夹算起。一个含 index.html、css/style.css、img/logo.png 和 fonts/roboto.woff 的 ZIP,这样转换:
curl -X POST https://pdf123.xyz/api/v1/convert/html/pdf \
-F [email protected] \
-o c.pdf
结果是一页,样式表生效,打包的字体以 @font-face 里的名字嵌入,300 x 120 的图片也在。第二个 ZIP 根目录没有 index.html,只有 site/page.html 和 site/logo.png,转换器用了 site/page.html,旁边的图片加载成功。完全没有 .html 文件的 ZIP 返回 HTTP 400,reason 是 unsupported_file_type:The ZIP archive must contain an .html file. Markdown 转 PDF 的做法不同,它的图片路径从压缩包根目录算起,见不装 Pandoc 和 LaTeX,把 Markdown 转成 PDF。
你自己电脑上的文件无法按路径访问。页面链接到的任何东西,要么在 ZIP 里,要么在公开地址上。公开的 http 或 https 地址上的资源,会在渲染时抓取。抓取器每次转换最多允许 500 次抓取,每个资源最大 64 MiB,每个请求 15 秒。私有或内部网络的地址会被拦截,所以上面那张环回地址上的图片打印的是替代文字。渲染默认在 180 秒后停止。
字体是不同机器之间结果会不同的地方。服务的 Docker 镜像安装了 DejaVu、Liberation、Noto 和 Noto CJK 字体包。CSS 里写的字体名,服务器上没有就会换成回退字体,所以在你机器上看着正常的 PDF,在服务器上可能用了另一种字体。用 @font-face 加载字体,无论来自 ZIP 还是公开地址,都能避开这个问题。
背后是哪个渲染器、哪个版本?
转换器是 WeasyPrint,由 Python 运行,没有无头浏览器。服务的 Docker 镜像安装 weasyprint==70.0,路径上是别的版本时构建会直接中止;我这台测试服务器产出的 PDF,Producer 字段里是 WeasyPrint 70.0(pdfinfo 会打印它)。WeasyPrint 的文档说它不基于 WebKit 或 Gecko 这样完整的渲染引擎,它的 CSS 支持页面写着弹性盒模块“works for simple use cases but is not deeply tested”,网格模块“works for simple cases, but has some limitations”。复杂的布局在依赖它之前请先测试,上面的表只覆盖了我试过的情形。
如果 HTML 只能通过一个地址拿到呢?
网址转 PDF接收 urlInput,自己抓取页面,再用同一个引擎渲染,所以同样不会运行 JavaScript。在浏览器里才构建内容的网站,打印出来没有那些内容。
curl -X POST https://pdf123.xyz/api/v1/convert/url/pdf \
-F urlInput=https://example.com \
-o output.pdf
它返回 HTTP 200 和一份 1 页 A4 的 PDF,Producer 字段是 WeasyPrint 70.0;下载的文件名固定是 output.pdf。只接受公开的 http 和 https 地址:把环回地址作为 urlInput,返回 HTTP 400,reason 为 url_not_allowed,信息是 Private, local and internal network addresses are not allowed. 页面需要登录,或者你要自己控制确切的字节,就把 HTML 存下来,改用 HTML 转 PDF。
常见问题
HTML 转 PDF 会运行 JavaScript 吗?
不会。测试里,一段设置元素文字并调用 document.write 的脚本,没有往 PDF 里添加任何内容。引擎只读 HTML 和 CSS,所以请发送脚本本来会生成的那份 HTML。
怎样设置纸张大小和页边距?
在你自己的 CSS 里写 @page 规则,例如 @page { size: A5; margin: 12mm }。这个工具没有纸张或页边距的选项,也不添加默认样式表,没有规则的页面转出来是 A4。
为什么字体和我浏览器里的不一样?
CSS 里的字体名是在服务器上查找的,不是在你的电脑上,服务器没有的名字会回退成别的字体。把字体打包进 ZIP,或用 @font-face 从公开地址加载,它就会嵌入 PDF。
能打印需要登录的页面吗?
网址转 PDF 不行,它不发送 cookie。登录状态下把 HTML 存下来,连同资源放进 ZIP,再用 HTML 转 PDF。