一、背景
基于html生成pdf的使用场景比较多,我在的上家公司做电子合同需求的时候,是我这边完成的,当时是基于itext生成PDF的。电子合同作为一个电签中必不可少的一部分,如何高效实现html生成pdf,是我们必须要解决的问题。如果使用电子合同,此时必然需要对接相关的电子合同厂家,比如E签宝、法大大、上上签、电子牵等。当时生成的pdf没有作者想的这么周到,不过这次做需求,再次看到这个项目,还是很开心很乐意和大家分享这个项目的,也感谢rayin的作者开源了这么好的项目。
项目地址:https://gitee.com/Rayin/rayin
二、使用场景
各种电子合同、病历模板、动态模板、各种结算单、各种账单、发票等等,使用场景非常广泛。
当然本次我这边需要完成一个财务那边的结算单电子存档,此时由于涉及图片、表单、二维码、相关结算金额问题。起初我打算使用基于itext直接生成。我之前做过电子合同的需求,知道itext对于样式的兼容不是很好。因此本次,我这边使用rayin来进行pdf生成,主要开源的这个项目很好的满足了我的需求。
之所以推荐rayin来生成pdf,在于rayin的便利性。有快速上手的example,对样式进行了兼容,不会出现截断,同时快速生成的效率。
三、基于html+json数据生成效果
四、基于html+json数据+excel生成效果
从上面生成的效果可以出生成的效果,样式得到了很好的渲染,同时支持图片的插入。看起来很精美。
五、如何实现上面效果
我们看到了效果,那它是怎么实现上面的效果的呢?
1)快速开始
可以看到excel里面基于excel+json数据+html生成、基于html+json数据、基于模板+规则groovy生成,除此之外,还有性能测试。
可以看到作者很贴心的准备了很多的example,让我们可以快速开始,了解使用rayin生成不同的pdf。
根据example5来看:
public void exp05ComplexElementBindDataGenerateTest() throws Exception { //获取数据路径信息 String jsonDataFilePath = ResourceUtil.getResourceAbsolutePathByClassPath("examples/example5/data.json"); // 将json数据进行解析 JsonNode jsonDataNode = JsonSchemaValidator.getJsonNodeFromFile(jsonDataFilePath); //依据单个构建配置生成PDF JSONObject jsonData = JSONObject.parseObject(jsonDataNode.toString()); String outputFileClass = ResourceUtil.getResourceAbsolutePathByClassPath(""); // 生成pdf路径 String outputFile = new File(outputFileClass) .getParentFile().getParent() + "/tmp/" + "example05_openhtmltopdf_"+System.currentTimeMillis() + ".pdf"; StopWatch watch = StopWatch.createStarted(); // 生成PDF: html的路径、json数据、输出pdf文件的路径 pdfGenerator.generatePdfFileByHtmlAndData(ResourceUtil.getResourceAbsolutePathByClassPath("examples/example5/element1.html"),jsonData,outputFile); watch.stop(); }
2)调用生成pdf文件的核心方法
pdfGenerator.generatePdfFileByHtmlAndData(ResourceUtil.getResourceAbsolutePathByClassPath("examples/example5/element1.html"),jsonData,outputFile); 这个方法中,可以看到需要html的路径,json数据,输出文件路径。
也即基于html和jsonData,来构建pdf,从而生成PDF。从resource中,我们可以看到里面包含html和json数据,当然这里的json数据是为了测试方便,在真实的业务场景下,json数据来源于业务中。
此时我们关心的是json数据如何填充到html中,然后渲染相关样式,然后生成PDF。
3)填充html构件字符串过程
public String templateEngineProcessByString(String htmlStr, JSONObject jsonData){ // 创建上下文对象,如果json数据不为空,则设置变量 Context context = new Context(); if(jsonData != null){ context.setVariables(JSON.parseObject(jsonData.toJSONString(), Map.class)); } String r = null; try{ // 通过模板引擎处理,拿到json的字符串 r = templateEngine.process(htmlStr, context); }catch(Exception e){ r = e.getCause().toString().replace("org.attoparser.ParseException: Exception evaluating OGNL expression:","The Data paraeter error:"); } return r; }
而这个过程是通过模板引擎thyemleaf进行处理得到的。核心方法:templateEngine.process(htmlStr, context),调用模板引擎处理从而完成数据的填充工作。
4)样式渲染处理
完成数据填充之后,还需要考虑样式的问题。因此在这个方法中,进行数据样式相关信息的处理:generatePdfStreamByHtmlStr(String htmlContent, Set markKeys),下面是对相关标签的渲染处理:
可以看到最终生成PDF的渲染工作借助了openhtmltopdf这个开源项目。不得不说这个框架也是够强大的。
到这里,html完成了向PDF的过渡,最终FileUtils.writeByteArrayToFile(new File(outputFile), generatePdfStreamByHtmlStr(htmlStr).toByteArray())生成PDF。
当然这里涉及到这个字体的问题:
5) 字体的设置
public static void init() { synchronized(OpenhttptopdfRendererObjectFactory.class) { // 进行字体缓存 factory.FontCache(); //设置对象池的相关参数 GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); //最大空闲数 poolConfig.setMaxIdle(MaxIdle); //最大线程数 poolConfig.setMaxTotal(MaxTotal); //最小线程数 poolConfig.setMinIdle(MinIdle); poolConfig.setSoftMinEvictableIdleTimeMillis(SoftMinEvictableIdleTimeMillis); //新建一个对象池,传入对象工厂和配置 objectPool = new GenericObjectPool<OpenhttptopdfRenderBuilder>(factory, poolConfig); } }
可以看到对象池中存放了对象池的池配置信息和字体信息。方便后续的渲染使用,因为后续使用渲染成PDF的正是使用了这个构建器。
如果想自定义字体可以调用:
void init(String customizeFontPathDirectory);
除此之外,还可以自定义设置对象池的配置:
void init(int minIdle,int maxIdle,int maxTotal, String customizeFontPathDirectory);
6) rayin提供生成PDF的相关方式
从PdfGenerator接口,生成PDF的方式包含两种:
基于html+json数据生成PDF 基于html+json数据+excel生成PDF
还有基于groovy规则生成PDF。
当然除此之外,还有很多开源项目是基于html生成pdf的,比如经典的itext,openpdf、openhtmltopdf、jasper、x-easypdf等。