常见方案
前端实现,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">  |