pdf生成功能的生产级尝试及相关问题汇总

☆樱花仙子☆ 提交于 2019-12-16 10:50:37

【推荐】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环境下使用。

   以上四种我都或多或少听说过,并没有尝试。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!