0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

jf_ro2CN3Fa 来源:Java知音 作者:武哥 2022-10-31 11:08 次阅读

废话不多说直接进入正题,首先分析生成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 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

三、freemarker + aspose-words 导出PDF

使用方式:

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

方案优缺点:

优点:通用性强,基于模板引擎功能强大,无需手工管理字体(macOS),代码简单,导出格式与模板基本无差异。

缺点:需要编辑 XML 模板;该方案不是免费版(当然有大神)。

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

四、html + freemarker + itextpdf(html2pdf)

使用方式:

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

方案优缺点:

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

缺点:需要中文字体库。

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

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



com.itextpdf
itextpdf
5.5.13


com.itextpdf
html2pdf
3.0.3


org.xhtmlrenderer
flying-saucer-pdf-itext5
9.0.3


binarta.oss
groovy-template-enginex-freemarker
0.1.3


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

importcn.hutool.core.io.FileUtil;
importcn.hutool.core.map.MapUtil;
importcom.google.common.collect.Lists;
importcom.itextpdf.text.pdf.BaseFont;
importfreemarker.cache.ByteArrayTemplateLoader;
importfreemarker.template.Configuration;
importfreemarker.template.Template;
importlombok.SneakyThrows;
importorg.xhtmlrenderer.pdf.ITextFontResolver;
importorg.xhtmlrenderer.pdf.ITextRenderer;

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

publicclassHtmlToPdfUtil{

privatestaticfinalByteArrayTemplateLoaderTEMPLATE_LOADER=newByteArrayTemplateLoader();

//导入需要字体库的位置哦;simsun 为宋体
publicstaticfinalStringFRONT_PATH="/usr/share/fonts/simsun.ttc";

/**
*看明白的话只用这个方法就够
*/
publicstaticByteArrayOutputStreamhtmlToPdf(StringtemplateName,SupplierloadTemplateSupplier,MapmodeViewMap){

Stringhtml=xmlFormat(templateName,loadTemplateSupplier,modeViewMap);

returnhtmlToPdf(html);
}

@SneakyThrows
publicstaticByteArrayOutputStreamhtmlToPdf(StringhtmlStr){
ByteArrayOutputStreamoutputStream=newByteArrayOutputStream();
ITextRendererrenderer=newITextRenderer();
renderer.setDocumentFromString(htmlStr);
ITextFontResolverresolver=renderer.getFontResolver();
//添加字体,解决中文不显示的问题
resolver.addFont(FRONT_PATH,BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED);
renderer.layout();
renderer.createPDF(outputStream);
returnoutputStream;
}


publicstaticStringxmlFormat(StringtemplateName,SupplierloadTemplateSupplier,MapmodeViewMap){
if(Objects.isNull(TEMPLATE_LOADER.findTemplateSource(templateName))){
synchronized(TEMPLATE_LOADER){
if(Objects.isNull(TEMPLATE_LOADER.findTemplateSource(templateName))){
TEMPLATE_LOADER.putTemplate(templateName,loadTemplateSupplier.get());
}
}
}
returnxmlFormat(templateName,modeViewMap);
}

@SneakyThrows
publicstaticStringxmlFormat(StringtemplateName,MapmodeViewMap){
Configurationcfg=newConfiguration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
//指定FreeMarker模板文件的位置
cfg.setTemplateLoader(TEMPLATE_LOADER);
//设置模板的编码格式
cfg.setEncoding(Locale.CHINA,Charset.defaultCharset().name());
//获取模板文件template
Templatetemplate=cfg.getTemplate(templateName,Charset.defaultCharset().name());
StringWriterstringWriter=newStringWriter();

BufferedWriterwriter=newBufferedWriter(stringWriter);
template.process(modeViewMap,writer);
returnstringWriter.toString();
}

}

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





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • XML
    XML
    +关注

    关注

    0

    文章

    186

    浏览量

    32993
  • HTML
    +关注

    关注

    0

    文章

    276

    浏览量

    32659

