利用IText和Velocity 生成PDF

  Java中,生成PDF的方式有很多种:ITextPdf、Apache POI、Apache PdfBox等。本文介绍如何使用ITextPdf和Velocity生成PDF版式文件。

  · Maven依赖

<!-- IText -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.6</version>
</dependency>
<dependency>
    <groupId>com.itextpdf.tool</groupId>
    <artifactId>xmlworker</artifactId>
    <version>5.5.6</version>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-asian</artifactId>
    <version>5.2.0</version>
</dependency>
<!-- IText -->
<!-- velocity -->
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity</artifactId>
    <version>1.7</version>
</dependency>
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-tools</artifactId>
    <version>2.0</version>
</dependency>
<!-- velocity -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>

  · 工具类代码

  工具类代码分为PdfConstants、PdfFooterHelper、VMSpecialCharacterReference、PdfGeneratorUtil四个类。

  PdfConstants 主要包含工具类常量汇总:

package com.arhorchin.securitit.files.pdf;

public class PdfConstants {

    /**
     * 在用字体.
     */
    public final static String PDF_FONT = "STSong-Light";
    
    /**
     * 在用字体.
     */
    public final static String PDF_FONT_ENCODING = "UniGB-UCS2-H";

    /**
     * 字符集.
     */
    public final static String PDF_CHARSET = "UTF-8";
    
    /**
     * VM模板路径.
     */
    public final static String PDF_VM_DIR = "template";
    
    /**
     * VM加载模板方式.
     */
    public final static String VM_LOADER_CLASSPATH = "file.resource.loader.class";
    
    /**
     * VM加载模板方式.
     */
    public final static String VM_LOADER_CLASSPATH_VAL = "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader";
    
    /**
     * VM日志记录方式.
     */
    public final static String VM_LOGSYSTEM_CLASS = "runtime.log.logsystem.class";
    
    /**
     * VM日志记录方式.
     */
    public final static String VM_LOGSYSTEM_CLASS_VAL = "org.apache.velocity.runtime.log.NullLogChute";
    
    /**
     * VM 转义处理.
     */
    public final static String VM_REFERENCE_CLASS = "eventhandler.referenceinsertion.class";
    
    /**
     * VM 转义处理.
     */
    public final static String VM_REFERENCE_CLASS_VAL = "com.arhorchin.securitit.files.pdf.VMSpecialCharacterReference";
    
    /**
     * VM html标签处理.
     */
    public final static String VM_ESCAPE_HTML_MATCH = "eventhandler.escape.html.match";
    
    /**
     * VM html标签处理.
     */
    public final static String VM_ESCAPE_HTML_MATCH_VAL = "/.*/";

}

  PdfFooterHelper 负责绘制PDF文件的页脚,主要是’第N页/共N页‘格式的数据:

package com.arhorchin.securitit.files.pdf;

import com.itextpdf.text.Document;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfPageEventHelper;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;

public class PdfFooterHelper extends PdfPageEventHelper {

    /**
     * PdfTemplate.
     */
    private PdfTemplate pdfTemplate;

    /**
     * 字体.
     */
    private BaseFont baseFont = null;

    /**
     * 利用基础字体生成的字体对象,一般用于生成中文文字.
     */
    private Font fontDetail = null;

    /**
     * 字体大小.
     */
    private int presentFontSize = 12;

    /**
     * 文档打开事件.
     */
    @Override
    public void onOpenDocument(PdfWriter writer, Document document) {
        // 创建页码写入范围矩形.形式为:第n页/共n页.
        pdfTemplate = writer.getDirectContent().createTemplate(200, 200);
    }

    /**
     * 某页开始事件.
     */
    @Override
    public void onStartPage(PdfWriter writer, Document document) {

    }

