pom.xml引入组件itextpdf
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.4.2</version>
</dependency>
初始化全局变量
public static BaseFont chineseFont;
public static BaseFont chineseFont2;
public static Font bodyFootnoteBold;
public static Font bodyFontNormal;
public static Font paragraphFont;
static {
try {
chineseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
chineseFont2 = BaseFont.createFont(ScrmConfig.projectPath + "/font/msyhl.ttc"+",1", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
bodyFootnoteBold = new Font(chineseFont, 14, Font.BOLD, BaseColor.BLACK);
bodyFontNormal = new Font(chineseFont, 9, Font.NORMAL, BaseColor.BLACK);
paragraphFont = new Font(chineseFont, 12, Font.NORMAL, BaseColor.BLACK);
paragraphFont.setFamily("Simsun");
} catch (DocumentException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
定义生成报告的公共方法 generateReport,返回PDF,其中主要核心方法fillData
PDF定义:
@Data
@Builder
public class PDF {
// pdf字节流
private byte[] data;
// 页面宽度
private float width;
// 页面高度
private float height;
// 文件名
private String name;
@Builder.Default
private String extName = "pdf";
}
/**
* 根据pdf模板生成报告 返回字节流
* @param templatePath 模板文件路径
* @param template 模板文件名
* @param data 数据 bean对象或者map对象
* @param safety 是否设置权限
* @param password 设置权限的密码
* @param addPageNum 是否追加页码
* @param <T>
* @return
*/
public static <T> PDF generateReport(String templatePath, String template, T data, boolean safety, String password, boolean addPageNum){
if(StringUtils.isAnyBlank(templatePath, template)){
log.warn("模板路径或模板文件为空");
return null;
}
ByteArrayOutputStream bos = null;
PdfStamper pdfStamper = null;
try {
PdfReader templateReader = new PdfReader(templatePath+template);
bos = new ByteArrayOutputStream();
pdfStamper = new PdfStamper(templateReader, bos);
// 设置权限
if(safety) {
pdfStamper.setEncryption(null, password.getBytes(),
PdfWriter.ALLOW_PRINTING|PdfWriter.ALLOW_DEGRADED_PRINTING, PdfWriter.STANDARD_ENCRYPTION_128);
}
// 使用中文字体
ArrayList<BaseFont> fonts = Lists.newArrayList(chineseFont);
AcroFields fields = pdfStamper.getAcroFields();
fields.setSubstitutionFonts(fonts);
//pdfStamper.getWriter().setPageEvent(new PageNumPdfPageEvent());
// 填充模板数据
fillData(pdfStamper, fields, data);
// 必须要调佣这个,否则文档不会生成
pdfStamper.setFormFlattening(true);
try {
pdfStamper.close();
} catch (Exception e) {
//
}
// 返回pdf byte数组
Rectangle rectangle = templateReader.getPageSize(1);
PDF pdf = PDF.builder()
.data(bos.toByteArray())
.width(rectangle.getWidth())
.height(rectangle.getHeight())
.build();
// 添加页码
if(addPageNum) addPageNum(pdf, password);
return pdf;
} catch (IOException e) {
log.error("generate report failed. IOException[{}]", e.getMessage());
} catch (DocumentException e){
log.error("generate report failed. DocumentException[{}]", e.getMessage());
} finally {
try {
if(bos != null) bos.close();
if(pdfStamper != null) pdfStamper.close();
} catch (Exception e) {
log.error("stream close exception, [{}]", e.getMessage());
}
}
return null;
}
填充字段域方法 fillData 的参数data支持文本、图片、表格类型等
private static <T> void fillData(PdfStamper ps, AcroFields fields, T data){
if(fields == null || data == null) return;
Map<String, Object> dataMap = BeanUtils.transBeanToMap(data);
try {
for (String key : dataMap.keySet()){
Object object = dataMap.get(key);
if(object instanceof ImageField){
ImageField image = (ImageField) object;
fillImage(ps, fields, key, image.getImages());
}else if(object instanceof Table) {
Table table = (Table) object;
fillTable(ps, fields, key, table);
}else if(object instanceof TableField) {
TableField tables = (TableField) object;
fillTables(ps, fields, key, tables);
}else if(object instanceof RichField) {
fillRichText(ps, fields, key, (RichField) object);
}else if(object instanceof PdfField){
PdfField pdf = (PdfField)object;
fillPdf(ps, fields, key, pdf.getPdfs());
}else{
String value = CommonUtils.evalString(object);
fields.setField(key, value);
}
}
} catch (Exception e) {
log.error("fill data exception", e);
}
}
data各参数类型定义如下:
/**
* pdf图片域
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ImageField {
private List<byte[]> images = Lists.newLinkedList();
public ImageField(byte[] image){
if(images == null) images = Lists.newLinkedList();
images.add(image);
}
}
/**
* 表格
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Table<T> {
/**
* 表头
*/
private Map<String, Cell> head;
/**
* 数据列表
*/
private List<T> data;
/**
* 表格列宽比例
*/
private float[] colWidthRatio;
/**
* 是否显示边框
*/
private boolean borderFlag;
private BaseColor borderColor;
@Builder.Default
private float borderWidth = -1f;
private List<String> footnotes;
/**
* 单元格信息
* @param <T>
*/
@Data
@Builder
public static class Cell<T> {
private T value;
@Builder.Default
private int colNum = 1;
private Font font; //字体
/**
* 位置 如 {@link com.itextpdf.text.Element#ALIGN_CENTER}
*/
@Builder.Default
private int align = Element.ALIGN_CENTER;
private BaseColor backgroundColor; //背景色
private BaseColor borderColor;
@Builder.Default
private float paddingLeft = 2;
@Builder.Default
private float paddingRight = 2;
private Float height;
}
}
/**
* 表格列表
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TableField {
private List<Table> tables = Lists.newLinkedList();
public TableField(Table... table){
if(tables == null) tables = Lists.newLinkedList();
if (ArrayUtil.isNotEmpty(table)) {
tables.addAll(Arrays.stream(table).collect(Collectors.toList()));
}
}
}
/**
* 文本
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RichField {
private List<String> texts;
public RichField(String text){
if(texts == null) texts = Lists.newLinkedList();
texts.add(text);
}
}
/**
* pdf文件
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PdfField {
private List<PDF> pdfs = Lists.newLinkedList();
public PdfField(PDF pdf){
if(pdfs == null) pdfs = Lists.newLinkedList();
pdfs.add(pdf);
}
}
接下来看各参数类型数据填充方式实现
/**
* 填充图片数据
* @param ps
* @param fields
* @param field
* @param images
*/
private static void fillImage(PdfStamper ps, AcroFields fields, String field, List<byte[]> images){
if(CollUtil.isEmpty(images)) return;
List<AcroFields.FieldPosition> photograph = fields.getFieldPositions(field);
if(CollUtil.isNotEmpty(photograph)){
// 页面大小
PdfReader reader = ps.getReader();
Rectangle pageSize = reader.getPageSize(1);
// 当前页
int currentPageNum = photograph.get(0).page;
PdfContentByte content = ps.getOverContent(currentPageNum);
// 字段域大小
Rectangle rectangle = photograph.get(0).position;
float totalWidth = rectangle.getRight() - rectangle.getLeft() - 1;
PdfPTable pdfPTable = createTable(null, width(totalWidth, new float[]{1}), false);
for (byte[] data : images) {
try {
Image image = Image.getInstance(data, true);
// 通过这个比例来调整图片高度
float width = rectangle.getWidth()>image.getScaledWidth()?image.getScaledWidth():rectangle.getWidth();
float height = pageSize.getHeight()>image.getScaledHeight()?image.getScaledHeight():pageSize.getHeight();
float ratio1 = Arith.div(width, rectangle.getWidth());
float ratio2 = Arith.div(height, rectangle.getHeight());
//log.info(String.format("field:%s width:%f height:%f ratio1:%f ratio2:%f", field, width, height, ratio1, ratio2));
float ratio = ratio1 > ratio2 ? ratio2 : ratio1;
image.scaleToFit(new Rectangle(Arith.mul(width, ratio), Arith.mul(height, ratio)));
image.setBorder(1);
// 判断是否要新增页 并且写入数据
float resultHeight = pdfPTable.getTotalHeight()+image.getScaledHeight();
if(resultHeight >= pageSize.getHeight()){
ps.insertPage(currentPageNum+1, reader.getPageSizeWithRotation(1));
PdfContentByte canvas = ps.getOverContent(currentPageNum + 1);
PdfPTable newTable = createTable(null, width(totalWidth, new float[]{1}), false);
image.setAbsolutePosition(rectangle.getLeft(), rectangle.getBottom());
newTable.addCell(createImageCell(image));
image.setAbsolutePosition(rectangle.getLeft(), image.getHeight());
newTable.writeSelectedRows(0, -1, rectangle.getLeft(), rectangle.getTop(), canvas);
currentPageNum += 1;
}else {
image.setAbsolutePosition(rectangle.getLeft(), rectangle.getBottom());
pdfPTable.addCell(createImageCell(image));
}
} catch (BadElementException | IOException e){
log.error("fill image exception", e);
} catch (DocumentException e) {
e.printStackTrace();
}
};
pdfPTable.writeSelectedRows(0, -1, rectangle.getLeft(), rectangle.getTop(), content);
}
}
/**
* 往指定域生成多张表格,注意每个表上下需要相隔一定距离
* @param ps
* @param fields
* @param field
* @param tables
* @param <T>
*/
private static <T> void fillTables(PdfStamper ps, AcroFields fields, String field, TableField tables) {
List<AcroFields.FieldPosition> photograph = fields.getFieldPositions(field);
// 判断字段域是否有配置
if (CollUtil.isNotEmpty(photograph)) {
try {
PdfReader reader = ps.getReader();
// 页面位置
Rectangle pageSize = reader.getPageSize(1);
// 组件位置
Rectangle rectangle = photograph.get(0).position;
// 组件的TOP值
float fieldTop = rectangle.getTop();
// 当前页
int currentPageNum = photograph.get(0).page;
PdfContentByte content = ps.getOverContent(currentPageNum);
float maxHeight = fieldTop - pageSize.getBottom()- 108;
float totalWidth = rectangle.getRight() - rectangle.getLeft() - 1;
for (Table<T> tableData : tables.getTables()) {
PdfPTable table = createTable(tableData.getHead(), width(totalWidth, tableData.getColWidthRatio()),
tableData.isBorderFlag(), tableData.getBorderColor(), tableData.getBorderWidth());
for (int i=0, row=tableData.getData().size(); i<row; i++){
T data = tableData.getData().get(i);
if(tableData.getHead() == null || tableData.getHead().isEmpty()){
Map<String, Object> map = BeanUtils.transBeanToMap(data);
for (Map.Entry<String, Object> entry : map.entrySet()){
PdfPCell cell = createCell(false, entry.getValue(), bodyFontNormal, Element.ALIGN_CENTER,
null, tableData.getBorderColor(), tableData.getBorderWidth());
if (cell != null) {
table.addCell(cell);
}
}
} else {
for (String fieldName : tableData.getHead().keySet()) {
PdfPCell cell = createCell(true, BeanUtils.getProperties(data, fieldName), bodyFontNormal,
Element.ALIGN_CENTER, null, tableData.getBorderColor(), tableData.getBorderWidth());
if (cell != null) {
table.addCell(cell);
}
}
}
if (table.getTotalHeight() > maxHeight) {
// 如果表格行数小于2,则创建新页,表格不再创建
table.writeSelectedRows(0, -1, rectangle.getLeft(), fieldTop, content);
// 插入一新页 并重新创建一个表格
if ((i+1) <= row) {
ps.insertPage(currentPageNum + 1, reader.getPageSizeWithRotation(1));
content = ps.getOverContent(currentPageNum + 1);
}
if ((i+1) < row) {
table = createTable(tableData.getHead(), width(totalWidth, tableData.getColWidthRatio()),
tableData.isBorderFlag(), tableData.getBorderColor(), tableData.getBorderWidth());
} else {
table = null;
}
currentPageNum += 1;
fieldTop = pageSize.getTop() - 36;
maxHeight = fieldTop - pageSize.getBottom()- 72;
}
}
if (table != null) {
table.writeSelectedRows(0, -1, rectangle.getLeft(), fieldTop, content);
fieldTop = fieldTop - table.getTotalHeight() - 18;
maxHeight = fieldTop - pageSize.getBottom() - 72;
}
if (tableData.getFootnotes() != null) {
content.saveState();
content.beginText();
// 页脚的页码 展示
String footnote = tableData.getFootnotes().stream().collect(Collectors.joining());
Phrase phrase = new Phrase(footnote, bodyFootnoteBold);
// 页码的 横轴 坐标 居中
float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
// 页码的 纵轴 坐标
float y = pageSize.getBottom() + 36;
// 添加文本内容,进行展示页码
ColumnText.showTextAligned(content, Element.ALIGN_CENTER, phrase, x, y, 0);
content.endText();
content.restoreState();
}
}
} catch (Exception e) {
}
}
}
/**
* 填充段落
* @param ps
* @param fields
* @param field
* @param value
*/
private static void fillRichText(PdfStamper ps, AcroFields fields, String field, RichField value){
if(value == null || CollUtil.isEmpty(value.getTexts())) return;
List<AcroFields.FieldPosition> photograph = fields.getFieldPositions(field);
if(CollUtil.isNotEmpty(photograph)){
try {
Rectangle rectangle = photograph.get(0).position;
PdfContentByte content = ps.getOverContent((int) photograph.get(0).page);
float totalWidth = rectangle.getRight() - rectangle.getLeft() - 1;
PdfPTable pdfPTable = createTable(null, width(totalWidth, new float[]{1}), false);
value.getTexts().forEach(text -> {
pdfPTable.addCell(createParagraphCell(text));
});
pdfPTable.writeSelectedRows(0, -1, rectangle.getLeft(), rectangle.getTop(), content);
} catch (Exception e){
log.error("fill richtext exception", e);
}
}
}
/**
* 填充PDF文档
* @param ps
* @param fields
* @param field
* @param pdfs
*/
private static void fillPdf(PdfStamper ps, AcroFields fields, String field, List<PDF> pdfs) {
if(CollUtil.isEmpty(pdfs)) return;
List<AcroFields.FieldPosition> photograph = fields.getFieldPositions(field);
if(CollUtil.isNotEmpty(photograph)){
// 页面大小
PdfReader reader = ps.getReader();
PdfWriter writer = ps.getWriter();
Rectangle pageSize = reader.getPageSize(1);
// 当前页
int currentPageNum = photograph.get(0).page;
boolean first = true;
for (PDF pdf : pdfs) {
try {
PdfReader pdfReader = new PdfReader(pdf.getData());
for (int i=1,num=pdfReader.getNumberOfPages();i<=num;i++) {
if(!first) {
ps.insertPage(currentPageNum, pageSize);
}
PdfContentByte canvas = ps.getOverContent(currentPageNum);
PdfImportedPage page = writer.getImportedPage(pdfReader, i);
float scaleX = pageSize.getWidth() / page.getWidth();
float scaleY = pageSize.getHeight() / page.getHeight();
canvas.addTemplate(page, scaleX, 0, 0, scaleY, 0, 0);
currentPageNum += 1;
first = false;
}
} catch (Exception e) {
log.error("fill pdf exception", e);
}
}
}
}
其他一些方法:
private static float[] width(float totalWidth, float[] colWidthRatio) {
float[] result = new float[colWidthRatio.length];
for (int i=0; i<colWidthRatio.length; i++){
result[i] = Arith.mul(totalWidth, colWidthRatio[i]);
}
return result;
}
/**
* 创建表格
* @param head <字段名,字段中文名>表头信息
* @param width 列宽
* @param borderFlag 边框
* @param borderColor 边框颜色
* @param borderWidth 边框宽度
* @return
*/
private static PdfPTable createTable(Map<String, Table.Cell> head, float[] width, boolean borderFlag, BaseColor borderColor, float borderWidth) {
PdfPTable pdfPTable = new PdfPTable(width.length);
try {
pdfPTable.setSplitLate(false);
pdfPTable.setSplitRows(true);
pdfPTable.setTotalWidth(width);
pdfPTable.setLockedWidth(true);
pdfPTable.setHorizontalAlignment(Element.ALIGN_CENTER);
pdfPTable.getDefaultCell().setBorder(borderFlag?1:0);
if (borderWidth > -1) {
pdfPTable.getDefaultCell().setBorderWidth(borderWidth);
}
if (borderColor != null) {
pdfPTable.getDefaultCell().setBorderColor(borderColor);
}
if(head != null && !head.isEmpty()) {
for (Table.Cell title : head.values()) {
PdfPCell pdfPCell = createCell(true, title.getValue(), title.getFont(), Element.ALIGN_CENTER,
title.getBackgroundColor(), title.getBorderColor() == null ? borderColor :title.getBorderColor(), borderWidth);
if(pdfPCell != null) {
pdfPTable.addCell(pdfPCell);
}
}
}
} catch (Exception e) {
log.error("create table exception", e);
}
return pdfPTable;
}
/**
* 创建表格
* @param head <字段名,字段中文名>表头信息
* @param width 列宽
* @param borderFlag 边框
* @return
*/
private static PdfPTable createTable(Map<String, Table.Cell> head, float[] width, boolean borderFlag) {
return createTable(head, width, borderFlag, null, -1);
}
private static PdfPCell createParagraphCell(String text){
Paragraph paragraph = new Paragraph();
paragraph.setFirstLineIndent(12f);
paragraph.setFont(paragraphFont);
paragraph.add(text);
PdfPCell cell = new PdfPCell(paragraph);
cell.setBorder(0);
//cell.setPaddingTop(15.0f);
//cell.setPaddingBottom(8.0f);
cell.setLeading(2.25f, 2.25f);
return cell;
}
private static PdfPCell createImageCell(Image image){
PdfPCell cell = new PdfPCell(image);
cell.setBorder(0);
//cell.setPaddingTop(15.0f);
//cell.setPaddingBottom(8.0f);
cell.setLeading(0, 3.25f);
return cell;
}
/**
* 创建表格单元格
* @param value 单元格值,支持文本和图片
* @param font 字体
* @param align 对齐方式
* @param background 背景色,为空不设置
* @param <T>
* @return
*/
private static <T> PdfPCell createCell(boolean hasHead, T value, Font font, int align, BaseColor background, BaseColor borderColor, float borderWidth) {
PdfPCell cell = new PdfPCell();
if (hasHead) {
if (value == null) return null;
} else {
if (value == null) {
cell.setBorder(0);
return cell;
}
}
cell.setVerticalAlignment(cell.ALIGN_MIDDLE);
cell.setHorizontalAlignment(align);
cell.setNoWrap(false);
if (background != null) cell.setBackgroundColor(background);
if (borderColor != null) cell.setBorderColor(borderColor);
if (borderWidth > -1) cell.setBorderWidth(borderWidth);
if (value instanceof Image) {
cell.setImage((Image) value);
} else if (value instanceof Table.Cell) {
Table.Cell column = (Table.Cell) value;
cell.setColspan(column.getColNum());
if (column.getHeight() != null && column.getHeight() > 0) {
cell.setFixedHeight(column.getHeight());
}
if (column.getBackgroundColor() != null) {
cell.setBackgroundColor(column.getBackgroundColor());
}
if (column.getBorderColor() != null) {
cell.setBorderColor(column.getBorderColor());
}
if (column.getValue() == null) {
cell.setBorder(0);
return cell;
}
if (column.getValue() instanceof Image) {
cell.setImage((Image) value);
} else {
//cell.setPhrase(new Phrase(CommonUtils.evalString(column.getValue()), column.getFont()));
Paragraph paragraph = new Paragraph(CommonUtils.evalString(column.getValue()), column.getFont());
paragraph.setAlignment(Paragraph.ALIGN_CENTER);
if (column.getAlign() > -1) {
paragraph.setAlignment(column.getAlign());
}
cell.setPaddingLeft(column.getPaddingLeft());
cell.setPaddingRight(column.getPaddingRight());
cell.addElement(paragraph);
}
} else {
Paragraph paragraph = new Paragraph(CommonUtils.evalString(value), font);
paragraph.setAlignment(Paragraph.ALIGN_CENTER);
cell.addElement(paragraph);
}
cell.setPaddingTop(0f);
cell.setPaddingBottom(8f);
cell.setLeading(0, 1.4f);
return cell;
}
/**
* 给PDF文档追加页码
* @param pdf
*/
private static void addPageNum(PDF pdf, String password) {
ByteArrayOutputStream bos = null;
PdfStamper pdfStamper = null;
try {
PdfReader pdfReader = new PdfReader(pdf.getData(), password.getBytes());
bos = new ByteArrayOutputStream();
pdfStamper = new PdfStamper(pdfReader, bos);
Rectangle pageSize = pdfReader.getPageSize(1);
for (int i=1,num=pdfReader.getNumberOfPages();i<=num;i++) {
PdfContentByte page = pdfStamper.getOverContent(i);
page.saveState();
page.beginText();
// 页脚的页码 展示
String footerNum = String.format("%d", i);
Phrase phrase = new Phrase(footerNum, bodyFontNormal);
// 页码的 横轴 坐标 居中
float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
// 页码的 纵轴 坐标
float y = pageSize.getBottom() + 36;
// 添加文本内容,进行展示页码
ColumnText.showTextAligned(page, Element.ALIGN_CENTER, phrase, x, y, 0);
page.endText();
page.restoreState();
}
// 必须要调佣这个,否则文档不会生成
pdfStamper.setFormFlattening(true);
pdfStamper.close();
pdf.setData(bos.toByteArray());
} catch (Exception e) {
log.error("generate page num failed. IOException[{}]", e.getMessage());
} finally {
try {
if(pdfStamper != null) pdfStamper.close();
if(bos != null) bos.close();
} catch (Exception e) {
log.error("stream close exception, [{}]", e.getMessage());
}
}
}