在spring boot 中使用itext和itextrender生成pdf文件

Posted 天军

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在spring boot 中使用itext和itextrender生成pdf文件相关的知识,希望对你有一定的参考价值。

转载请注明出处 https://www.cnblogs.com/majianming/p/9539376.html
项目中需要对订单生成pdf文件,在第一版本其实已经有了比较满意的pdf文档,但是还是存在问题的,主要是itext的css支持能力实在是太差,测试过程中发现margin都不支持,和我对接pdf的html模板的伙伴也是一直在改,凭着不想一直被打的希望,终于找到了下面好一点的方案。总的来说,就是加了itextrender这个支持常见的css2.1的封装。

首选介绍一下这次使用的环境设置

  1. spring boot 2.0
  2. itext 5.5.13
  3. xmlworker 5.5.13
  4. itext-asin asian 5.2.0
  5. flying-saucer-pdf-itext5 9.1.12

在maven pom文件添加

    <!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>itextpdf</artifactId>
        <version>5.5.13</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.itextpdf/itext-asian -->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>itext-asian</artifactId>
        <version>5.2.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.itextpdf.tool/xmlworker -->
    <dependency>
        <groupId>com.itextpdf.tool</groupId>
        <artifactId>xmlworker</artifactId>
        <version>5.5.13</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.xhtmlrenderer/flying-saucer-pdf-itext5 -->
    <dependency>
        <groupId>org.xhtmlrenderer</groupId>
        <artifactId>flying-saucer-pdf-itext5</artifactId>
        <version>9.1.12</version>
    </dependency>

在代码中默认使用

    ITextRenderer iTextRenderer = new ITextRenderer();
    iTextRenderer.setPDFVersion(PdfWriter.VERSION_1_7);
    iTextRenderer.setDocumentFromString(html);//pdf的内容,有其他生成模式,方法名类似
    iTextRenderer.layout();
    try (ServletOutputStream outputStream = response.getOutputStream()) 
        iTextRenderer.createPDF(outputStream);
     catch (IOException e) 
        logger.error("输出pdf错误" + e.getMessage(), e);
     catch (DocumentException e) 
        logger.error("生成pdf错误" + e.getMessage(), e);
    

好了,启动跑一下。中文文字一个都没有显示。按照之前的想法,那设置一下字体文件路径就好了吧,下面的借鉴于itext的com.itextpdf.text.FontFactoryImp#registerDirectories(), 用于向itextRender注册默认的字体路径,

    ITextRenderer iTextRenderer = new ITextRenderer();
    
    try 
        ITextFontResolver fontResolver = iTextRenderer.getFontResolver();
        String windir = System.getenv("windir");
        String fileSeparator = System.getProperty("file.separator");
        if (windir != null && fileSeparator != null) 
            fontResolver.addFontDirectory(windir + fileSeparator + "fonts", BaseFont.NOT_EMBEDDED);
        
        fontResolver.addFontDirectory("/usr/share/X11/fonts", BaseFont.EMBEDDED);
        fontResolver.addFontDirectory("/usr/X/lib/X11/fonts", BaseFont.EMBEDDED);
        fontResolver.addFontDirectory("/usr/openwin/lib/X11/fonts", BaseFont.EMBEDDED);
        fontResolver.addFontDirectory("/usr/share/fonts", BaseFont.EMBEDDED);
        fontResolver.addFontDirectory("/usr/X11R6/lib/X11/fonts", BaseFont.EMBEDDED);
        fontResolver.addFontDirectory("/Library/Fonts", BaseFont.EMBEDDED);
        fontResolver.addFontDirectory("/System/Library/Fonts", BaseFont.EMBEDDED);
     catch (IOException e) 
        logger.error("字体路径读取异常", e);
        //相关处理
        return;
     catch (DocumentException e) 
        logger.error("字体解析异常", e);
         //相关处理
        return;
    
    iTextRenderer.setPDFVersion(PdfWriter.VERSION_1_7);
    iTextRenderer.setDocumentFromString(html);
    iTextRenderer.layout();
    try (ServletOutputStream outputStream = response.getOutputStream()) 
        iTextRenderer.createPDF(outputStream);
     catch (IOException e) 
        logger.error("输出pdf错误" + e.getMessage(), e);
     catch (DocumentException e) 
        logger.error("生成pdf错误" + e.getMessage(), e);
    

开心的运行起来,哈哈哈哈,要好了!
欸?还是一样,是我的问题?肯定是我的问题!那就继续看吧
看了许多的博客(忘记记录下来了,( ̄_, ̄ ))
看到许多添加字体文件的都是类似于fontResolver.addFont("simsun.ttc",BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);,向解析器注册宋体字体;第三个参数是字体的嵌入行为,有嵌入和不嵌入两种。那第二个参数是什么,查看命名,也就是encoding,字体的编码方式,看看ITextFontResolver#addFontDirectory(Sting path,boolean embedded)这个方法中,调用了addFont(String path, boolean embedded),在这个方法中找到了问题的根源,上面调用fontResolver.addFontDirectory这个方法设置字体的默认编码为Cp1252,这个编码并不支持中文,也就很好的说明了为什么pdf上的中文字符都没有显示,那么我们设置为Identity-H,这个时候将上面的代码加以修改
首先修改默认的addFontDirectory方法

/**
 * 因为默认设置的字体编码为西文,需要重写改为<code>BaseFont.IDENTITY_H</code>
 *
 * @param dir
 * @param embedded
 * @param fontResolver
 * @throws DocumentException
 * @throws IOException
 */