    /**
     * 某页结束事件.
     */
    @Override
    public void onEndPage(PdfWriter writer, Document document) {
        int currentPage = 0;
        String prefixText = null;
        Phrase footer = null;
        float prefixTextLen = 0f;
        PdfContentByte contentByte = null;

        try {
            // 初始化字体实例.
            if (baseFont == null) {
                baseFont = BaseFont.createFont(PdfGeneratorUtil.commFont, PdfGeneratorUtil.commFontEncoding,
                        BaseFont.NOT_EMBEDDED);
            }
            if (fontDetail == null) {
                fontDetail = new Font(baseFont, presentFontSize, Font.NORMAL);
            }
            // 页面页眉处理.
            /*
             * ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_LEFT, new Phrase(header, fontDetail),
             * document.left(), document.top() + 20, 0);
             */
            // 获取当前页码.
            currentPage = writer.getPageNumber();
            // 设置前半部分文本.格式为:第n页/共.
            prefixText = "第 " + currentPage + " 页 / 共";
            // 初始化页脚实例.
            footer = new Phrase(prefixText, fontDetail);
            // 计算前半部分文办长度,注意字体大小对文本长度的影响.
            prefixTextLen = baseFont.getWidthPoint(prefixText, presentFontSize);
            // 获取当前PdfContentByte实例.
            contentByte = writer.getDirectContent();
            // 写入前半部分内容,主要涉及x轴坐标计算:(右margin+左margin + right() -left()- len)/2.0F + 30F.
            ColumnText.showTextAligned(
                    contentByte, Element.ALIGN_CENTER, footer, (document.rightMargin() + document.right()
                            + document.leftMargin() - document.left() - prefixTextLen) / 2.0F + 30F,
                    document.bottom() - 5, 0);
            // 计算后半部分需要长度.
            contentByte.addTemplate(pdfTemplate,
                    (document.rightMargin() + document.right() + document.leftMargin() - document.left()) / 2.0F + 30F,
                    document.bottom() - 5);
        } catch (Exception ex) {
            throw new RuntimeException("PdfFooterHelper.onEndPage." + ex.getMessage());
        }

    }

    /**
     * 文档关闭事件.
     */
    @Override
    public void onCloseDocument(PdfWriter writer, Document document) {
        String suffixText = null;

        // 最后一步了,就是关闭文档的时候,将模板替换成实际的 Y 值,至此,page x of y 制作完毕,完美兼容各种文档size.
        pdfTemplate.beginText();
        // 设置模板字体和字体大小.
        pdfTemplate.setFontAndSize(baseFont, presentFontSize);
        // 设置前半部分文本.格式为:n页.
        suffixText = " " + (writer.getPageNumber() - 1) + " 页";
        // 模版显示的内容.
        pdfTemplate.showText(suffixText);
        pdfTemplate.endText();
        pdfTemplate.closePath();
    }

}

  VMSpecialCharacterReference 负责数据内容转义,防止出现不可解析的情况:

package com.arhorchin.securitit.files.pdf;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.velocity.app.event.implement.EscapeReference;

public class VMSpecialCharacterReference extends EscapeReference {

    /**
     * 特殊字符集.
     */
    private Map<Character, String> specialCharacterMap = new TreeMap<Character, String>() {

        /**
         * serialVersionUID.
         */
        private static final long serialVersionUID = -6562607647369006848L;

        {
            put('&', "&#38;");
            put('#', "&#35;");
            put('!', "&#33;");
            put('"', "&#34;");
            put('$', "&#36;");
            put('%', "&#37;");
            put('\'', "&#39;");
            put('(', "&#40;");
            put(')', "&#41;");
            put('*', "&#42;");
            put('+', "&#43;");
            put(',', "&#44;");
            put('-', "&#45;");
            put('.', "&#46;");
            put('/', "&#47;");
            put(':', "&#58;");
            put(';', "&#59;");
            put('<', "&#60;");
            put('=', "&#61;");
            put('>', "&#62;");
            put('?', "&#63;");
            put('@', "&#64;");
            put('[', "&#91;");
            put('\\', "&#92;");
            put(']', "&#93;");
            put('^', "&#94;");
            put('_', "&#95;");
            put('`', "&#96;");
            put('{', "&#123;");
            put('|', "&#124;");
            put('}', "&#125;");
            put('~', "&#126;");
        }
    };

