PDF生成
此版PDF生成包含: 封面, 目录,页码等功能,暂时没有实现目录跳转功能
POM依赖
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>layout</artifactId>
<version>7.0.3</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>font-asian</artifactId>
<version>7.0.3</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.4.3</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
字体处理
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Font;
import com.itextpdf.text.pdf.BaseFont;
import lombok.extern.slf4j.Slf4j;
import com.weifengqi18.investigation.constants.ReportConstant;
import com.weifengqi18.investigation.exception.BizErrorException;
/**
* PDF字体工具类
*
* @author wl
* @version 1.0
* @date 2021-10-22
*/
@Slf4j
public class PdfFontUtil {
/**
* 获取字体
*
* @param size 字号
* @param systel 字体
* @param color 颜色
* @return Font
*/
public static Font createFont(float size, int systel, BaseColor color) {
Font font;
if (null != color) {
font = new Font(createBaseFont(), size, systel, color);
} else {
font = new Font(createBaseFont(), size, systel);
}
return font;
}
/**
* 获取自定义字体
*
* @param size 字号
* @param systel 字体
* @param color 颜色
* @return Font
*/
public static Font createCustomerFont(float size, int systel, BaseColor color, String ttfPath) {
Font font;
if (null != color) {
font = new Font(createCustomerBaseFont(ttfPath), size, systel, color);
} else {
font = new Font(createCustomerBaseFont(ttfPath), size, systel);
}
return font;
}
/**
* 获取基础字体信息
*
* @return BaseFont
*/
public static BaseFont createBaseFont() {
try {
return BaseFont.createFont(ReportConstant.FONT, ReportConstant.CHARACTER,
BaseFont.NOT_EMBEDDED);
} catch (Exception e) {
log.error("云尽调_商务报告_生成商务报告PDF获取字体信息出错,错误字体为: {}, 编码为: {}, 错误信息为: {}",
ReportConstant.FONT,
ReportConstant.CHARACTER, e);
throw new BizErrorException("云尽调获取字体失败!");
}
}
/**
* 获取自定义字体信息
*
* @return BaseFont
*/
public static BaseFont createCustomerBaseFont(String fontPath) {
try {
return BaseFont.createFont(fontPath, BaseFont.IDENTITY_H,
BaseFont.EMBEDDED);
} catch (Exception e) {
log.error("云尽调_商务报告_生成商务报告PDF获取字体信息出错,错误字体为: {}, 编码为: {}, 错误信息为: {}",
ReportConstant.FONT,
ReportConstant.CHARACTER, e);
throw new BizErrorException("云尽调获取字体失败!");
}
}
/**
* 段落标题字体
*
* @return 字体
*/
public static Font paragraHeadFont() {
return createCustomerFont(ReportConstant.HEAD_FONT_SIZE, Font.NORMAL,
BaseColor.BLACK, ReportConstant.FZHTK_FNOT);
}
/**
* 段落子标题字体
*
* @return 字体
*/
public static Font paragraSubHeadFont() {
return createCustomerFont(ReportConstant.SUB_HEAD_FONT_SIZE, Font.NORMAL,
BaseColor.BLACK, ReportConstant.FZHTK_FNOT);
}
/**
* 段落字体
*
* @return 段落字体
*/
public static Font paragraFont() {
return createCustomerFont(ReportConstant.PHASE_FONT_SIZE, Font.NORMAL,
ReportConstant.BRODER_COLOR,ReportConstant.MSYH_FNOT);
}
/**
* 段落字体
*
* @param fontSize 字体大小
* @param sytle 字体样式
* @param color 颜色
* @return 段落字体
*/
public static Font paragraFont(int fontSize, int sytle, BaseColor color) {
return createCustomerFont(fontSize, sytle, color, ReportConstant.MSYH_FNOT);
}
/**
* cell head 字体
*
* @return cell字体
*/
public static Font cellHeadFont() {
return createCustomerFont(ReportConstant.CELL_FONT_SIZE, Font.NORMAL,
BaseColor.BLACK,ReportConstant.MSYH_FNOT);
}
/**
* cell head 字体
*
* @return cell字体
*/
public static Font cellFont() {
return createCustomerFont(ReportConstant.CELL_FONT_SIZE, Font.NORMAL,
ReportConstant.BRODER_COLOR,ReportConstant.MSYH_FNOT);
}
/**
* cell head 字体
*
* @param fontSize 字体大小
* @param sytle 字体样式
* @param color 颜色
* @return cell字体
*/
public static Font cellFont(float fontSize, int sytle, BaseColor color) {
return PdfFontUtil.createFont(fontSize, sytle, color);
}
}
PDF内容处理
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.fastjson.JSONObject;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Chunk;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfCopy;
import com.itextpdf.text.pdf.PdfImportedPage;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.draw.DottedLineSeparator;
import io.swagger.annotations.ApiModel;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import com.weifengqi18.investigation.constants.ReportConstant;
import com.weifengqi18.investigation.event.TOCEvent;
import com.weifengqi18.investigation.vo.report.business.ChangesInfo;
import com.weifengqi18.investigation.vo.report.business.LawStatusVo;
/**
* PDF 上下文工具类
*
* @author wl
* @version 1.0
* @date 2021-10-25
*/
@Slf4j
public class PdfContextUtil {
private PdfContextUtil() {
}
/** 合并列下表数组 */
private static final int CELL_ARRAY_SIZE = 2;
/**
* 解析股东情况
*
* @param table table
* @param tList list
*/
@SneakyThrows
public static void parseListInfo(PdfPTable table, List<?> tList, Class<?> clazz,
boolean needSerial,
boolean hasTotal, Font cellHeadFont, Font cellFont, String cloumName) {
// 设置表头
Field[] declaredFields = clazz.getDeclaredFields();
if (needSerial) {
table.addCell(PdfContextUtil.createPdfpCell(cloumName, cellHeadFont));
}
for (Field declaredField : declaredFields) {
String value = DateConvertUtil.changeNumToYear(declaredField);
table.addCell(PdfContextUtil.createPdfpCell(value, cellHeadFont));
}
// 设置cell值
List<Object> objects = new ArrayList<>();
if (CollectionUtils.isEmpty(tList)) {
Object o = clazz.newInstance();
objects.add(o);
}
int size;
boolean isEmpty = CollectionUtils.isEmpty(tList);
if (isEmpty) {
size = objects.size();
} else {
size = tList.size();
}
log.info("云尽调_PDF报告解析属性,属性集合为: {}", JSONObject.toJSONString(tList));
for (int i = 0; i < size; i++) {
Object o;
if (isEmpty) {
o = objects.get(i);
} else {
o = tList.get(i);
}
if (!ObjectUtils.isEmpty(o)) {
if (needSerial) {
PdfContextUtil.assignSerialNumberCell(table, i + 1, size, hasTotal, cellFont);
}
for (Field declaredField : declaredFields) {
declaredField.setAccessible(true);
PdfContextUtil.assignCellByField(table, o, declaredField, null, cellFont);
}
}
}
}
/**
* 创建段
*
* @param value 值
* @param paragraFont 字体
* @return Paragraph
*/
public static Paragraph createParagraph(String value, Font paragraFont) {
return new Paragraph(value, paragraFont);
}
/**
* 创建空白段
*
* @param spcae
* @return
*/
public static Paragraph createSpace(float spcae) {
Paragraph paragraph = new Paragraph();
paragraph.setSpacingAfter(spcae);
return paragraph;
}
/**
* 创建PdfpHeadCell
*
* @param value cellvalue
* @param cellHeadFont 字体
* @return PdfPCell
*/
public static PdfPCell createPdfpCell(String value, Font cellHeadFont) {
PdfPCell cell = new PdfPCell();
Paragraph paragraph = new Paragraph(value, cellHeadFont);
paragraph.setAlignment(Paragraph.ALIGN_CENTER);
cell.addElement(paragraph);
setCellStyle(cell);
return cell;
}
/**
* 设置cell 样式
*
* @param cell cell
*/
private static void setCellStyle(PdfPCell cell) {
cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
cell.setBorderColor(ReportConstant.BRODER_COLOR);
cell.setPaddingTop(0f);
cell.setPaddingBottom(10f);
}
/**
* 创建table
*
* @param floats 列数组
* @return Table
*/
public static PdfPTable createTable(float[] floats) {
PdfPTable table = new PdfPTable(floats);
table.setWidthPercentage(100);
table.setSpacingBefore(1);
table.setHorizontalAlignment(Element.ALIGN_CENTER);
return table;
}
/**
* 创建标题块
*
* @param serialNumber 序号
* @param clazz clazz
* @param baseColor 字体颜色
* @param paragraHeadFont 字体
* @param isSubTitle 是否是子标题
* @return Chunk 块
*/
public static Chunk createTitleChunk(String serialNumber, Class<?> clazz, BaseColor baseColor,
Font paragraHeadFont, boolean isSubTitle) {
String value;
if (clazz.equals(ChangesInfo.class)) {
value = serialNumber;
} else {
value = serialNumber + clazz.getAnnotation(ApiModel.class).value();
}
Chunk chunk;
if (isSubTitle) {
chunk = new Chunk(value, paragraHeadFont);
} else {
chunk = new Chunk(value + "\n", paragraHeadFont);
}
if (null != baseColor) {
chunk.setBackground(baseColor);
}
// 这里就是统一添加目录的地方
chunk.setGenericTag(value);
return chunk;
}
/**
* 给属性赋值
*
* @param table pdf table
* @param obj 解析对象
* @param declaredField 声明字段
* @param rowSpanArray 合并列
* @param cellFont 字体
*/
public static void assignCellByField(PdfPTable table, Object obj,
Field declaredField, int[] rowSpanArray, Font cellFont) {
Object o = null;
log.info("云尽调_PDF报告解析属性,解析的属性为: {}", JSONObject.toJSONString(obj));
try {
o = declaredField.get(obj);
} catch (IllegalAccessException e) {
log.error("云尽调_商务报告_生成商务报告PDF解析字段出错,字段为: {}, 错误信息为: {}", declaredField.getName(), e);
}
List<Phrase> paragraphList = new ArrayList<>();
if (null != o) {
String s = o.toString();
if (o instanceof List) {
dealListField((List) o, paragraphList, s, cellFont);
} else {
setParagraphValue(paragraphList, s, cellFont);
}
}
PdfPCell cell = new PdfPCell();
int size = paragraphList.size();
for (int i = 1; i <= size; i++) {
Phrase phrase = paragraphList.get(i - 1);
cell.addElement(phrase);
}
if (null != rowSpanArray && rowSpanArray.length == CELL_ARRAY_SIZE) {
cell.setColspan(3);
}
setCellStyle(cell);
table.addCell(cell);
}
/**
* 处理数组属性
*
* @param list 原对象
* @param paragraphList list
* @param s value
* @param cellFont 字体
*/
public static void dealListField(List<?> list, List<Phrase> paragraphList, String s,
Font cellFont) {
Object o1 = list.get(0);
if (o1 instanceof LawStatusVo) {
for (Object o2 : list) {
LawStatusVo lawStatusVo = (LawStatusVo) o2;
String value = "(" + lawStatusVo.getDate() + " , " + lawStatusVo.getStatus() + ")";
setParagraphValue(paragraphList, value, cellFont);
}
} else {
setParagraphValue(paragraphList, s, cellFont);
}
}
/**
* 设置代码段信息
*
* @param paragraphList 断码段List
* @param s value
* @param paragraFont 字体
*/
public static void setParagraphValue(List<Phrase> paragraphList, String s, Font paragraFont) {
Paragraph paragraph = new Paragraph(s, paragraFont);
paragraph.setAlignment(Element.ALIGN_CENTER);
paragraphList.add(paragraph);
}
/**
* 设置表格序号
*
* @param table 表
* @param serialNumber 序号
* @param size 位置
* @param hasTotal 是否有合计
* @param cellFont 字体
*/
public static void assignSerialNumberCell(PdfPTable table, int serialNumber, int size,
boolean hasTotal, Font cellFont) {
PdfPCell cell;
if (size == serialNumber && hasTotal) {
cell = PdfContextUtil.createPdfpCell("合计", cellFont);
} else {
cell = PdfContextUtil.createPdfpCell(String.valueOf(serialNumber), cellFont);
}
table.addCell(cell);
}
/**
* 生成目录
*
* @param document 文档
* @param event 事件
* @param totalFont 大标题字体
* @param title 标题
* @param subFont 子标题字体
* @throws DocumentException 异常
*/
public static void createCatalog(Document document, TOCEvent event, Font totalFont,
String title, Font subFont)
throws DocumentException {
document.newPage();
document.add(new Paragraph(title, totalFont));
Chunk dottedLine = new Chunk(new DottedLineSeparator());
List<SimpleEntry<String, Integer>> entries = event.getTOC();
Paragraph p;
for (SimpleEntry<String, Integer> entry : entries) {
p = new Paragraph(entry.getKey(), subFont);
p.add(dottedLine);
p.add(String.valueOf(entry.getValue()));
document.add(p);
}
}
/**
* 操作PDF (添加页码和封面)
*
* @param src 源文件路径
* @param dest 目标文件
* @throws IOException
* @throws DocumentException
*/
@SneakyThrows
public static void manipulatePdf(String src, String dest, Font cellFont, String titlePath) {
PdfReader reader = new PdfReader(src);
int n = reader.getNumberOfPages();
// 把目录移动到最前面(把d%页放到第一页,然后排序1~(d-1))
reader.selectPages(String.format("%d, 1-%d", n, n-1));
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
PdfContentByte pagecontent;
for (int i = 1; i < n; ) {
pagecontent = stamper.getOverContent(++i);
ColumnText.showTextAligned(pagecontent, Element.ALIGN_RIGHT,
new Phrase(String.format("第 %s 页", i-1), cellFont), 300, 15, 0);
}
PdfReader cover = new PdfReader(titlePath);
stamper.insertPage(1, cover.getPageSizeWithRotation(1));
PdfContentByte page1 = stamper.getOverContent(1);
PdfImportedPage page = stamper.getImportedPage(cover, 1);
page1.addTemplate(page, 0, 0);
stamper.close();
cover.close();
reader.close();
File file = new File(src);
System.out.println(file.delete());
}
}
生成目录事件
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.List;
import com.itextpdf.text.Document;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfPageEventHelper;
import com.itextpdf.text.pdf.PdfWriter;
/**
* @author wl
* @version 1.0
* @date 2021-10-27
*/
public class TOCEvent extends PdfPageEventHelper {
protected List<SimpleEntry<String, Integer>> toc = new ArrayList<>();
@Override
public void onGenericTag(PdfWriter writer, Document document, Rectangle rect, String text) {
toc.add(new SimpleEntry(text, writer.getPageNumber()));
}
public List getTOC() {
return toc;
}
}
**目录生成事件使用: **
- writer中写入事件
Document document = new Document(PageSize.A4);
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(tempFile));
TOCEvent event = new TOCEvent();
writer.setPageEvent(event);
document.open();
- 写入目录值
String title = "整体概述";
Chunk c = new Chunk("整体概述", paragraHeadFont);
c.setGenericTag(title);
document.add(new Paragraph(c));
- 生成目录
/**
* 生成目录
*
* @param document 文档
* @param event 事件
* @param totalFont 大标题字体
* @param title 标题
* @param subFont 子标题字体
* @throws DocumentException 异常
*/
public static void createCatalog(Document document, TOCEvent event, Font totalFont,
String title, Font subFont)
throws DocumentException {
document.newPage();
document.add(new Paragraph(title, totalFont));
Chunk dottedLine = new Chunk(new DottedLineSeparator());
List<SimpleEntry<String, Integer>> entries = event.getTOC();
Paragraph p;
for (SimpleEntry<String, Integer> entry : entries) {
p = new Paragraph(entry.getKey(), subFont);
p.add(dottedLine);
p.add(String.valueOf(entry.getValue()));
document.add(p);
}
}
- 生成页码并把目录移动到首页并添加封面
/**
* 操作PDF (添加页码和封面)
*
* @param src 源文件路径
* @param dest 目标文件
* @throws IOException
* @throws DocumentException
*/
@SneakyThrows
public static void manipulatePdf(String src, String dest, Font cellFont, String titlePath) {
PdfReader reader = new PdfReader(src);
int n = reader.getNumberOfPages();
// 把目录移动到最前面(把d%页放到第一页,然后排序1~(d-1))
reader.selectPages(String.format("%d, 1-%d", n, n-1));
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
PdfContentByte pagecontent;
// 添加页码
for (int i = 1; i < n; ) {
pagecontent = stamper.getOverContent(++i);
ColumnText.showTextAligned(pagecontent, Element.ALIGN_RIGHT,
new Phrase(String.format("第 %s 页", i-1), cellFont), 300, 15, 0);
}
// 添加封面
PdfReader cover = new PdfReader(titlePath);
stamper.insertPage(1, cover.getPageSizeWithRotation(1));
PdfContentByte page1 = stamper.getOverContent(1);
PdfImportedPage page = stamper.getImportedPage(cover, 1);
page1.addTemplate(page, 0, 0);
stamper.close();
cover.close();
reader.close();
File file = new File(src);
System.out.println(file.delete());
}