1.背景
在开发中遇到一个需求,需要生成pdf数据, 刚开始想到了两个方案,根据freemarker 生成pdf(存在样式问题),填充word,然后转换成pdf(存在样式问题),后来发现了itext直接在pdf上绘制 样式
2.设计思路
此处才是本偏文章的重点
1.定于注解
itext没有横坐标和纵坐标的概念,要想绘制回执单类型的数据,只能设置整个pdf多少列,然后依次排序,然后可以对每一个单元格(cell)设置单独的样式效果,例如,字体,字高,字色,字号,边框,水平和垂直样式等等,那么基于此分析,我们就可以采用模板类的方式来实现此功能,对于模板类定义了两个注解CellLayout和TableLayout分别用来定于模板的全局样式和单个单元格的样式
2.反射编写
2.itext使用
基本使用 :blog.csdn.net/weixin_4420…
在使用的过程中,我发现itext 的使用由一些基本的元素组成,我们需要绘制的pdf,里面是好多事动态的数据,于是我基于此,提取出了,一些基本元素,通过自定义注解和反射,实现了动态渲染
1.pom.xml
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.itextpdf/itext-asian -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
复制代码
3.自定义实现
1.自定义注解
CellLayout
itext没有行的概念,只有单元格,所以自定义的单元格的样式
import com.itextpdf.text.pdf.PdfPCell;
import java.lang.annotation.*;
/**
* @ClassName: CellLayout
* @description: pdf单元格的样式
* @author: find me
* @create: 2022-04-13 13:42
* @Version 1.0
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME) // 运行时起作用
@Documented
public @interface CellLayout {
// 字段的中文key
String title() default "";
// 顺序
int index();
// 水平样式 0-left, 1-center, 2-right
int horizontalAlignment() default PdfPCell.ALIGN_LEFT;
// 垂直样式 4-top, 5-middle, 6-bottom;
int verticalAlignment() default PdfPCell.ALIGN_TOP;
// 列合并
int colspan() default 0;
// 行合并
int rowspan() default 0;
/**
* 外边框
* 0-默认
* 1-隐藏上边框
* 2-隐藏下边框
* 3-隐藏上、下边框
* 4-隐藏左边框
* 5-隐藏左、上边框
* 6-隐藏左、下边框
* 7-隐藏左、上、下边框
* 8-隐藏右边框
* 9-隐藏右、上边框
* 10-隐藏右、下边框
* 11-隐藏右、上、下边框
* 12-隐藏左、右边框
* 13-隐藏上、左、右边框
* 14-隐藏下、左、右边框
* 15-隐藏全部
*/
int borderSide() default 15;
// 行高
float minimumHeight() default 17f;
// // 换行缩进比
// float followingIndent() default 0f;
// 换行缩进比
int followingIndentRatio() default 0;
}
复制代码
TableLayout
对挥着的表格多少列
import java.lang.annotation.*;
/**
* @ClassName: TableLayout
* @description: 表格的整体样式
* @author: find me
* @create: 2022-04-13 13:43
* @Version 1.0
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME) // 运行时起作用
@Documented
public @interface TableLayout {
// 每列的宽度
float[] cols() ;
// 字体大小
float fontSize() default 10f;
}
复制代码
FontFactory
字体等基本样式的工厂,这里只是自己封装的工具类
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.pdf.BaseFont;
import java.io.IOException;
/**
* @ClassName: FontFactory
* @description:
* @author: find me
* @create: 2022-04-13 13:50
* @Version 1.0
*/
public class FontFactory {
/**
* 获取标题样式
*
* @return
* @throws DocumentException
* @throws IOException
*/
public static Font getTitleFont() throws DocumentException, IOException {
BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED, true);
Font font = new Font(bf, 22, Font.BOLD, BaseColor.BLACK);//字体
return font;
}
/**
* 获取正文样式
*
* @return
* @throws DocumentException
* @throws IOException
*/
public static Font getBodyFont() throws DocumentException, IOException {
BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED, true);
Font font = new Font(bf, 22, Font.BOLD, BaseColor.BLACK);//字体
return font;
}
/**
* 获取头部样式
*
* @return
* @throws DocumentException
* @throws IOException
*/
public static Font getHeadFont() throws DocumentException, IOException {
BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED, true);
Font font = new Font(bf, 22, Font.BOLD, BaseColor.BLACK);//字体
return font;
}
/**
* 获取上下文样式
*
* @return
* @throws DocumentException
* @throws IOException
*/
public static Font getContextFont() throws DocumentException, IOException {
BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED, true);
Font font = new Font(bf, 10, Font.NORMAL, BaseColor.BLACK);//字体
return font;
}
/**
* 获回执信息样式
*
* @return
* @throws DocumentException
* @throws IOException
*/
public static Font getReceiptHolderFont() throws DocumentException, IOException {
BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED, true);
Font font = new Font(bf, 10, Font.NORMAL, BaseColor.BLACK);//字体
return font;
}
}
复制代码
HeadLayout
execl类型的表格的头部数据
import com.itextpdf.text.pdf.PdfPCell;
import java.lang.annotation.*;
/**
* @ClassName: CellLayout
* @description: pdf单元格的样式
* @author: find me
* @create: 2022-04-13 13:42
* @Version 1.0
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME) // 运行时起作用
@Documented
public @interface HeadLayout {
// 头部标题
String headTitle() default "";
// 顺序
int index();
// 水平样式 0-left, 1-center, 2-right
int horizontalAlignment() default PdfPCell.ALIGN_LEFT;
// 垂直样式 4-top, 5-middle, 6-bottom;
int verticalAlignment() default PdfPCell.ALIGN_TOP;
// 列合并
int colspan() default 0;
// 行合并
int rowspan() default 0;
/**
* 外边框
* 0-默认
* 1-隐藏上边框
* 2-隐藏下边框
* 3-隐藏上、下边框
* 4-隐藏左边框
* 5-隐藏左、上边框
* 6-隐藏左、下边框
* 7-隐藏左、上、下边框
* 8-隐藏右边框
* 9-隐藏右、上边框
* 10-隐藏右、下边框
* 11-隐藏右、上、下边框
* 12-隐藏左、右边框
* 13-隐藏上、左、右边框
* 14-隐藏下、左、右边框
* 15-隐藏全部
*/
int borderSide() default 15;
// 行高
float minimumHeight() default 17f;
// // 换行缩进比
// float followingIndent() default 0f;
// 换行缩进比
int followingIndentRatio() default 0;
}
复制代码
CellFactory
生成cell样式
/**
* @ClassName: CellFactory
* @description:
* @author: find me
* @create: 2022-04-13 13:50
* @Version 1.0
*/
public class CellFactory {
// 字体
private Font font;
// 水平样式 0-left, 1-center, 2-right
private int horizontalAlignment;
// 垂直样式 4-top, 5-middle, 6-bottom;
private int verticalAlignment;
// 列合并
private int colspan;
// 行合并
private int rowspan;
/**
* 外边框
* 0-默认
* 1-隐藏上边框
* 2-隐藏下边框
* 3-隐藏上、下边框
* 4-隐藏左边框
* 5-隐藏左、上边框
* 6-隐藏左、下边框
* 7-隐藏左、上、下边框
* 8-隐藏右边框
* 9-隐藏右、上边框
* 10-隐藏右、下边框
* 11-隐藏右、上、下边框
* 12-隐藏左、右边框
* 13-隐藏上、左、右边框
* 14-隐藏下、左、右边框
* 15-隐藏全部
*/
private int borderSide;
// 最小高度
private float minimumHeight;
/**
* @param value 文本
* @param font 字体
* @param horizontalAlignment 水平样式 0-left, 1-center, 2-right
* @param verticalAlignment 垂直样式 4-top, 5-middle, 6-bottom;
* @param colspan 列合并
* @param rowspan 行合并
* @param borderSide 外边框
* 0-默认
* 1-隐藏上边框
* 2-隐藏下边框
* 3-隐藏上、下边框
* 4-隐藏左边框
* 5-隐藏左、上边框
* 6-隐藏左、下边框
* 7-隐藏左、上、下边框
* 8-隐藏右边框
* 9-隐藏右、上边框
* 10-隐藏右、下边框
* 11-隐藏右、上、下边框
* 12-隐藏左、右边框
* 13-隐藏上、左、右边框
* 14-隐藏下、左、右边框
* 15-隐藏全部
* @return
*/
public PdfPCell setCelText(PdfPCell cell, String value) {
cell.setPhrase(new Phrase(value, font));//存值
return cell;
}
public CellFactory(Font font, int horizontalAlignment, int verticalAlignment, int colspan, int rowspan, int borderSide, float minimumHeight) {
this.font = font;
this.horizontalAlignment = horizontalAlignment;
this.verticalAlignment = verticalAlignment;
this.colspan = colspan;
this.rowspan = rowspan;
this.borderSide = borderSide;
this.minimumHeight = minimumHeight;
}
public CellFactory() {
}
public PdfPCell setCellStyle(PdfPCell cell) {
cell.setHorizontalAlignment(this.horizontalAlignment);
cell.setMinimumHeight(this.minimumHeight);
if (this.verticalAlignment > 0) {
cell.setUseAscender(true);//垂直居中
}
cell.setVerticalAlignment(this.verticalAlignment);
if (this.colspan > 0) {
cell.setColspan(this.colspan);
}
if (this.rowspan > 0) {
cell.setRowspan(this.rowspan);
}
if (this.borderSide > 0) {
cell.disableBorderSide(this.borderSide);
}
return cell;
}
public PdfPCell generatePdfCell(String value) {
PdfPCell cell = setCellStyle(new PdfPCell());
cell.setPhrase(new Phrase(value, font));
return cell;
}
public PdfPCell generatePdfCell(String value, float followingIndent) {
PdfPCell cell = generatePdfCell(value);
cell.setFollowingIndent(followingIndent);
return cell;
}
public PdfPCell generatePdfCell() {
PdfPCell cell = setCellStyle(new PdfPCell());
return cell;
}
public PdfPCell generatePdfCell(float followingIndent) {
PdfPCell cell = setCellStyle(new PdfPCell());
cell.setFollowingIndent(followingIndent);
return cell;
}
}
复制代码
2.实现反射
PdfTemplate
此处是核心代码,利用反射生成itext的PdfPTable
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
/**
* @ClassName: PdfTemplate
* @description:
* @author: find me
* @create: 2022-04-13 13:45
* @Version 1.0
*/
public class PdfTemplate<T> {
public Paragraph drawTitle(String title) throws DocumentException, IOException {
Font titleFont = FontFactory.getTitleFont();
Paragraph paragraph = drawTitle(title, titleFont);
return paragraph;
}
public Paragraph drawTitle(String title, Font font) {
Paragraph titleParagraph = new Paragraph(title, font);
titleParagraph.setAlignment(PdfPCell.ALIGN_RIGHT);
//行间距
titleParagraph.setLeading(14f);
//设置左缩进
//paragraph1.setIndentationLeft(12);
//设置右缩进
titleParagraph.setIndentationRight(100);
//设置首行缩进
//paragraph1.setFirstLineIndent(24);
//设置段落上空白
//paragraph1.setSpacingBefore(5f);
//设置段落下空白
titleParagraph.setSpacingAfter(10f);
return titleParagraph;
}
public PdfPTable drawPdfContext(Object obj) throws DocumentException, IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Font contextFont = FontFactory.getContextFont();
PdfPTable table = drawPdfBody(obj, contextFont);
return table;
}
public PdfPTable drawPdfReceiptHolder(Object obj) throws DocumentException, IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Font receiptHolderFont = FontFactory.getReceiptHolderFont();
PdfPTable table = drawPdfBody(obj, receiptHolderFont);
return table;
}
public PdfPTable drawPdfBody(Object obj, Font font) throws NoSuchMethodException, InvocationTargetException,
IllegalAccessException {
Class aClass = obj.getClass();
TableLayout tableLayout = (TableLayout) aClass.getAnnotation(TableLayout.class);
Field[] declaredFields = aClass.getDeclaredFields();
// 对于有 CellLayout 的字段 按照index排序,并且
LinkedList<Field> collect = Arrays.stream(declaredFields).filter(item -> item.getAnnotation(CellLayout.class) != null).sorted(Comparator.comparingInt(field -> field.getAnnotation(CellLayout.class).index())).collect(Collectors.toCollection(LinkedList::new));
// 设置样式
PdfPTable contextTable = new PdfPTable(tableLayout.cols());
for (Field field : collect) {
CellLayout cellLayout = field.getAnnotation(CellLayout.class);
CellFactory cellFactory = new CellFactory(font, cellLayout.horizontalAlignment(), cellLayout.verticalAlignment(), cellLayout.colspan(), cellLayout.rowspan(), cellLayout.borderSide(), cellLayout.minimumHeight());
String name = field.getName();
name = name.substring(0, 1).toUpperCase() + name.substring(1);
Method m = aClass.getMethod("get" + name);
PdfPCell cell = cellFactory.generatePdfCell(cellLayout.title() + ((m.invoke(obj) != null) ? m.invoke(obj) :
""));
cell.setFollowingIndent(tableLayout.fontSize() * cellLayout.followingIndentRatio());
contextTable.addCell(cell);
}
return contextTable;
}
public PdfPTable drawPdfExcel(List<T> objs, Class aClass, Font font) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
TableLayout tableLayout = (TableLayout) aClass.getAnnotation(TableLayout.class);
PdfPTable excelTable = new PdfPTable(tableLayout.cols());
Field[] declaredFields = aClass.getDeclaredFields();
LinkedList<Field> collect = Arrays.stream(declaredFields).filter(item -> item.getAnnotation(HeadLayout.class) != null).sorted(Comparator.comparingInt(field -> field.getAnnotation(HeadLayout.class).index())).collect(Collectors.toCollection(LinkedList::new));
for (Field field : collect) {
HeadLayout headLayout = field.getAnnotation(HeadLayout.class);
CellFactory cellFactory = new CellFactory(font, headLayout.horizontalAlignment(), headLayout.verticalAlignment(), headLayout.colspan(), headLayout.rowspan(), headLayout.borderSide(), headLayout.minimumHeight());
PdfPCell cell = cellFactory.generatePdfCell(headLayout.headTitle());
excelTable.addCell(cell);
}
for (T obj : objs) {
Class<?> obj1 = obj.getClass();
// Class obj1 = (Class) obj;
Field[] qq = aClass.getDeclaredFields();
// 对于有 CellLayout 的字段 按照index排序,并且
LinkedList<Field> collect1 =
Arrays.stream(qq).filter(item -> item.getAnnotation(CellLayout.class) != null).sorted(Comparator.comparingInt(field -> field.getAnnotation(CellLayout.class).index())).collect(Collectors.toCollection(LinkedList::new));
// 设置样式
for (Field field : collect1) {
CellLayout cellLayout = field.getAnnotation(CellLayout.class);
CellFactory cellFactory = new CellFactory(font, cellLayout.horizontalAlignment(), cellLayout.verticalAlignment(), cellLayout.colspan(), cellLayout.rowspan(), cellLayout.borderSide(), cellLayout.minimumHeight());
String name = field.getName();
name = name.substring(0, 1).toUpperCase() + name.substring(1);
Method m = obj1.getMethod("get" + name);
PdfPCell cell = cellFactory.generatePdfCell(cellLayout.title() + ((m.invoke(obj) != null) ? m.invoke(obj) :
""));
cell.setFollowingIndent(tableLayout.fontSize() * cellLayout.followingIndentRatio());
excelTable.addCell(cell);
}
}
return excelTable;
}
}
复制代码
GeneratePdf
调用方法
import com.google.common.io.ByteArrayDataOutput;
import com.itextpdf.text.Document;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import org.springframework.lang.NonNull;
import java.io.*;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* @ClassName: GeneratePdf
* @description:
* @author: find me
* @create: 2022-04-13 13:44
* @Version 1.0
*/
public class GeneratePdf<T> {
/**
* @param document 文档
* @param title title
* @param context 凭证主题数据
* @param receiptHolder 回执留存方
*/
public OutputStream generatePDFVoucher(Document document, String title, Object context, Object receiptHolder) throws Exception {
PdfTemplate pdfTemplate = new PdfTemplate();
try (OutputStream out = new ByteArrayOutputStream()) {
SecureRandom random = new SecureRandom();
int i = random.nextInt(1000000);
File file = new File("D:\" + i + ".pdf");
//生成目标文件
// PdfWriter writer2 = PdfWriter.getInstance(document, new FileOutputStream(file));
// PdfWriter writer = PdfWriter.getInstance(document, out);
PdfWriter.getInstance(document, out);
document.open();
Paragraph paragraphTitle = pdfTemplate.drawTitle(title);
document.add(paragraphTitle);
if (context != null) {
PdfPTable contextTable = pdfTemplate.drawPdfContext(context);
document.add(contextTable);
}
if (receiptHolder != null) {
PdfPTable receiptHolderTable = pdfTemplate.drawPdfReceiptHolder(receiptHolder);
document.add(receiptHolderTable);
}
return out;
} catch (Exception e) {
e.printStackTrace();
throw new Exception(e);
} finally {
// 5.关闭文档
if (document != null) {
document.close();
}
}
}
/**
* 详情
*
* @param document 文档
* @param title 标题
* @param head 头部数据
* @param excelContext 表格具体数据
* @param excelTail 表格尾部备注
* @param receiptHolder 回执人
* @return
*/
public OutputStream generatePDFVoucherDetail(Document document, String title, @NonNull Object head,
@NonNull List<T> excelContext, @NonNull Object excelTail,
@NonNull Object receiptHolder, Class aClass) throws Exception {
PdfTemplate pdfTemplate = new PdfTemplate<T>();
try (OutputStream out = new ByteArrayOutputStream()) {
PdfWriter writer = PdfWriter.getInstance(document, out);
// SecureRandom random = new SecureRandom();
// int i = random.nextInt(1000000);
// File file = new File("D:\" + i + ".pdf");//生成目标文件
// file.createNewFile();
// PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(file));
document.open();
// 标题
Paragraph paragraphTitle = pdfTemplate.drawTitle(title);
document.add(paragraphTitle);
if (head != null) {
PdfPTable headTable = pdfTemplate.drawPdfContext(head);
document.add(headTable);
}
if (excelContext != null) {
PdfPTable excelTable = pdfTemplate.drawPdfExcel(excelContext, aClass, FontFactory.getContextFont());
document.add(excelTable);
}
if (excelTail != null) {
PdfPTable tailTable = pdfTemplate.drawPdfContext(excelTail);
document.add(tailTable);
}
if (receiptHolder != null) {
PdfPTable receiptInfoTable = pdfTemplate.drawPdfReceiptHolder(receiptHolder);
document.add(receiptInfoTable);
}
return out;
} catch (Exception e) {
e.printStackTrace();
throw new Exception(e);
} finally {
// 5.关闭文档
if (document != null) {
document.close();
}
}
}
}
复制代码