    /**
     * Velocity特殊字符二层转义还原原意处理.
     */
    protected String escape(Object text) {
        String escapeStr = null;
        char[] escapeCharArr = null;
        char[] tmpEscapeCharArr = null;
        List<Character> tarEscapeCharArr = null;
        
        escapeStr = null == text ? "" : String.valueOf(text);
        escapeCharArr = escapeStr.toCharArray();
        tarEscapeCharArr = new ArrayList<Character>();
        
        for(int i = 0; i < escapeCharArr.length; i++) {
            if(specialCharacterMap.containsKey(escapeCharArr[i])) {
                tmpEscapeCharArr = specialCharacterMap.get(escapeCharArr[i]).toCharArray();
                for(int j = 0; j < tmpEscapeCharArr.length; j ++) {
                    tarEscapeCharArr.add(tmpEscapeCharArr[j]);
                }
            } else {
                tarEscapeCharArr.add(escapeCharArr[i]);
            }
        }
        tmpEscapeCharArr = new char[tarEscapeCharArr.size()];
        for(int i = 0; i < tarEscapeCharArr.size(); i++) {
            tmpEscapeCharArr[i] = tarEscapeCharArr.get(i);
        }
        
        escapeStr = new String(tmpEscapeCharArr);
        // escapeStr = StringEscapeUtils.escapeHtml(escapeStr);
        return escapeStr;
    }

    /**
     * @return attribute "eventhandler.escape.html.match"
     */
    protected String getMatchAttribute() {
        return "eventhandler.escape.html.match";
    }

}

  PdfGeneratorUtil 是主要工具类,负责Velocity模板读取、数据替换,以及根据转换后的Velocity模板生成PDF:

package com.arhorchin.securitit.files.pdf;

import java.io.ByteArrayOutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Map;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;

import com.itextpdf.text.Document;
import com.itextpdf.text.Font;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerHelper;

public class PdfGeneratorUtil {

    /**
     * 全局字体.
     */
    protected static String commFont = "";

    /**
     * 全局字体编码.
     */
    protected static String commFontEncoding = "";

    /**
     * 字体.
     */
    private String font = "";

    /**
     * 字体编码.
     */
    private String fontEncoding = "";

    /**
     * 字符集.
     */
    private String charset = "UTF-8";

    /**
     * 默认板式(横版).
     */
    private boolean isHorizontal = true;

    /**
     * 默认上下边距.
     */
    private int marginTop = 20;

    /**
     * 默认左右边距.
     */
    private int marginLeft = 20;

    /**
     * 是否需要页码.
     */
    private boolean isFooter = true;

    /**
     * 私有化构造函数.
     */
    private PdfGeneratorUtil() {

    }

    /**
     * 获取实例方法. @return.
     */
    public static PdfGeneratorUtil getInstance() {
        return new PdfGeneratorUtil();
    }

