前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot 下PDF生成使用填坑总结

SpringBoot 下PDF生成使用填坑总结

作者头像
林淮川
发布2021-12-20 15:55:13
3.9K0
发布2021-12-20 15:55:13
举报
文章被收录于专栏:分布式架构分布式架构
一、PDF生成

由于直接使用IText生成PDF,数据填充较为繁琐,故:

选用Freemarker和IText生成pdf,引入依赖:

环境:jdk 1.8 + SpringBoot

1.引入freemarker starter (版本跟随SpringBootparent即可)

代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
    <version>1.5.17.RELEASE</version>
</dependency>

2.将html渲染转换组件(使用版本5)

代码语言:javascript
复制
<dependency>
    <groupId>com.itextpdf.tool</groupId>
    <artifactId>xmlworker</artifactId>
    <version>5.5.11</version>
</dependency>

3.引入Itext PDF生成组件(使用版本5,最新版位 7, itext有open source和colsesource之分,目前7版本在网上少有例子)

代码语言:javascript
复制
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.13</version>
</dependency>

引入依赖后,代码如下:

代码语言:javascript
复制
public static void pdfTpl(Map<String, Object> data,Configuration cfg,String ftlTemplateName,String fileUrl){
// 指定FreeMarker模板文件的位置
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
// 设置模板的编码格式
        cfg.setDefaultEncoding("UTF-8");
        cfg.setEncoding(Locale.CHINA, "UTF-8");
        cfg.setDateFormat("yyyy-MM-dd");
        cfg.setDateTimeFormat("yyyy-MM-dd HH:mm:ss");
        cfg.setClassicCompatible(true);
// 获取模板文件
        Template template = cfg.getTemplate(ftlTemplateName, "UTF-8");
        StringWriter writer = new StringWriter();
// 将数据输出到html中
        template.process(data, writer);
        Document doc = new Document(PageSize.A4, 50, 50, 60, 60);
        PdfWriter pdfWriter = PdfWriter.getInstance(doc,out);
        Paragraph context = new Paragraph();
        ElementList elementList =    MyXMLWorkerHelper.parseToElementList(writer.toString(), null);
for (Element element : elementList) {
            context.add(element);
        }
        doc.open();
        doc.add(context);
        doc.close();
byte [] contents = out.toByteArray();
        writer.close();
        pdfWriter.close();
    }catch (Exception e){
        e.printStackTrace();
    }
 }

一阵代码撸完然后debug测试,发现中文pdf文件中含有中文的都不显示(空白)

心里有点慌,因为服务马上要上线,最终在组内大佬的帮助下解决了

解释: 1、Configuration cfg 使用了freemaker starter后,在项目启动时即会自动初始化 Configuration 对象到Spring容器中; 2、Template template = cfg.getTemplate("test.ftl","UTF-8"); 模板因cfg本身在Spring容器中,则在获取test.ftl模板是就会自动在resource/templates下寻找模板,默认:ftl 格式,可以修改 3、因为找了很多例子都是使用ITextRenderer 对象来渲染输出渲染的PDF,但ITextRenderer有一个问题是要解决中文不显示问题,必须把字体放在一个以 文件夹 路径访问的形式引入,SpringBoot打包后,经测试,无法获取打包后的FONT字体; 则,再另辟途径,又找到以Document方式,但document需要的是,没一个dom对象都必须一个个添加进去,网上很多都是new 专门的对象,比如:块 Paragraph 然后添加文字(数字)内容。 所以又搜索:是否可以往document插入html 最终找到https://www.cnblogs.com/mvilplss/p/5646675.html

代码语言:javascript
复制
package com.***.***(省略);

import com.itextpdf.text.Font;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.tool.xml.ElementList;
import com.itextpdf.tool.xml.XMLWorker;
import com.itextpdf.tool.xml.XMLWorkerFontProvider;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import com.itextpdf.tool.xml.css.CssFile;
import com.itextpdf.tool.xml.css.StyleAttrCSSResolver;
import com.itextpdf.tool.xml.html.CssAppliers;
import com.itextpdf.tool.xml.html.CssAppliersImpl;
import com.itextpdf.tool.xml.html.Tags;
import com.itextpdf.tool.xml.parser.XMLParser;
import com.itextpdf.tool.xml.pipeline.css.CSSResolver;
import com.itextpdf.tool.xml.pipeline.css.CssResolverPipeline;
import com.itextpdf.tool.xml.pipeline.end.ElementHandlerPipeline;
import com.itextpdf.tool.xml.pipeline.html.HtmlPipeline;
import com.itextpdf.tool.xml.pipeline.html.HtmlPipelineContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;

