常见方案
前端实现,html转canvas再转pdf(缺点:pdf中的文字不能复制,而且文字失真)。
后端实现,freemarker模板引擎+itextpdf(缺点:实现起来比较复杂,很多html中的css不兼容,导致样式严重错乱,如果想要更正样式需要更改itexpdf源码)。
wkhtmptopdf(推荐)
wkhtmltopdf基于WebKit渲染引擎将HTML内容转换为HTML页面,之后再转换成PDF,所以其转换后的PDF文件的显示效果可以和HTML页面基本保持一致,是一个相当完美的解决方案,美中不足的是他需要你安装软件,并不能像前两种解决方案那样以jar包的形式嵌入到项目中。
这里推荐使用方案3。
后记
wkhtmltopdf
使用的是旧版 WebKit 内核,可能不支持某些现代 JavaScript 或 CSS3 特性。不支持css3,JS只支持到ES5,不支持ES6及以上的语法,想要更好的效果可以使用node环境的
puppeteer
。
安装wkhtmltopdf
官网:https://wkhtmltopdf.org/downloads.html
参数文档:https://wkhtmltopdf.org/usage/wkhtmltopdf.txt
Github:https://github.com/wkhtmltopdf/packaging/releases/
Windows
wkhtmltox-0.12.6-1.msvc2015-win64.exe
链接: https://pan.baidu.com/s/1rnhfnkV_bzq84tUG2hvvLw?pwd=9pej 提取码: 9pej
添加环境变量
1 | D:\Tools\wkhtmltopdf\bin |
Linux
Linux安装比较麻烦,以我们的服务器Centos7为例具体如下:
https://wkhtmltopdf.org/downloads.html
下载wkhtmltopdf文件wkhtmltox-0.12.6-1.centos7.x86_64.rpm
。
将文件导入到需要安装的服务器。
安装依赖
1 | yum install -y libXrender libjpeg xorg-x11-fonts-75dpi xorg-x11-fonts-Type1 |
使用命令安装:
1 | yum localinstall -y wkhtmltox-0.12.6-1.centos7.x86_64.rpm |
此时测试发现还存在很多空白展示不正确,需要从Win上复制字体到Linux。
需要将windows中的msyh.ttc、msyhbd.ttc、msyhl.ttc复制到linux服务器/usr/share/fonts/msyh
,如果没有则mkdir创建目录
1 | cd C:\Windows\Fonts |
创建目录
1 | mkdir /usr/share/fonts/msyh |
上传到Linux中
或者服务器间复制
1 | scp -r /usr/share/fonts/msyh/* root@182.92.208.106:/usr/share/fonts/msyh/ |
设置
1 | yum install -y fontconfig mkfontscale |
执行:
1 | fc-list :lang=zh |
可以看到已经安装的中文字体。
程序的路径为
1 | /usr/local/bin/wkhtmltopdf |
使用
导出
本地HTML导出
1 | wkhtmltopdf D:\html2pdf\test.html D:\html2pdf\test.pdf |
1 | wkhtmltopdf --window-status completed --debug-javascript http://localhost:8080/#/ D:\html2pdf\test.pdf |
设置页眉页脚
1 | wkhtmltopdf --header-html D:\html2pdf\header.html --footer-html D:\html2pdf\footer.html D:\html2pdf\test.html D:\html2pdf\test.pdf |
注意
本地导出的时候引用的外部css和js并不会生效,要保证js和css都在html内。
导出在线网页
1 | wkhtmltopdf https://www.psvmc.cn/ D:\html2pdf\test2.pdf |
注意
- 导出在线网页的时候,外部引用的JS和CSS是生效的,但是页面不能有渐渐显示的动画,因为导出的是页面刚加载完的状态。
- 不支持CSS3(不支持Flex布局、不支持vw和vh)
- 不支持JS更改页面样式
- Echarts也要取消动画效果
animation: false,
添加延迟
1 | wkhtmltopdf --javascript-delay 3000 https://www.psvmc.cn/article/2025-05-15-git-cooperation.html D:\html2pdf\test10.pdf |
等待 3 秒(3000 毫秒)后生成 PDF。
页面加载完成判断
由于页面加载完成需要一定时间,wkhtml提供了两种方式来等待页面加载完成:延时和页面状态判断。两者使用一种即可,都配置的话页面状态判断方式生效。
延时
通过配置项:--javascript-delay <msec>
实现,msec单位为毫秒。
页面状态
通过配置项:--window-status <windowStatus>
实现,其中 windowStatus
为html页面上的 window.status
值。
如
1 | --window-status completed |
然后在ajax完成回调时 ,
1 | window.status = "completed"; |
这样的话,就会完全支持异步调用。
1 | wkhtmltopdf --window-status completed http://localhost:8080/#/ D:\html2pdf\test.pdf |
注意:
封面页面和内容页面都要设置,页眉和页脚不用设置。
测试
1 | wkhtmltopdf --dpi 110 --disable-smart-shrinking --page-width 210 --page-height 297 --window-status completed http://127.0.0.1:5500/index.html#/report_school D:\html2pdf\test10.pdf |
1 | wkhtmltopdf --dpi 110 --disable-smart-shrinking --page-width 210 --page-height 297 https://www.psvmc.cn/article/2025-05-15-git-cooperation.html D:\html2pdf\test10.pdf |
1 | wkhtmltopdf --dpi 110 --disable-smart-shrinking --page-width 210 --page-height 297 --window-status completed --header-left "码客说有限公司" --header-right "[date] [time] 机密文件" --header-line --header-spacing 3 --footer-spacing 3 --footer-center "- 第 [page] 页-" toc --toc-header-text "目录" http://localhost:5173/report_school D:\html2pdf\test10.pdf |
调试
如果一直不转换可以添加调试参数--debug-javascript
1 | wkhtmltopdf --debug-javascript --window-status completed http://127.0.0.1:5500/index.html#/ D:\html2pdf\test3.pdf |
我这里报错
SyntaxError: Parse error
原因
ES6 语法兼容性问题:
wkhtmltopdf
基于旧版 PhantomJS(仅支持 ES5),若页面使用箭头函数、let/const
等 ES6+ 语法会直接报错。
封面
1 | cover <url> //使用HTML文件作为封面。 |
测试
1 | wkhtmltopdf --outline --outline-depth 2 --header-html http://127.0.0.1:5572/source/zjtools/z/echart/header.html --footer-html http://127.0.0.1:5572/source/zjtools/z/echart/footer.html cover http://127.0.0.1:5572/source/zjtools/z/echart/cover.html http://127.0.0.1:5572/source/zjtools/z/echart/index.html D:\html2pdf\test6.pdf |
目录
1 | toc --toc-header-text "目录" |
测试
1 | wkhtmltopdf --header-html http://127.0.0.1:5572/source/zjtools/z/echart/header.html --footer-html http://127.0.0.1:5572/source/zjtools/z/echart/footer.html toc --toc-header-text "目录" http://127.0.0.1:5572/source/zjtools/z/echart/index.html D:\html2pdf\test6.pdf |
书签(侧边栏)
1 | --outline 显示目录(文章中h1,h2来定) |
测试
1 | wkhtmltopdf --outline --outline-depth 2 --header-html http://127.0.0.1:5572/source/zjtools/z/echart/header.html --footer-html http://127.0.0.1:5572/source/zjtools/z/echart/footer.html http://127.0.0.1:5572/source/zjtools/z/echart/index.html D:\html2pdf\test6.pdf |
自定义页面边距
-B, --margin-bottom <unitreal>
下边距,单位毫米,默认10毫米
-L, --margin-left <unitreal>
左边距,单位毫米,默认10毫米
-R, --margin-right <unitreal>
右边距,单位毫米,默认10毫米
-T, --margin-top <unitreal>
上边距,单位毫米,默认10毫米
页面分页
添加如下样式的元素 后面的元素会换到下一页
1 | .page { |
页眉页脚
纯文本
1 | wkhtmltopdf --header-left "码客说有限公司" --header-right "[date] [time] 机密文件" --header-line --header-spacing 3 --footer-spacing 3 --footer-center "- 第 [page] 页-" http://localhost:5173/report_school D:\html2pdf\test7.pdf |
HTML
--header-html <url>
页眉内容,通过html自定义
--footer-html <url>
页脚内容,通过html自定义
wkhtmltopdf 将自动带上一些参数供页面使用:
1 | 'page', 'frompage', 'topage', 'webpage', 'section', 'subsection', 'date', 'isodate', 'time', 'title', 'doctitle', 'sitepage', 'sitepages' |
header.html
1 |
|
footer.html
1 |
|
页面大小
页面高度要排除页眉页脚
1 | .page { |
单位毫米
1 | --disable-smart-shrinking 页面不缩放 页面就不缩小了 很重要 |
测试1
wkhtmltopdf --dpi 110 --disable-smart-shrinking --page-width 210 --page-height 297 --header-left "码客说有限公司" --header-right "[date] [time] 机密文件" --header-line --header-spacing 3 --footer-spacing 3 --footer-center "- 第 [page] 页-" http://127.0.0.1:5572/source/zjtools/z/echart/index.html D:\html2pdf\test8.pdf
控制导出区域
1 | --print-media-type Use print media-type instead of screen 使用打印媒体类型代替屏幕 |
页面中
1 | <style media="print"> |
全部配置
1 | wkhtmltopdf --dpi 110 --disable-smart-shrinking --page-width 210 --page-height 297 --window-status completed --header-left "码客说有限公司" --header-right "[date] [time] 机密文件" --header-line --header-spacing 3 --footer-spacing 3 --footer-center "- 第 [page] 页-" cover http://127.0.0.1:5572/source/zjtools/z/echart/cover.html toc --toc-header-text "目录" http://127.0.0.1:5572/source/zjtools/z/echart/index.html D:\html2pdf\test10.pdf |
注意
cover 要放在 toc 的前面,这两个的顺序决定了生成的顺序。
Java代码
HtmlToPdfInterceptor
1 | package wkhtmltopdf; |
HtmlToPdf
1 | package wkhtmltopdf; |
其他环境
Java:https://github.com/jhonnymertz/java-wkhtmltopdf-wrapper
NodeJS:https://github.com/devongovett/node-wkhtmltopdf
C#:https://github.com/codaxy/wkhtmltopdf
GO:https://github.com/SebastiaanKlippert/go-wkhtmltopdf
Python:https://github.com/JazzCore/python-pdfkit
小知识
判断是否支持Flex
JS中判断
JS里也提供了Window.CSS.supports()
方法,用来检查浏览器对css3属性是否支持:
使用两个参数:一个是属性名,另一个是属性值 。
1 | var supportsFlex = CSS.supports("display", "flex"); |
REM
1 | //判断是否支持rem单位 |
CSS中判断
1 | @supports ( display: flex ) { |
其它语法
1 | /* 支持Flex布局 */ |
支持Flex
但是wkhtmltopdf不支持这种方式。
https://github.com/skin2skin/flex-native/blob/master/README-zh_CN.md
在普通的HTML中使用
1 | <script src="https://unpkg.com/flex-native@1.2.0/dist/flex.native.min.js"></script> |
在模块化中使用
1 | import('flex-native'); |
使用时请在CSS中的任何display: flex
声明之前添加一个 -js-display: flex
声明, 或在构建过程中使用PostCSS Flexibility自动添加-js前缀。
CSS中
1 | .wrapper{ |
元素上
1 | <div style='display:flex;align-items:center' /> |
如下的方式是行不通的
本来想不支持Flex,只要使用JS来兼容Flex,但是实际测试是行不通的。
JS中判断引用JS
1 | <script type="text/javascript"> |