SpringBoot生成和操作PDF的代碼詳解
本文簡介
本文涉及pdf操作,如下:
- PDF模板制作
- 基于PDF模板生成,并支持下載
- 自定義中文字體
- 完全基于代碼生成,并保存到指定目錄
- 合并PDF,并保存到指定目錄
- 合并PDF,并支持下載
基于PDF模板生成:適用于固定格式的PDF模板,基于內容進行填空,例如:合同信息生成、固定格式表格等等
完全基于代碼生成:適用于不固定的PDF,例如:動態(tài)表格、動態(tài)添加某塊內容、不確定的內容大小等不確定的場景
PDF文件簡介
PDF是可移植文檔格式,是一種電子文件格式,具有許多其他電子文檔格式無法相比的優(yōu)點。PDF文件格式可以將文字、字型、格式、顏色及獨立于設備和分辨率的圖形圖像等封裝在一個文件中。該格式文件還可以包含超文本鏈接、聲音和動態(tài)影像等電子信息,支持特長文件,集成度和安全可靠性都較高。在系統(tǒng)開發(fā)中通常用來生成比較正式的報告或者合同類的電子文檔。
代碼實現(xiàn)PDF操作
首先需要引入我們的依賴,這里通過maven管理依賴
在pom.xml文件添加以下依賴
<!--pdf操作-->
<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.6</version>
</dependency>
<!-- 這個主要用來設置樣式什么的 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
基于PDF模板生成,并下載
PDF模板制作
首先在word或者其他軟件里面制作模板,挑選你熟悉的軟件即可,前提是可生成pdf。

將word文件轉為pdf文件。

使用Adobe Acrobat軟件操作pdf,這里用的是這個軟件,只要能實現(xiàn)這個功能,其他的軟件也可~
選擇表單編輯哈,我們要在對應的坑上添加表單占位

在表單上添加文本域即可,所有的格式都用文本域即可,這里只是占坑。
對應的域名稱要與程序的名稱對應,方便后面數(shù)據(jù)填充,不然后面需要手動處理賦值。


創(chuàng)建個簡單的模板吧,要注意填充的空間要充足,不然會出現(xiàn)數(shù)據(jù)展示不全呦~
效果如下:

好了,到這里模板就生成好了,我們保存一下,然后放在我們的/resources/templates目錄下
PDF生成代碼編寫
在util包下創(chuàng)建PdfUtil.java工具類,代碼如下:
package com.maple.demo.util;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import javax.servlet.ServletOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
/**
* @author 笑小楓
* @date 2022/8/15
* @see <a rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >https://www.xiaoxiaofeng.com</a>
*/
public class PdfUtil {
private PdfUtil() {
}
/**
* 利用模板生成pdf
*
* @param data 寫入的數(shù)據(jù)
* @param photoMap 圖片信息
* @param out 自定義保存pdf的文件流
* @param templatePath pdf模板路徑
*/
public static void fillTemplate(Map<String, Object> data, Map<String, String> photoMap, ServletOutputStream out, String templatePath) {
PdfReader reader;
ByteArrayOutputStream bos;
PdfStamper stamper;
try {
// 讀取pdf模板
reader = new PdfReader(templatePath);
bos = new ByteArrayOutputStream();
stamper = new PdfStamper(reader, bos);
AcroFields acroFields = stamper.getAcroFields();
// 賦值
for (String name : acroFields.getFields().keySet()) {
String value = data.get(name) != null ? data.get(name).toString() : null;
acroFields.setField(name, value);
}
// 圖片賦值
for (Map.Entry<String, String> entry : photoMap.entrySet()) {
if (Objects.isNull(entry.getKey())) {
continue;
}
String key = entry.getKey();
String url = entry.getValue();
// 根據(jù)地址讀取需要放入pdf中的圖片
Image image = Image.getInstance(url);
// 設置圖片在哪一頁
PdfContentByte overContent = stamper.getOverContent(acroFields.getFieldPositions(key).get(0).page);
// 獲取模板中圖片域的大小
Rectangle signRect = acroFields.getFieldPositions(key).get(0).position;
float x = signRect.getLeft();
float y = signRect.getBottom();
// 圖片等比縮放
image.scaleAbsolute(signRect.getWidth(), signRect.getHeight());
// 圖片位置
image.setAbsolutePosition(x, y);
// 在該頁加入圖片
overContent.addImage(image);
}
// 如果為false那么生成的PDF文件還能編輯,一定要設為true
stamper.setFormFlattening(true);
stamper.close();
Document doc = new Document();
PdfCopy copy = new PdfCopy(doc, out);
doc.open();
PdfImportedPage importPage = copy.getImportedPage(new PdfReader(bos.toByteArray()), 1);
copy.addPage(importPage);
doc.close();
bos.close();
} catch (IOException | DocumentException e) {
e.printStackTrace();
}
}
}
在controller包下創(chuàng)建TestPdfController.java類,并i代碼如下:
package com.maple.demo.controller;
import com.maple.demo.util.PdfUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* @author 笑小楓
* @date 2022/8/15
* @see <a rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >https://www.xiaoxiaofeng.com</a>
*/
@Slf4j
@RestController
@RequestMapping("/example")
@Api(tags = "實例演示-PDF操作接口")
public class TestPdfController {
@ApiOperation(value = "根據(jù)PDF模板導出PDF")
@GetMapping("/exportPdf")
public void exportPdf(HttpServletResponse response) {
Map<String, Object> dataMap = new HashMap<>(16);
dataMap.put("nickName", "笑小楓");
dataMap.put("age", 18);
dataMap.put("sex", "男");
dataMap.put("csdnUrl", "https://zhangfz.blog.csdn.net/");
dataMap.put("siteUrl", "https://www.xiaoxiaofeng.com/");
dataMap.put("desc", "大家好,我是笑小楓。");
Map<String, String> photoMap = new HashMap<>(16);
photoMap.put("logo", "https://profile.csdnimg.cn/C/9/4/2_qq_34988304");
// 設置response參數(shù),可以打開下載頁面
response.reset();
response.setCharacterEncoding("UTF-8");
// 定義輸出類型
response.setContentType("application/PDF;charset=utf-8");
// 設置名稱
response.setHeader("Content-Disposition", "attachment; filename=" + "xiaoxiaofeng.pdf");
try (ServletOutputStream out = response.getOutputStream()) {
// 模板路徑記
PdfUtil.fillTemplate(dataMap, photoMap, out, "src/main/resources/templates/xiaoxiaofeng.pdf");
} catch (IOException e) {
e.printStackTrace();
}
}
}
重啟項目,在瀏覽器訪問:http://localhost:6666/example/exportPdf
導出的文件效果如下:

