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('&', "&");
put('#', "#");
put('!', "!");
put('"', """);
put('$', "$");
put('%', "%");
put('\'', "'");
put('(', "(");
put(')', ")");
put('*', "*");
put('+', "+");
put(',', ",");
put('-', "-");
put('.', ".");
put('/', "/");
put(':', ":");
put(';', ";");
put('<', "<");
put('=', "=");
put('>', ">");
put('?', "?");
put('@', "@");
put('[', "[");
put('\\', "\");
put(']', "]");
put('^', "^");
put('_', "_");
put('`', "`");
put('{', "{");
put('|', "|");
put('}', "}");
put('~', "~");
}
};
/**
* 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‘,在无对应字体时,需先安装字体再进行生成,负责中文会无法转换,导致无法显示。