手把手教你使用Java开发在线生成pdf文档

我是程序汪

共 6792字,需浏览 14分钟

 ·

2021-10-16 12:12


一、介绍

在实际的业务开发的时候,研发人员往往会碰到很多这样的一些场景,需要提供相关的电子凭证信息给用户,例如网银/支付宝/微信购物支付的电子发票、订单的库存打印单、各种电子签署合同等等,以方便用户查看、打印或者下载。

例如下图的电子发票!

熟悉这块业务的童鞋,一定特别清楚,目前最常用的解决方案是:把相关的数据信息,通过一些技术手段生成对应的 PDF 文件,然后返回给用户,以便预览、下载或者打印。

不太熟悉这项技术的童鞋,也不用着急,今天我们一起来详细了解一下在线生成 PDF 文件的技术实现手段!

二、案例实现

在介绍这个代码实践之前,我们先来了解一下这个第三方库:iText,对,没错,它就是我们今天的主角。

iText是著名的开放源码站点sourceforge一个项目,是用于生成PDF文档的一个java类库,通过iText不仅可以生成PDFrtf的文档,而且还可以将XMLHtml文件转化为PDF文件。

iText目前有两套版本,分别是iText5iText7iText5应该是网上用的比较多的一个版本。iText5因为是很多开发者参与贡献代码,因此在一些规范和设计上存在不合理的地方。iText7是后来官方针对iText5的重构,两个版本差别还是挺大的。不过在实际使用中,一般用到的都比较简单的 API,所以不用特别拘泥于使用哪个版本。

2.1、添加 iText 依赖包

在使用它之前,我们先引人相关的依赖包!

<dependencies>
    
    <dependency>
        <groupId>com.itextpdfgroupId>
        <artifactId>itextpdfartifactId>
        <version>5.5.11version>
    dependency>
    <dependency>
        <groupId>com.itextpdf.toolgroupId>
        <artifactId>xmlworkerartifactId>
        <version>5.5.11version>
    dependency>
    
    <dependency>
        <groupId>com.itextpdfgroupId>
        <artifactId>itext-asianartifactId>
        <version>5.2.0version>
    dependency>
    
    <dependency>
        <groupId>org.xhtmlrenderergroupId>
        <artifactId>flying-saucer-pdf-itext5artifactId>
        <version>9.1.16version>
    dependency>
    
    <dependency>
        <groupId>net.sf.jtidygroupId>
        <artifactId>jtidyartifactId>
        <version>r938version>
    dependency>
        
dependencies>

2.2、简单实现

老规矩,我们先来一个hello world,代码如下:

public class CreatePDFMainTest {

    public static void main(String[] args) throws Exception {
        Document document = new Document(PageSize.A4);
        //第二步,创建Writer实例
        PdfWriter.getInstance(document, new FileOutputStream("hello.pdf"));
        //创建中文字体
        BaseFont bfchinese = BaseFont.createFont("STSong-Light""UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
        Font fontChinese = new Font(bfchinese, 12, Font.NORMAL);
        //第三步,打开文档
        document.open();
        //第四步,写入内容
        Paragraph paragraph = new Paragraph("hello world", fontChinese);
        document.add(paragraph);
        //第五步,关闭文档
        document.close();
    }
}

打开hello.pdf文件,内容如下!

2.3、复杂实现

在实际的业务开发中,因为业务场景非常复杂,而且变化快,我们往往不会采用上面介绍的写入内容方式来生成文件,而是采用HTML文件转化为PDF文件。

例如下面这张入库单!

我们应该如何快速实现呢?

首先,我们采用html语言编写一个入库单页面,将其命令为printDemo.html,源代码如下:

<html>
 <head>head>
 <body>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <title>出库单title>
  <div>
   <div>
    <table width="100%" border="0" cellspacing="0" cellpadding="0">
     <tbody>
      <tr>
       <td height="40" colspan="2"><h3 style="font-weight: bold; text-align: center; letter-spacing: 5px; font-size: 24px;">入库单h3>td>
       <td width="12%" height="20" rowspan="2">
        <img style="width: 105px;height: 105px;" src="data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAH0AAAB9AQAAAACn+1GIAAAAqElEQVR42u3VMQ7DMAwDQP6A//8lx24qKRRw0s1yu8Uw4OQGIaHsBHUfLzzwAxCAInoZg6dI9dUUBIOyHEG56CmodAaxwtfbboLTVWpeU9+EDAH37m9CmkTYxDGUE0agMIakk3y4Ut8G37iom02M4bPniHWAtqFDTjjSGLrZvXAOmTnL1124C73r6Yo8Ane61k6eQeVjIM2h482D1RwScrpNjuH5R/0b3s6ZZNyKlt3iAAAAAElFTkSuQmCC" />
       td>
      tr>
      <tr>
       <td width="50%" height="30">操作人:xxxtd>
       <td width="50%" height="30" colspan="2">创建时间:2021-09-14 12:00:00td>
      tr>
     tbody>
    table>
   div>
   <div style="margin-top: 5px; margin-bottom: 6px; margin-left: 4px">div>
   <div>
    <table width="100%"
     style="border-collapse: collapse; border-spacing: 0;border:0px;">