完全基于代碼生成,并保存
完全基于代碼生成PDF文件,這個就比較定制化了,這里只講常見的操作,大家可以用作參考:
自定義字體
PDF生成的時候,有沒有遇到過丟失中文字體的問題呢,唉~解決一下
先下載字體,這里用黑體演示哈,下載地址:https://www.zitijia.com/downloadpage?itemid=281258939050380345
下載完會得到一個Simhei.ttf文件,對我們就是要它
先在util包下創(chuàng)建一個字體工具類吧,代碼如下:
注意:代碼中相關的絕對路徑要替換成自己的
package com.maple.demo.util;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfPTable;
import java.io.IOException;
import java.util.List;
/**
* @author 笑小楓
* @date 2022/8/15
* @see <a rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >https://www.xiaoxiaofeng.com</a>
*/
public class PdfFontUtil {
private PdfFontUtil() {
}
/**
* 基礎配置,可以放相對路徑,這里演示絕對路徑,因為字體文件過大,這里不傳到項目里面了,需要的自己下載
* 下載地址:https://www.zitijia.com/downloadpage?itemid=281258939050380345
*/
public static final String FONT = "D:\\font/Simhei.ttf";
/**
* 基礎樣式
*/
public static final Font TITLE_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 20, Font.BOLD);
public static final Font NODE_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 15, Font.BOLD);
public static final Font BLOCK_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 13, Font.BOLD, BaseColor.BLACK);
public static final Font INFO_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 12, Font.NORMAL, BaseColor.BLACK);
public static final Font CONTENT_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
/**
* 段落樣式獲取
*/
public static Paragraph getParagraph(String content, Font font, Integer alignment) {
Paragraph paragraph = new Paragraph(content, font);
if (alignment != null && alignment >= 0) {
paragraph.setAlignment(alignment);
}
return paragraph;
}
/**
* 圖片樣式
*/
public static Image getImage(String imgPath, float width, float height) throws IOException, BadElementException {
Image image = Image.getInstance(imgPath);
image.setAlignment(Image.MIDDLE);
if (width > 0 && height > 0) {
image.scaleAbsolute(width, height);
}
return image;
}
/**
* 表格生成
*/
public static PdfPTable getPdfTable(int numColumns, float totalWidth) {
// 表格處理
PdfPTable table = new PdfPTable(numColumns);
// 設置表格寬度比例為%100
table.setWidthPercentage(100);
// 設置寬度:寬度平均
table.setTotalWidth(totalWidth);
// 鎖住寬度
table.setLockedWidth(true);
// 設置表格上面空白寬度
table.setSpacingBefore(10f);
// 設置表格下面空白寬度
table.setSpacingAfter(10f);
// 設置表格默認為無邊框
table.getDefaultCell().setBorder(0);
table.setPaddingTop(50);
table.setSplitLate(false);
return table;
}
/**
* 表格內容帶樣式
*/
public static void addTableCell(PdfPTable dataTable, Font font, List<String> cellList) {
for (String content : cellList) {
dataTable.addCell(getParagraph(content, font, -1));
}
}
}
pdf生成工具類
繼續(xù)在util包下的PdfUtil.java工具類里添加,因為本文涉及到的操作比較多,這里只貼相關代碼,最后統(tǒng)一貼一個完成的文件
注意:代碼中相關的絕對路徑要替換成自己的
/**
* 創(chuàng)建PDF,并保存到指定位置
*
* @param filePath 保存路徑
*/
public static void createPdfPage(String filePath) {
// FileOutputStream 需要關閉,釋放資源
try (FileOutputStream outputStream = new FileOutputStream(filePath)) {
// 創(chuàng)建文檔
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, outputStream);
document.open();
// 報告標題
document.add(PdfFontUtil.getParagraph("笑小楓的網站介紹", TITLE_FONT, 1));
document.add(PdfFontUtil.getParagraph("\n網站名稱:笑小楓(www.xiaoxiaofeng.com)", INFO_FONT, -1));
document.add(PdfFontUtil.getParagraph("\n生成時間:2022-07-02\n\n", INFO_FONT, -1));
// 報告內容
// 段落標題 + 報表圖
document.add(PdfFontUtil.getParagraph("文章數(shù)據(jù)統(tǒng)計", NODE_FONT, -1));
document.add(PdfFontUtil.getParagraph("\n· 網站首頁圖\n\n", BLOCK_FONT, -1));
// 設置圖片寬高
float documentWidth = document.getPageSize().getWidth() - document.leftMargin() - document.rightMargin();
float documentHeight = documentWidth / 580 * 320;
document.add(PdfFontUtil.getImage("D:\\xiaoxiaofeng.jpg", documentWidth - 80, documentHeight - 80));
// 數(shù)據(jù)表格
document.add(PdfFontUtil.getParagraph("\n· 數(shù)據(jù)詳情\n\n", BLOCK_FONT, -1));
// 生成6列的表格
PdfPTable dataTable = PdfFontUtil.getPdfTable(6, 500);
// 設置表格
List<String> tableHeadList = tableHead();
List<List<String>> tableDataList = getTableData();
PdfFontUtil.addTableCell(dataTable, CONTENT_FONT, tableHeadList);
for (List<String> tableData : tableDataList) {
PdfFontUtil.addTableCell(dataTable, CONTENT_FONT, tableData);
}
document.add(dataTable);
document.add(PdfFontUtil.getParagraph("\n· 報表描述\n\n", BLOCK_FONT, -1));
document.add(PdfFontUtil.getParagraph("數(shù)據(jù)報告可以監(jiān)控每天的推廣情況," +
"可以針對不同的數(shù)據(jù)表現(xiàn)進行分析,以提升推廣效果。", CONTENT_FONT, -1));
document.newPage();
document.close();
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 模擬數(shù)據(jù)
*/
private static List<String> tableHead() {
List<String> tableHeadList = new ArrayList<>();
tableHeadList.add("省份");
tableHeadList.add("城市");
tableHeadList.add("數(shù)量");
tableHeadList.add("百分比1");
tableHeadList.add("百分比2");
tableHeadList.add("百分比3");
return tableHeadList;
}
/**
* 模擬數(shù)據(jù)
*/
private static List<List<String>> getTableData() {
List<List<String>> tableDataList = new ArrayList<>();
for (int i = 0; i < 3; i++) {
List<String> tableData = new ArrayList<>();
tableData.add("浙江" + i);
tableData.add("杭州" + i);
tableData.add("276" + i);
tableData.add("33.3%");
tableData.add("34.3%");
tableData.add("35.3%");
tableDataList.add(tableData);
}
return tableDataList;
}
在controller包下的TestPdfController.java類中添加代碼,因為本文涉及到的操作比較多,這里只貼相關代碼,最后統(tǒng)一貼一個完成的文件,相關代碼如下:
注意:代碼中相關的絕對路徑要替換成自己的
@ApiOperation(value = "測試純代碼生成PDF到指定目錄")
@GetMapping("/createPdfLocal")
public void create() {
PdfUtil.createPdfPage("D:\\xxf.pdf");
}
重啟項目,在瀏覽器訪問:http://localhost:6666/example/createPdfLocal
可以在D:\\test目錄下看到xxf.pdf文件

我們打開看一下效果:

合并PDF,并保存
繼續(xù)在util包下的PdfUtil.java工具類里添加,因為本文涉及到的操作比較多,這里只貼相關代碼,最后統(tǒng)一貼一個完成的文件。
/**
* 合并pdf文件
*
* @param files 要合并文件數(shù)組(絕對路徑如{ "D:\\test\\1.pdf", "D:\\test\\2.pdf" , "D:\\test\\3.pdf"})
* @param newFile 合并后存放的目錄D:\\test\\xxf-merge.pdf
* @return boolean 生成功返回true, 否則返回false
*/
public static boolean mergePdfFiles(String[] files, String newFile) {
boolean retValue = false;
Document document;
try (FileOutputStream fileOutputStream = new FileOutputStream(newFile)) {
document = new Document(new PdfReader(files[0]).getPageSize(1));
PdfCopy copy = new PdfCopy(document, fileOutputStream);
document.open();
for (String file : files) {
PdfReader reader = new PdfReader(file);
int n = reader.getNumberOfPages();
for (int j = 1; j <= n; j++) {
document.newPage();
PdfImportedPage page = copy.getImportedPage(reader, j);
copy.addPage(page);
}
}
retValue = true;
document.close();
} catch (Exception e) {
e.printStackTrace();
}
return retValue;
}
在controller包下的TestPdfController.java類中添加代碼,因為本文涉及到的操作比較多,這里只貼相關代碼,最后統(tǒng)一貼一個完成的文件,相關代碼如下:
注意:代碼中相關的絕對路徑要替換成自己的
@ApiOperation(value = "測試合并PDF到指定目錄")
@GetMapping("/mergePdf")
public Boolean mergePdf() {
String[] files = {"D:\\test\\1.pdf", "D:\\test\\2.pdf"};
String newFile = "D:\\test\\xxf-merge.pdf";
return PdfUtil.mergePdfFiles(files, newFile);
}
我們首先要準備兩個文件D:\\test\\1.pdf,D:\\test\\2.pdf,這里可以指定文件,也可以是生成的pdf文件。
如果是處理生成的文件,這里說下思想:程序創(chuàng)建一個臨時目錄,注意要唯一命名,然后將生成PDF文件保存到這個目錄,然后從這個目錄下拿到pdf進行處理,最后處理完成,保存到對應的目錄下,刪除這個臨時目錄和下面的文件。這里不做演示。《合并PDF,并下載》里面略有涉及,但是處理的單個文件,稍微改造即可
重啟項目,在瀏覽器訪問:http://localhost:6666/example/mergePdf
可以在D:\\test目錄下看到xxf-merge文件

打開看一下效果:

合并PDF,并下載
繼續(xù)在util包下的PdfUtil.java工具類里添加,這里只貼將文件轉為輸出流,并刪除文件的相關代碼,合并相關代碼見《合并PDF,并保存》的util類。
/**
* 讀取PDF,讀取后刪除PDF,適用于生成后需要導出PDF,創(chuàng)建臨時文件
*/
public static void readDeletePdf(String fileName, ServletOutputStream outputStream) {
File file = new File(fileName);
if (!file.exists()) {
System.out.println(fileName + "文件不存在");
}
try (InputStream in = new FileInputStream(fileName)) {
IOUtils.copy(in, outputStream);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
Files.delete(file.toPath());
} catch (IOException e) {
e.printStackTrace();
}
}
}
在controller包下的TestPdfController.java類中添加代碼,因為本文涉及到的操作比較多,這里只貼相關代碼,最后統(tǒng)一貼一個完成的文件,相關代碼如下:
注意:代碼中相關的絕對路徑要替換成自己的
@ApiOperation(value = "測試合并PDF后并導出")
@GetMapping("/exportMergePdf")
public void createPdf(HttpServletResponse response) {
// 設置response參數(shù),可以打開下載頁面
response.reset();
response.setCharacterEncoding("UTF-8");
// 定義輸出類型
response.setContentType("application/PDF;charset=utf-8");
// 設置名稱
response.setHeader("Content-Disposition", "attachment; filename=" + "xiaoxiaofeng.pdf");
try (ServletOutputStream out = response.getOutputStream()) {
String[] files = {"D:\\test\\1.pdf", "D:\\test\\2.pdf"};
// 生成為臨時文件,轉換為流后,再刪除該文件
String newFile = "src\\main\\resources\\templates\\" + UUID.randomUUID() + ".pdf";
boolean isOk = PdfUtil.mergePdfFiles(files, newFile);
if (isOk) {
PdfUtil.readDeletePdf(newFile, out);
}
} catch (IOException e) {
e.printStackTrace();
}
}
重啟項目,在瀏覽器訪問:http://localhost:6666/example/exportMergePdf會提示我們下載。