原文标题:服务端自定义生成PDF的几种方案

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    创建自定义的基于闪存的引导加载程序(BSL)

    电子发烧友网站提供《创建自定义的基于闪存的引导加载程序(BSL).pdf》资料免费下载
    发表于 09-19 10:50 0次下载
    创建<b class='flag-5'>自定义</b>的基于闪存的引导加载程序(BSL)

    NVIDIA AI Foundry 为全球企业打造自定义 Llama 3.1 生成式 AI 模型

    Foundry 提供从数据策管、合成数据生成、微调、检索、防护到评估的全方位生成式 AI 模型服务,以便部署自定义 Llama 3.1 NVIDIA NIM 微
    发表于 07-24 09:39 623次阅读
    NVIDIA AI Foundry 为全球企业打造<b class='flag-5'>自定义</b> Llama 3.1 <b class='flag-5'>生成</b>式 AI 模型

    如何使用云服务器刷写自定义固件?

    我们正在尝试在没有以前刷新固件的情况下刷新我们的自定义固件。所以里面有原装AT固件。 当模块连接到 WiFi 和互联网时,在 AT CIUPDATE 之后一切正常。但它正在下载/更新您的原始
    发表于 07-15 08:23

    服务端测试包括什么类型

    服务端测试是确保软件系统在服务器端正常运行和满足性能要求的重要环节。本文将详细介绍服务端测试的类型、方法和最佳实践。 1. 服务端测试的定义
    的头像 发表于 05-30 16:03 406次阅读

    服务端测试是web测试吗为什么

    服务端测试和Web测试是两个不同的概念,但它们在软件开发和测试过程中是相互关联的。本文将详细解释这两个概念以及它们之间的关系。 服务端测试 服务端测试主要关注服务器端的软件组件,这些组
    的头像 发表于 05-30 15:30 430次阅读

    服务端测试和客户测试区别在哪

    服务端测试和客户测试是软件开发过程中的两个重要环节,它们分别针对服务器端和客户的软件进行测试。本文将详细介绍服务端测试和客户
    的头像 发表于 05-30 15:27 1420次阅读

    服务端的测试主要是测什么内容

    服务端测试是软件开发过程中的一个重要环节,主要目的是确保服务端程序的稳定性、性能、安全性和可靠性。 功能测试 功能测试是服务端测试的基础,主要验证服务端程序是否按照需求实现了所有功能。
    的头像 发表于 05-30 15:24 2564次阅读

    TSMaster 自定义 LIN 调度表编程指导

    LIN(LocalInterconnectNetwork)协议调度表是用于LIN总线通信中的消息调度的一种机制,我们收到越来越多来自不同用户希望能够通过接口实现自定义LIN调度表的需求。所以在
    的头像 发表于 05-11 08:21 425次阅读
    TSMaster <b class='flag-5'>自定义</b> LIN 调度表编程指导

    HarmonyOS开发实例:【自定义Emitter】

    使用[Emitter]实现事件的订阅和发布,使用[自定义弹窗]设置广告信息。
    的头像 发表于 04-14 11:37 882次阅读
    HarmonyOS开发实例:【<b class='flag-5'>自定义</b>Emitter】

    鸿蒙ArkUI实例:【自定义组件】

    组件是 OpenHarmony 页面最小显示单元,一个页面可由多个组件组合而成,也可只由一个组件组合而成,这些组件可以是ArkUI开发框架自带系统组件,比如 `Text` 、 `Button` 等,也可以是自定义组件,本节笔者简单介绍一下自定义组件的语法规范。
    的头像 发表于 04-08 10:17 486次阅读

    NVIDIA 在 Microsoft Azure 上推出面向全球企业和初创公司的生成式 AI Foundry 服务

    应用。 这项 NVIDIA AI foundry 服务整合了  NVIDIA AI Foundation Models 、 NVIDIA NeMo  框架和工具,以及  NVIDIA DGX 云  AI 超算服务三大要素,为企业提供创建
    的头像 发表于 11-16 21:15 431次阅读

    NVIDIA 加快企业自定义生成式 AI 模型开发

    的业务数据进行自定义。 如今,免费、开源的大语言模型对企业来说就像是一顿“自助餐”。但对于构建自定义生成式 AI 应用的开发者来说,这顿“大餐”可能会让他们应接不暇,因为他们需要满足各种不同的项目和业务
    的头像 发表于 11-16 21:15 493次阅读
    NVIDIA 加快企业<b class='flag-5'>自定义生成</b>式 AI 模型开发

    NVIDIA 在 Microsoft Azure 上推出面向全球企业和初创公司的 生成式 AI Foundry 服务

    和初创公司在 Microsoft Azure 上开发、调优和部署其自定义生成式 AI 应用。   这项 NVIDIA AI foundry 服务整合了 NVIDIA AI Foundation
    发表于 11-16 14:13 281次阅读
    NVIDIA 在 Microsoft Azure 上推出面向全球企业和初创公司的 <b class='flag-5'>生成</b>式 AI Foundry <b class='flag-5'>服务</b>

    如何在Matlab中自定义Message

    自定义Message 当我们的 message 消息比较复杂时,通常要用到自定义的 message 消息,MATLAB 2020b以上的版本自带了ROS Toolbox Interface
    的头像 发表于 11-15 18:12 1039次阅读
    如何在Matlab中<b class='flag-5'>自定义</b>Message

    Android自定义铃声 MobPush对安卓自定义铃声的教程

    如何为APP推送设置独特的通知铃声呢?本次带来的是MobPush对安卓自定义铃声的教程,快来看看吧~
    的头像 发表于 10-21 15:34 1020次阅读
    Android<b class='flag-5'>端</b><b class='flag-5'>自定义</b>铃声 MobPush对安卓<b class='flag-5'>端</b><b class='flag-5'>自定义</b>铃声的教程