SpringBoot使用itext填充pdf表單及導出pdf的流程
前言
由于最近開發(fā)的項目需要用到打印單據(jù),就在網(wǎng)上找了一下方案,反反復復,都沒有找到合適的,借鑒了網(wǎng)上資源,使用itext5、itext7的工具包,自己寫了一個通用的pdf表單填充工具,簡單易用,支持單頁、分頁單據(jù)打印,當然還有更多的定制化,需要大家自行擴展。
一、設(shè)計表單
1.使用excel設(shè)計了一個pdf單據(jù),因為比較整齊設(shè)計方便,如下圖:

2.使用免費工具PDFgear將excel文件轉(zhuǎn)為pdf文件,如下圖:

3.使用免費Adobe Acrobat DC工具編輯pdf文件
打開轉(zhuǎn)換好的pdf文件,選擇工具欄右邊的編輯表單,然后拖動頂部欄的表單域設(shè)計屬性,屬性值和和代碼填充的要一致,字體下拉最后面的,不然你打印出來的單據(jù)字體會很難看,當然也可以在程序代碼里去設(shè)計字體,一般設(shè)計好就夠用了,如果沒有特定要求的話。
ps:有些可能編輯表單,使用默認的,有時候可能顯示不了數(shù)據(jù),這個把這個表單域刪除了,重新添加一個即可。





OK,表單設(shè)計好,下面就是編碼啦
二、項目搭建
我這里是使用阿里云官方提供快速創(chuàng)建腳手架的在線網(wǎng)站生成了一個springboot項目,項目結(jié)構(gòu)圖如下:

