常见方案
前端实现,html转canvas再转pdf(缺点:pdf中的文字不能复制,而且文字失真)。
后端实现,freemarker模板引擎+itextpdf(缺点:实现起来比较复杂,很多html中的css不兼容,导致样式严重错乱,如果想要更正样式需要更改itexpdf源码)。
wkhtmptopdf(推荐)
wkhtmltopdf基于WebKit渲染引擎将HTML内容转换为HTML页面,之后再转换成PDF,所以其转换后的PDF文件的显示效果可以和HTML页面基本保持一致,是一个相当完美的解决方案,美中不足的是他需要你安装软件,并不能像前两种解决方案那样以jar包的形式嵌入到项目中。
这里推荐使用方案3。
安装wkhtmltopdf
官网:https://wkhtmltopdf.org/downloads.html
参数文档:https://wkhtmltopdf.org/usage/wkhtmltopdf.txt
Github:https://github.com/wkhtmltopdf/packaging/releases/
Windows
添加环境变量
1 | D:\Program Files\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:\html\test.html D:\html\test.pdf |
设置页眉页脚
1 | wkhtmltopdf --header-html D:\html\header.html --footer-html D:\html\footer.html D:\html\test.html D:\html\test.pdf |
注意
本地导出的时候引用的外部css和js并不会生效,要保证js和css都在html内。
导出在线网页
1 | wkhtmltopdf https://www.psvmc.cn/ D:\html\test2.pdf |
注意
- 导出在线网页的时候,外部引用的JS和CSS是生效的,但是页面不能有渐渐显示的动画,因为导出的是页面刚加载完的状态。
- 不支持CSS3(不支持Flex布局、不支持vw和vh)
- 不支持JS更改页面样式
- Echarts也要取消动画效果
animation: false,
封面
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:\html\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:\html\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:\html\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://127.0.0.1:5572/source/zjtools/z/echart/index.html D:\html\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:\html\test8.pdf
控制导出区域
1 | --print-media-type Use print media-type instead of screen 使用打印媒体类型代替屏幕 |
页面中
1 | <style media="print"> |
页面加载完成判断
由于页面加载完成需要一定时间,wkhtml提供了两种方式来等待页面加载完成:延时和页面状态判断。两者使用一种即可,都配置的话页面状态判断方式生效。
延时
通过配置项:--javascript-delay <msec>
实现,msec单位为毫秒。
页面状态
通过配置项:--window-status <windowStatus>
实现,其中 windowStatus
为html页面上的 window.status
值。
如
1 | --window-status completed |
然后在ajax完成回调时 ,
1 | window.status = "completed"; |
这样的话,就会完全支持异步调用。
注意:
封面页面和内容页面都要设置,页眉和页脚不用设置。
全部配置
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:\html\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"> |