IText生成PDF

一、场景

在做单位OA项目的时候有个功能,合同打印的功能,之前的想法是打印PDF。既然是打印PDF就需要用到PDF插件,java比较常用方便的插件有几种,我选择了IText,当然IText版本众多,也让我走了不少的弯路。

二、实现方法

要说实现方式IText有三种方式来生成pdf:
1、绘制
2、PDF模板
3、html转PDF
这三种方法各有千秋,从我试验的顺序开始说起

1、绘制

适用场景:
适用于内容较少的文字、表格或者图片生成PDF
优点:
可以做成多样结构的PDF,书写方便、不需要模板。
缺点:
对于复杂的多样PDF不适合、写样式会累死人。
实现:

1、初始化pdf

// 设置纸张的大小
Document document = new Document(PageSize.A4, 80, 80, 20, 36);
try {
    PdfWriter.getInstance(document, new FileOutputStream(distDir));
    document.open();
    Paragraph p = TemplateUtil.paragraphFonts("员工日常费用报销流程", Font.BOLD, 14);
    p.setAlignment(Element.ALIGN_CENTER);
    document.add(p);
    PdfPTable table = createTable(gaFlowNormalExpenseInfo,gaDetails,flowOpnions);
    document.add(table);
} catch (Exception e) {
    e.printStackTrace();
}

2、绘制表格

public PdfPTable createTable(GaFlowNormalExpenseInfo gaFlowNormalExpenseInfo,List gaDetails,List flowOpnions){
        PdfPTable table = new PdfPTable(4);
        table.setWidthPercentage(100);// 设置表格宽度为100%
        table.setSpacingBefore(15f);// 设置段落前间距
        table.setSpacingAfter(10f);
        table.setFooterRows(2);
        PdfPCell cell = TemplateUtil.customCell("员工日常费用报销", Font.BOLD);
        cell.setBackgroundColor(Color.LIGHT_GRAY);
        cell.setColspan(4);
        table.addCell(cell);
        cell = TemplateUtil.customCell("业务编号");
        table.addCell(cell);
        cell = TemplateUtil.customCell(gaFlowNormalExpenseInfo.getCode(),false,Font.NORMAL,7);
        cell.setColspan(3);
        table.addCell(cell);
        ……


        for(GaFlowNormalExpenseDetail detail : gaDetails){
            cell = TemplateUtil.customCell("发票时间");
            table.addCell(cell);
            cell = TemplateUtil.customCell(DateUtils.dateTimeToString(detail.getInvoiceTime()));
            table.addCell(cell);
            ……
        }


        return table;
    }

代码结构比较简单,但是打印出来的样子如果想做的特别美观需要慢慢的去写!


2、PDF模板

应用场景:适合模板文本较多,动态内容较少的,只需要填写个别的空,空的内容较少
优点:样子美观,开发容易便捷
缺点:针对于循环,或者大文本上不适合,不易于扩展
实现:
需要准备用于创建PDF模板下载地址:链接:http://pan.baidu.com/s/1kVbRyPD 密码:k8ky
首先用word编辑好需要的样式
然后转成PDF再用这个软件打开
点击编辑表单IText生成PDF_第1张图片
这里就会留出一个个坑,然后我们将这里的坑编好编号

//将需要填入模板的字段扔到Map(dataMap)里,distDir:生成的位置
ITextUtil.genPdfByTemplate("pdftemp/CLFBX_TEMPLETE_10.pdf", distDir,dataMap);

ITextUtil.java

package com.yinker.oa.sys.util;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.AcroFields;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfCopy;
import com.lowagie.text.pdf.PdfGState;
import com.lowagie.text.pdf.PdfImportedPage;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfStamper;
import com.lowagie.text.pdf.PdfWriter;

public class ITextUtil {
    // 所有者密码
    private static final String OWNERPASSWORD = "123123";
    private static final Logger logger = LoggerFactory.getLogger(ITextUtil.class);