private void addFontDirectory(String dir, boolean embedded, ITextFontResolver fontResolver)
        throws DocumentException, IOException 
    File f = new File(dir);
    if (f.isDirectory()) 
        File[] files = f.listFiles((dir1, name) -> 
            String lower = name.toLowerCase();
            return lower.endsWith(".otf") || lower.endsWith(".ttf") || lower.endsWith(".ttc");
        );
        for (int i = 0; i < files.length; i++) 
            fontResolver.addFont(files[i].getAbsolutePath(), BaseFont.IDENTITY_H, embedded);
        
    

所以生成的代码改为

    ITextRenderer iTextRenderer = new ITextRenderer();
    
    try 
        ITextFontResolver fontResolver = iTextRenderer.getFontResolver();
        String windir = System.getenv("windir");
        String fileSeparator = System.getProperty("file.separator");
        if (windir != null && fileSeparator != null) 
            addFontDirectory(windir + fileSeparator + "fonts", BaseFont.NOT_EMBEDDED, fontResolver);
        
        addFontDirectory("/usr/share/X11/fonts", BaseFont.EMBEDDED, fontResolver);
        addFontDirectory("/usr/X/lib/X11/fonts", BaseFont.EMBEDDED, fontResolver);
        addFontDirectory("/usr/openwin/lib/X11/fonts", BaseFont.EMBEDDED, fontResolver);
        addFontDirectory("/usr/share/fonts", BaseFont.EMBEDDED, fontResolver);
        addFontDirectory("/usr/X11R6/lib/X11/fonts", BaseFont.EMBEDDED, fontResolver);
        addFontDirectory("/Library/Fonts", BaseFont.EMBEDDED, fontResolver);
        addFontDirectory("/System/Library/Fonts", BaseFont.EMBEDDED, fontResolver);
      catch (IOException e) 
        logger.error("字体路径读取异常", e);
        //相关处理
        return;
     catch (DocumentException e) 
        logger.error("字体解析异常", e);
         //相关处理
        return;
    
    iTextRenderer.setPDFVersion(PdfWriter.VERSION_1_7);
    iTextRenderer.setDocumentFromString(html);
    iTextRenderer.layout();
     try (ServletOutputStream outputStream = response.getOutputStream()) 
        iTextRenderer.createPDF(outputStream);
     catch (IOException e) 
        logger.error("输出pdf错误" + e.getMessage(), e);
     catch (DocumentException e) 
        logger.error("生成pdf错误" + e.getMessage(), e);
    

运行,ok了

Itext PdfSmartCopy获取Null指针异常

我现在正在使用Itext PdfSmartCopy。我正在使用XMLworker向文档对象添加一些业务内容。然后我宣布了一个读者(用于将pdf文件连接到此文档对象)。然后我用相同的文档对象和输出文件流作为参数调用PdfSmartCopy。然后我使用常规步骤将页面复制到文档,

addHTML(document, htmlStringToBeAdded);
document.newPage();
com.itextpdf.text.pdf.PdfCopy copy = new com.itextpdf.text.pdf.PdfSmartCopy(document, new FileOutputStream("c:\pdf_issue\bad_itext3.pdf"));
com.itextpdf.text.pdf.PdfReader reader=new com.itextpdf.text.pdf.PdfReader("c:\pdf_issue\bad1.pdf");
 // loop over the pages in that document
 n = reader.getNumberOfPages();
  for (int page = 0; page < n; ) {
                copy.addPage(copy.getImportedPage(reader, ++page));
            }
copy.freeReader(reader);
    reader.close();

但是我在getPageReference上得到Null指针异常?有什么问题?

Exception in thread "main" java.lang.NullPointerException
    at com.itextpdf.text.pdf.PdfWriter.getPageReference(PdfWriter.java:1025)
    at com.itextpdf.text.pdf.PdfWriter.getCurrentPage(PdfWriter.java:1043)
    at com.itextpdf.text.pdf.PdfCopy.addPage(PdfCopy.java:356)
    at com.jci.util.PdfTest.main(PdfTest.java:627)

但是,如果我使用新的文档对象,即不添加业务内容,这部分就可以正常工作。

答案

我们在封闭式问题跟踪器中遇到了类似的问题。在那张票中,似乎需要在创建Document实例后立即打开PdfCopy

在您的情况下,我们看到了类似的问题:您使用Document对象从头开始创建PDF,然后使用相同的Document创建现有PDF的副本。这永远不会起作用:您需要先从头开始创建您创建的文档,然后为复制过程创建一个新的Document

// first create the document
Document document = new Document();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfWriter.getInstance(document, baos);
document.open();
addHTML(document, htmlStringToBeAdded);
document.close();
// Now you can use the document you've just created
PdfReader reader = new PdfReader(baos.toArray());
PdfReader existing = new PdfReader("c:\pdf_issue\bad1.pdf");
document = new Document();
PdfCopy copy = new PdfSmartCopy(document, new FileOutputStream("c:\pdf_issue\bad_itext3.pdf"));
document.open();
copy.addDocument(reader);
copy.addDocument(existing);
document.close();
reader.close();
existing.close();

以上是关于在spring boot 中使用itext和itextrender生成pdf文件的主要内容,如果未能解决你的问题,请参考以下文章

Itext PdfSmartCopy获取Null指针异常

在 Spring Boot 中嵌入 tomcat 中禁用 Jar Scan 的 scanManifest

Java 代码实例 13Java操作pdf的工具类itext

从《Java核心技术卷Ⅱ》看Java操作pdf的工具类itext

项目结构iText7

Spring Boot:在Spring Boot中使用Mysql和JPA