Redian新闻
>
服务端自定义生成PDF的几种方案

服务端自定义生成PDF的几种方案

公众号新闻

点击上方“芋道源码”,选择“设为星标

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 
来源:Java知音

这个文档太复杂了,还要导出pdf?

废话不多说直接进入正题,首先分析生成pdf场景及生成内容,考虑复用性和维护难度是我们当前开发工作的第一要务!

下面是调研的几个主要方案:

一、itext 表单填充

使用方式:

  • itext表单填充方案是以pdf作为基础模板,通过在pdf中嵌入表单元素组件的方式(需要使用pdf编辑工具),最后由程序进行数据填充并另存为pdf结果。

方案优缺点:

  • 优点:代码优雅,生成后格式变化影响极小。
  • 缺点:原始模板变化需要重新生成pdf,重新编辑表单元素;不支持列表填充数据。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

二、freemarker + doc4J 基于Word 生成 PDF

使用方式:

  • 首先将调整好格式的原始 word 导出为 XML 格式,编辑 XML 模板中需要填充元素的位置,最后由程序处理先由freemarker模板工具替换元素内容,再使用doc4J进行pdf导出。

方案优缺点:

  • 优点:通用性强,基于模板引擎功能强大。
  • 缺点:XML 格式的word真的有够复杂,想要在此模板上调整样式真的难上加难;由于系统不支持的原因需要导入中文字体库;doc4J 部分 doc 元素不支持(例如直线),导出格式差异较大。

这可能是由于doc4J迭代问题无法保证新元素的支持,导出结果比较奔放。。。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

三、freemarker + aspose-words 导出PDF

使用方式:

  • 类似于 freemarker+doc4J 方式,同样需要编辑XML,导出格式相较doc4J而言有极大提升。

方案优缺点:

  • 优点:通用性强,基于模板引擎功能强大,无需手工管理字体(macOS),代码简单,导出格式与模板基本无差异。
  • 缺点:需要编辑 XML 模板;该方案不是免费版(当然有大神)。

受限于调试前期需要的修修改改,模板能给人整吐了,所以才有了下一个方案。

四、html + freemarker + itextpdf(html2pdf)

使用方式:

  • 翻译 word 为 html 页面(当然就是手写啦,还原度很重要!),html中模板元素插入(文字填充、列表循环 freemarker 支持的全都能写),最后由程序处理先由freemarker模板工具替换元素内容,再使用html2pdf进行pdf导出。

方案优缺点:

  • 优点:可维护性相较与上面方案都有极大提升(调试可见性,动态替换生效);通用性强,基于模板引擎功能强大;导出格式可控性较强;
  • 缺点:需要中文字体库。

这个方案是综合以上多次踩坑的结果,结果是显而易见的。

浅浅来一点代码,省的大家到处找

 <dependencies>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>html2pdf</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-itext5</artifactId>
<version>9.0.3</version>
</dependency>
<dependency>
<groupId>binarta.oss</groupId>
<artifactId>groovy-template-enginex-freemarker</artifactId>
<version>0.1.3</version>
</dependency>
<dependencies>

个人以为自己的代码会自解释,就不贴太多注释了

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapUtil;
import com.google.common.collect.Lists;
import com.itextpdf.text.pdf.BaseFont;
import freemarker.cache.ByteArrayTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.SneakyThrows;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;

import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;

public class HtmlToPdfUtil {

    private static final ByteArrayTemplateLoader TEMPLATE_LOADER = new ByteArrayTemplateLoader();

    // 导入需要字体库的位置哦;simsun 为 宋体
    public static final String FRONT_PATH = "/usr/share/fonts/simsun.ttc";

    /**
     * 看明白的话只用这个方法就够
     */

    public static ByteArrayOutputStream htmlToPdf(String templateName, Supplier<byte[]> loadTemplateSupplier, Map<String, Object> modeViewMap) {

        String html = xmlFormat(templateName, loadTemplateSupplier, modeViewMap);

        return htmlToPdf(html);
    }