    /**
     * 使用场景:同一个模板,打印多份,每份填充不同的内容
     * 
     * @例如:《共有人声明》,有几个共有人则打几份,虽然使用同一个模板,但是每一份都填充不同的信息。
     * @实现方式:先生成多个临时文件,然后复制过去
     * @param srcFile
     * @param distFile
     * @param dataList
     */
    public static void genPdfByTemplate(String srcFile, String distFile, List> dataList) throws Exception {
        List pdfList = new ArrayList();
        if (null == dataList) {
            return;
        }
        if (dataList.size() <= 1) { // 只需要打印一次时没必要循环,因为循环的话会产生多余文件
            if (dataList.size() == 0) { // 为
                dataList.add(new HashMap());
            }
            genPdfByTemplate(srcFile, distFile, dataList.get(0), false, "", false, "");
            return;
        }

        for (int i = 0; i < dataList.size(); i++) {
            Map dataMap = dataList.get(i);
            String tempDistFile = distFile + i;
            genPdfByTemplate(srcFile, tempDistFile, dataMap, false, "", false, "");
            pdfList.add(tempDistFile);
        }
        OutputStream out = new FileOutputStream(distFile);
        mergePdfFiles(pdfList, out);
    }

    public static void genPdfByTemplate(String srcFile, String distFile, Map dataMap) {
        genPdfByTemplate(srcFile, distFile, dataMap, false, "", false, "");
    }

    public static void genPdfByTemplate(String srcFile, String distFile, Map dataMap, boolean isWatermark, String wartermarkName) {
        genPdfByTemplate(srcFile, distFile, dataMap, isWatermark, wartermarkName, false, "");
    }

    public static void genPdfByTemplate(String srcFile, String distFile, Map dataMap, boolean isWatermark, String wartermarkName,
                    boolean isPwd, String pwd) {
        PdfReader reader = null;
        PdfStamper stamp = null;
        try {
            reader = new PdfReader(srcFile);
            // 模版文件目录
            stamp = new PdfStamper(reader, new FileOutputStream(distFile)); // 生成的输出流
            // 文字水印
            if (isWatermark) {
                addWatermark(stamp, wartermarkName);
            }
            // 设置密码
            if (isPwd) {
                stamp.setEncryption(pwd.getBytes(), OWNERPASSWORD.getBytes(), PdfWriter.ALLOW_SCREENREADERS, false);
            }

            AcroFields s = stamp.getAcroFields();
            for (Map.Entry entry : dataMap.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                if (null != value) {
                    s.setField(key, value.toString());
                }
            }

            stamp.setFormFlattening(true); // 这句不能少
        } catch (IOException e) {
            logger.error("生成pdf文件失败:srcFile:" + srcFile + ";desFile:" + distFile, e);
        } catch (DocumentException e) {
            logger.error("生成pdf文件失败:srcFile:" + srcFile + ";desFile:" + distFile, e);
        } finally {
            try {
                stamp.close();
                reader.close();
            } catch (DocumentException e) {
                logger.error("生成pdf文件失败:srcFile:" + srcFile + ";desFile:" + distFile, e);
            } catch (IOException e) {
                logger.error("生成pdf文件失败:srcFile:" + srcFile + ";desFile:" + distFile, e);
            }
        }
    }

    /**
     * 复制pdf文档(根据多个文件流直接生成PDF文件)
     * 
     * @param sourceFileList
     *            源文件列表
     * @param targetFile
     *            目标文件
     */
    public static void copyPdf(List<byte[]> sourceFileList, String targetFile) throws Exception {
        try {
            Document document = new Document();
            PdfCopy copy = new PdfCopy(document, new FileOutputStream(targetFile));
            document.open();
            for (byte[] sourceFile : sourceFileList) {
                PdfReader pdfReader = new PdfReader(sourceFile);
                int n = pdfReader.getNumberOfPages();
                for (int i = 1; i <= n; i++) {
                    document.newPage();
                    PdfImportedPage page = copy.getImportedPage(pdfReader, i);
                    copy.addPage(page);
                }
            }
            document.close();
        } catch (IOException e) {
            logger.error("根据多个文件流直接生成pdf文件失败:targetFile:" + targetFile, e);
            throw new Exception("根据多个文件流直接生成pdf文件失败:targetFile:" + targetFile);
        } catch (DocumentException e) {
            logger.error("根据多个文件流直接生成pdf文件失败:targetFile:" + targetFile, e);
            throw new Exception("根据多个文件流直接生成pdf文件失败:targetFile:" + targetFile);
        }
    }