      <tr style="height: 25px;">
       <td style="background: #eaeaea; text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;"
        width="10%">
序号td>
       <td style="background: #eaeaea; text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;"
        width="30%">
商品td>
       <td style="background: #eaeaea; text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;"
        width="30%">
单位td>
       <td style="background: #eaeaea; text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000; border-right: 1px solid #000000;"
        width="30%">
数量td>
      tr>
      <tr>
       <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;">1td>
       <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;">xxx沐浴露td>
       <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;">td>
       <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000; border-right: 1px solid #000000;">3td>
      tr>
      <tr>
       <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;">2td>
       <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;">xxx洗发水td>
       <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;">td>
       <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000; border-right: 1px solid #000000;">4td>
      tr>
      <tr>
       <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;">3td>
       <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;">xxx洗衣粉td>
       <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000;">td>
       <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000; border-right: 1px solid #000000;">5td>
      tr>
      <tr>
       <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000; border-bottom: 1px solid #000000;">4td>
       <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000; border-bottom: 1px solid #000000;">xxx洗面奶td>
       <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000; border-bottom: 1px solid #000000;">td>
       <td style="text-align: center; border-left: 1px solid #000000; border-top: 1px solid #000000; border-right: 1px solid #000000; border-bottom: 1px solid #000000;">5td>
      tr>
    table>
   div>
  div>
 body>

html>

接着,我们将html文件转成PDF文件,源码如下:

public class CreatePDFMainTest {


    /**
     * 创建PDF文件
     * @param htmlStr
     * @throws Exception
     */

    private static void writeToOutputStreamAsPDF(String htmlStr) throws Exception {
        String targetFile = "pdfDemo.pdf";
        File targeFile = new File(targetFile);
        if(targeFile.exists()) {
            targeFile.delete();
        }

        //定义pdf文件尺寸,采用A4横切
        Document document = new Document(PageSize.A4, 25251540);// 左、右、上、下间距
        //定义输出路径
        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(targetFile));
        PdfReportHeaderFooter header = new PdfReportHeaderFooter(""8, PageSize.A4);
        writer.setPageEvent(header);
        writer.addViewerPreference(PdfName.PRINTSCALING, PdfName.NONE);
        document.open();

