SpringBoot生成和操作PDF的代碼詳解
本文簡介
本文涉及pdf操作,如下:
- PDF模板制作
- 基于PDF模板生成,并支持下載
- 自定義中文字體
- 完全基于代碼生成,并保存到指定目錄
- 合并PDF,并保存到指定目錄
- 合并PDF,并支持下載
基于PDF模板生成:適用于固定格式的PDF模板,基于內(nèi)容進行填空,例如:合同信息生成、固定格式表格等等
完全基于代碼生成:適用于不固定的PDF,例如:動態(tài)表格、動態(tài)添加某塊內(nèi)容、不確定的內(nè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文件轉(zhuǎn)為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; } /** * 表格內(nèi)容帶樣式 */ 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("笑小楓的網(wǎng)站介紹", TITLE_FONT, 1)); document.add(PdfFontUtil.getParagraph("\n網(wǎng)站名稱:笑小楓(www.xiaoxiaofeng.com)", INFO_FONT, -1)); document.add(PdfFontUtil.getParagraph("\n生成時間:2022-07-02\n\n", INFO_FONT, -1)); // 報告內(nèi)容 // 段落標題 + 報表圖 document.add(PdfFontUtil.getParagraph("文章數(shù)據(jù)統(tǒng)計", NODE_FONT, -1)); document.add(PdfFontUtil.getParagraph("\n· 網(wǎng)站首頁圖\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進行處理,最后處理完成,保存到對應的目錄下,刪除這個臨時目錄和下面的文件。這里不做演示?!逗喜DF,并下載》里面略有涉及,但是處理的單個文件,稍微改造即可
重啟項目,在瀏覽器訪問:http://localhost:6666/example/mergePdf
可以在D:\\test
目錄下看到xxf-merge
文件
打開看一下效果:
合并PDF,并下載
繼續(xù)在util
包下的PdfUtil.java
工具類里添加,這里只貼將文件轉(zhuǎn)為輸出流,并刪除文件的相關代碼,合并相關代碼見《合并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"}; // 生成為臨時文件,轉(zhuǎn)換為流后,再刪除該文件 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("笑小楓的網(wǎng)站介紹", TITLE_FONT, 1)); document.add(PdfFontUtil.getParagraph("\n網(wǎng)站名稱:笑小楓(www.xiaoxiaofeng.com)", INFO_FONT, -1)); document.add(PdfFontUtil.getParagraph("\n生成時間:2022-07-02\n\n", INFO_FONT, -1)); // 報告內(nèi)容 // 段落標題 + 報表圖 document.add(PdfFontUtil.getParagraph("文章數(shù)據(jù)統(tǒng)計", NODE_FONT, -1)); document.add(PdfFontUtil.getParagraph("\n· 網(wǎng)站首頁圖\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; } /** * 表格內(nèi)容帶樣式 */ 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"}; // 生成為臨時文件,轉(zhuǎn)換為流后,再刪除該文件 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的代碼詳解的詳細內(nèi)容,更多關于SpringBoot生成和操作PDF的資料請關注腳本之家其它相關文章!
相關文章
如何在JDK 9中更簡潔使用 try-with-resources 語句
本文詳細介紹了自 JDK 7 引入的 try-with-resources 語句的原理和用法,以及介紹了 JDK 9 對 try-with-resources 的改進,使得用戶可以更加方便、簡潔的使用 try-with-resources 語句。,需要的朋友可以參考下2019-06-06Mybatis中通用Mapper的InsertList()用法
文章介紹了通用Mapper中的insertList()方法在批量新增時的使用方式,包括自增ID和自定義ID的情況,對于自增ID,使用tk.mybatis.mapper.additional.insert.InsertListMapper包下的insertList()方法;對于自定義ID,需要重寫insertList()方法2025-02-02Windows系統(tǒng)下Eclipse搭建ESP32編譯環(huán)境及安裝過程
Ecppse 使用了 ESP-IDF 中的 Makefile 支持。這意味著您需要從創(chuàng)建 ESP-IDF 項目開始。您可以使用 github 中的 idf-template 項目,接下來通過本文給大家介紹Windows系統(tǒng)下Eclipse搭建ESP32編譯環(huán)境及安裝過程,感興趣的朋友一起看看吧2021-10-10基于Spring框架由ConditionalOnMissingBean注解引發(fā)的問題
這篇文章主要介紹了基于Spring框架由ConditionalOnMissingBean注解引發(fā)的問題,具有很好2023-11-11SpringBoot+MyBatisPlus+MySQL8實現(xiàn)樹形結構查詢
這篇文章主要為大家詳細介紹了SpringBoot+MyBatisPlus+MySQL8實現(xiàn)樹形結構查詢,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-06-06maven <repositories>標簽和<pluginRepositories>標簽的使用
這篇文章主要介紹了maven <repositories>標簽和<pluginRepositories>標簽的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-07-07