Java實(shí)現(xiàn)生成pdf并解決表格分割的問(wèn)題
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頁(yè)面直接后綴而成,模版名稱定為template-01.ftl
注意事項(xiàng)
中文亂碼問(wèn)題
需要在模板中添加font-family: SimSun, serif;標(biāo)簽,可解決中文亂碼問(wèn)題
body {
/*解決中文亂碼*/
font-family: SimSun, serif;
/*自動(dòng)換行*/
word-break: break-all;
}頁(yè)眉和頁(yè)腳
其實(shí)頁(yè)眉和頁(yè)腳可以通過(guò)定義的ftl模板來(lái)實(shí)現(xiàn)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Title</title>
<style>
/*頁(yè)眉的上下左右邊距*/
@page {
margin: 30mm 20mm 30mm 20mm;
}
@page {
/*頁(yè)眉*/
@top-center {
content: element(header)
}
/*頁(yè)腳*/
@bottom-center {
content: element(footer)
}
}
/*頁(yè)眉*/
#header {
position: running(header);
margin-top: 10mm;
}
/*頁(yè)腳*/
#footer {
position: running(footer);
}
/*分頁(yè)*/
#page-number:before {
content: counter(page);
}
/*分頁(yè)*/
#page-count:before {
content: counter(pages);
}
</style>
</head>
<body>
<!--頁(yè)眉-->
<div id="header">
深圳市xxx有限公司
<hr/>
</div>
<!--頁(yè)腳-->
<div id="footer">
頁(yè)碼<span id="page-number"></span>/<span id="page-count"></span>
</div>
</body>
</html>完整ftl模板頁(yè)面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Title</title>
<style>
/*頁(yè)眉的上下左右邊距*/
@page {
margin: 30mm 20mm 30mm 20mm;
}
@page {
/*頁(yè)眉*/
@top-center {
content: element(header)
}
/*頁(yè)腳*/
@bottom-center {
content: element(footer)
}
}
/*頁(yè)眉*/
#header {
position: running(header);
margin-top: 10mm;
}
/*頁(yè)腳*/
#footer {
position: running(footer);
}
/*分頁(yè)*/
#page-number:before {
content: counter(page);
}
/*分頁(yè)*/
#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>
<!--頁(yè)眉-->
<div id="header">
深圳市xxx有限公司
<hr/>
</div>
<!--頁(yè)腳-->
<div id="footer">
頁(yè)碼<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í)間里靜靜等待著成年那一刻的全速奔跑??陕L(zhǎng)的等待過(guò)后卻發(fā)現(xiàn),形形色色的欲望與世俗觀念像橡皮泥一樣粘在身上,越積越重,最后竟無(wú)限膨脹,束縛了我們的雙腿,減緩了我們的步伐。我們不能輕松上路,也不能全速奔跑。它們甚至遮蔽住我們的雙眼,遮掩住我們純真的心,讓我們的腳步開始凌亂,旋轉(zhuǎn)在燈紅酒綠的花花世界里……
</p>
<table>
<thead>
<tr>
<th>姓名</th>
<th>年齡</th>
<th>性別</th>
</tr>
</thead>
<tbody>
<#if !data?? || (data?size==0)>
<tr>
<td colspan="3">無(wú)</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頁(yè)面
String html = writer.toString();
renderer.setDocumentFromString(html);
renderer.layout();
// 通過(guò)html頁(yè)面字符串轉(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ì)存在一些問(wèn)題,
表格的形式會(huì)被自動(dòng)切割,出現(xiàn)以下情況
按照不同的需求,可以使用不同的方式來(lái)處理。
一是,當(dāng)被分頁(yè)時(shí),每頁(yè)都需要一個(gè)標(biāo)題的存在。
二是,分頁(yè)的頭部和尾部需要閉合起來(lái)
還有擴(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總頁(yè)數(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每頁(yè)的長(zhǎng)寬
float width = pageRect.getWidth();
float top = pageRect.getTop();
// 獲取縮放之后水印圖片的長(zhǎng)寬
float scaledWidth = image.getScaledWidth();
float scaledHeight = image.getScaledHeight();
// 通過(guò)計(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總頁(yè)數(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每頁(yè)的長(zhǎng)寬
float width = pageRect.getWidth();
float top = pageRect.getTop();
// 通過(guò)計(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);
}
}表格分頁(yè)被切割問(wèn)題-方式一
在ftl模板中的style標(biāo)簽中添加css樣式
tr {
page-break-inside: avoid;
page-break-after: auto;
}同一個(gè)表格在分頁(yè)時(shí),會(huì)被自動(dòng)添加上下邊框

表格分頁(yè)被切割問(wèn)題-方式二
在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">無(wú)</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>被分頁(yè)時(shí),表格的標(biāo)題也會(huì)攜帶下來(lái)

以上就是Java實(shí)現(xiàn)生成pdf并解決表格分割的問(wèn)題的詳細(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-01
Springboot MDC+logback實(shí)現(xiàn)日志追蹤的方法
MDC(Mapped Diagnostic Contexts)映射診斷上下文,該特征是logback提供的一種方便在多線程條件下的記錄日志的功能,這篇文章主要介紹了Springboot MDC+logback實(shí)現(xiàn)日志追蹤的方法,需要的朋友可以參考下2024-04-04
SpringBoot項(xiàng)目中的多數(shù)據(jù)源支持的方法
本篇文章主要介紹了SpringBoot項(xiàng)目中的多數(shù)據(jù)源支持的方法,主要介紹在SpringBoot項(xiàng)目中利用SpringDataJpa技術(shù)如何支持多個(gè)數(shù)據(jù)庫(kù)的數(shù)據(jù)源,有興趣的可以了解一下2017-10-10
Mybatis攔截器實(shí)現(xiàn)公共字段填充的示例代碼
本文介紹了使用Spring Boot和MyBatis實(shí)現(xiàn)公共字段的自動(dòng)填充功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12
Spring Boot中單例類實(shí)現(xiàn)對(duì)象的注入方式
這篇文章主要介紹了Spring Boot中單例類實(shí)現(xiàn)對(duì)象的注入方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Springmvc自定義異常處理器實(shí)現(xiàn)流程解析
這篇文章主要介紹了Springmvc自定義異常處理器實(shí)現(xiàn)流程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07

