Java實(shí)現(xiàn)生成pdf并解決表格分割的問題
Maven依賴
<dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.31</version> </dependency> <dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>flying-saucer-pdf</artifactId> <version>9.1.16</version> </dependency>
自定義ftl模板
該模板是由html頁面直接后綴而成,模版名稱定為template-01.ftl
注意事項(xiàng)
中文亂碼問題
需要在模板中添加font-family: SimSun, serif;
標(biāo)簽,可解決中文亂碼問題
body { /*解決中文亂碼*/ font-family: SimSun, serif; /*自動(dòng)換行*/ word-break: break-all; }
頁眉和頁腳
其實(shí)頁眉和頁腳可以通過定義的ftl模板來實(shí)現(xiàn)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"/> <title>Title</title> <style> /*頁眉的上下左右邊距*/ @page { margin: 30mm 20mm 30mm 20mm; } @page { /*頁眉*/ @top-center { content: element(header) } /*頁腳*/ @bottom-center { content: element(footer) } } /*頁眉*/ #header { position: running(header); margin-top: 10mm; } /*頁腳*/ #footer { position: running(footer); } /*分頁*/ #page-number:before { content: counter(page); } /*分頁*/ #page-count:before { content: counter(pages); } </style> </head> <body> <!--頁眉--> <div id="header"> 深圳市xxx有限公司 <hr/> </div> <!--頁腳--> <div id="footer"> 頁碼<span id="page-number"></span>/<span id="page-count"></span> </div> </body> </html>
完整ftl模板頁面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"/> <title>Title</title> <style> /*頁眉的上下左右邊距*/ @page { margin: 30mm 20mm 30mm 20mm; } @page { /*頁眉*/ @top-center { content: element(header) } /*頁腳*/ @bottom-center { content: element(footer) } } /*頁眉*/ #header { position: running(header); margin-top: 10mm; } /*頁腳*/ #footer { position: running(footer); } /*分頁*/ #page-number:before { content: counter(page); } /*分頁*/ #page-count:before { content: counter(pages); } * { padding: 0; margin: 0; } body { /*解決中文亂碼*/ font-family: SimSun, serif; /*自動(dòng)換行*/ word-break: break-all; } .main { width: 100%; height: auto; margin: 0 auto; text-align: center; } table { width: 100%; border-collapse: collapse; } td, th { line-height: 20px; padding: 7px 5px; border: 1px solid #999999; } </style> </head> <body> <!--頁眉--> <div id="header"> 深圳市xxx有限公司 <hr/> </div> <!--頁腳--> <div id="footer"> 頁碼<span id="page-number"></span>/<span id="page-count"></span> </div> <div class="main"> <h1>深圳市xxx有限公司</h1> <p style="margin: 30px 0 50px 0;text-align: left;"> 人在世俗的世界中行走著,在慢慢流逝的時(shí)間里靜靜等待著成年那一刻的全速奔跑。可漫長(zhǎng)的等待過后卻發(fā)現(xiàn),形形色色的欲望與世俗觀念像橡皮泥一樣粘在身上,越積越重,最后竟無限膨脹,束縛了我們的雙腿,減緩了我們的步伐。我們不能輕松上路,也不能全速奔跑。它們甚至遮蔽住我們的雙眼,遮掩住我們純真的心,讓我們的腳步開始凌亂,旋轉(zhuǎn)在燈紅酒綠的花花世界里…… </p> <table> <thead> <tr> <th>姓名</th> <th>年齡</th> <th>性別</th> </tr> </thead> <tbody> <#if !data?? || (data?size==0)> <tr> <td colspan="3">無</td> </tr> <#else> <#list data as item> <tr> <td>${item.name}</td> <td>${item.age}</td> <td>${item.sex}</td> </tr> </#list> </#if> </tbody> </table> </div> </body> </html>
PDF工具類
字體包SimSun.ttc、ArialUni.ttf自行下載
package org.example; import com.lowagie.text.pdf.BaseFont; import freemarker.template.Configuration; import freemarker.template.Template; import org.xhtmlrenderer.pdf.ITextFontResolver; import org.xhtmlrenderer.pdf.ITextRenderer; import java.io.IOException; import java.io.StringWriter; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Locale; /** * @author 苦瓜不苦 * @date 2023/11/28 18:33 **/ public class PDFUtil { /** * 模板生成器 * * @param createFile 生成文件的路徑 * @param ftlName 模板名稱 * @param object 數(shù)據(jù) */ public static void processTemplate(String createFile, String ftlName, Object object) { Configuration configuration = null; StringWriter writer = null; ByteArrayOutputStream outputStream = null; try { // 初始化模版 configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); writer = new StringWriter(); outputStream = new ByteArrayOutputStream(); // 加載模板目錄 configuration.setClassForTemplateLoading(MainApi.class, "/module"); configuration.setClassicCompatible(true); ITextRenderer renderer = new ITextRenderer(); // 設(shè)置字體 ITextFontResolver fontResolver = renderer.getFontResolver(); fontResolver.addFont("fonts/SimSun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); fontResolver.addFont("fonts/ArialUni.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); configuration.setEncoding(Locale.CHINA, "UTF-8"); // 讀取模板文件 Template template = configuration.getTemplate(ftlName, "UTF-8"); // 寫入數(shù)據(jù)到模板中 template.process(object, writer); writer.flush(); // 獲取填充好數(shù)據(jù)的html頁面 String html = writer.toString(); renderer.setDocumentFromString(html); renderer.layout(); // 通過html頁面字符串轉(zhuǎn)換成pdf文件 renderer.createPDF(outputStream); renderer.finishPDF(); return outputStream.toByteArray(); } catch (Exception e) { throw new RuntimeException(e); } finally { try { if (Objects.nonNull(outputStream)) { outputStream.close(); } if (Objects.nonNull(writer)) { writer.close(); } if (Objects.nonNull(configuration)) { configuration.clone(); } } catch (IOException e) { throw new RuntimeException(e); } } } }
調(diào)用測(cè)試
public class Main { public static void main(String[] args) { String fromFile = "./" + System.currentTimeMillis() + ".pdf"; String toFile = "template-01.ftl"; List<JSONObject> data = new ArrayList<>(); for (int i = 0; i < 60; i++) { JSONObject object = new JSONObject(); object.set("name", "張三"); object.set("sex", "男"); object.set("age", "18"); data.add(object); } JSONObject object = new JSONObject(); object.set("data", data); byte[] bytes = PDFUtil.processTemplate(toFile, object); File file = FileUtil.writeBytes(bytes, fromFile); System.err.println(file); } }
擴(kuò)展情況
以上代碼即可生成好一份PDF文檔了,但是會(huì)存在一些問題,
表格的形式會(huì)被自動(dòng)切割,出現(xiàn)以下情況
按照不同的需求,可以使用不同的方式來處理。
一是,當(dāng)被分頁時(shí),每頁都需要一個(gè)標(biāo)題的存在。
二是,分頁的頭部和尾部需要閉合起來
還有擴(kuò)展于圖片水印或者文字水印的需求
圖片水印方法
/** * 添加圖片水印 * * @param bytes pdf字節(jié) * @return */ public static byte[] appendImageWatermark(byte[] bytes) { try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { PdfReader reader = new PdfReader(bytes); PdfStamper stamper = new PdfStamper(reader, byteArrayOutputStream); // 加載水印圖片 URL url = PDFUtil.class.getClassLoader().getResource("fonts/bg.png"); Image image = Image.getInstance(url); // 設(shè)置等比縮放 圖片大小 image.scalePercent(20); // 自定義大小 // image.scaleAbsolute(200,100); // 設(shè)置旋轉(zhuǎn)弧度 image.setRotation(0); // 設(shè)置旋轉(zhuǎn)角度 image.setRotationDegrees(0); // 創(chuàng)建PdfGState對(duì)象并設(shè)置透明度 PdfGState gState = new PdfGState(); // 填充透明度 gState.setFillOpacity(0.3f); // 描邊透明度 gState.setStrokeOpacity(0.3f); // PDF總頁數(shù) int total = reader.getNumberOfPages() + 1; for (int i = 1; i < total; i++) { Rectangle pageRect = reader.getPageSizeWithRotation(i); PdfContentByte content = stamper.getOverContent(i); content.saveState(); content.setGState(gState); // 設(shè)置圖片水印 // 獲取pdf每頁的長(zhǎng)寬 float width = pageRect.getWidth(); float top = pageRect.getTop(); // 獲取縮放之后水印圖片的長(zhǎng)寬 float scaledWidth = image.getScaledWidth(); float scaledHeight = image.getScaledHeight(); // 通過計(jì)算將水印添加到中間 float x = (width - scaledWidth) / 2; float y = (top - scaledHeight) / 2; content.addImage(image, scaledWidth, 60, 0, scaledHeight, x, y); content.restoreState(); } stamper.close(); reader.close(); return byteArrayOutputStream.toByteArray(); } catch (Exception e) { throw new RuntimeException(e); } }
文字水印方法
/** * 添加文字水印 * * @param bytes pdf字節(jié) * @param text 水印文字 * @param size 文字大小 * @return */ public static byte[] appendTextWatermark(byte[] bytes, String text, Integer size) { try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { PdfReader reader = new PdfReader(bytes); PdfStamper stamper = new PdfStamper(reader, byteArrayOutputStream); // 創(chuàng)建PdfGState對(duì)象并設(shè)置透明度 PdfGState gState = new PdfGState(); // 填充透明度 gState.setFillOpacity(0.3f); // 描邊透明度 gState.setStrokeOpacity(0.3f); // 加載字體 BaseFont baseFont = BaseFont.createFont("fonts/ArialUni.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); // PDF總頁數(shù) int total = reader.getNumberOfPages() + 1; for (int i = 1; i < total; i++) { Rectangle pageRect = reader.getPageSizeWithRotation(i); PdfContentByte content = stamper.getOverContent(i); content.saveState(); // 獲取pdf每頁的長(zhǎng)寬 float width = pageRect.getWidth(); float top = pageRect.getTop(); // 通過計(jì)算將水印添加到中間 float x = (width - (size * text.length())) / 2; float y = (top - size) / 2; // 設(shè)置字體水印 content.beginText(); content.setGState(gState); // 字體 content.setFontAndSize(baseFont, size); // 顏色 content.setColorFill(Color.BLACK); // 水印位置 content.showTextAligned(Element.ALIGN_LEFT, text, x, y, 30); content.endText(); content.restoreState(); } stamper.close(); reader.close(); return byteArrayOutputStream.toByteArray(); } catch (Exception e) { throw new RuntimeException(e); } }
表格分頁被切割問題-方式一
在ftl模板中的style標(biāo)簽中添加css樣式
tr { page-break-inside: avoid; page-break-after: auto; }
同一個(gè)表格在分頁時(shí),會(huì)被自動(dòng)添加上下邊框
表格分頁被切割問題-方式二
在ftl模板中的style標(biāo)簽中添加css樣式,需要注意的是表格的標(biāo)題需要使用thead標(biāo)簽包裹,表格其他行用tbody標(biāo)簽包裹
<style> table { page-break-inside: auto; -fs-table-paginate: paginate; border-spacing: 0; } tr { page-break-inside: avoid; page-break-after: auto; } </style> <body> <table> <thead> <tr> <th>姓名</th> <th>年齡</th> <th>性別</th> </tr> </thead> <tbody> <#if !data?? || (data?size==0)> <tr> <td colspan="3">無</td> </tr> <#else> <#list data as item> <tr> <td>${item.name}</td> <td>${item.age}</td> <td>${item.sex}</td> </tr> </#list> </#if> </tbody> </table> </body>
被分頁時(shí),表格的標(biāo)題也會(huì)攜帶下來
以上就是Java實(shí)現(xiàn)生成pdf并解決表格分割的問題的詳細(xì)內(nèi)容,更多關(guān)于Java生成pdf的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
淺析Java中對(duì)象的創(chuàng)建與對(duì)象的數(shù)據(jù)類型轉(zhuǎn)換
這篇文章主要介紹了Java中對(duì)象的創(chuàng)建與對(duì)象的數(shù)據(jù)類型轉(zhuǎn)換,是Java入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2016-01-01Springboot MDC+logback實(shí)現(xiàn)日志追蹤的方法
MDC(Mapped Diagnostic Contexts)映射診斷上下文,該特征是logback提供的一種方便在多線程條件下的記錄日志的功能,這篇文章主要介紹了Springboot MDC+logback實(shí)現(xiàn)日志追蹤的方法,需要的朋友可以參考下2024-04-04SpringBoot項(xiàng)目中的多數(shù)據(jù)源支持的方法
本篇文章主要介紹了SpringBoot項(xiàng)目中的多數(shù)據(jù)源支持的方法,主要介紹在SpringBoot項(xiàng)目中利用SpringDataJpa技術(shù)如何支持多個(gè)數(shù)據(jù)庫的數(shù)據(jù)源,有興趣的可以了解一下2017-10-10Mybatis攔截器實(shí)現(xiàn)公共字段填充的示例代碼
本文介紹了使用Spring Boot和MyBatis實(shí)現(xiàn)公共字段的自動(dòng)填充功能,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12Spring Boot中單例類實(shí)現(xiàn)對(duì)象的注入方式
這篇文章主要介紹了Spring Boot中單例類實(shí)現(xiàn)對(duì)象的注入方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08Springmvc自定義異常處理器實(shí)現(xiàn)流程解析
這篇文章主要介紹了Springmvc自定義異常處理器實(shí)現(xiàn)流程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07