完整代碼
PdfUtil.java
package com.maple.demo.util;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import org.apache.commons.compress.utils.IOUtils;
import javax.servlet.ServletOutputStream;
import java.io.*;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static com.maple.demo.util.PdfFontUtil.*;
/**
* @author 笑小楓
* @date 2022/8/15
* @see <a rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >https://www.xiaoxiaofeng.com</a>
*/
public class PdfUtil {
private PdfUtil() {
}
/**
* 利用模板生成pdf
*
* @param data 寫入的數(shù)據(jù)
* @param photoMap 圖片信息
* @param out 自定義保存pdf的文件流
* @param templatePath pdf模板路徑
*/
public static void fillTemplate(Map<String, Object> data, Map<String, String> photoMap, ServletOutputStream out, String templatePath) {
PdfReader reader;
ByteArrayOutputStream bos;
PdfStamper stamper;
try {
// 讀取pdf模板
reader = new PdfReader(templatePath);
bos = new ByteArrayOutputStream();
stamper = new PdfStamper(reader, bos);
AcroFields acroFields = stamper.getAcroFields();
// 賦值
for (String name : acroFields.getFields().keySet()) {
String value = data.get(name) != null ? data.get(name).toString() : null;
acroFields.setField(name, value);
}
// 圖片賦值
for (Map.Entry<String, String> entry : photoMap.entrySet()) {
if (Objects.isNull(entry.getKey())) {
continue;
}
String key = entry.getKey();
String url = entry.getValue();
// 根據(jù)地址讀取需要放入pdf中的圖片
Image image = Image.getInstance(url);
// 設置圖片在哪一頁
PdfContentByte overContent = stamper.getOverContent(acroFields.getFieldPositions(key).get(0).page);
// 獲取模板中圖片域的大小
Rectangle signRect = acroFields.getFieldPositions(key).get(0).position;
float x = signRect.getLeft();
float y = signRect.getBottom();
// 圖片等比縮放
image.scaleAbsolute(signRect.getWidth(), signRect.getHeight());
// 圖片位置
image.setAbsolutePosition(x, y);
// 在該頁加入圖片
overContent.addImage(image);
}
// 如果為false那么生成的PDF文件還能編輯,一定要設為true
stamper.setFormFlattening(true);
stamper.close();
Document doc = new Document();
PdfCopy copy = new PdfCopy(doc, out);
doc.open();
PdfImportedPage importPage = copy.getImportedPage(new PdfReader(bos.toByteArray()), 1);
copy.addPage(importPage);
doc.close();
bos.close();
} catch (IOException | DocumentException e) {
e.printStackTrace();
}
}
/**
* 創(chuàng)建PDF,并保存到指定位置
*
* @param filePath 保存路徑
*/
public static void createPdfPage(String filePath) {
// FileOutputStream 需要關閉,釋放資源
try (FileOutputStream outputStream = new FileOutputStream(filePath)) {
// 創(chuàng)建文檔
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, outputStream);
document.open();
// 報告標題
document.add(PdfFontUtil.getParagraph("笑小楓的網站介紹", TITLE_FONT, 1));
document.add(PdfFontUtil.getParagraph("\n網站名稱:笑小楓(www.xiaoxiaofeng.com)", INFO_FONT, -1));
document.add(PdfFontUtil.getParagraph("\n生成時間:2022-07-02\n\n", INFO_FONT, -1));
// 報告內容
// 段落標題 + 報表圖
document.add(PdfFontUtil.getParagraph("文章數(shù)據(jù)統(tǒng)計", NODE_FONT, -1));
document.add(PdfFontUtil.getParagraph("\n· 網站首頁圖\n\n", BLOCK_FONT, -1));
// 設置圖片寬高
float documentWidth = document.getPageSize().getWidth() - document.leftMargin() - document.rightMargin();
float documentHeight = documentWidth / 580 * 320;
document.add(PdfFontUtil.getImage("D:\\xiaoxiaofeng.jpg", documentWidth - 80, documentHeight - 80));
// 數(shù)據(jù)表格
document.add(PdfFontUtil.getParagraph("\n· 數(shù)據(jù)詳情\n\n", BLOCK_FONT, -1));
// 生成6列的表格
PdfPTable dataTable = PdfFontUtil.getPdfTable(6, 500);
// 設置表格
List<String> tableHeadList = tableHead();
List<List<String>> tableDataList = getTableData();
PdfFontUtil.addTableCell(dataTable, CONTENT_FONT, tableHeadList);
for (List<String> tableData : tableDataList) {
PdfFontUtil.addTableCell(dataTable, CONTENT_FONT, tableData);
}
document.add(dataTable);
document.add(PdfFontUtil.getParagraph("\n· 報表描述\n\n", BLOCK_FONT, -1));
document.add(PdfFontUtil.getParagraph("數(shù)據(jù)報告可以監(jiān)控每天的推廣情況," +
"可以針對不同的數(shù)據(jù)表現(xiàn)進行分析,以提升推廣效果。", CONTENT_FONT, -1));
document.newPage();
document.close();
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 模擬數(shù)據(jù)
*/
private static List<String> tableHead() {
List<String> tableHeadList = new ArrayList<>();
tableHeadList.add("省份");
tableHeadList.add("城市");
tableHeadList.add("數(shù)量");
tableHeadList.add("百分比1");
tableHeadList.add("百分比2");
tableHeadList.add("百分比3");
return tableHeadList;
}
/**
* 模擬數(shù)據(jù)
*/
private static List<List<String>> getTableData() {
List<List<String>> tableDataList = new ArrayList<>();
for (int i = 0; i < 3; i++) {
List<String> tableData = new ArrayList<>();
tableData.add("浙江" + i);
tableData.add("杭州" + i);
tableData.add("276" + i);
tableData.add("33.3%");
tableData.add("34.3%");
tableData.add("35.3%");
tableDataList.add(tableData);
}
return tableDataList;
}
/**
* 合并pdf文件
*
* @param files 要合并文件數(shù)組(絕對路徑如{ "D:\\test\\1.pdf", "D:\\test\\2.pdf" , "D:\\test\\3.pdf"})
* @param newFile 合并后存放的目錄D:\\test\\xxf-merge.pdf
* @return boolean 生成功返回true, 否則返回false
*/
public static boolean mergePdfFiles(String[] files, String newFile) {
boolean retValue = false;
Document document;
try (FileOutputStream fileOutputStream = new FileOutputStream(newFile)) {
document = new Document(new PdfReader(files[0]).getPageSize(1));
PdfCopy copy = new PdfCopy(document, fileOutputStream);
document.open();
for (String file : files) {
PdfReader reader = new PdfReader(file);
int n = reader.getNumberOfPages();
for (int j = 1; j <= n; j++) {
document.newPage();
PdfImportedPage page = copy.getImportedPage(reader, j);
copy.addPage(page);
}
}
retValue = true;
document.close();
} catch (Exception e) {
e.printStackTrace();
}
return retValue;
}
/**
* 讀取PDF,讀取后刪除PDF,適用于生成后需要導出PDF,創(chuàng)建臨時文件
*/
public static void readDeletePdf(String fileName, ServletOutputStream outputStream) {
File file = new File(fileName);
if (!file.exists()) {
System.out.println(fileName + "文件不存在");
}
try (InputStream in = new FileInputStream(fileName)) {
IOUtils.copy(in, outputStream);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
Files.delete(file.toPath());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
PdfFontUtil.java
package com.maple.demo.util;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfPTable;
import java.io.IOException;
import java.util.List;
/**
* @author 笑小楓
* @date 2022/8/15
* @see <a rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >https://www.xiaoxiaofeng.com</a>
*/
public class PdfFontUtil {
private PdfFontUtil() {
}
/**
* 基礎配置,可以放相對路徑,這里演示絕對路徑,因為字體文件過大,這里不傳到項目里面了,需要的自己下載
* 下載地址:https://www.zitijia.com/downloadpage?itemid=281258939050380345
*/
public static final String FONT = "D:\\font/Simhei.ttf";
/**
* 基礎樣式
*/
public static final Font TITLE_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 20, Font.BOLD);
public static final Font NODE_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 15, Font.BOLD);
public static final Font BLOCK_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 13, Font.BOLD, BaseColor.BLACK);
public static final Font INFO_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, 12, Font.NORMAL, BaseColor.BLACK);
public static final Font CONTENT_FONT = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
/**
* 段落樣式獲取
*/
public static Paragraph getParagraph(String content, Font font, Integer alignment) {
Paragraph paragraph = new Paragraph(content, font);
if (alignment != null && alignment >= 0) {
paragraph.setAlignment(alignment);
}
return paragraph;
}
/**
* 圖片樣式
*/
public static Image getImage(String imgPath, float width, float height) throws IOException, BadElementException {
Image image = Image.getInstance(imgPath);
image.setAlignment(Image.MIDDLE);
if (width > 0 && height > 0) {
image.scaleAbsolute(width, height);
}
return image;
}
/**
* 表格生成
*/
public static PdfPTable getPdfTable(int numColumns, float totalWidth) {
// 表格處理
PdfPTable table = new PdfPTable(numColumns);
// 設置表格寬度比例為%100
table.setWidthPercentage(100);
// 設置寬度:寬度平均
table.setTotalWidth(totalWidth);
// 鎖住寬度
table.setLockedWidth(true);
// 設置表格上面空白寬度
table.setSpacingBefore(10f);
// 設置表格下面空白寬度
table.setSpacingAfter(10f);
// 設置表格默認為無邊框
table.getDefaultCell().setBorder(0);
table.setPaddingTop(50);
table.setSplitLate(false);
return table;
}
/**
* 表格內容帶樣式
*/
public static void addTableCell(PdfPTable dataTable, Font font, List<String> cellList) {
for (String content : cellList) {
dataTable.addCell(getParagraph(content, font, -1));
}
}
}
TestPdfController.java
package com.maple.demo.controller;
import com.maple.demo.util.PdfUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author 笑小楓
* @date 2022/8/15
* @see <a rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >https://www.xiaoxiaofeng.com</a>
*/
@Slf4j
@RestController
@RequestMapping("/example")
@Api(tags = "實例演示-PDF操作接口")
public class TestPdfController {
@ApiOperation(value = "根據(jù)PDF模板導出PDF")
@GetMapping("/exportPdf")
public void exportPdf(HttpServletResponse response) {
Map<String, Object> dataMap = new HashMap<>(16);
dataMap.put("nickName", "笑小楓");
dataMap.put("age", 18);
dataMap.put("sex", "男");
dataMap.put("csdnUrl", "https://zhangfz.blog.csdn.net/");
dataMap.put("siteUrl", "https://www.xiaoxiaofeng.com/");
dataMap.put("desc", "大家好,我是笑小楓。");
Map<String, String> photoMap = new HashMap<>(16);
photoMap.put("logo", "https://profile.csdnimg.cn/C/9/4/2_qq_34988304");
// 設置response參數(shù),可以打開下載頁面
response.reset();
response.setCharacterEncoding("UTF-8");
// 定義輸出類型
response.setContentType("application/PDF;charset=utf-8");
// 設置名稱
response.setHeader("Content-Disposition", "attachment; filename=" + "xiaoxiaofeng.pdf");
try {
ServletOutputStream out = response.getOutputStream();
// 模板路徑記
PdfUtil.fillTemplate(dataMap, photoMap, out, "src/main/resources/templates/xiaoxiaofeng.pdf");
} catch (IOException e) {
e.printStackTrace();
}
}
@ApiOperation(value = "測試純代碼生成PDF到指定目錄")
@GetMapping("/createPdfLocal")
public void create() {
PdfUtil.createPdfPage("D:\\test\\xxf.pdf");
}
@ApiOperation(value = "測試合并PDF到指定目錄")
@GetMapping("/mergePdf")
public Boolean mergePdf() {
String[] files = {"D:\\test\\1.pdf", "D:\\test\\2.pdf"};
String newFile = "D:\\test\\xxf-merge.pdf";
return PdfUtil.mergePdfFiles(files, newFile);
}
@ApiOperation(value = "測試合并PDF后并導出")
@GetMapping("/exportMergePdf")
public void createPdf(HttpServletResponse response) {
// 設置response參數(shù),可以打開下載頁面
response.reset();
response.setCharacterEncoding("UTF-8");
// 定義輸出類型
response.setContentType("application/PDF;charset=utf-8");
// 設置名稱
response.setHeader("Content-Disposition", "attachment; filename=" + "xiaoxiaofeng.pdf");
try (ServletOutputStream out = response.getOutputStream()) {
String[] files = {"D:\\test\\1.pdf", "D:\\test\\2.pdf"};
// 生成為臨時文件,轉換為流后,再刪除該文件
String newFile = "src\\main\\resources\\templates\\" + UUID.randomUUID() + ".pdf";
boolean isOk = PdfUtil.mergePdfFiles(files, newFile);
if (isOk) {
PdfUtil.readDeletePdf(newFile, out);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
以上就是SpringBoot生成和操作PDF的代碼詳解的詳細內容,更多關于SpringBoot生成和操作PDF的資料請關注腳本之家其它相關文章!
相關文章
springboot接口接收數(shù)組及多個參數(shù)的問題及解決
這篇文章主要介紹了springboot接口接收數(shù)組及多個參數(shù)的問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11
java8 LocalDate LocalDateTime等時間類用法實例分析
這篇文章主要介紹了java8 LocalDate LocalDateTime等時間類用法,結合具體實例形式分析了LocalDate、LocalTime、LocalDateTime等日期時間相關類的功能與具體使用技巧,需要的朋友可以參考下2017-04-04
Springboot實現(xiàn)通用Auth認證的幾種方式
本文主要介紹了Springboot實現(xiàn)通用Auth認證的幾種方式,主要介紹了4種方式,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-07-07
SpringBoot3利用AOP實現(xiàn)IP黑名單功能
在Web應用開發(fā)中,控制對特定IP地址的訪問權限是一個常見的需求,通過實現(xiàn)IP黑白名單功能,我們可以允許某些IP地址訪問應用,同時拒絕其他IP地址的訪問,本文將詳細介紹SpringBoot3利用AOP實現(xiàn)IP黑名單功能,并附上相應的代碼片段,需要的朋友可以參考下2024-09-09
Netty進階之EventExecutorGroup源碼詳解
這篇文章主要介紹了Netty進階之EventExecutorGroup源碼詳解,EventExecutorGroup繼承了JDK的ScheduledExecutroService,那么它就擁有了執(zhí)行定時任務,執(zhí)行提交的普通任務,需要的朋友可以參考下2023-11-11