    /**
     * 根据指定模板名称和模板数据生成 PDF.
     * @param vmPath 模板名称.
     * @param templateMap 模板参数.
     * @return 已生成PDF数据.
     * @throws Exception.
     */
    public byte[] pdfCreator(String vmPath, Map<String, Object> templateMap) throws Exception {
        BaseFont baseFont = null;
        Document document = null;
        Reader reader = null;
        String templateStr = null;
        ByteArrayOutputStream baoResult = null;

        try {
            // 文件内容字体.
            baseFont = BaseFont.createFont(font, fontEncoding, BaseFont.NOT_EMBEDDED);
            // 设置文档格式.
            if (isHorizontal)
                document = new Document(PageSize.A4.rotate());
            else
                document = new Document(PageSize.A4);
            // 设置文档边距.
            document.setMargins(marginLeft, marginLeft, marginTop, marginTop);
            // 定义输出流接收输出.
            baoResult = new ByteArrayOutputStream();
            PdfWriter writer = PdfWriter.getInstance(document, baoResult);
            // 设置页脚页码.
            if (isFooter) {
                writer.setPageEvent(new PdfFooterHelper());
            }
            // 开始生成文档.
            document.open();
            // 生成模板内容.
            templateStr = vmProcessor(vmPath, templateMap);
            reader = new StringReader(templateStr);
            // 设置文档字体.
            document.add(new Paragraph("", new Font(baseFont)));
            // 用字节输出流接受信息.
            XMLWorkerHelper.getInstance().parseXHtml(writer, document, reader);
            document.close();
            if (null != writer) {
                writer.close();
            }
        } catch (Exception ex) {
            throw new Exception("PdfGeneratorUtil.pdfCreator." + ex.getMessage());
        } finally {
            if (null != baoResult) {
                baoResult.close();
            }
        }
        return baoResult.toByteArray();
    }

    /**
     * 根据VM模板生成包含数据内容.
     * @param vmPath 模板名称.
     * @param templateMap 模板参数.
     * @return 模板填充后数据.
     * @throws Exception.
     */
    public String vmProcessor(String vmPath, Map<String, Object> templateMap) throws Exception {
        String resultStr = null;
        VelocityEngine velocityEngine = null;
        Template velocityTemplate = null;
        VelocityContext velocityContext = null;
        StringWriter stringwriter = null;

        // 初始化velocity引擎.
        velocityEngine = new VelocityEngine();
        velocityEngine.setProperty(Velocity.ENCODING_DEFAULT, charset);
        velocityEngine.setProperty(Velocity.INPUT_ENCODING, charset);
        velocityEngine.setProperty(Velocity.OUTPUT_ENCODING, charset);
        velocityEngine.setProperty(PdfConstants.VM_LOADER_CLASSPATH, PdfConstants.VM_LOADER_CLASSPATH_VAL);
        velocityEngine.setProperty(PdfConstants.VM_LOGSYSTEM_CLASS, PdfConstants.VM_LOGSYSTEM_CLASS_VAL);
        velocityEngine.setProperty(PdfConstants.VM_REFERENCE_CLASS, PdfConstants.VM_REFERENCE_CLASS_VAL);
        velocityEngine.setProperty(PdfConstants.VM_ESCAPE_HTML_MATCH, PdfConstants.VM_ESCAPE_HTML_MATCH_VAL);
        velocityEngine.init();
        // 取得velocity的模版.
        velocityTemplate = velocityEngine.getTemplate(vmPath, charset);
        // 取得并设置velocity的上下文context.
        velocityContext = new VelocityContext();
        // 将数据写入上下文context.
        for (Map.Entry<String, Object> entry : templateMap.entrySet()) {
            velocityContext.put(entry.getKey(), entry.getValue());
        }
        // 模板数据填充.
        stringwriter = new StringWriter();
        velocityTemplate.merge(velocityContext, stringwriter);
        // 获取返回输出.
        resultStr = stringwriter.toString();
        if (null != stringwriter) {
            stringwriter.close();
        }
        return resultStr;
    }

    /**
     * 设置字体.
     * @param font 字体.
     * @return this.
     */
    public PdfGeneratorUtil setFont(String font) {
        this.font = font;
        PdfGeneratorUtil.commFont = font;
        return this;
    }

    /**
     * 设置字体编码.
     * @param fontEncoding 字体编码.
     * @return this.
     */
    public PdfGeneratorUtil setFontEncoding(String fontEncoding) {
        this.fontEncoding = fontEncoding;
        PdfGeneratorUtil.commFontEncoding = fontEncoding;
        return this;
    }