        // CSS
        CSSResolver cssResolver = new StyleAttrCSSResolver();
        CssAppliers cssAppliers = new CssAppliersImpl(new XMLWorkerFontProvider(){

            @Override
            public Font getFont(String fontname, String encoding, boolean embedded, float size, int style, BaseColor color) {
                try {
                    //用于中文显示的Provider
                    BaseFont bfChinese = BaseFont.createFont("STSongStd-Light""UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
                    return new Font(bfChinese, size, style);
                } catch (Exception e) {
                    return super.getFont(fontname, encoding, size, style);
                }
            }
        });

        //html
        HtmlPipelineContext htmlContext = new HtmlPipelineContext(cssAppliers);
        htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
        htmlContext.setImageProvider(new AbstractImageProvider() {
            @Override
            public Image retrieve(String src) {
                //支持图片显示
                int pos = src.indexOf("base64,");
                try {
                    if (src.startsWith("data") && pos > 0) {
                        byte[] img = Base64.decode(src.substring(pos + 7));
                        return Image.getInstance(img);
                    } else if (src.startsWith("http")) {
                        return Image.getInstance(src);
                    }
                } catch (BadElementException ex) {
                    return null;
                } catch (IOException ex) {
                    return null;
                }
                return null;
            }

            @Override
            public String getImageRootPath() {
                return null;
            }
        });


        // Pipelines
        PdfWriterPipeline pdf = new PdfWriterPipeline(document, writer);
        HtmlPipeline html = new HtmlPipeline(htmlContext, pdf);
        CssResolverPipeline css = new CssResolverPipeline(cssResolver, html);

        // XML Worker
        XMLWorker worker = new XMLWorker(css, true);
        XMLParser p = new XMLParser(worker);
        p.parse(new ByteArrayInputStream(htmlStr.getBytes()));

        document.close();
    }

    /**
     * 读取 HTML 文件
     * @return
     */

    private static String readHtmlFile() {
        StringBuffer textHtml = new StringBuffer();
        try {
            File file = new File("printDemo.html");
            BufferedReader reader = new BufferedReader(new FileReader(file));
            String tempString = null;
            // 一次读入一行,直到读入null为文件结束
            while ((tempString = reader.readLine()) != null) {
                textHtml.append(tempString);
            }
            reader.close();
        } catch (IOException e) {
            return null;
        }
        return textHtml.toString();
    }

    public static void main(String[] args) throws Exception {
        //读取html文件
        String htmlStr = readHtmlFile();
        //将html文件转成PDF
        writeToOutputStreamAsPDF(htmlStr);
    }
}

运行程序,打开pdfDemo.pdf,结果如下!

2.4、变量替换方式

上面的html文件,是我们事先已经编辑好的,才能正常渲染。

但是在实际的业务开发的时候,例如下面的商品内容,完全是动态的,还是xxx-202109入库单的名称,以及二维码,都是动态的。

这个时候,我们可以采用freemarker模板引擎,通过定义变量来动态填充内容,直到转换出来的结果就是我们想要的html页面。

当然,还有一种办法,例如下面这个,我们也可以在html页面里面定义${name}变量,然后在读取完文件之后,我们将其变量进行替换成我们想填充的任何值,这其实也是模板引擎最核心的一个玩法。

<html>
 <head>
  <meta charset="utf-8">
  <title>title>
 head>
 <body>
  <div>您好:${name}div>
  <div>欢迎,登录博客网站div>
 body>
html>

三、总结

itext框架是一个非常实用的第三方pdf文件生成库,尤其是面对比较简单的pdf文件内容渲染的时候,它完全满足我们的需求。

但是对于那种复杂的pdf文档,可能需要我们自己单独进行适配开发。具体的深度玩法,大家可以参阅itext官方API。

鉴于笔者才疏学浅,难免会有理解不到位的地方,欢迎网友批评指出!

四、参考

1、博客园 - JAVA使用ItextPDF


< END >

程序汪资料链接

程序汪接的7个私活都在这里,经验整理

Java项目分享  最新整理全集,找项目不累啦 04版

堪称神级的Spring Boot手册,从基础入门到实战进阶

卧槽!字节跳动《算法中文手册》火了,完整版 PDF 开放下载!

卧槽!阿里大佬总结的《图解Java》火了,完整版PDF开放下载!

字节跳动总结的设计模式 PDF 火了,完整版开放下载!

欢迎添加程序汪个人微信 itwang008  进粉丝群或围观朋友圈

浏览 39
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报