最近有个需求,生成信用报告。
1、生成pdf有页眉页脚
2、生成目录
3、目录加锚点可跳转。
1、生成的目录不能实时读取页码
2、目录是后生成的,属于两份pdf拼接的,不能添加锚点跳转
1、freemaker进行html页面布局及动态变量替换
2、生成一份pdf文档,用于关键字查询,获取所在页码
3、再生成一份目录+内容的pdf,目录的页码从刚生成的文档中查询,由于此文档目录和内容是同一个文档,所以可以添加锚点
1、引入 freemaker
org.springframework.boot
spring-boot-starter-freemarker
2、freemaker 默认 文件路径是 resources/tempates
3、读取freemaker页面布局及变量替换
Map appendix = new HashMap<>();
appendix.put("iprTrademarkList", iprTrademarkList);
appendix.put("iprPatentList", iprPatentList);
Template appendixtemp = configurer.getConfiguration().getTemplate("risk_appendix.html");
content += FreeMarkerTemplateUtils.processTemplateIntoString(appendixtemp, appendix);
4、html转为pdf,添加页眉页脚
public void html2Pdf(String content, String outPath, String type) {
try {
BufferedOutputStream outputStream = FileUtil.getOutputStream(outPath);
if ("head".equals(type) || "content".equals(type)) { // 封面、临时content文件 无页眉,无水印,无页脚
PdfUtils.convertHtmlToPdf(content, reportLogoUrl, "", "", false,PageSize.A4, outputStream);
} else if ("directAndContent".equals(type)) { // 目录+内容 有页眉、有水印,有页脚
PdfUtils.convertHtmlToPdf(content, reportLogoUrl, reportHeadText, reportWaterText, true,PageSize.A4, outputStream);
} else { // 说明、概览 有页眉、有水印,无页脚
PdfUtils.convertHtmlToPdf(content, reportLogoUrl, reportHeadText, reportWaterText, false,PageSize.A4, outputStream);
}
} catch (Exception e) {
System.out.println("生成模板内容失败"+e.fillInStackTrace());
}
}
/**
* Itext7转换pdf工具类
*/
@Slf4j
public class PdfUtils {
// 字体文件路径
private static final String fontPath = "pdf/font/MSYH.TTF";
// 字体文件 列表
private static final Set fontSet = new HashSet<>();
static {
ClassPathResource classPathResource = new ClassPathResource(fontPath);
fontSet.add(classPathResource.getPath());
}
public static void convertHtmlToPdf(String htmlStr, String logoUrl, String headerText, String waterMark, boolean hasFooter, PageSize pageSize, OutputStream outputStream) {
convertHtmlToPdf(new ByteArrayInputStream(htmlStr.getBytes(StandardCharsets.UTF_8)), logoUrl, headerText, waterMark, hasFooter, pageSize, fontSet, outputStream);
}
/**
* html转 pdf
* @param inputStream 输入流
* @param headerText 页眉
* @param waterMark 水印
* @param pageSize 纸张大小
* @param fontPathList 字体路径列表,ttc后缀的字体需要添加,0 例:C:\front\msyh.ttc,0
* @param outputStream 输出流
*/
public static void convertHtmlToPdf(InputStream inputStream, String logoUrl, String headerText, String waterMark, boolean hasFooter,PageSize pageSize,
Set fontPathList, OutputStream outputStream) {
// 验空
Objects.requireNonNull(inputStream);
Objects.requireNonNull(outputStream);
PdfWriter pdfWriter = new PdfWriter(outputStream);
PdfDocument pdfDocument = new PdfDocument(pdfWriter);
//设置纸张大小
pdfDocument.setDefaultPageSize(pageSize);
//添加中文字体支持
ConverterProperties properties = new ConverterProperties();
FontProvider fontProvider = new FontProvider();
//添加自定义字体,例如微软雅黑
if (CollectionUtils.isNotEmpty(fontPathList)) {
fontPathList.forEach(e -> fontProvider.addFont(e, PdfEncodings.IDENTITY_H));
}
PdfFont pdfFont = fontProvider.getFontSet()
.getFonts()
.stream()
.findFirst()
.map(fontProvider::getPdfFont)
.orElse(null);
// 添加页眉
pdfDocument.addEventHandler(PdfDocumentEvent.START_PAGE, new PdfHeaderMarkerEventHandler(pdfFont, headerText, logoUrl));
// 添加水印
if (StringUtils.isNotBlank(waterMark)) {
pdfDocument.addEventHandler(PdfDocumentEvent.INSERT_PAGE, new PdfWaterMarkEventHandler(pdfFont, waterMark));
}
if (hasFooter) {
// 添加页脚
pdfDocument.addEventHandler(PdfDocumentEvent.END_PAGE, new PdfPageMarkerEventHandler(pdfFont));
}
properties.setFontProvider(fontProvider);
// 读取Html文件流,查找出当中的 或出现类似的符号空格字符
inputStream = readInputStrem(inputStream);
try {
// 生成pdf文档
HtmlConverter.convertToPdf(inputStream, pdfDocument, properties);
} catch (IOException e) {
e.printStackTrace();
log.error("错误信息:pdf转换失败{}", e.getMessage());
} finally {
try {
pdfWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
pdfDocument.close();
}
}
/**
* 读取HTML 流文件,并查询当中的 或类似符号直接替换为空格
*
* @param inputStream 输入流
* @return 去掉特殊标记的输入流
*/
private static InputStream readInputStrem(InputStream inputStream) {
// 定义一些特殊字符的正则表达式 如:
String regEx_special = "\\&[a-zA-Z]{1,10};";
try(ByteArrayOutputStream baos = new ByteArrayOutputStream();) {
// 创建缓存大小
byte[] buffer = new byte[1024]; // 1KB
// 每次读取到内容的长度
int len = -1;
// 开始读取输入流中的内容
while ((len = inputStream.read(buffer)) != -1) { //当等于-1说明没有数据可以读取了
baos.write(buffer, 0, len); //把读取到的内容写到输出流中
}
// 把字节数组转换为字符串 设置utf-8字符编码
String content = baos.toString(String.valueOf(StandardCharsets.UTF_8));
// 关闭输入流和输出流
inputStream.close();
// 判断HTML内容是否具有HTML的特殊字符标记
Pattern compile = Pattern.compile(regEx_special, Pattern.CASE_INSENSITIVE);
Matcher matcher = compile.matcher(content);
String replaceAll = matcher.replaceAll("");
// 将字符串转化为输入流返回
return getStringStream(replaceAll);
} catch (Exception e) {
e.printStackTrace();
log.error("错误信息:pdf字符串格式化特殊字符失败{}", e.getMessage());
return null;
}
}
/**
* 将一个字符串转化为输入流
* @param sInputString 字符串
* @return 字符串对应的输入流
*/
public static InputStream getStringStream(String sInputString) {
if (sInputString != null && !sInputString.trim().equals("")) {
try {
return new ByteArrayInputStream(sInputString.getBytes(StandardCharsets.UTF_8)); // 设置utf-8字符编码
} catch (Exception e) {
e.printStackTrace();
log.error("错误信息:pdf字符串转输入流失败,{}", e.getMessage());
}
}
return null;
}
/**
* 将给定List集合中的pdf文档,按照顺序依次合并,生成最终的目标PDF文档
*
* @param pdfPathLists 待合并的PDF文档路径集合,可以是本地PDF文档,也可以是网络上的PDF文档
* @param destPath 目标合并生成的PDF文档路径
*/
public static void mergeMultiplePdfs(List pdfPathLists, String destPath) {
try {
int size = pdfPathLists.size();
byte[] pdfData = getPdfBytes(pdfPathLists.get(0));
for (int i = 1; i < size; i++) {
pdfData = mergePdfBytes(pdfData, getPdfBytes(pdfPathLists.get(i)));
}
if (pdfData != null) {
FileOutputStream fis = new FileOutputStream(destPath);
fis.write(pdfData);
fis.close();
}
} catch (Exception e) {
log.error("合并PDF异常:", e);
}
}
/**
* 基于内存中的字节数组进行PDF文档的合并
* @param firstPdf 第一个PDF文档
* @param secondPdf 第二个PDF文档
*/
private static byte[] mergePdfBytes(byte[] firstPdf, byte[] secondPdf) throws IOException {
if (firstPdf != null && secondPdf != null) {
// 创建字节数组,基于内存进行合并
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfDocument destDoc = new PdfDocument(new PdfWriter(baos));
// 合并的pdf文件对象
PdfDocument firstDoc = new PdfDocument(new PdfReader(new ByteArrayInputStream(firstPdf)));
PdfDocument secondDoc = new PdfDocument(new PdfReader(new ByteArrayInputStream(secondPdf)));
// 合并对象
PdfMerger merger = new PdfMerger(destDoc);
merger.merge(firstDoc, 1, firstDoc.getNumberOfPages());
merger.merge(secondDoc, 1, secondDoc.getNumberOfPages());
// 关闭文档流
merger.close();
firstDoc.close();
secondDoc.close();
destDoc.close();
return baos.toByteArray();
}
return null;
}
/**
* 将pdf文档转换成字节数组
* @param pdf PDF文档路径
* @return 返回对应PDF文档的字节数组
*/
private static byte[] getPdfBytes(String pdf) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream is = Files.newInputStream(Paths.get(pdf));
byte[] data = new byte[2048];
int len;
while ((len = is.read(data)) != -1) {
out.write(data, 0, len);
}
return out.toByteArray();
}
/**
* 远程文件转化为字节
* @param pdf
* @return
* @throws Exception
*/
public static byte[] getRemotePdfBytes(String pdf) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
URL url = new URL(pdf);
InputStream is =url.openStream();
byte[] data = new byte[2048];
int len;
while ((len = is.read(data)) != -1) {
out.write(data, 0, len);
}
return out.toByteArray();
}
}
/**
* 页眉实现类
*/
public class PdfHeaderMarkerEventHandler implements IEventHandler {
/**
* pdf字体
*/
private final PdfFont pdfFont;
/**
* 页眉显示
*/
private final String title;
private final String logoUrl;
public PdfHeaderMarkerEventHandler(PdfFont pdfFont, String title, String logoUrl) {
this.pdfFont = pdfFont;
this.title = title;
this.logoUrl = logoUrl;
}
@Override
public void handleEvent(Event event) {
if (StringUtils.isEmpty(title)) return;
PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
PdfDocument pdf = docEvent.getDocument();
PdfPage page = docEvent.getPage();
Rectangle pageSize = page.getPageSize();
PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf);
Canvas canvas = new Canvas(pdfCanvas, pageSize);
float x = pageSize.getRight() -60;
float y = pageSize.getTop() - 32;
Paragraph p = new Paragraph(title)
.setFontSize(9)
.setFont(pdfFont);
// 显示在顶部右侧位置
canvas.showTextAligned(p, x, y, TextAlignment.RIGHT);
URL url = null;
try {
url = new URL(logoUrl);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
Image logo = new Image(ImageDataFactory.create(url));
logo.scaleAbsolute(100, 20);
logo.setMarginLeft(30);
logo.setMarginTop(15);
canvas.add(logo);
canvas.close();
}
}
/**
* 页脚(页码)实现类
*/
public class PdfPageMarkerEventHandler implements IEventHandler {
/**
* pdf字体
*/
private final PdfFont pdfFont;
public PdfPageMarkerEventHandler(PdfFont pdfFont) {
this.pdfFont = pdfFont;
}
@Override
public void handleEvent(Event event) {
PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
PdfDocument pdf = docEvent.getDocument();
PdfPage page = docEvent.getPage();
int pageNumber = pdf.getPageNumber(page);
Rectangle pageSize = page.getPageSize();
PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf);
Canvas canvas = new Canvas(pdfCanvas, pageSize);
float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
float y = pageSize.getBottom() + 15;
Paragraph p = new Paragraph();
if (1 == pageNumber) {
p = new Paragraph("")
.setFontSize(10f)
.setFont(pdfFont);
} else {
p = new Paragraph("第" + (pageNumber -1) + "页")
.setFontSize(10f)
.setFont(pdfFont);
}
// 绘制到底部中间位置
canvas.showTextAligned(p, x, y, TextAlignment.CENTER);
canvas.close();
}
}
/**
* pdf水印
*/
public class PdfWaterMarkEventHandler implements IEventHandler {
/**
* 水印内容
*/
private final String waterMarkContent;
/**
* 一页中有几行水印
*/
private final int waterMarkX;
/**
* 一页中每列有多少水印
*/
private final int waterMarkY;
/**
* Pdf字体
*/
private final PdfFont pdfFont;
/**
* 默认水印效果5行5列
* @param pdfFont pdf字体
* @param waterMarkContent 水印内容
*/
public PdfWaterMarkEventHandler(PdfFont pdfFont, String waterMarkContent) {
this(pdfFont, waterMarkContent, 5, 5);
}
/**
* 水印效果
*
* @param pdfFont pdf字体
* @param waterMarkContent 水印内容
* @param waterMarkX 一页中有多少行水印
* @param waterMarkY 一页中有多少列水印
*/
public PdfWaterMarkEventHandler(PdfFont pdfFont, String waterMarkContent, int waterMarkX, int waterMarkY) {
this.waterMarkContent = waterMarkContent;
this.waterMarkX = waterMarkX;
this.waterMarkY = waterMarkY;
this.pdfFont = pdfFont;
}
@Override
public void handleEvent(Event event) {
if (StringUtils.isEmpty(waterMarkContent)) return;
// 获取pdf对象、页面属性
PdfDocumentEvent documentEvent = (PdfDocumentEvent) event;
PdfDocument document = documentEvent.getDocument();
PdfPage page = documentEvent.getPage();
Rectangle pageSize = page.getPageSize();
// 设置画布属性、水印属性
PdfCanvas pdfCanvas = new PdfCanvas(page.newContentStreamAfter(), page.getResources(), document);
Paragraph waterMark = new Paragraph(waterMarkContent).setOpacity(0.5f);
Canvas canvas = new Canvas(pdfCanvas, pageSize)
.setFontColor(WebColors.getRGBColor("lightgray")) // 水印颜色效果
.setFontSize(16)
.setFont(pdfFont);
float y = pageSize.getHeight(); // 页面高度
float x = pageSize.getWidth(); // 页面宽度
// 根据行列进行绘制
for (int i = 0; i < waterMarkX; i++) {
float width = (x / waterMarkX) * i + 50;
for (int j = waterMarkY; j > 0; j--) {
float height = (y / waterMarkY) * j - 50;
// System.out.println("waterMarkX: "+width+" waterMarkY: "+height);
canvas.showTextAligned(waterMark, width, height, document.getNumberOfPages(), TextAlignment.CENTER, VerticalAlignment.MIDDLE, 120);
}
}
canvas.close();
}
5、 内容html页面上特定字符及锚点id:
directory_1.企业信息
6、查询特定内容所在页码
int companyInfoNum = searchFileNum(fileName, zongtiNum, "directory_1.企业信息");
* 查询标题所在页码方法
* @param fileUrl
* @param startPage
* @param content
* @return
* @throws IOException
*/
public int searchFileNum(String fileUrl, int startPage, String content) throws IOException {
int curent = 0;
// 创建PdfReader对象
PdfReader pdfWriter = new PdfReader(fileUrl);
PdfDocument pdfDocument = new PdfDocument(pdfWriter);
int numberOfPages = pdfDocument.getNumberOfPages();
for (int i = startPage; i <= numberOfPages; i++) {
PdfPage page = pdfDocument.getPage(i);
String textFromPage = PdfTextExtractor.getTextFromPage(page);
int index = textFromPage.indexOf(content);
if (index >= 0) {
curent = i;
}
}
pdfDocument.close();
pdfWriter.close();
return curent;
}
7、目录html页面替换变量、添加锚点
1.企业信息
${companyInfoNum}
8、合并生成的多个pdf
PdfUtils.mergeMultiplePdfs(Arrays.asList(headerName, pdfDescName, pdfOverViewName, directoryAndContentName), pdfAllName);
9、删除多余的pdf文件
File headerFile = new File(headerName);
if (headerFile.exists()) {
headerFile.delete();
}