【首先强烈推荐大家去看itext官方文档,里边有很多问题的回答 https://kb.itextpdf.com/itext/installation-guidelines】
一、前提
1,做一个能动态改变样式的pdf,并且将文本内容填充进去,那么使用PdfRender就做不到了,e签宝的模板接口也做不到动态改变字体的颜色等。百度查到可以使用itext的html2pdf,可是却没想到在使用过程中有那么多坑,而且很多教程都不贴html,所说html规范严格也没说到底咋严格,最终还是跟源码也解决问题。
2,最终需要的pdf样式如下:
一些信息我马赛克了,可以看到标题是优设标题黑,正文是微软雅黑,部分字体要根据所填内容不同变换颜色
二、代码
代码在这里:https://www.cnblogs.com/hsql/p/17980241
用到的字体放在百度网盘了:
链接:https://pan.baidu.com/s/1O7sqPnWOHTd1ftruB-pYnQ
提取码:azxq
此文档参考的官方文档为:itext官网文档 官方文档2
三、一些注意事项
1,加载html模板的方式
① 加载本地模板(不是放在程序resources下,而是服务器的一个位置)
② 加载url模板
2,html中的css格式不生效
①首先html的格式一定要标准,标签结尾一定要有,比如<div>开头,必须有</div>
②检查引入的itext是否是最新版本,我一开始引入的是5.0.2,结果css的渐变背景(linear-gradient)标签就没有生效,查看maven仓库发现最新的到8.0.2了,html2pdf升到5.0.2,freeMaker到2.3.31
③建议使用Visual Studio Code格式化一下html,方便检查格式
3,html填充内容的velocity语法
如果用过idea的easyCode插件那一定对velocity不陌生,关于velocity的语法可以参考以下文档:Java中Velocity ,freeMaker使用的稍微有点区别:模板语言参考 - FreeMarker 中文官方参考手册 (foofun.cn)
① 一般填充使用 ${}的方式,如
② if else
一般在easyCode模板中是这样使用的
但在这里的html中,应该这样使用
例如:
或者
其余用法请参考上面文档
4,字体不生效 font-family的问题
首先不用系统自带的字体,会浪费资源
也不使用网上下载的乱七八糟的漂亮字体(有些会乱码),所以这里我将使用标准字体,系统字体都设置为false(注意,即使三个都是false,但是在pdf搜索不到可用字体时,也会使用内置的14种字体比如Times)
官网对使用字体的解释:第 6 章:在 pdfHTML 中使用字体
有两种方式加载字体, 第一种方式,在html中指定@font-face 从网络下载字体,第二种方式,在后台指定字体,两种方式都需要在html样式中指定“font-family”(注:font-family后一定要加单引号);
① html中指定font-face,如下图
此时后台就可以不加字体或加一个默认的字体。(注意,此时两条线的两端连接处设置的名称一定要一致,下面会说匹配字体的事儿,这里设置 @font-face中的font-family的名字等于给此字体设置了一个别名alias)
(我在测试时发现使用这个html生成的pdf有些文字字体不对,没有使用指定的,我一直以为是font-family没有生效,跟源码才发现是这个woff不支持某些字符,所以一定要先验证这字体好不好用啊,真坑,下面有验证方法。)
② 后台指定字体
由于使用前端下载字体的方式特别慢,所以我选择在后台指定,html中去掉@font-face, 只写font-family与后台加载的字体对应,比如
那么html中的font-family和后台加载的字体是如何对应的呢? 由于之前生成的字体都是混乱的,网上也找不到答案,贴点儿边的就说ttf的名字要是英文的,最后无可奈何跟了一下源码,才发现在layout包的字体选择器fontSelector中,是这样判断的
所以与ttf是否是英文也没关系,html中的font-family要与后台加载ttf的 familyNameLowerCase 相同,html中也不用写@font-face了,那么如何知道ttf的familyNameLowerCase呢?如下:
或者这样创建字体(设置一个别名,要与html中的font-family对应):
匹配familyNameLowerCase的过程其实是将所有的字体排序的过程,将匹配到的放在第一位,接下来还会继续循环排好序的字体集合逐字校验是否支持,如下:
所以这里要保证,加载的字体一定要支持设置了font-family标签的文字
如何验证是否支持某字呢?使用fontTools如下:
校验字体是否支持某个字
可以看到截图中的woff文件,支持“姓”,却不支持“长”。
总结:如果html中没有指定font-family,那么最终会使用后台addFont的第一个字体;如果指定了font-family,但是名称与后台字体的familyNameLowerCase或alias不同,那么最终会使用后台addFont的第一个字体(或者使用系统自带的默认字体或乱码);
但是要注意,后台加载的ttf越多,生成pdf的时间越慢,所以最好不要加载系统字体,即设置:
FontProvider fontProvider = new DefaultFontProvider(false, false, false)
由于fontSelector会将font-family跟每一个ttf作对比,所以如果想更快的生成pdf,应调整ttf的加载顺序,将pdf中使用次数较多的字体放在第一个,这样能确保第一次循环就匹配到;
出现字体混乱,第一时间验证字体文件是否支持。
5,生成的pdf出现偏移、缺失、分页
一开始设置的pdf纸张大小是A4,与html中定义的大小不同,那就会出现一部分被截断,或分页的情况(我的需求要不分页),所以要根据html的大小进行调试,直到合适
6,生成的pdf文件太大
有可能是嵌入字体的问题,由于我这里使用的是自定义字体(中文),并且pdf内部会嵌入字体否则在其他电脑可能看不到,对于这个问题官方解释是这样的:为什么即使我指定不嵌入,iText 也会嵌入字体?
由于中文字符一般使用Identity-H的编码,所以这时如果设置为 FORCE_NOT_EMBEDDED 会报错:Cannot create Type0 font with true type font program without embedding it.
不设置 EmbeddingStrategy 的话,默认是 PREFER_EMBEDDED (趋向于嵌入),所以我这里设置为 PREFER_NOT_EMBEDDED
生成的pdf大小会小很多,我猜测(趋向于不嵌入)仅仅嵌入了使用字体的子字符集(即pdf中的文字),搜索了官网回答,对于嵌入字体的解释,官方回答如下: 如何仅部分嵌入字体?
查看文档属性也确实如此
也就是说,使用了Identity-H,就永远不会嵌入完整字体,但是我测试的使用PREFER_NOT_EMBEDDED和PREFER_EMBEDDED生成的文件大小相同,但是PREFER_EMBEDDED生成的时间却慢很多,这里可以判定使用PREFER_NOT_EMBEDDED最佳。
然后我又在网上找了另一种微软雅黑的字体,测试发现用这个字体生成的pdf大小比原来的小一倍,当使用思源宋体的时候pdf更小,所以在不嵌入整个字体的情况下,与引用的三方字体也有关系。
另外:在css中设置加粗、倾斜是不生效的,如果想使用加粗字体,应该使用单独的加粗ttf文件。