    /**
     * 设置字符集.
     * @param charset 字符集.
     * @return this.
     */
    public PdfGeneratorUtil setCharset(String charset) {
        this.charset = charset;
        return this;
    }

    /**
     * 设置版式,横版或竖版.
     * @param isHorizontal 是否横版.
     * @return this.
     */
    public PdfGeneratorUtil setIsHorizontal(boolean isHorizontal) {
        this.isHorizontal = isHorizontal;
        return this;
    }

    /**
     * 设置上下边距.
     * @param marginTop 上下边距.
     * @return this.
     */
    public PdfGeneratorUtil setMarginTop(int marginTop) {
        this.marginTop = marginTop;
        return this;
    }

    /**
     * 设置左右边距.
     * @param marginLeft 左右边距.
     * @return this.
     */
    public PdfGeneratorUtil setMarginLeft(int marginLeft) {
        this.marginLeft = marginLeft;
        return this;
    }

    /**
     * 设置是否需要页脚.
     * @param isFooter 是否需要页脚.
     * @return this.
     */
    public PdfGeneratorUtil setIsFooter(boolean isFooter) {
        this.isFooter = isFooter;
        return this;
    }

}

  测试类代码如下:

package com.arhorchin.securitit.com.files.pdf;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.io.FileUtils;

import com.arhorchin.securitit.files.pdf.PdfConstants;
import com.arhorchin.securitit.files.pdf.PdfGeneratorUtil;

public class PdfGeneratorUtilTester {

    public static void main(String[] args) throws Exception {
        byte[] pdfBts = null;
        Map<String, Object> vmParams = null;

        vmParams = new HashMap<String, Object>();
        // 设置模板参数.
        vmParams.put("name", "securitit");
        vmParams.put("language", "中文");
        vmParams.put("description",
                "本人是一个Java程序员,每天敲键盘度日,一直在循环,不信你看!本人是一个Java程序员,每天敲键盘度日,一直在循环,不信你看!本人是一个Java程序员,每天敲键盘度日,一直在循环,不信你看!本人是一个Java程序员,每天敲键盘度日,一直在循环,不信你看!");
        vmParams.put("date", "2020-07-17");
        // 生成PDF文件,并落地.
        pdfBts = PdfGeneratorUtil.getInstance().setIsHorizontal(false).setCharset(PdfConstants.PDF_CHARSET)
                .setFont(PdfConstants.PDF_FONT).setFontEncoding(PdfConstants.PDF_FONT_ENCODING).setMarginTop(30)
                .setMarginLeft(40).pdfCreator("files/pdf/demo.vm", vmParams);
        FileUtils.writeByteArrayToFile(new File("C:/Users/Administrator/Downloads/个人文件/demo.pdf"), pdfBts);
    }

}

  测试类中读取了一个Velocity模板,其内容如下:

<HTML>
<HEAD>
	<meta charset="utf-8"/>
	<style>
		body {
			font-family:STSONG; 
			font-size: 20px;
		}
		p {
			line-height: 30px;
		}
	</style>
</HEAD>
<BODY style="margin-left: 10px; margin-right: 10px;">
	<H1 width="100%" align="center">IText Velocity 生成PDF演示</H1>
	<p></p>
	<p>$!{name}</p>
	<p>$!{language}</p>
	<p>$!{description}</p>
	<p align="right">$!{date}</p>
</BODY>
</HTML>

  经过转换生成PDF文件如下:
在这里插入图片描述

  · 总结

  · Velocity模板中Html编写必须按照严格模式编写,Css尚未支持到3.0,书写时需谨慎,但仍能满足你几乎所有需求。

  · 生成PDF时,ITextePdf需要依赖字体,本例子使用的是’STSong-Light‘和’UniGB-UCS2-H‘,在无对应字体时,需先安装字体再进行生成,负责中文会无法转换,导致无法显示。

猜你喜欢

转载自blog.csdn.net/securitit/article/details/107441589