    @SneakyThrows
    public static ByteArrayOutputStream htmlToPdf(String htmlStr) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ITextRenderer renderer = new ITextRenderer();
        renderer.setDocumentFromString(htmlStr);
        ITextFontResolver resolver = renderer.getFontResolver();
        //添加字体,解决中文不显示的问题
        resolver.addFont(FRONT_PATH, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        renderer.layout();
        renderer.createPDF(outputStream);
        return outputStream;
    }


    public static String xmlFormat(String templateName, Supplier<byte[]> loadTemplateSupplier, Map<String, Object> modeViewMap) {
        if (Objects.isNull(TEMPLATE_LOADER.findTemplateSource(templateName))) {
            synchronized (TEMPLATE_LOADER) {
                if (Objects.isNull(TEMPLATE_LOADER.findTemplateSource(templateName))) {
                    TEMPLATE_LOADER.putTemplate(templateName, loadTemplateSupplier.get());
                }
            }
        }
        return xmlFormat(templateName, modeViewMap);
    }

    @SneakyThrows
    public static String xmlFormat(String templateName, Map<String, Object> modeViewMap) {
        Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        // 指定FreeMarker模板文件的位置
        cfg.setTemplateLoader(TEMPLATE_LOADER);
        // 设置模板的编码格式
        cfg.setEncoding(Locale.CHINA, Charset.defaultCharset().name());
        // 获取模板文件 template
        Template template = cfg.getTemplate(templateName, Charset.defaultCharset().name());
        StringWriter stringWriter = new StringWriter();

        BufferedWriter writer = new BufferedWriter(stringWriter);
        template.process(modeViewMap, writer);
        return stringWriter.toString();
    }

}

解决这个问题的核心思路方案其实一直没变,变化的只是工具,一定要思路清晰!



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。

谢谢支持哟 (*^__^*)

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
美国今年出版总收入下降,桑德斯将出版《对资本主义生气是应该的》|文化周报高血压的几种常见症状,哪种情况下必须服药治疗?松耦合式的权限控制设计,自定义权限表达式“街区改造”还是“街区毁掉”?(附PDF/CAD/SU下载)遗产(4)在中东赚大钱的老板夫人周作宇|未来关怀:时空互渗与意义生成SpringBoot+Prometheus+Grafana 实现自定义监控2022年品牌抖音电商增长新打法秘籍.pdfAI自动生成prompt媲美人类,网友:工程师刚被聘用,又要淘汰了男人越来越爱你的几种表现,女人遇到了就很幸福!分享5篇医疗数据挖掘论文(附pdf下载)干货速领!高盛2023全球展望报告原件.PDF一定提升自我的几种好习惯建筑师不得不看的建筑细部神书!(附电子版PDF下载)一个人的徒步,900公里法国之路+世界尽头:D43~途经圣地亚哥分享5篇自动驾驶分割领域论文(附pdf下载)限时领丨稀缺资源《国家地理Look》7级教材全套[PDF+MP3+视频课]持有美国B类签证入境,怎么转换身份?最佳的几种转换身份介绍!UBS前财富管理总裁官宣: 商科生暑期必读的8本书(完整版+PDF)大规模GNN如何学习?北邮最新《分布式图神经网络训练》综述,35页pdf阐述分布式GNN训练算法和系统Excel如何快速自定义填充表格?​“一晚1700,可无套” 男友曝光女学生包养价目表,私密照pdf多达92页!极简主义生活方式 :了解自己的真实欲望PD-L1/PD-1研究2022丨诺奖加持,论文和基金均火箭速度增长;成果及转化正在其时!大数据分析及19篇论文帮你理清思路龙卷风健康快递 208《扫黑行动》:扫黑故事的几种叙述可能如何生成「好」的图?面向图生成的深度生成模型系统综述|TPAMI2022终身不婚的我告别世界的几种方式抢个小屋一一厨房装修〈一〉机会欲望5 年工作经验,Docker 的几种网络模式都说不清,你敢信?图学习如何可解释?首篇《图反事实解释:定义、方法、评价》综述,46页pdf165篇文献全面概述图反事实解释进展持有美国旅游签证入境,最佳的几种转换身份介绍!炸裂!PDF转Word彻底告别收费时代,这个OCR开源项目要逆天!58岁PDF发明人离世!他给男友订了一架直升机,从此相伴13年
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。