前言
对于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⊙)…