public class MyXMLWorkerHelper {
private static Logger logger = LoggerFactory.getLogger(MyXMLWorkerHelper.class);

public static class MyFontsProvider extends XMLWorkerFontProvider {
public MyFontsProvider() {
super(null, null);
        }

@Override
public Font getFont(final String fontname, String encoding, float size, final int style) {
try {
                BaseFont base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
return new Font(base, size, style);
            } catch (Exception e) {
                e.printStackTrace();
            }
return super.getFont(fontname, encoding, size, style);
        }
    }

public static ElementList parseToElementList(String html, String css) throws IOException {
long start = System.currentTimeMillis();
// CSS
        CSSResolver cssResolver = new StyleAttrCSSResolver();
if (css != null) {
            CssFile cssFile = XMLWorkerHelper.getCSS(new ByteArrayInputStream(css.getBytes()));
            cssResolver.addCss(cssFile);
        }
// HTML
//        XMLWorkerFontProvider fontProvider = new XMLWorkerFontProvider(XMLWorkerFontProvider.DONTLOOKFORFONTS);
        MyFontsProvider fontProvider = new MyFontsProvider();
        CssAppliers cssAppliers = new CssAppliersImpl(fontProvider);
        HtmlPipelineContext htmlContext = new HtmlPipelineContext(cssAppliers);
        htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
        htmlContext.autoBookmark(false);

// Pipelines
        ElementList elements = new ElementList();
        ElementHandlerPipeline end = new ElementHandlerPipeline(elements, null);
        HtmlPipeline htmlPipeline = new HtmlPipeline(htmlContext, end);
        CssResolverPipeline cssPipeline = new CssResolverPipeline(cssResolver, htmlPipeline);


// XML Worker
        XMLWorker worker = new XMLWorker(cssPipeline, true);
        XMLParser p = new XMLParser(worker);
        p.parse(new ByteArrayInputStream(html.getBytes()));
return elements;
    }
}

以上确实可以将html模板内容渲染,但是中文仍不显示,解决方案有两种:

1.方案1 直接引入字体文件

代码语言:javascript
复制
renderer.getFontResolver().addFont(fontsPath ,BaseFont.IDENTITY_H,  BaseFont.NOT_EMBEDDED);

2.方案2 引入iTextAsian.jar 包

代码语言:javascript
复制
BaseFont base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);

所以,毫无疑问,选择方案2,引入iTextAsian pom,注意版本要跟itextpdf 一致,至少大版本要一致,如 5 对 5。否则会报 com.lowagie.text.DocumentException: Font'STSong-Light' with 'UniGB-UCS2-H' is not recognized.

代码语言:javascript
复制
<dependency>
  <groupId>com.itextpdf</groupId>
  <artifactId>itext-asian</artifactId>
  <version>5.2.0</version>
</dependency>

但是如何把 设置字体 操作在添加每个 element 时进行呢?发现在 继承 XMLWorkerFontProvider 时有个getFont方法,则在此返回即可,则最终得出上述代码。

注意:模板标签(h5)容易报错,一旦模板出现问题,可优先排查标签嵌套问题,例:table标签不能嵌套div标签

二、PDF转换为图片

pdf转图片有两种方式:icepdf和pdfbox

上面两种方式都实现过,都存在中文不显示或者乱码问题,但是由于icepdf最多只能支持转换10页pdf,所以果断选择pdfbox。

1.引入依赖

代码语言:javascript
复制
<!--pdf转图片-->
<dependency>
  <groupId>org.apache.pdfbox</groupId>
  <artifactId>fontbox</artifactId>
  <version>2.0.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox -->
<dependency>
  <groupId>org.apache.pdfbox</groupId>
  <artifactId>pdfbox</artifactId>
  <version>2.0.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
  <groupId>commons-logging</groupId>
  <artifactId>commons-logging</artifactId>
  <version>1.2</version>
</dependency>

引入以上依赖,编写代码运行(原始有问题的代码没有上传git,缺失了),报错信息如下:

在实际使用中遇到问题

代码语言:javascript
复制
1)ERROR o.a.p.contentstream.PDFStreamEngine 911 - Cannot read JBIG2 image: jbig2-imageio is not installed
2)Cannot read JPEG2000 image: Java Advanced Imaging (JAI) Image I/O Tools are not installed
3) java.lang.IllegalArgumentException: Numbers of source Raster bands and source color space components do not match at java.awt.image.ColorConvertOp.filter

