对于PDF模板填充,有很多现有的Java库,付费版本略过。
较出名的有Apache的PDFBox,以及ITextPdf。
而后者具有两个很大的版本ITextPdf-5和ITextPdf-7,ITextPdf-7功能更强大,但可能存在商业版权问题。之前也用过一阵,没驾驭住。
今天使用?ITextPdf-5,支持文本填充、图片填充 、添加页码 及 PDF文档合并。
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
/**
* PDF模板填充,基于itext-pdf-v5
*
*/
public class PDFTemplateFiller {
/**
* 模板
*/
private final transient PdfReader reader;
/**
* 字体
*/
private BaseFont baseFont;
/**
* 添加页码
*/
private boolean addPageNumber;
/**
* 页码字体大小
* {@link #addPageNumber}为true时有效
*/
private float pageNumberFontSize = 14F;
/**
* 页码左边样式字符
* {@link #addPageNumber}为true时有效
*/
private String pageNumberLeft = "";
/**
* 页码邮编样式字符
* {@link #addPageNumber}为true时有效
*/
private String pageNumberRight = "";
/**
* 填充模板,返回填充后文件
*
* @param fillData - 待填充数据
* @return - 填充后文件字节数据
*/
public byte[] fill(Map<String, Object> fillData) throws DocumentException, IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
PdfStamper stamper = new PdfStamper(reader, out);
AcroFields form = stamper.getAcroFields();
// 设置字体
form.addSubstitutionFont(getDefaultFont());
// 执行填充
doFill(stamper, form, fillData);
// 添加页码
if (addPageNumber) {
int pages = reader.getNumberOfPages();
for (int i = 1; i < pages; i++) {
addPageNumber(stamper, i);
}
}
// 清除表单域可编辑状态
stamper.setFormFlattening(true);
stamper.close();
return out.toByteArray();
}
/**
* 是否添加页码(默认:否)
*
* @param isAddPageNumber - true-添加/false-不添加
*/
public PDFTemplateFiller pageNumber(boolean isAddPageNumber) {
this.addPageNumber = isAddPageNumber;
return this;
}
/**
* <pre>
* 设置字体(默认:STSong-Light)
* 通过 {@link com.itextpdf.text.pdf.BaseFont#createFont(java.lang.String, java.lang.String, boolean)}创建
* </pre>
*
* @param baseFont - 待设值字体
*/
public PDFTemplateFiller font(BaseFont baseFont) {
this.baseFont = baseFont;
return this;
}
/**
* 页码样式设置
* <p>
* {@link #addPageNumber}为true时有效
*
* @param pageNumberLeft - 页码左边样式字符
* @param pageNumberRight - 页码邮编样式字符
*/
public PDFTemplateFiller pageNumberStyle(String pageNumberLeft, String pageNumberRight) {
this.pageNumberLeft = pageNumberLeft;
this.pageNumberRight = pageNumberRight;
return this;
}
/**
* 根据表单域字段名查找其对应所在页码
*
* @param form - 表单对象
* @param fieldName - 表单域属性名
* @return - 找到返回具体页码(起始页为1);否则返回-1
*/
private int findPageNumber(AcroFields form, String fieldName) {
List<AcroFields.FieldPosition> positions = form.getFieldPositions(fieldName);
if (positions == null || positions.isEmpty()) {
return -1;
}
return positions.get(0).page;
}
/**
* 填充文本
*/
private void doFill(PdfStamper stamper, AcroFields form, Map<String, Object> fillData) throws DocumentException, IOException {
for (Map.Entry<String, Object> entry : fillData.entrySet()) {
if (entry.getValue() instanceof byte[]) {
doFillImage(stamper, entry.getKey(), (byte[]) entry.getValue());
} else {
form.setField(entry.getKey(), String.valueOf(entry.getValue()));
}
}
}
/**
* 填充图片
*/
private void doFillImage(PdfStamper stamper, String fieldName, byte[] image) throws DocumentException, IOException {
AcroFields form = stamper.getAcroFields();
List<AcroFields.FieldPosition> positions = form.getFieldPositions(fieldName);
if (positions != null && !positions.isEmpty()) {
AcroFields.FieldPosition position = positions.get(0);
Rectangle rectangle = position.position;
com.itextpdf.text.Image img = com.itextpdf.text.Image.getInstance(image);
// 根据域的大小缩放图片
img.scaleToFit(rectangle.getWidth(), rectangle.getHeight());
img.setAbsolutePosition(rectangle.getLeft(), rectangle.getBottom());
stamper.getOverContent(position.page).addImage(img);
}
}
/**
* 添加页码
*/
private void addPageNumber(PdfStamper stamper, int pageNumber) {
PdfContentByte contentByte = stamper.getOverContent(pageNumber);
contentByte.beginText();
contentByte.setFontAndSize(baseFont, pageNumberFontSize);
Rectangle rectangle = stamper.getReader().getPageSize(pageNumber);
// 页码的 横轴 坐标 居中
float x = (rectangle.getLeft() + rectangle.getRight()) / 2;
contentByte.showTextAligned(Element.ALIGN_CENTER, String.format("%s%d%s", pageNumberLeft, pageNumber, pageNumberRight), x, 20, 0);
contentByte.endText();
}
/**
* 合并多个PDF文件到一个PDF中
*
* @param pdfs - 待合并的PDF 文件
* @return byte - 合并后的PDF流
*/
public static byte[] merge(List<PdfReader> pdfs) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Document document = new Document();
PdfCopy copy = new PdfCopy(document, out);
document.open();
for (PdfReader reader : pdfs) {
// 获取PDF文件总页数
int n = reader.getNumberOfPages();
for (int i = 1; i <= n; i++) {
document.newPage();// 创建新页面
PdfImportedPage page = copy.getImportedPage(reader, i);
copy.addPage(page);
}
}
document.close();
return out.toByteArray();
}
public static PDFTemplateFiller load(InputStream pdfTemplate) throws IOException, DocumentException {
return new PDFTemplateFiller(new PdfReader(pdfTemplate));
}
public static PDFTemplateFiller load(File pdfTemplate) throws IOException, DocumentException {
return new PDFTemplateFiller(new PdfReader(pdfTemplate.getAbsolutePath()));
}
public static PDFTemplateFiller load(byte[] pdfTemplate) throws IOException, DocumentException {
return new PDFTemplateFiller(new PdfReader(pdfTemplate));
}
public static PDFTemplateFiller load(ByteArrayOutputStream pdfTemplate) throws IOException, DocumentException {
return load(pdfTemplate.toByteArray());
}
private PDFTemplateFiller(PdfReader pdfReader) throws DocumentException, IOException {
this.reader = pdfReader;
this.baseFont = getDefaultFont();
this.addPageNumber = false;
}
/**
* 获取默认的字体(宋体)
*/
private BaseFont getDefaultFont() throws DocumentException, IOException {
return BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
}
}
动态表格填充实现较为困难,知道的大佬欢迎骚扰(⊙o⊙)…