一、场景
在做单位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<GaFlowNormalExpenseDetail> gaDetails,List<FlowOpnion> 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模板
应用场景:适合模板文本较多,动态内容较少的,只需要填写个别的空,空的内容较少
优点:样子美观,开发容易便捷
缺点:针对于循环,或者大文本上不适合,不易于扩展
实现:
需要准备下载地址:链接:http://pan.baidu.com/s/1kVbRyPD 密码:k8ky
首先用word编辑好需要的样式
然后转成PDF再用这个软件打开
点击编辑表单
这里就会留出一个个坑,然后我们将这里的坑编好编号
//将需要填入模板的字段扔到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<Map<String, Object>> dataList) throws Exception {
List<String> pdfList = new ArrayList<String>();
if (null == dataList) {
return;
}
if (dataList.size() <= 1) { // 只需要打印一次时没必要循环,因为循环的话会产生多余文件
if (dataList.size() == 0) { // 为
dataList.add(new HashMap<String, Object>());
}
genPdfByTemplate(srcFile, distFile, dataList.get(0), false, "", false, "");
return;
}
for (int i = 0; i < dataList.size(); i++) {
Map<String, Object> 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<String, Object> dataMap) {
genPdfByTemplate(srcFile, distFile, dataMap, false, "", false, "");
}
public static void genPdfByTemplate(String srcFile, String distFile, Map<String, Object> dataMap, boolean isWatermark, String wartermarkName) {
genPdfByTemplate(srcFile, distFile, dataMap, isWatermark, wartermarkName, false, "");
}
public static void genPdfByTemplate(String srcFile, String distFile, Map<String, Object> 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<String, Object> 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<String> 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.text</groupId>
<artifactId>iTextAsian</artifactId>
<version>1.0</version>
</dependency>
<!-- itext用2.0.8版本,之前用过2.1.7的和core-renderer会有兼容性问题 -->
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<version>2.0.8</version>
</dependency>
<!--版本用R8,然而这里有个坑,中文不支持换行,so各种百度找到一个大牛改的jar包替换上就可以了
下载地址:链接:http://pan.baidu.com/s/1jHVNNbK 密码:hvoh
-->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>core-renderer</artifactId>
<version>R8</version>
</dependency>
2、volicity模板生成方法
private String createHtml(GaFlowContractExamineApply GaFlowContractExamineApply,List<FlowOpnion> 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:图片路径我是从后台传过来的
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8" />
<title>Document</title>
<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>$SSSYB</span></td>
<td class="titR"><span>合同编号</span></td>
<td colspan="2" class="contR cont"><span>$HTBH</span></td>
</tr>
<tr>
<td class="titL"><span>所属机构</span></td>
<td colspan="2" class="contL cont"><span>$SSJG</span></td>
<td class="titR"><span>二级部门</span></td>
<td colspan="2" class="contR cont"><span>$EJBM</span></td>
</tr>
…此处省略…
#foreach($option in $OPTIONS)
<tr class="dataCont">
<td class="titL"><span>$option.flowNodeName</span></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中,大功告成!
转载请注明出处