三、引入依賴
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>7.2.5</version>
<type>pom</type>
</dependency>
四、編寫工具類PdfItext7Util代碼
package com.example.pdfdemo.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import com.example.pdfdemo.form.PdfFormTemplate;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.itextpdf.forms.PdfAcroForm;
import com.itextpdf.forms.fields.PdfFormField;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.utils.PdfMerger;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;
import java.io.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* <p>pdfItext7工具類</p>
*
* @author aliqingge
* @date 2024/8/30 14:57
*/
@Slf4j
public class PdfItext7Util {
/**
* 每頁顯示數(shù),可自行修改
*/
public static final int PAGE_SIZE = 12;
/**
* 臨時文件路徑
*/
public final static String FOLDER = "/tempPdfFile";
/**
* 文件路徑
*/
public final static String FOLDER_PATH = FOLDER + "/";
/**
* 填充表單
*
* @param template 模版對象
* @return 填充表單文件路徑
*/
public static String fillTemplate(PdfFormTemplate template) {
Assert.notNull(template, "模版對象不能為空");
// 創(chuàng)建臨時文件夾
File file = new File(FOLDER);
if (!file.exists() && !file.isDirectory()) {
file.setWritable(true, false);
file.mkdirs();
}
// 合并后生成文件的目標路徑
String destPdfPath = StringUtils.join(
template.isGenerateToLocalFileFlag() ? template.getLocalFileStoragePath() : FOLDER_PATH,
System.currentTimeMillis(),
template.getTemplateFileName(),
".pdf"
);
// 獲取pdf字節(jié)數(shù)組列表
List<byte[]> pdfByteList = getPdfByteList(template);
// 合并文件
mergePdf(pdfByteList, destPdfPath);
return destPdfPath;
}
/**
* 填充表單,并返回字節(jié)數(shù)組
*
* @param template 模版對象
* @return 填充表單文件路徑
*/
public static byte[] fillAndGetPdfByte(PdfFormTemplate template) {
Assert.notNull(template, "模版對象不能為空");
return mergePdf(getPdfByteList(template));
}
/**
* 獲取pdf字節(jié)數(shù)組列表
*
* @param template
* @return
*/
private static List<byte[]> getPdfByteList(PdfFormTemplate template) {
List<byte[]> pdfByteList = Lists.newArrayList();
// 數(shù)據(jù)分頁
List<List<Map<String, String>>> pageList = Lists.partition(template.getPageDataList(), PAGE_SIZE);
int pageTotal = pageList.size();
for (int i = 0; i < pageList.size(); i++) {
// 生成每頁pdf的字節(jié)數(shù)組
byte[] currentPagePdf = createCurrentPagePdf(template, pageList.get(i), (i + 1), pageTotal);
pdfByteList.add(currentPagePdf);
}
return pdfByteList;
}
/**
* 生成當前頁pdf字節(jié)數(shù)組
*
* @param template
* @param currentPageDataList
* @param currentPageNo
* @param pageTotal
* @return
*/
private static byte[] createCurrentPagePdf(PdfFormTemplate template, List<Map<String, String>> currentPageDataList, int currentPageNo, int pageTotal) {
PdfDocument pdfDoc = null;
try {
// 輸出流用于存儲填充好數(shù)據(jù)的PDF文件
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 打開PDF模板文件
PdfReader reader = new PdfReader(template.getTemplatePath());
// 創(chuàng)建PDF寫入器
PdfWriter writer = new PdfWriter(bos);
// 創(chuàng)建PDF文檔對象,連接讀取器和寫入器
pdfDoc = new PdfDocument(reader, writer);
// 設(shè)置默認頁面大小為A4
pdfDoc.setDefaultPageSize(PageSize.A4);
// 獲取PDF表單
PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
// 設(shè)置頁碼
PdfFormField formField = form.getField(template.getCustomPageNoFiledName());
if (formField != null) {
formField.setValue(StringUtils.join(currentPageNo, "/", pageTotal));
}
Map<String, String> baseDataMap = template.getBaseDataMap();
if (CollUtil.isNotEmpty(baseDataMap)) {
// 填充基本信息
for (String dataKey : baseDataMap.keySet()) {
PdfFormField field = form.getField(dataKey);
if (field != null) {
field.setValue(baseDataMap.get(dataKey));
}
}
}
if (CollUtil.isNotEmpty(currentPageDataList)) {
int i = 1;
// 填充分頁數(shù)據(jù)
for (Map<String, String> currentPageDataMap : currentPageDataList) {
// 填充序號, 可以自定義屬性,從外部傳入
PdfFormField noFormField = form.getField(StringUtils.join("no", i));
if (noFormField != null) {
noFormField.setValue(String.valueOf((currentPageNo - 1) * PAGE_SIZE + i));
}
// 填充其他數(shù)據(jù)
Set<Map.Entry<String, String>> entries = currentPageDataMap.entrySet();
int finalNo = i;
entries.forEach(entry -> {
PdfFormField field = form.getField(StringUtils.join(entry.getKey(), finalNo));
if (field != null) {
field.setValue(entry.getValue());
}
});
i++;
}
}
// 設(shè)置表單不可編輯
form.flattenFields();
// 可以自定義字體
/*PdfFont pdfFont = loadPdfFont();
if (pdfFont != null) {
// 給表單域字段設(shè)置字體和大小
Map<String, PdfFormField> allFormFields = form.getFormFields();
for (String formFiled : allFormFields.keySet()) {
if (template.getFontSize() > 0) {
allFormFields.get(formFiled).setFontAndSize(pdfFont, template.getFontSize());
} else {
allFormFields.get(formFiled).setFont(pdfFont);
}
}
}*/
pdfDoc.close();
return bos.toByteArray();
} catch (IOException e) {
log.error("createCurrentPagePdf 填充PDF表單失敗", e);
throw new RuntimeException(e);
} finally {
if (pdfDoc != null) {
pdfDoc.close();
}
}
}
/**
* 合并pdf文件
*
* @param pdfByteList 待合并PDF文檔字節(jié)集合
* @param destPath 生成合并PDF目標文檔路徑
*/
public static void mergePdf(List<byte[]> pdfByteList, String destPath) {
try {
int size = pdfByteList.size();
byte[] pdfData = pdfByteList.get(0);
for (int i = 1; i < size; i++) {
pdfData = mergePdfBytes(pdfData, pdfByteList.get(i));
}
if (pdfData != null) {
FileOutputStream fis = new FileOutputStream(destPath);
fis.write(pdfData);
fis.close();
}
} catch (Exception e) {
log.error("mergePdf 合并PDF異常", e);
throw new RuntimeException("合并PDF異常");
}
}
/**
* 合并pdf文件
*
* @param pdfByteList 待合并PDF文檔字節(jié)集合
*/
public static byte[] mergePdf(List<byte[]> pdfByteList) {
try {
int size = pdfByteList.size();
byte[] pdfData = pdfByteList.get(0);
for (int i = 1; i < size; i++) {
pdfData = mergePdfBytes(pdfData, pdfByteList.get(i));
}
return pdfData;
} catch (Exception e) {
log.error("mergePdf 合并PDF異常", e);
throw new RuntimeException("合并PDF異常");
}
}
/**
* 基于內(nèi)存中的字節(jié)數(shù)組進行PDF文檔的合并
*
* @param firstPdf 第一個PDF文檔字節(jié)數(shù)組
* @param secondPdf 第二個PDF文檔字節(jié)數(shù)組
*/
@SneakyThrows
private static byte[] mergePdfBytes(byte[] firstPdf, byte[] secondPdf) {
if (firstPdf == null || secondPdf == null) {
return null;
}
// 1.創(chuàng)建字節(jié)數(shù)組,基于內(nèi)存進行合并
ByteArrayOutputStream os = new ByteArrayOutputStream();
PdfDocument destDoc = new PdfDocument(new PdfWriter(os));
// 2.合并的pdf文件對象
PdfDocument firstDoc = new PdfDocument(new PdfReader(new ByteArrayInputStream(firstPdf)));
PdfDocument secondDoc = new PdfDocument(new PdfReader(new ByteArrayInputStream(secondPdf)));
// 3.合并對象
PdfMerger merger = new PdfMerger(destDoc);
log.info("mergePdfBytes 合并PDF文檔,第一個PDF文檔頁數(shù):{}, 第二個PDF文檔頁數(shù):{}", firstDoc.getNumberOfPages(), secondDoc.getNumberOfPages());
merger.merge(firstDoc, 1, firstDoc.getNumberOfPages());
merger.merge(secondDoc, 1, secondDoc.getNumberOfPages());
log.info("mergePdfBytes 合并PDF文檔成功");
// 4.關(guān)閉文檔流
merger.close();
firstDoc.close();
secondDoc.close();
destDoc.close();
return os.toByteArray();
}
/**
* 判斷當前系統(tǒng)是否是Windows系統(tǒng)
*
* @return true:Windows系統(tǒng),false:Linux系統(tǒng)
*/
public static boolean isWindowsSystem() {
String property = System.getProperty("os.name").toLowerCase();
return property.contains("windows");
}
/**
* 加載字體
* @return pdf字體
*/
public static PdfFont loadPdfFont(String fontName) {
PdfFont pdfFont = null;
try {
if (StrUtil.isBlank(fontName)) {
return pdfFont;
}
// 自行擴展
String fontStr = isWindowsSystem() ? "/fonts/STSONG.TTF" : "/fonts/extend/simsun.ttc,0";
pdfFont = PdfFontFactory.createFont(fontStr, PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
} catch (IOException e) {
log.error("loadPdfFont 無法加載字體", e);
}
return pdfFont;
}
public static void main(String[] args) {
// 構(gòu)建基本信息
Map<String, String> baseDataMap = Maps.newHashMap();
baseDataMap.put("orderNo", "X202408301625");
baseDataMap.put("memberName", "張三");
baseDataMap.put("memberPhone", "18888888888");
baseDataMap.put("orderDate", LocalDateTimeUtil.format(LocalDate.now(), DatePattern.NORM_DATE_PATTERN));
baseDataMap.put("remark", "我是備注呀");
baseDataMap.put("auditUserName", "李四");
baseDataMap.put("totalAmount", "¥182.25");
List<Map<String, String>> pageDataList = Lists.newArrayList();
for (int i = 1; i < 15; i++) {
Map<String, String> pageDataMap = Maps.newHashMap();
BigDecimal price = BigDecimal.valueOf(i * Math.random()).setScale(2, RoundingMode.HALF_UP);
pageDataMap.put("goodsName", StringUtils.join("商品名稱", i));
pageDataMap.put("price", StringUtils.join("¥", price));
pageDataMap.put("totalAmount", StringUtils.join("¥", price));
pageDataMap.put("address", StringUtils.join("廣東省廣州市越秀區(qū)北京路", i, "號"));
pageDataList.add(pageDataMap);
}
// 構(gòu)建業(yè)務(wù)數(shù)據(jù)
/*PdfFormTemplate formTemplate = PdfFormTemplate.builder()
.baseDataMap(baseDataMap)
.pageDataList(pageDataList)
.customPageNoFiledName("pageNo")
.templatePath("E:\\soft\\IdeaProjects\\pdf-form-fill-demo\\src\\main\\resources\\telephone\\出庫單據(jù)模版form.pdf")
.templateFileName("pdf表單模版")
.build();*/
PdfFormTemplate formTemplate = new PdfFormTemplate()
.setBaseDataMap(baseDataMap)
.setPageDataList(pageDataList)
// 本地測試是絕對路徑、發(fā)布web環(huán)境就相對路徑
.setTemplatePath("E:\\soft\\IdeaProjects\\pdf-form-fill-demo\\src\\main\\resources\\template\\出庫單據(jù)模版form.pdf")
.setTemplateFileName("pdf表單模版");
String pdfPath = fillTemplate(formTemplate);
// byte[] andGetPdfByte = fillAndGetPdfByte(formTemplate);
log.info("PDF路徑:{}", pdfPath);
}
}
五、編輯工具類PdfItext5Util代碼
package com.example.pdfdemo.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.example.pdfdemo.form.PdfFormTemplate;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.itextpdf.text.Document;
import com.itextpdf.text.pdf.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.*;
/**
* <p>pdfItext5工具類</p>
*
* @author aliqingge
* @date 2024/8/30 18:38
*/
@Slf4j
public class PdfItext5Util {
/**
* 每頁顯示數(shù),可自行修改
*/
public static final int PAGE_SIZE = 12;
/**
* 臨時文件路徑
*/
public final static String FOLDER = "/tempPdfFile";
/**
* 文件路徑
*/
public final static String FOLDER_PATH = FOLDER + "/";
/**
* 獲取pdf字節(jié)數(shù)組列表
*
* @param template
* @return
*/
private static String createPdf(PdfFormTemplate template) throws Exception {
List<PdfReader> pdfReaderList = new ArrayList<>(Collections.emptyList());
// 數(shù)據(jù)分頁
List<List<Map<String, String>>> pageList = Lists.partition(template.getPageDataList(), PAGE_SIZE);
int pageTotal = pageList.size();
for (int i = 0; i < pageList.size(); i++) {
// 生成每頁pdf的字節(jié)數(shù)組
PdfReader currentPagePdf = createCurrentPagePdf(template, pageList.get(i), (i + 1), pageTotal);
pdfReaderList.add(currentPagePdf);
}
// 合并后生成文件的目標路徑
String destPdfPath = StringUtils.join(
template.isGenerateToLocalFileFlag() ? template.getLocalFileStoragePath() : FOLDER_PATH,
System.currentTimeMillis(),
template.getTemplateFileName(),
".pdf"
);
//輸出流
FileOutputStream outputStream = new FileOutputStream(destPdfPath);
Document document = new Document(new PdfReader(pdfReaderList.get(0)).getPageSize(1));
PdfSmartCopy copy = new PdfSmartCopy(document, outputStream);
document.open();
//合并所有分頁pdf
for (int k = 1; k <= pdfReaderList.size(); k++) {
PdfReader reader = pdfReaderList.get(k - 1);
document.newPage();
PdfImportedPage page = copy.getImportedPage(reader, 1);
copy.addPage(page);
}
copy.close();
outputStream.close();
return destPdfPath;
}
/**
* 獲取分頁pdf
*
* @param template pdf表單模版
* @param currentTableData 當前頁表格數(shù)據(jù)
* @param currentPage 當前頁
* @param pageCount 總頁數(shù)
* @return
* @throws Exception
*/
/**
* 生成當前頁pdf字節(jié)數(shù)組
*
* @param template
* @param currentPageDataList
* @param currentPageNo
* @param pageTotal
* @return
*/
private static PdfReader createCurrentPagePdf(PdfFormTemplate template, List<Map<String, String>> currentPageDataList, int currentPageNo, int pageTotal) throws Exception {
PdfReader templateReader;
ByteArrayOutputStream bos;
//讀取pdf模板
templateReader = new PdfReader(template.getTemplatePath());
bos = new ByteArrayOutputStream();
PdfStamper stamper = new PdfStamper(templateReader, bos);
AcroFields form = stamper.getAcroFields();
// 自定義字體設(shè)置
/*BaseFont baseFont;
if (isWindowsSystem()) {
baseFont = BaseFont.createFont("/fonts/STSONG.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
} else {
baseFont = BaseFont.createFont("/fonts/extend/simsun.ttc,0", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
}
form.setSubstitutionFonts(Lists.newArrayList(baseFont));*/
// 設(shè)置頁碼
form.setField(template.getCustomPageNoFiledName(), StringUtils.join(currentPageNo, "/", pageTotal));
// 填充基本信息
Map<String, String> baseDataMap = template.getBaseDataMap();
if (CollUtil.isNotEmpty(baseDataMap)) {
for (String dataKey : baseDataMap.keySet()) {
form.setField(dataKey, baseDataMap.get(dataKey));
}
}
if (CollUtil.isNotEmpty(currentPageDataList)) {
int i = 1;
// 填充分頁數(shù)據(jù)
for (Map<String, String> currentPageDataMap : currentPageDataList) {
// 填充序號, 可以自定義屬性,從外部傳入
form.setField("no", String.valueOf((currentPageNo - 1) * PAGE_SIZE + i));
// 填充其他數(shù)據(jù)
Set<Map.Entry<String, String>> entries = currentPageDataMap.entrySet();
int finalNo = i;
entries.forEach(entry -> {
try {
form.setField(StringUtils.join(entry.getKey(), finalNo), entry.getValue());
} catch (Exception e) {
log.error("填充表單數(shù)據(jù)異常", e);
}
});
i++;
}
}
/*Map<String, AcroFields.Item> formFields = form.getFields();
for (String key : formFields.keySet()) {
form.setFieldProperty(key, "textfont", baseFont, null);
form.setFieldProperty(key, "textsize", 8f, null);
}*/
String pagePdfPath = FOLDER_PATH + System.currentTimeMillis() + ".pdf";
FileOutputStream outputStream = new FileOutputStream(pagePdfPath);
//如果為false那么生成的PDF文件還能編輯,一定要設(shè)為true
stamper.setFormFlattening(true);
stamper.close();
Document doc = new Document();
PdfSmartCopy copy = new PdfSmartCopy(doc, outputStream);
doc.open();
PdfReader reader = new PdfReader(bos.toByteArray());
PdfImportedPage importPage = copy.getImportedPage(reader, 1);
copy.addPage(importPage);
doc.close();
outputStream.close();
return reader;
}
/**
* 判斷當前系統(tǒng)是否是Windows系統(tǒng)
*
* @return
*/
public static boolean isWindowsSystem() {
String property = System.getProperty("os.name").toLowerCase();
return property.contains("windows");
}
public static void main(String[] args) throws Exception {
// 構(gòu)建基本信息
Map<String, String> baseDataMap = Maps.newHashMap();
baseDataMap.put("orderNo", "X202408301625");
baseDataMap.put("memberName", "張三");
baseDataMap.put("memberPhone", "18888888888");
baseDataMap.put("orderDate", LocalDateTimeUtil.format(LocalDate.now(), DatePattern.NORM_DATE_PATTERN));
baseDataMap.put("remark", "我是備注呀");
baseDataMap.put("auditUserName", "李四");
baseDataMap.put("totalAmount", "¥182.25");
List<Map<String, String>> pageDataList = Lists.newArrayList();
for (int i = 1; i < 15; i++) {
Map<String, String> pageDataMap = Maps.newHashMap();
BigDecimal price = BigDecimal.valueOf(i * Math.random()).setScale(2, RoundingMode.HALF_UP);
pageDataMap.put("goodsName", StringUtils.join("商品名稱", i));
pageDataMap.put("price", StringUtils.join("¥", price));
pageDataMap.put("totalAmount", StringUtils.join("¥", price));
pageDataMap.put("address", StringUtils.join("廣東省廣州市越秀區(qū)北京路", i, "號"));
pageDataList.add(pageDataMap);
}
// 構(gòu)建業(yè)務(wù)數(shù)據(jù)
/*PdfFormTemplate formTemplate = PdfFormTemplate.builder()
.baseDataMap(baseDataMap)
.pageDataList(pageDataList)
.customPageNoFiledName("pageNo")
.templatePath("E:\\soft\\IdeaProjects\\pdf-form-fill-demo\\src\\main\\resources\\telephone\\出庫單據(jù)模版form.pdf")
.templateFileName("pdf表單模版")
.build();*/
PdfFormTemplate formTemplate = new PdfFormTemplate()
.setBaseDataMap(baseDataMap)
.setPageDataList(pageDataList)
.setTemplatePath("E:\\soft\\IdeaProjects\\pdf-form-fill-demo\\src\\main\\resources\\template\\出庫單據(jù)模版form.pdf")
.setTemplateFileName("測試pdf表單模版");
String pdf = createPdf(formTemplate);
log.info("pdf文件路徑: {}", pdf);
}
}
六、統(tǒng)一模版代碼類說明
統(tǒng)一模板類,對業(yè)務(wù)數(shù)據(jù)、模板、字體等屬性進行封裝,要對哪個pdf模版進行填充數(shù)據(jù),入口處只需按照模版要求組裝業(yè)務(wù)數(shù)據(jù),調(diào)用填充接口即可,無需過多操作,當然很多操作還可以自行擴展。
package com.example.pdfdemo.form;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* <p>pdf表單模版對象</p>
*
* @author aliqingge
* @date 2024/8/30 14:57
*/
@Data
@Builder
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class PdfFormTemplate implements Serializable {
private static final long serialVersionUID = 3935687509620254261L;
/**
* 基本數(shù)據(jù)
*/
private Map<String, String> baseDataMap;
/**
* 分頁數(shù)據(jù)集合
*/
private List<Map<String, String>> pageDataList;
/**
* 自定義頁碼字段名,默認pageNumber
*/
private String customPageNoFiledName = "pageNo";
/**
* 填充模版路徑
*/
private String templatePath;
/**
* 模版文件名, 默認pdf表單模版
*/
private String templateFileName = "pdf表單模版";
/**
* 字體大小,默認8f
*/
private float fontSize = 8f;
/**
* 字體名稱,默認宋體
*/
private String fontName;
/**
* 是否生成到本地文件,默認生成
*/
private boolean generateToLocalFileFlag = true;
/**
* 本地文件存儲路徑
*/
private String localFileStoragePath = "C:\\tempPdfFile\\";
}
七、填充pdf表單核心代碼
1.填充入口
/**
* 填充表單
*
* @param template 模版對象
* @return 填充表單文件路徑
*/
public static String fillTemplate(PdfFormTemplate template) {
Assert.notNull(template, "模版對象不能為空");
// 創(chuàng)建臨時文件夾
File file = new File(FOLDER);
if (!file.exists() && !file.isDirectory()) {
file.setWritable(true, false);
file.mkdirs();
}
// 合并后生成文件的目標路徑
String destPdfPath = StringUtils.join(
template.isGenerateToLocalFileFlag() ? template.getLocalFileStoragePath() : FOLDER_PATH,
System.currentTimeMillis(),
template.getTemplateFileName(),
".pdf"
);
// 獲取pdf字節(jié)數(shù)組列表
List<byte[]> pdfByteList = getPdfByteList(template);
// 合并文件
mergePdf(pdfByteList, destPdfPath);
return destPdfPath;
}
2.填充模版
/**
* 獲取pdf字節(jié)數(shù)組列表
*
* @param template
* @return
*/
private static List<byte[]> getPdfByteList(PdfFormTemplate template) {
List<byte[]> pdfByteList = Lists.newArrayList();
// 數(shù)據(jù)分頁
List<List<Map<String, String>>> pageList = Lists.partition(template.getPageDataList(), PAGE_SIZE);
int pageTotal = pageList.size();
for (int i = 0; i < pageList.size(); i++) {
// 生成每頁pdf的字節(jié)數(shù)組
byte[] currentPagePdf = createCurrentPagePdf(template, pageList.get(i), (i + 1), pageTotal);
pdfByteList.add(currentPagePdf);
}
return pdfByteList;
}
/**
* 生成當前頁pdf字節(jié)數(shù)組
*
* @param template
* @param currentPageDataList
* @param currentPageNo
* @param pageTotal
* @return
*/
private static byte[] createCurrentPagePdf(PdfFormTemplate template, List<Map<String, String>> currentPageDataList, int currentPageNo, int pageTotal) {
PdfDocument pdfDoc = null;
try {
// 輸出流用于存儲填充好數(shù)據(jù)的PDF文件
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 打開PDF模板文件
PdfReader reader = new PdfReader(template.getTemplatePath());
// 創(chuàng)建PDF寫入器
PdfWriter writer = new PdfWriter(bos);
// 創(chuàng)建PDF文檔對象,連接讀取器和寫入器
pdfDoc = new PdfDocument(reader, writer);
// 設(shè)置默認頁面大小為A4
pdfDoc.setDefaultPageSize(PageSize.A4);
// 獲取PDF表單
PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
// 設(shè)置頁碼
PdfFormField formField = form.getField(template.getCustomPageNoFiledName());
if (formField != null) {
formField.setValue(StringUtils.join(currentPageNo, "/", pageTotal));
}
Map<String, String> baseDataMap = template.getBaseDataMap();
if (CollUtil.isNotEmpty(baseDataMap)) {
// 填充基本信息
for (String dataKey : baseDataMap.keySet()) {
PdfFormField field = form.getField(dataKey);
if (field != null) {
field.setValue(baseDataMap.get(dataKey));
}
}
}
if (CollUtil.isNotEmpty(currentPageDataList)) {
int i = 1;
// 填充分頁數(shù)據(jù)
for (Map<String, String> currentPageDataMap : currentPageDataList) {
// 填充序號, 可以自定義屬性,從外部傳入
PdfFormField noFormField = form.getField(StringUtils.join("no", i));
if (noFormField != null) {
noFormField.setValue(String.valueOf((currentPageNo - 1) * PAGE_SIZE + i));
}
// 填充其他數(shù)據(jù)
Set<Map.Entry<String, String>> entries = currentPageDataMap.entrySet();
int finalNo = i;
entries.forEach(entry -> {
PdfFormField field = form.getField(StringUtils.join(entry.getKey(), finalNo));
if (field != null) {
field.setValue(entry.getValue());
}
});
i++;
}
}
// 設(shè)置表單不可編輯
form.flattenFields();
pdfDoc.close();
return bos.toByteArray();
} catch (IOException e) {
log.error("createCurrentPagePdf 填充PDF表單失敗", e);
throw new RuntimeException(e);
} finally {
if (pdfDoc != null) {
pdfDoc.close();
}
}
}
八、效果圖


以上就是SpringBoot使用itext填充pdf表單及導出pdf的流程的詳細內(nèi)容,更多關(guān)于SpringBoot itext填充pdf及導出的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot+Elasticsearch實現(xiàn)數(shù)據(jù)搜索的方法詳解
Elasticsearch是一個基于Lucene的搜索服務(wù)器。它提供了一個分布式多用戶能力的全文搜索引擎,基于RESTful?web接口。本文將利用SpringBoot整合Elasticsearch實現(xiàn)海量級數(shù)據(jù)搜索,需要的可以參考一下2022-05-05
springboot配置請求超時時間(Http會話和接口訪問)
本文主要介紹了springboot配置請求超時時間,包含Http會話和接口訪問兩種,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-07-07
使用Java通過OAuth協(xié)議驗證發(fā)送微博的教程
這篇文章主要介紹了使用Java通過OAuth協(xié)議驗證發(fā)送微博的教程,使用到了新浪微博為Java開放的API weibo4j,需要的朋友可以參考下2016-02-02