    /**
     * 复制pdf文档
     * 
     * @param sourceFile
     *            源文件
     * @param targetFile
     *            目标文件
     */
    public static void copyPdf(byte[] sourceFile, String targetFile) throws Exception{
        PdfReader pdfReader;
        try {
            pdfReader = new PdfReader(sourceFile);
            PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileOutputStream(targetFile));
            pdfStamper.close();
        } catch (IOException e) {
            logger.error("复制pdf文件失败:targetFile:" + targetFile, e);
            throw new Exception("复制pdf文件失败:targetFile:" + targetFile);
        } catch (DocumentException e) {
            logger.error("复制pdf文件失败:targetFile:" + targetFile, e);
            throw new Exception("复制pdf文件失败:targetFile:" + targetFile);
        }
    }

    /**
     * 创建pdf文件
     * 
     * @param writer
     * @param document
     * @param headerStr
     * @param footerStr
     */
    public static PdfWriter createPdf(String tempFile, Document document, String headerLeft, String headerRight) {
        // 横线
        PdfWriter writer = null;
        try {
            writer = PdfWriter.getInstance(document, new FileOutputStream(tempFile));
        } catch (DocumentException e) {
            logger.error("创建pdf文件失败:tempFile:" + tempFile, e);
        } catch (FileNotFoundException e) {
            logger.error("创建pdf文件失败:tempFile:" + tempFile, e);
        }

        return writer;
    }

    /**
     * 多个PDF合并功能
     * 
     * @param files多个PDF的文件路径
     * @param os生成的输出流
     */
    public static void mergePdfFiles(List pdfList, OutputStream os) {
        try {
            Document document = new Document(new PdfReader(pdfList.get(0)).getPageSize(1));
            PdfCopy copy = new PdfCopy(document, os);
            document.open();
            for (int i = 0; i < pdfList.size(); i++) {
                PdfReader reader = new PdfReader(pdfList.get(i));
                int n = reader.getNumberOfPages();
                for (int j = 1; j <= n; j++) {
                    document.newPage();
                    PdfImportedPage page = copy.getImportedPage(reader, j);
                    copy.addPage(page);
                }
            }
            document.close();
        } catch (IOException e) {
            logger.error("多个PDF合并失败:", e);
        } catch (DocumentException e) {
            logger.error("多个PDF合并失败:", e);
        } finally {
            try {
                os.close();
            } catch (IOException e) {
                logger.error("多个PDF合并失败:", e);
            }
        }
    }

    /**
     * 添加水印
     * 
     * @param pdfStamper
     * @param waterMarkName
     */
    public static void addWatermark(PdfStamper pdfStamper, String waterMarkName) {
        PdfContentByte content = null;
        BaseFont base = null;
        Rectangle pageRect = null;
        PdfGState gs = new PdfGState();
        try {
            // 设置字体
            base = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED);
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            if (base == null || pdfStamper == null) {
                return;
            }
            // 设置透明度为0.4
            gs.setFillOpacity(0.1f);
            gs.setStrokeOpacity(0.1f);
            int toPage = pdfStamper.getReader().getNumberOfPages();
            for (int i = 1; i <= toPage; i++) {
                pageRect = pdfStamper.getReader().getPageSizeWithRotation(i);
                // 计算水印X,Y坐标
                float x = pageRect.getWidth() / 2;
                float y = pageRect.getHeight() / 2;
                // 获得PDF最顶层
                content = pdfStamper.getOverContent(i);
                content.saveState();
                // set Transparency
                content.setGState(gs);
                content.beginText();
                content.setFontAndSize(base, 60);
                // 水印文字成45度角倾斜
                content.showTextAligned(Element.ALIGN_CENTER, waterMarkName, x, y, 45);
                content.endText();
            }
        } catch (Exception ex) {
            logger.error("添加水印失败:", ex);
        } finally {
            content = null;
            base = null;
            pageRect = null;
        }
    }
}

3、html转PDF

实现思路:
利用volicity模板引擎生成HTML代码(适应各种复杂结构的变化),然后利用IText将HTML文件转成PDF文件
优点:方便,快捷,使用样式比较复杂的输出,适用于网页另存为pdf的转存方式
缺点:对中文的支持需要替换一下jar包
实现:
1、所需maven

<dependency>
    <groupId>com.lowagie.textgroupId>
    <artifactId>iTextAsianartifactId>
    <version>1.0version>
dependency>

<dependency>
    <groupId>com.lowagiegroupId>
    <artifactId>itextartifactId>
    <version>2.0.8version>
dependency>

<dependency>
    <groupId>org.xhtmlrenderergroupId>
    <artifactId>core-rendererartifactId>
    <version>R8version>
dependency>

2、volicity模板生成方法

