1、背景
最近项目中需要生成日报文件,日报文件的格式为pdf
,且日报的样式相对而言比较复杂,存在多段文字,存在多个表格,且存在样式。目前想到的解决办法是 先生成html文件,让后将html文件转换成pdf文件。通过网上搜索,发现openhtmltopdf
可以实现我们的需求,此处记录一下。
2、需求
生成的pdf需要支持中文。
生成的pdf支持简单的样式。(此处可以使用css样式来解决,但不是所有的css样式都支持)
生成的pdf存在表格,每行应完整地出现在同一页,不要一半在上一页、一半在下一页。
生成的pdf可以自己指定到分页,比如某个表格的数据渲染完之后,需要单独开启一页。
生成的pdf支持密码加密。
生成的pdf可以支持纸张规格,比如是a3还是a4,并且还可设置横向还是纵向。
3、思路
1、html的生成,我们可以通过freemarker来实现。
2、html转pdf,通过openhtmltopdf来实现。
4、实现步骤
4.1 搭建一个简单的工程
首先搭建一个简单的可运行的程序,可实现freemarker渲染模板,然后生成pdf文件
引入依赖
<dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> <version>2.6.0</version> </dependency> <!-- 模板引擎,用于渲染html --> <dependency> <groupid>org.freemarker</groupid> <artifactid>freemarker</artifactid> <version>2.3.30</version> </dependency> <!-- 用于将html转换成pdf --> <dependency> <groupid>com.openhtmltopdf</groupid> <artifactid>openhtmltopdf-pdfbox</artifactid> <version>1.0.10</version> </dependency> <dependency> <groupid>org.projectlombok</groupid> <artifactid>lombok</artifactid> <version>1.18.36</version> </dependency> </dependencies>
编写freemarker工具类
加载程序中src/main/resources/templates/ftls
目录下的模板文件,然后渲染成html内容。
package com.huan.pdf.utils; import freemarker.cache.classtemplateloader; import freemarker.template.configuration; import freemarker.template.template; import freemarker.template.templateexceptionhandler; import lombok.extern.slf4j.slf4j; import java.io.stringwriter; import java.util.map; /** * freemarker 工具类 * * @author admin */ @slf4j public class freemarkerutils { /** * 模板文件夹路径 */ private static final string template_dir = "/templates/ftls"; private static final configuration configuration; static { configuration = new configuration(configuration.version_2_3_30); configuration.settemplateloader(new classtemplateloader(freemarkerutils.class, template_dir)); configuration.setdefaultencoding("utf-8"); configuration.settemplateexceptionhandler(templateexceptionhandler.rethrow_handler); configuration.setlogtemplateexceptions(false); configuration.setwrapuncheckedexceptions(true); } /** * 根据模板名称和数据模型生成字符串 * * @param templatename 模板名称 * @param datamodel 数据模型 * @return 生成的字符串 */ public static string processtemplate(string templatename, map<string, object> datamodel) { try { template template = configuration.gettemplate(templatename); stringwriter writer = new stringwriter(); template.process(datamodel, writer); return writer.tostring(); } catch (exception e) { log.error("解析模板出现问题", e); } return ""; } }
编写pdf工具类
编写pdf工具类,用于将html内容渲染成pdf文件,此处只是简单实现,后期该类还需要修改
package com.huan.pdf.utils; import com.openhtmltopdf.pdfboxout.pdfrendererbuilder; import lombok.extern.slf4j.slf4j; import javax.servlet.http.httpservletresponse; import java.io.ioexception; import java.io.outputstream; import java.util.uuid; /** * pdf工具类 * * @author admin */ @slf4j public class pdfutils { /** * 生成pdf文件 * * @param pdftemplate pdf模板 * @param response http response */ public static void generatepdf(string pdftemplate, httpservletresponse response) { // 设置响应头 string filename = uuid.randomuuid() + ".pdf"; response.setcontenttype("application/pdf"); response.setheader("content-disposition", "attachment; filename=" + filename); try (outputstream os = response.getoutputstream()) { pdfrendererbuilder builder = new pdfrendererbuilder(); builder.withhtmlcontent(pdftemplate, null); builder.tostream(os); builder.run(); } catch (ioexception e) { log.error("生成pdf文件失败", e); throw new runtimeexception("生成pdf文件失败", e); } } }
增加一个模板
<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <title>生成pdf</title> <style> .main-title { text-align: center; font-size:25px; } </style> </head> <body> <div class="main-title">${maintitle}</div> </body> </html>
该模板中存在变量maintitle
,这个变量的值通过后台来赋值
增加一个控制层
package com.huan.pdf.controller; import com.huan.pdf.utils.freemarkerutils; import com.huan.pdf.utils.pdfutils; import org.springframework.web.bind.annotation.getmapping; import org.springframework.web.bind.annotation.restcontroller; import javax.servlet.http.httpservletresponse; import java.time.localdatetime; import java.time.format.datetimeformatter; import java.util.hashmap; import java.util.map; /** * pdf控制器 * * @author admin */ @restcontroller public class pdfcontroller { @getmapping("pdf") public void pdf(httpservletresponse response) { map<string, object> params = new hashmap<>(16); params.put("maintitle", "这是一个标题 - " + localdatetime.now().format(datetimeformatter.ofpattern("yyyy-mm-dd hh:mm:ss"))); // 渲染模板 string htmlcontent = freemarkerutils.processtemplate("pdf.ftl", params); // 生成pdf pdfutils.generatepdf(htmlcontent, response); } }
注意:此处的maintitle中存在中文,生产的pdf会乱码待会儿在处理
运行
可以看到可以正常的生成pdf了,但是中文乱码了。 至此我们一个简单的程序就搭建完成了,下面让我们来完善功能。
4.2 功能完善
生成的pdf需要支持中文
默认情况下生成的pdf,中文是乱码的,若需要解决这个问题,就需要引入中文字体。此处我们使用宋体
。
1.程序中引入宋体
在程序的src/main/resources/fonts
目录下,引入宋体(simsun.ttf
)
2.pdf工具类中增加使用中文字体
builder.usefont(() -> pdfutils.class.getclassloader().getresourceasstream("fonts/simsun.ttf"), "simsun");
3.freemarker模板中使用中文字体
<style> body { font-family: "simsun"; font-size: 16px; line-height: 1.5; color: #000;} </style>
4.运行
从上图中可以看到,现在已经可以展示中文了。
生成的pdf支持简单的样式
此处实现将生成的pdf中的 这是一个标题-时间
这句话的字体修改成红色
。
1.freemarker模板中使用css样式
.main-title { text-align: center; font-size:25px; color:#ff0000; }
2.运行
通过上图可知,样式已经生效了。
表格的某一行不要出现跨页
1.freemarker模板中增加一个表格
<style> table { border-collapse: collapse; } td { border: 1px solid black; padding: 70px;} </style> <table> <tr><td>序号</td></tr> <tr><td>1</td></tr> <tr><td>2</td></tr> <tr><td>3</td></tr> <tr><td>4</td></tr> <tr><td>5</td></tr> </table>
2.查看效果
从上图可以看到,生成的pdf,内容跨了2页,那么如何解决这个问题呢?通过css样式解决
3.css解决
table { border-collapse: collapse; page-break-inside: auto;} tr { page-break-inside: avoid;}
4.查看效果
单独开启一页pdf
1.freemarker模板修改
通过css
样式page-break-before:always
开启新的一页pdf。
2.查看效果
指定pdf页面的规格
默认情况是a4 纵向
,现在我想修改成a3 横向
。这个指定对所有的页面都生效,不可只对某一个页面生效,若想对某一个页面生效,可以生成多个pdf文件,然后进行pdf文件的合并操作。
css样式指定页面规则
@page{ size:a3 landscape; }
查看效果
从上图中可知 正好是a3横向
pdf 加密
实现思路:通过pdfbox生成加密的密码
,此处给默认密码a0nin13s
1.修改pdf生成的工具类
/** * 生成带密码的 pdf 文件(用户密码 a0min13s) * * @param pdftemplate html 模板字符串 * @param response http 响应 */ public static void generatepdf(string pdftemplate, httpservletresponse response) { string filename = uuid.randomuuid() + ".pdf"; response.setcontenttype("application/pdf"); response.setheader("content-disposition", "attachment; filename=" + filename); // 1. 先用 openhtmltopdf 生成未加密 pdf(内存) bytearrayoutputstream temp = new bytearrayoutputstream(); try { pdfrendererbuilder builder = new pdfrendererbuilder(); builder.usefont(() -> pdfutils.class.getclassloader().getresourceasstream("fonts/simsun.ttf"), "simsun"); builder.withhtmlcontent(pdftemplate, null); builder.tostream(temp); // 完成渲染 builder.run(); } catch (ioexception e) { log.error("生成pdf失败", e); throw new runtimeexception("生成pdf失败"); } // 用 pdfbox 加载并加密 try (pddocument doc = pddocument.load(temp.tobytearray()); outputstream os = response.getoutputstream()) { accesspermission ap = new accesspermission(); // 可选:禁止打印、复制等 ap.setcanprint(false); ap.setcanextractcontent(false); // 用户密码,所有者密码一样即可(也可设不同) standardprotectionpolicy policy = // ownerpwd userpwd new standardprotectionpolicy("a0min13s", "a0min13s", ap); // 128 位 aes policy.setencryptionkeylength(128); policy.setpermissions(ap); // 执行加密 doc.protect(policy); // 写给浏览器 doc.save(os); // 确保全部送出 os.flush(); } catch (ioexception e) { log.error("pdf加密输出失败", e); throw new runtimeexception("pdf加密输出失败"); } }
2.查看效果
完整代码:https://gitee.com/huan1993/spring-cloud-parent/tree/master/pdf/openhtmltopdf
以上就是基于java编写一个html转pdf的工具类的详细内容,更多关于java html转pdf的资料请关注代码网其它相关文章!
发表评论