前不久工作中遇到这样的需求:点击列表数据要将页面上展示的列表数据导出为PDF文件进行下载,如果将HTML
页面转化为PDF文件,这样数据会导出不全面,所以只能能后台数据库取出数据直接进行生成
(不论全面与否均可利用iText5实现)。
利用开源的API iText5 将数据导出为PDF,在此做点总结:
(一)自定义PdfPageEventHelper
iText5中并没有之前版本HeaderFooter对象设置页眉和页脚,可以利用PdfPageEventHelper来完成页眉页脚
及相关水印的设置工作。
PdfPageEventHelper中包含以下事件处理器:
onOpenDocument() — 当打开一个文档时触发,可以用于初始化文档的全局变量。
onStartPage() — 当一个页面初始化时触发,可用于初始化页面的设置参数,但是注意这个函数触发时,
该页面并没有创建好,不能利用这个函数添加内容,应该利用onEndPage()进行页面内容初始化。
onEndPage() — 在创建一个新页面完成但写入内容之前触发,是添加页眉、页脚、水印等最佳时机。
onCloseDocument() — 在文档关闭之前触发,可以用于释放一些资源。
/**
* @author : ShiLei
* @time :2018年5月7日 上午10:06:44
* @introduction : 自定义PdfPageEventHelper:iText5中并没有之前版本HeaderFooter对象设置页眉和页脚,可以利用PdfPageEventHelper来完成页眉页脚及相关水印的设置工作。
*/
import java.io.IOException;
import com.itextpdf.text.BadElementException;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
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 MyPdfPageEventHelper extends PdfPageEventHelper {
// 页眉
public String yeMei;
// 页脚签名
public String footerDesc;
// 纸张大小
public Rectangle pageSize;
// 模板
public PdfTemplate template;
// 设置 中文
public BaseFont baseFont;
// 字体 样式
public Font font;
// 字体大小
public static final int fontSize=12;
// 文字水印内容
public String waterMarkText;
// 水印图片路径
public String imgUrl;
/**
*
* @param yeMei
* 页眉详情描述
* @param footerDesc
* 页脚签名
* @param baseFont
* 中文字体 基本对象
* @param pageSize
* 页面文档大小,A4,A5,A6横转翻转等Rectangle对象
* @param waterMarkText
* 文字水印内容
* @param imgUrl
* 水印图片路径
*/
public MyPdfPageEventHelper(String yeMei,String footerDesc, BaseFont baseFont, Rectangle pageSize) {
this.yeMei = yeMei;
this.footerDesc = footerDesc;
this.pageSize = pageSize;
this.baseFont = baseFont;
}
public MyPdfPageEventHelper(String yeMei,String footerDesc, BaseFont baseFont, Rectangle pageSize,String waterMarkText) {
this.yeMei = yeMei;
this.footerDesc = footerDesc;
this.baseFont = baseFont;
this.pageSize = pageSize;
this.waterMarkText=waterMarkText;
}
public MyPdfPageEventHelper(String yeMei, String footerDesc,String imgUrl,BaseFont baseFont, Rectangle pageSize) {
this.yeMei = yeMei;
this.footerDesc = footerDesc;
this.imgUrl=imgUrl;
this.baseFont = baseFont;
this.pageSize = pageSize;
}
// 文档打开时创建模板
public void onOpenDocument(PdfWriter writer, Document document) {
// 添加所生成的pdf文件 信息
document.addSubject("GCX表格数据生成PDF");
document.addCreationDate();
document.addCreator("GCX");
template = writer.getDirectContent().createTemplate(50, 50);// 共 页 的矩形的长宽高
}
// 关闭每页的时候,写入页眉,写入'第几页共'这几个字。
public void onEndPage(PdfWriter writer, Document document) {
// 设置所支持的中文字体的 字体样式大小
font = new Font(baseFont, fontSize, Font.NORMAL);
// 获取PdfContentByte
PdfContentByte cb = writer.getDirectContent();
// 1.写入页眉
ColumnText.showTextAligned(cb, Element.ALIGN_LEFT, new Phrase(yeMei,font), document.left(), document.top() + 20, 0);
// 2.写入前半部分的 第 X页/共
int pageS = writer.getCurrentPageNumber();
String foot1 = "第 " + pageS + " 页 /共";
Phrase footer = new Phrase(foot1, font);
// 3.计算前半部分的foot1的长度,后面好定位最后一部分的'Y页'这俩字的x轴坐标,字体长度也要计算进去 = len
float len = baseFont.getWidthPoint(foot1, fontSize);
// 4.设置 页脚签名
Phrase footerLeft = new Phrase(footerDesc, font);
ColumnText.showTextAligned(cb, Element.ALIGN_LEFT, footerLeft, document.left(), document.bottom() - 20, 0);
// 5.写入页脚1
// x轴就是(右margin+左margin + right() -left()- len)/2.0F 再给偏移20F适合人类视觉感受,否则肉眼看上去太偏左 ,
// y轴就是底边界-20,否则容易与页脚签名重叠;注意Y轴是从下往上累加的,最上方的Top值是大于Bottom好几百开外的。
ColumnText.showTextAligned(cb, Element.ALIGN_CENTER, footer,
(document.rightMargin() + document.right() + document.leftMargin() - document.left() - len) / 2.0F + 20F, document.bottom() - 20, 0);
// 6.写入页脚2的模板(就是页脚的Y页这俩字)添加到文档中,计算模板的和Y轴,X=(右边界-左边界 - 前半部分的len值)/2.0F + len , y 轴和之前的保持一致,底边界-20
cb.addTemplate(template, (document.rightMargin() + document.right() + document.leftMargin() - document.left()) / 2.0F + 20F, document.bottom() - 20); // 调节模版显示的位置
// 添加图片水印
if (!"".equals(imgUrl) && imgUrl!=null) {
try {
Image image = Image.getInstance(imgUrl);
for(int i=1; i<= writer.getPageNumber(); i++){
PdfContentByte content = writer.getDirectContentUnder();
try {
content.addImage(getSingletonWaterMarkImage(image,20f,40f));
} catch (DocumentException e) {
e.printStackTrace();
}
}
} catch (BadElementException | IOException e) {
e.printStackTrace();
}
}
// 添加文字水印
if (!"".equals(waterMarkText) && waterMarkText!=null) {
for(int i=1; i<= writer.getPageNumber(); i++){
PdfContentByte content = writer.getDirectContentUnder();
content.beginText();
content.setColorFill(BaseColor.CYAN);// 文字水印 颜色
content.setFontAndSize(baseFont,38);// 文字水印 字体及字号
content.setTextMatrix(300, 350);// 文字水印 起始位置
content.showText(waterMarkText);
content.endText();
}
}
}
// 关闭文档时,替换模板,完成整个页眉页脚组件
public void onCloseDocument(PdfWriter writer, Document document) {
// 7.关闭文档的时候,将模板替换成实际的 Y 值
template.beginText();
template.setFontAndSize(baseFont, fontSize);// 生成的模版的字体、颜色
String foot2 = " " + (writer.getPageNumber()) + " 页";
template.showText(foot2);// 模版显示的内容
template.endText();
template.closePath();
}
//调整图片位置
public Image getSingletonWaterMarkImage(Image waterMarkImage,float xPosition,float yPosition){
waterMarkImage.setAbsolutePosition(xPosition, yPosition);//坐标
waterMarkImage.setRotation(-20);//旋转 弧度
waterMarkImage.setRotationDegrees(-45);//旋转 角度
waterMarkImage.scalePercent(100);//依照比例缩放
return waterMarkImage;
}
}
(二)在程序中触发该自定义PdfPageEventHelper的一系列事件:
MyPdfPageEventHelper helper = new MyPdfPageEventHelper();
writer.setPageEvent(helper);
@GetMapping(path="/toPdf")
@ApiOperation(value = "数据表格PDF生成")
public void tableToPdf(HttpServletResponse response,Statistic stat) throws ParseException, DocumentException, IOException{
// 默认的iText5字体设置不支持中文字体,需要下载远东字体包,否则不能往PDF文档中输出中文字体。
BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
Font titleCN = new Font(bfChinese, 17,Font.NORMAL);
Font contentCN = new Font(bfChinese, 12,Font.NORMAL);
String[] titles = {"时间","新增条目数"};
//获取数据集合 及 数据总数
MyResult result = dataStatistics.statisticAnalysis(stat);
@SuppressWarnings("unchecked")
List<Map<String, Object>> data = (List<Map<String, Object>>)result.getData();
int total = result.getCount();
try {
// step 1 创建文档
Document document = new Document();
// step 2 设置下载头 并将PdfWriter定向到response的输出流上
// response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment;filename="+ new String(("表格数据生成PDF"+System.currentTimeMillis() + ".pdf").getBytes(), "iso-8859-1"));
PdfWriter writer = PdfWriter.getInstance(document, response.getOutputStream());
// step 3 通过setPageEvent事件 添加页眉,页脚,分页及水印等信息
MyPdfPageEventHelper helper = new MyPdfPageEventHelper("GCX有限公司","GCX您一生的信用记录者",bfChinese,PageSize.A4);
writer.setPageEvent(helper);
// step 3 开启文档
document.open();
// step 4 设置基本表格样式 及 添加列数据
PdfPTable table = new PdfPTable(2); //指定为2列
table.setWidthPercentage(100); // 自定义宽度 100%
table.setSpacingBefore(10f);
table.setSpacingAfter(10f);
float[] columnWidths = {1f, 1f};//设置列宽
table.setWidths(columnWidths);
PdfPCell cell = createCell("部门:"+stat.getDep()+" 数据总数(条):"+total,titleCN);
cell.setRowspan(1);
cell.setColspan(2);
table.addCell(cell);
//时间
table.addCell(createCell(titles[0],titleCN));
//新增条目数
table.addCell(createCell(titles[1],titleCN));
for(int i = 0;i < data.size();i++){
Map<String, Object> map = data.get(i);
table.addCell(createCell(map.get("time"),contentCN));
table.addCell(createCell(map.get("count"),contentCN));
}
//step 5 将表格挂载到文档上 并进行资源关闭
document.add(table);
document.close();
writer.close();
System.out.println( "PDF Created!" );
} catch (Exception e) {
e.printStackTrace();
}
}
//生成单元格
public static PdfPCell createCell(Object value,Font font) {
PdfPCell cell = new PdfPCell(new Paragraph(value.toString(),font));
cell.setBorderColor(BaseColor.BLACK);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
return cell;
}
(三)需引入依赖
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.12</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
(四)注意 "STSong-Light' with 'UniGB-UCS2-H' is not recognized"问题原因:
iText5.x版本以上中的font和encoding文件都是从String RESOURCE_PATH = “com/itextpdf/text/pdf/fonts/”
加载的,而itextasian1.5.x.jar的包名是com.lowagie.text.pdf.fonts, 包名不一致,导致路径错误。
引入上述itext-asian依赖即可完美解决。
参考:
https://www.cnblogs.com/chenpi/p/5534595.html
http://www.anyrt.com/blog/list/itextpdf.html
http://fruitking.iteye.com/blog/1961421
https://blog.csdn.net/younkerjqb/article/details/14125955