private String createHtml(GaFlowContractExamineApply GaFlowContractExamineApply,List opnions,String distFileName){
        PmParamService pmParamService = (PmParamService)SpringBeanContext.getBean("pmParamService");
        PmParam pmParam = pmParamService.selectByCode("tempFilePath");
        Properties p = new Properties();  
        p.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, "");  
        p.setProperty(Velocity.ENCODING_DEFAULT, "UTF-8");  
        p.setProperty(Velocity.INPUT_ENCODING, "UTF-8");  
        p.setProperty(Velocity.OUTPUT_ENCODING, "UTF-8");  
        String htmlPath="";
        try {  
            String path =ServletActionContext.getServletContext().getRealPath("/pdfjs-dist/web/");
            Velocity.init(p);  
            Template template = Velocity.getTemplate(path+"/vm/HTSPSQ.vm");  
            VelocityContext context = new VelocityContext();  
            context=putContext(GaFlowContractExamineApply, opnions);
            String fileLocation = pmParam.getValue() + "/htmltemp/";
            try {
                if (!(new File(fileLocation).isDirectory())) {
                    new File(fileLocation).mkdir();
                }
            } catch (SecurityException e) {
                e.printStackTrace();
            }
            FileOutputStream fos = new FileOutputStream(fileLocation +distFileName + ".html");  
            htmlPath=fileLocation +distFileName + ".html";
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fos, "UTF-8"));//设置写入的文件编码,解决中文问题  
            template.merge(context, writer);  
            writer.close();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return htmlPath;
    }

volicity模板。ps:图片路径我是从后台传过来的

  
<html xmlns="http://www.w3.org/1999/xhtml">  
<head>
    <meta charset="UTF-8" />
    <title>Documenttitle>
    <style>
        *{margin: 0;padding: 0;}
        body{
            font-family:"Microsoft YaHei";
            width:664px;
            margin:0 auto;
        }
        h5{
            width: 100%;
            font-weight: 900;
            text-align: center;
            font-size: 26px;
            padding: 16px 0;
            background: url($basePath) no-repeat 0 8px;
            background-size: 180px 35px;

        }
        …省略…
    style>
head>
<body>
    <h5>合同审批申请h5>
    <table >
        <tbody>
            <tr>
              <td class="titL"><span>所属事业部span>td>
              <td colspan="2" class="contL cont"><span>$SSSYBspan>td>
              <td class="titR"><span>合同编号span>td>
              <td colspan="2" class="contR cont"><span>$HTBHspan>td>
            tr>
            <tr>
              <td class="titL"><span>所属机构span>td>
              <td colspan="2" class="contL cont"><span>$SSJGspan>td>
              <td class="titR"><span>二级部门span>td>
              <td colspan="2" class="contR cont"><span>$EJBMspan>td>
            tr>
            …此处省略…
            #foreach($option in $OPTIONS)
            <tr class="dataCont">
              <td class="titL"><span>$option.flowNodeNamespan>td>
              <td colspan="5" class="longCont cont">
                <span>
                #if (!$option.opnion)
                #end
                #if ($option.opnion)
                $option.opnion
                #end
                span>
               td>
            tr>
            #end
          tbody>
        table>
body>
html>

3、html转pdf方法

private void html2Pdf(String htmlPath, String pdfPath) throws Exception {
        try{
        String url = new File(htmlPath).toURI().toURL().toString();
        OutputStream os = new FileOutputStream(pdfPath);
        ITextRenderer renderer = new ITextRenderer();
        renderer.setDocument(url);

        // 解决中文支持问题
        ITextFontResolver fontResolver = renderer.getFontResolver();
        //宋体
        fontResolver.addFont(ServletActionContext.getServletContext().getRealPath("/pdfjs-dist/web/vm/")+"/simsun.ttf",BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        //微软雅黑
        fontResolver.addFont(ServletActionContext.getServletContext().getRealPath("/pdfjs-dist/web/vm/")+"/msyh.ttf",BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

        renderer.layout();
        renderer.createPDF(os);

        os.close();
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }

这种方法说白了也没有什么难点,但是有几个地方我捉摸了挺长时间才走通
PS
①:中文换行的问题,由于老外的字体都是以空格来分词的,所以之前的程序是遇到空格如果到边界就会换行,中文则不是,所以对这个地方进行了优化
②:中文字体,中文字体需要服务器上有这个字体多个字体需要多次添加
③:图片路径问题,之前按照百度以及Google的方法试验了很久,一直没走通,windows下毫无压力,Linux环境下的图片设置文件根目录会有问题,所以索性,所有的图片都搞成了绝对路径的方式回填到html中,大功告成!

转载请注明出处

你可能感兴趣的:(Spring,IText,PDF,HTML转PDF)