前言
由于最近开发的项目需要用到打印单据,就在网上找了一下方案,反反复复,都没有找到合适的,借鉴了网上资源,使用itext5、itext7的工具包,自己写了一个通用的pdf表单填充工具,简单易用,支持单页、分页单据打印,当然还有更多的定制化,需要大家自行扩展。
一、设计表单
1.使用excel设计了一个pdf单据,因为比较整齐设计方便,如下图:
2.使用免费工具PDFgear将excel文件转为pdf文件,如下图:
3.使用免费Adobe Acrobat DC工具编辑pdf文件
打开转换好的pdf文件,选择工具栏右边的编辑表单,然后拖动顶部栏的表单域设计属性,属性值和和代码填充的要一致,字体下拉最后面的,不然你打印出来的单据字体会很难看,当然也可以在程序代码里去设计字体,一般设计好就够用了,如果没有特定要求的话。
ps:有些可能编辑表单,使用默认的,有时候可能显示不了数据,这个把这个表单域删除了,重新添加一个即可。
OK,表单设计好,下面就是编码啦
一、项目搭建
我这里是使用阿里云官方提供快速创建脚手架的在线网站生成了一个springboot项目,项目结构图如下:
二、引入依赖
<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 {
/**
* 每页显示数,可自行修改
*/
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, "模版对象不能为空");
// 创建临时文件夹
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字节数组列表
List<byte[]> pdfByteList = getPdfByteList(template);
// 合并文件
mergePdf(pdfByteList, destPdfPath);
return destPdfPath;
}
/**
* 填充表单,并返回字节数组
*
* @param template 模版对象
* @return 填充表单文件路径
*/
public static byte[] fillAndGetPdfByte(PdfFormTemplate template) {
Assert.notNull(template, "模版对象不能为空");
return mergePdf(getPdfByteList(template));
}
/**
* 获取pdf字节数组列表
*
* @param template
* @return
*/
private static List<byte[]> getPdfByteList(PdfFormTemplate template) {
List<byte[]> pdfByteList = Lists.newArrayList();
// 数据分页
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的字节数组
byte[] currentPagePdf = createCurrentPagePdf(template, pageList.get(i), (i + 1), pageTotal);
pdfByteList.add(currentPagePdf);
}
return pdfByteList;
}
/**
* 生成当前页pdf字节数组
*
* @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 {
// 输出流用于存储填充好数据的PDF文件
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 打开PDF模板文件
PdfReader reader = new PdfReader(template.getTemplatePath());
// 创建PDF写入器
PdfWriter writer = new PdfWriter(bos);
// 创建PDF文档对象,连接读取器和写入器
pdfDoc = new PdfDocument(reader, writer);
// 设置默认页面大小为A4
pdfDoc.setDefaultPageSize(PageSize.A4);
// 获取PDF表单
PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
// 设置页码
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;
// 填充分页数据
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));
}
// 填充其他数据
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++;
}
}
// 设置表单不可编辑
form.flattenFields();
// 可以自定义字体
/*PdfFont pdfFont = loadPdfFont();
if (pdfFont != null) {
// 给表单域字段设置字体和大小
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文档字节集合
* @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文档字节集合
*/
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异常");
}
}
/**
* 基于内存中的字节数组进行PDF文档的合并
*
* @param firstPdf 第一个PDF文档字节数组
* @param secondPdf 第二个PDF文档字节数组
*/
@SneakyThrows
private static byte[] mergePdfBytes(byte[] firstPdf, byte[] secondPdf) {
if (firstPdf == null || secondPdf == null) {
return null;
}
// 1.创建字节数组,基于内存进行合并
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文档页数:{}, 第二个PDF文档页数:{}", firstDoc.getNumberOfPages(), secondDoc.getNumberOfPages());
merger.merge(firstDoc, 1, firstDoc.getNumberOfPages());
merger.merge(secondDoc, 1, secondDoc.getNumberOfPages());
log.info("mergePdfBytes 合并PDF文档成功");
// 4.关闭文档流
merger.close();
firstDoc.close();
secondDoc.close();
destDoc.close();
return os.toByteArray();
}
/**
* 判断当前系统是否是Windows系统
*
* @return true:Windows系统,false:Linux系统
*/
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) {
// 构建基本信息
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("广东省广州市越秀区北京路", i, "号"));
pageDataList.add(pageDataMap);
}
// 构建业务数据
/*PdfFormTemplate formTemplate = PdfFormTemplate.builder()
.baseDataMap(baseDataMap)
.pageDataList(pageDataList)
.customPageNoFiledName("pageNo")
.templatePath("E:\\soft\\IdeaProjects\\pdf-form-fill-demo\\src\\main\\resources\\telephone\\出库单据模版form.pdf")
.templateFileName("pdf表单模版")
.build();*/
PdfFormTemplate formTemplate = new PdfFormTemplate()
.setBaseDataMap(baseDataMap)
.setPageDataList(pageDataList)
// 本地测试是绝对路径、发布web环境就相对路径
.setTemplatePath("E:\\soft\\IdeaProjects\\pdf-form-fill-demo\\src\\main\\resources\\template\\出库单据模版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 {
/**
* 每页显示数,可自行修改
*/
public static final int PAGE_SIZE = 12;
/**
* 临时文件路径
*/
public final static String FOLDER = "/tempPdfFile";
/**
* 文件路径
*/
public final static String FOLDER_PATH = FOLDER + "/";
/**
* 获取pdf字节数组列表
*
* @param template
* @return
*/
private static String createPdf(PdfFormTemplate template) throws Exception {
List<PdfReader> pdfReaderList = new ArrayList<>(Collections.emptyList());
// 数据分页
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的字节数组
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 当前页表格数据
* @param currentPage 当前页
* @param pageCount 总页数
* @return
* @throws Exception
*/
/**
* 生成当前页pdf字节数组
*
* @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();
// 自定义字体设置
/*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));*/
// 设置页码
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;
// 填充分页数据
for (Map<String, String> currentPageDataMap : currentPageDataList) {
// 填充序号, 可以自定义属性,从外部传入
form.setField("no", String.valueOf((currentPageNo - 1) * PAGE_SIZE + i));
// 填充其他数据
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("填充表单数据异常", 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文件还能编辑,一定要设为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;
}
/**
* 判断当前系统是否是Windows系统
*
* @return
*/
public static boolean isWindowsSystem() {
String property = System.getProperty("os.name").toLowerCase();
return property.contains("windows");
}
public static void main(String[] args) throws Exception {
// 构建基本信息
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("广东省广州市越秀区北京路", i, "号"));
pageDataList.add(pageDataMap);
}
// 构建业务数据
/*PdfFormTemplate formTemplate = PdfFormTemplate.builder()
.baseDataMap(baseDataMap)
.pageDataList(pageDataList)
.customPageNoFiledName("pageNo")
.templatePath("E:\\soft\\IdeaProjects\\pdf-form-fill-demo\\src\\main\\resources\\telephone\\出库单据模版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\\出库单据模版form.pdf")
.setTemplateFileName("测试pdf表单模版");
String pdf = createPdf(formTemplate);
log.info("pdf文件路径: {}", pdf);
}
}
五、统一模版代码类说明
统一模板类,对业务数据、模板、字体等属性进行封装,要对哪个pdf模版进行填充数据,入口处只需按照模版要求组装业务数据,调用填充接口即可,无需过多操作,当然很多操作还可以自行扩展。
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;
/**
* 基本数据
*/
private Map<String, String> baseDataMap;
/**
* 分页数据集合
*/
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, "模版对象不能为空");
// 创建临时文件夹
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字节数组列表
List<byte[]> pdfByteList = getPdfByteList(template);
// 合并文件
mergePdf(pdfByteList, destPdfPath);
return destPdfPath;
}
2.填充模版
/**
* 获取pdf字节数组列表
*
* @param template
* @return
*/
private static List<byte[]> getPdfByteList(PdfFormTemplate template) {
List<byte[]> pdfByteList = Lists.newArrayList();
// 数据分页
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的字节数组
byte[] currentPagePdf = createCurrentPagePdf(template, pageList.get(i), (i + 1), pageTotal);
pdfByteList.add(currentPagePdf);
}
return pdfByteList;
}
/**
* 生成当前页pdf字节数组
*
* @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 {
// 输出流用于存储填充好数据的PDF文件
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 打开PDF模板文件
PdfReader reader = new PdfReader(template.getTemplatePath());
// 创建PDF写入器
PdfWriter writer = new PdfWriter(bos);
// 创建PDF文档对象,连接读取器和写入器
pdfDoc = new PdfDocument(reader, writer);
// 设置默认页面大小为A4
pdfDoc.setDefaultPageSize(PageSize.A4);
// 获取PDF表单
PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
// 设置页码
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;
// 填充分页数据
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));
}
// 填充其他数据
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++;
}
}
// 设置表单不可编辑
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();
}
}
}
七、效果图
八、源码、模版
1.创建脚手架网站:https://start.aliyun.com/
2.完全免费的PDF工具:https://www.pdfgear.com/zh/
3.PDF编辑工具:Adobe Acrobat DC
链接:https://pan.baidu.com/s/1jeB57op3hxW1-h31T14Fqw?pwd=uipu
提取码: uipu