最近有個需求,生成信用報告。
需求:
1、生成pdf有頁眉頁腳
2、生成目錄
3、目錄加錨點可跳轉(zhuǎn)。
難點:
1、生成的目錄不能實時讀取頁碼
2、目錄是后生成的,屬于兩份pdf拼接的,不能添加錨點跳轉(zhuǎn)
思路:
1、freemaker進(jìn)行html頁面布局及動態(tài)變量替換
2、生成一份pdf文檔,用于關(guān)鍵字查詢,獲取所在頁碼
3、再生成一份目錄+內(nèi)容的pdf,目錄的頁碼從剛生成的文檔中查詢,由于此文檔目錄和內(nèi)容是同一個文檔,所以可以添加錨點
實現(xiàn):
1、引入 freemaker
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
2、freemaker 默認(rèn) 文件路徑是 resources/tempates
3、讀取freemaker頁面布局及變量替換
Map<String,Object> 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轉(zhuǎn)為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)) { // 目錄+內(nèi)容 有頁眉、有水印,有頁腳
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("生成模板內(nèi)容失敗"+e.fillInStackTrace());
}
}
/**
* Itext7轉(zhuǎn)換pdf工具類
*/
@Slf4j
public class PdfUtils {
// 字體文件路徑
private static final String fontPath = "pdf/font/MSYH.TTF";
// 字體文件 列表
private static final Set<String> 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轉(zhuǎn) pdf
* @param inputStream 輸入流
* @param headerText 頁眉
* @param waterMark 水印
* @param pageSize 紙張大小
* @param fontPathList 字體路徑列表,ttc后綴的字體需要添加<b>,0<b/> 例:C:\front\msyh.ttc,0
* @param outputStream 輸出流
*/
public static void convertHtmlToPdf(InputStream inputStream, String logoUrl, String headerText, String waterMark, boolean hasFooter,PageSize pageSize,
Set<String> fontPathList, OutputStream outputStream) {
// 驗空
Objects.requireNonNull(inputStream);
Objects.requireNonNull(outputStream);
PdfWriter pdfWriter = new PdfWriter(outputStream);
PdfDocument pdfDocument = new PdfDocument(pdfWriter);
//設(shè)置紙張大小
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文件流,查找出當(dāng)中的 或出現(xiàn)類似的符號空格字符
inputStream = readInputStrem(inputStream);
try {
// 生成pdf文檔
HtmlConverter.convertToPdf(inputStream, pdfDocument, properties);
} catch (IOException e) {
e.printStackTrace();
log.error("錯誤信息:pdf轉(zhuǎn)換失敗{}", e.getMessage());
} finally {
try {
pdfWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
pdfDocument.close();
}
}
/**
* 讀取HTML 流文件,并查詢當(dāng)中的 或類似符號直接替換為空格
*
* @param inputStream 輸入流
* @return 去掉特殊標(biāo)記的輸入流
*/
private static InputStream readInputStrem(InputStream inputStream) {
// 定義一些特殊字符的正則表達(dá)式 如:
String regEx_special = "\\&[a-zA-Z]{1,10};";
try(ByteArrayOutputStream baos = new ByteArrayOutputStream();) {
// 創(chuàng)建緩存大小
byte[] buffer = new byte[1024]; // 1KB
// 每次讀取到內(nèi)容的長度
int len = -1;
// 開始讀取輸入流中的內(nèi)容
while ((len = inputStream.read(buffer)) != -1) { //當(dāng)?shù)扔?1說明沒有數(shù)據(jù)可以讀取了
baos.write(buffer, 0, len); //把讀取到的內(nèi)容寫到輸出流中
}
// 把字節(jié)數(shù)組轉(zhuǎn)換為字符串 設(shè)置utf-8字符編碼
String content = baos.toString(String.valueOf(StandardCharsets.UTF_8));
// 關(guān)閉輸入流和輸出流
inputStream.close();
// 判斷HTML內(nèi)容是否具有HTML的特殊字符標(biāo)記
Pattern compile = Pattern.compile(regEx_special, Pattern.CASE_INSENSITIVE);
Matcher matcher = compile.matcher(content);
String replaceAll = matcher.replaceAll("");
// 將字符串轉(zhuǎn)化為輸入流返回
return getStringStream(replaceAll);
} catch (Exception e) {
e.printStackTrace();
log.error("錯誤信息:pdf字符串格式化特殊字符失敗{}", e.getMessage());
return null;
}
}
/**
* 將一個字符串轉(zhuǎn)化為輸入流
* @param sInputString 字符串
* @return 字符串對應(yīng)的輸入流
*/
public static InputStream getStringStream(String sInputString) {
if (sInputString != null && !sInputString.trim().equals("")) {
try {
return new ByteArrayInputStream(sInputString.getBytes(StandardCharsets.UTF_8)); // 設(shè)置utf-8字符編碼
} catch (Exception e) {
e.printStackTrace();
log.error("錯誤信息:pdf字符串轉(zhuǎn)輸入流失敗,{}", e.getMessage());
}
}
return null;
}
/**
* 將給定List集合中的pdf文檔,按照順序依次合并,生成最終的目標(biāo)PDF文檔
*
* @param pdfPathLists 待合并的PDF文檔路徑集合,可以是本地PDF文檔,也可以是網(wǎng)絡(luò)上的PDF文檔
* @param destPath 目標(biāo)合并生成的PDF文檔路徑
*/
public static void mergeMultiplePdfs(List<String> 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);
}
}
/**
* 基于內(nèi)存中的字節(jié)數(shù)組進(jìn)行PDF文檔的合并
* @param firstPdf 第一個PDF文檔
* @param secondPdf 第二個PDF文檔
*/
private static byte[] mergePdfBytes(byte[] firstPdf, byte[] secondPdf) throws IOException {
if (firstPdf != null && secondPdf != null) {
// 創(chuàng)建字節(jié)數(shù)組,基于內(nèi)存進(jìn)行合并
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());
// 關(guān)閉文檔流
merger.close();
firstDoc.close();
secondDoc.close();
destDoc.close();
return baos.toByteArray();
}
return null;
}
/**
* 將pdf文檔轉(zhuǎn)換成字節(jié)數(shù)組
* @param pdf PDF文檔路徑
* @return 返回對應(yīng)PDF文檔的字節(jié)數(shù)組
*/
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();
}
/**
* 遠(yuǎn)程文件轉(zhuǎn)化為字節(jié)
* @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();
}
}
/**
* 頁眉實現(xiàn)類
*/
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);
// 顯示在頂部右側(cè)位置
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();
}
}
/**
* 頁腳(頁碼)實現(xiàn)類
*/
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 {
/**
* 水印內(nèi)容
*/
private final String waterMarkContent;
/**
* 一頁中有幾行水印
*/
private final int waterMarkX;
/**
* 一頁中每列有多少水印
*/
private final int waterMarkY;
/**
* Pdf字體
*/
private final PdfFont pdfFont;
/**
* 默認(rèn)水印效果5行5列
* @param pdfFont pdf字體
* @param waterMarkContent 水印內(nèi)容
*/
public PdfWaterMarkEventHandler(PdfFont pdfFont, String waterMarkContent) {
this(pdfFont, waterMarkContent, 5, 5);
}
/**
* 水印效果
*
* @param pdfFont pdf字體
* @param waterMarkContent 水印內(nèi)容
* @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();
// 設(shè)置畫布屬性、水印屬性
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(); // 頁面寬度
// 根據(jù)行列進(jìn)行繪制
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、 內(nèi)容html頁面上特定字符及錨點id:
<div class="marginTop20" id="companyinfo">
<span style="color: #FFFFFF">directory_1.企業(yè)信息</span>
<img style="width: 100%" src="/upload/20231031/26e3e390f15ce1eae58367405960519f.jpg"/>
</div>
6、查詢特定內(nèi)容所在頁碼
int companyInfoNum = searchFileNum(fileName, zongtiNum, "directory_1.企業(yè)信息");
* 查詢標(biāo)題所在頁碼方法
* @param fileUrl
* @param startPage
* @param content
* @return
* @throws IOException
*/
public int searchFileNum(String fileUrl, int startPage, String content) throws IOException {
int curent = 0;
// 創(chuàng)建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頁面替換變量、添加錨點
<div class="directory1 level1">
<span class="directory-name"><a href="#companyinfo">1.企業(yè)信息</a></span>
<span class="directory-dot"></span>
<span class="directory-page">${companyInfoNum}</span>
</div>
8、合并生成的多個pdf文章來源:http://www.zghlxwxcb.cn/news/detail-829087.html
PdfUtils.mergeMultiplePdfs(Arrays.asList(headerName, pdfDescName, pdfOverViewName, directoryAndContentName), pdfAllName);
9、刪除多余的pdf文件文章來源地址http://www.zghlxwxcb.cn/news/detail-829087.html
File headerFile = new File(headerName);
if (headerFile.exists()) {
headerFile.delete();
}
到了這里,關(guān)于itextpdf7 使用之 html轉(zhuǎn)pdf,生成目錄可跳轉(zhuǎn)、添加頁眉頁腳的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!