【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
生成pdf的应用场景特别多,只要是业务类系统必不可少,本篇文章即在针对该解决场景中涉及的多方面问题进行记录,以期为各位提供解决思路。
- 需求
目标系统是一个偏业务类的B端Saas系统,涉及到多个单据的生成,之前遗留的实现方案是使用itext5一行一行绘制pdf模板,当时没有过多的时间评审团队成员的实现方式,虽然满足功能要求,但是极端的不方便,一旦单据结构调整,需要调整代码,这是一个很恶心的恶性循环。
上图是其中一张单据的结构。
那么需求就来了:
1、需要有模板的概念,且模板的制作得标准化,能快速响应需求而进行调整;
2、动态值填入,进行单据实例化(这是普遍需求);
3、功能是在一个独立的能力中心里实现,要求实例化的单据不要落地,而直接返回给调用端,减少文件缓存的处理工作量。
- 解决思路
商业收费的不论,主要结合开源的,实践成本较低的方案。那么思路便是基于已经绘制好的pdf模板,使用标准开源API构件库完成数据的写入,并生成新的pdf,根据这个思路,便开始技术选项和验证。
- 技术选项和验证
1、iReport
iReport是为jasperReports Library和JasperReports Server设计的报表可视化设计器,其遵循AGPL自由开源协议,在SourceForge.net开源社区发布,在国内的应用还是不少的。iReport Designer工具已经在14年停止维护了,最终版本是v5.6.0,其用于绘制单据结构还是非常的方便,通过拖拽就行,默认库中也有部分字体可以使用,基本能满足需求,所以首选的便是这个,网上一搜有不少帖子,基本应用通过这些帖子还是可以达到。
iReport Designer绘制完后会生成两个文件有一个.jrxml和.jasper文件,基本在应用端使用.jrxml就行,.jasper是一个过程产物文件,使用iReport按照上图的单据结构画完,然后应用端使用
<jasperreports.version>6.0.0</jasperreports.version>
<itextpdf.version>5.5.0</itextpdf.version>
<itext-asian.version>5.2.0</itext-asian.version>
<groovy.version>2.4.11</groovy.version>
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>${jasperreports.version}</version>
</dependency>
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports-fonts</artifactId>
<version>${jasperreports.version}</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>${itextpdf.version}</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-pdfa</artifactId>
<version>${itextpdf.version}</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>${itext-asian.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
</dependency>
代码示例就不粘贴了,网上都能搜到,使用的默认字体,满心欢喜的看着最后单据的生成(iReport工具有预览功能,可以看到效果),完成功能交付。恶心的事情便出现了,在chrome、edge、adobe和福昕等工具中,查看是正常的,但是在部分“不标准”的工具中,如CFCA在IE8下的控件、2345看图王工具等,有网上反映像QQ浏览器、360浏览器也有这种情况,看到的效果是中英文数字(含英文大写)混排下的错位感
这是一个很大的问题,让企业看到这种效果的单据,有理也说不清了。对于这种问题,首先需要定位原因,初步觉得是字体导致的,在网上也找到类似的情况,但是很少。https://www.jianshu.com/p/e721ea2df000,这个简书记录中描述的问题类似,但是按照文中的操作方式并未生效,估计还是因为出现问题的工具“毒害太深”。
问题出在字体上,自带的字体有问题,就只能自定义安装字体,网上也是一堆信息(筛选起来很累)
按图123步进入字体的安装,我已经安装过微软雅黑 Light(然并卵)了,使用的是系统字体库里的文件。
很多人在安装字体是PDF Encoding选择有疑惑,这块我暂时没有找到非常清楚的说明,我自己也尝试了两种编码设置,至少字体这块的设置跟我遇到的混编问题应该是无关的。
个人觉得应该是字体安装不成功,但未找到正确安装字体的方式,没有有效解决中英文混编的问题(部分pdf展示工具里),只能重新捋思路(本人也想翻一下到国外查查,只是留给自己的时间不多了)。
2、itext 5
返回使用itext还是因为突然想到一个问题,之前就是使用的itext,只是是一行一行画出来的,可以尝试使用之前的技术方向,只是将模板的制作改为工具化。说干就干,首先使用的是itext 5的版本,itext 5版本是社区版本(一堆人可以共享代码),导致代码风格、规范等已经不可控了,只是之前就是使用的5的版本。如下地址是当时查资料时有过借鉴的地址https://blog.csdn.net/yamadeee/article/details/83384071;然而,又遇到更加诡异的问题。
上图是模板,实例化后pdf在chrome、adobe等工具中,都是显示正常
但是一到不标准工具中,就出现下图的效果,除了一个“汇”字,其他的全没有,包括中文、数字和字母。
我相信绝大部分人在面对次功能时,使用主流工具看一下生成没问题,功能就算可以交差了,可是,一把泪,只能顶着头继续解决问题。
下面是itext5下实现的部分代码
String templatePath = UtilPath.getWEB_INF() + "pdf/面试报告模板.pdf";
// 生成的新文件路径
String newPDFPath ="F:/pdf/interviewReport/"+map.get("phone")+"-"+FileNameUtil.newFileName()+".pdf";
PdfReader reader;
FileOutputStream out;
ByteArrayOutputStream bos;
PdfStamper stamper;
try {
out = new FileOutputStream(newPDFPath);// 输出流
reader = new PdfReader(templatePath);// 读取pdf模板
bos = new ByteArrayOutputStream();
stamper = new PdfStamper(reader, bos);
AcroFields form = stamper.getAcroFields();
// 给表单添加中文字体 这里采用系统字体。不设置的话,中文可能无法显示
BaseFont bf = BaseFont.createFont(UtilPath.getRootPath() + "fonts/simsun.ttc,0", BaseFont.IDENTITY_H,
BaseFont.EMBEDDED);
form.addSubstitutionFont(bf);
//遍历map装入数据
for (Entry<String, String> entry : map.entrySet()) {
form.setField(entry.getKey(), entry.getValue());
//System.out.println("插入PDF数据----> key= " + entry.getKey() + " and value= " + entry.getValue());
}
stamper.setFormFlattening(true);// 如果为false那么生成的PDF文件还能编辑,一定要设为true
stamper.close();
Document doc = new Document();
PdfCopy copy = new PdfCopy(doc, out);
doc.open();
PdfImportedPage importPage = copy.getImportedPage(new PdfReader(bos.toByteArray()), 1);
copy.addPage(importPage);
doc.close();
} catch (IOException e) {
} catch (DocumentException e) {
}
个人觉得还是字体的问题,只是BaseFont换了很多,依然没有效果,不得不放弃itext5
3、itext7
经过尝试,itext7相对比较顺利,简直可以说是“一气呵成”,下面只贴代码
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { PdfDocument pdfDocument = new PdfDocument( new PdfReader(VOUCHER), new PdfWriter(outputStream)); PdfAcroForm pdfAcroForm = PdfAcroForm.getAcroForm(pdfDocument, true); Map<String, PdfFormField> formFieldMap = pdfAcroForm.getFormFields(); PdfFont pdfFont = PdfFontFactory.createFont(FONT, PdfEncodings.IDENTITY_H, true); PdfFormField pdfFormField; //将单据中所有的配置属性项同传入数据map做映射,匹配则进行赋值操作 Set<Map.Entry<String, PdfFormField>> set = formFieldMap.entrySet(); Iterator<Map.Entry<String, PdfFormField>> iterator = set.iterator(); while (iterator.hasNext()) { Map.Entry<String, PdfFormField> entry = (Map.Entry<String, PdfFormField>) iterator.next(); if (params.containsKey(entry.getKey())) { pdfFormField = formFieldMap.get(entry.getKey()); pdfFormField.setFont(pdfFont); pdfFormField.setValue(params.get(entry.getKey())); } } pdfAcroForm.flattenFields(); pdfDocument.close(); return outputStream.toByteArray(); } catch (IOException e) { log.error("单据模板实例化异常!"); e.printStackTrace(); return null; }
使用pdf模板会有一个问题,模板格局无法自动伸缩,暂时还没处理这块的问题。
4、其他方案
Jaspersoft Studio,这个和ireport一个公司,只是ireport不维护,转而维护Jaspersoft Studio,这是一个机遇eclipse的定制工具,有兴趣的可以尝试;
finereport,这是帆软的一个工具,反馈也是不错的,有兴趣的可以尝试;
使用html画模板,再使用itext来生成pdf,也是可以的;
将word模板转为xml,再将占位符${}加入,也可以加部分的条件表达式,再转成word,使用pageoffice来实现,只是pageoffice只能在windows环境下使用。
以上四种我都或多或少听说过,并没有尝试。
来源:oschina
链接:https://my.oschina.net/u/1456786/blog/3143454