以上两个问题需要使用 JAI 插件和 jbig2 插件支持,通过引入 jai-imageio-core、jai-imageio-jpeg2000、jbig2-imageio

代码语言:javascript
复制
<dependency>
  <groupId>com.github.jai-imageio</groupId>
  <artifactId>jai-imageio-core</artifactId>
  <version>1.3.1</version>
</dependency>
<dependency>
  <groupId>com.github.jai-imageio</groupId>
  <artifactId>jai-imageio-jpeg2000</artifactId>
  <version>1.3.0</version>
</dependency>
<dependency>
  <groupId>org.apache.pdfbox</groupId>
  <artifactId>jbig2-imageio</artifactId>
  <version>3.0.2</version>
</dependency>

引入上述依赖,本地开发环境mac下,pdf转图片正常转换,一旦到Linux环境下会出现生成pdf那样的情况:中文不显示或者乱码。报错信息如下

代码语言:javascript
复制
Using fallback LiberationSans for CID-keyed font AdobeKaitiStd-Regular
Using fallback LiberationSans for CID-keyed font STSong-Light

注意:其中font部分在Linux和Mac系统下显示特殊字符。

网上有说缺少字体需要导入字体文件,有说覆写FontMapperImpl

类,通过字体映射解决

详见:

http://www.luyixian.cn/news_show_301650.aspx

https://blog.csdn.net/kea_iv/article/details/103734279

上述方案中引入字体库公司不支持,原因:对运维同学不友好。

第二种通过映射关系解决中文不显示的尝试过,没有作用。

这也不行,那也不行,那到底怎么办。

最后延用解决生成pdf的思路,解决字体问题,结果没想到成了。代码如下:

代码语言:javascript
复制
public static List<byte[]> pdf2Png(InputStream inputStream) {
try {
        PDDocument pdDocument = PDDocument.load(inputStream);
        PDFRenderer renderer = new PDFRenderer(pdDocument);
int pageCount = pdDocument.getNumberOfPages();
        List<byte[]> byteList = new ArrayList<>();
for (int i = 0; i < pageCount; i++) {
            BufferedImage bim = renderer.renderImage(i);
            PDImageXObject img = JPEGFactory.createFromImage(pdDocument, bim);
            PDPageContentStream contentStream = new PDPageContentStream(pdDocument, pdDocument.getPage(i));
            contentStream.drawImage(img, PDRectangle.A4.getHeight(),PDRectangle.A4.getWidth());
            contentStream.setFont(PDType1Font.HELVETICA_BOLD, 15);
            contentStream.close();
try (ByteArrayOutputStream outStream = new ByteArrayOutputStream()) {
                ImageIO.write(bim, "PNG", outStream);
                byteList.add(outStream.toByteArray());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        pdDocument.close();
        logger.debug("pdf转换图片成功:{}");
return byteList;
    } catch (IOException e) {
        logger.error("pdf转换图片失败:{}", e.getMessage());
        e.printStackTrace();
    }
return null;
}

使用上述代码,不用引入字体文件,完全可以解决中文显示的问题,但是后来在测试同学的细致测试下发现,如果将生成pdf和pdf文件转图片连起来还是会有中文显示乱码的问题

复现方式:生成pdf文件后下载,然后上传(上传的时候,pdf转图片存储),预览图片,发现图片中的中文显示乱码。

虽然中文显示乱码但是完全可以满足用户的需求,但是本着细致负责的态度,仍着力去解决这个问题:终于问题解决了

解决方案:引入外部字体文件(好像是又回到了引入字体文件的那种解决方式,但是两者有区别,一个是解决,一个是更好的优化提升用户体验)

找到组内其他用windows的研发同学,拷贝“SIMSUN.TTC”字体文件到linux服务器,安装。千万要注意:一定要刷新缓存,因为这个问题搞了好久,虽然运维同学刷新过字体库,但是一次不生效,切记,尽量刷新多次。

到此,中文不显示的问题完美解决。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 川聊架构 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.引入freemarker starter (版本跟随SpringBootparent即可)
  • 2.将html渲染转换组件(使用版本5)
  • 3.引入Itext PDF生成组件(使用版本5,最新版位 7, itext有open source和colsesource之分,目前7版本在网上少有例子)
  • 1.方案1 直接引入字体文件
  • 2.方案2 引入iTextAsian.jar 包
  • 最后延用解决生成pdf的思路,解决字体问题,结果没想到成了。代码如下:
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档