欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > PDF模板填充新姿势,开箱即用

PDF模板填充新姿势,开箱即用

2024/10/24 20:21:10 来源:https://blog.csdn.net/gengzhy/article/details/140840333  浏览:    关键词:PDF模板填充新姿势,开箱即用

写在前面

由于之前使用Itext5工具填充PDF模板后,会导致填充后的PDF文件体积变得很庞大。

怀疑了嵌入字体、PDF模板编辑转换和编辑等等的原因,但最后都无功而返,查阅了官方文档,也没得出解决方案。

因此,退而求其次,换上了Itext7,官方说过,性能相较Itext5更出色。

依赖

<dependency><groupId>com.itextpdf</groupId><artifactId>itext7-core</artifactId><version>8.0.1</version><type>pom</type>
</dependency>
<dependency><groupId>com.itextpdf</groupId><artifactId>bouncy-castle-adapter</artifactId><version>8.0.1</version>
</dependency>

注:引入bouncy-castle-adapter是因为Itext7解析PDF时用到了加密算法,因此可能还需引入

<dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.70</version>
</dependency>
<dependency><groupId>org.bouncycastle</groupId><artifactId>bcpkix-jdk15on</artifactId><version>1.70</version>
</dependency>

工具

import com.itextpdf.forms.PdfAcroForm;
import com.itextpdf.forms.fields.PdfFormCreator;
import com.itextpdf.forms.fields.PdfFormField;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.io.image.ImageData;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.*;
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
import com.itextpdf.kernel.pdf.annot.PdfWidgetAnnotation;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.properties.TextAlignment;
import lombok.extern.slf4j.Slf4j;import java.io.*;
import java.util.HashMap;
import java.util.Map;/*** PDF 模板填充工具类(基于itext7)*/
@Slf4j
public final class Pdf7TemplateFillUtil {/*** 待填充的PDF模板文档*/private final transient PdfReader template;/*** 未填充的表单域可编辑*/private boolean unfilledFormEditable = false;/*** 显示页码*/private boolean pageNo = false;/*** 首页显示页码*/private boolean firstPageNo = false;/*** 填充及返回填充后的数据** @param fillData - 待填充数据* @return - 填充后bytes数据*/public byte[] fill(Map<String, Object> fillData) throws IOException {ByteArrayOutputStream dest = new ByteArrayOutputStream();PdfDocument doc = null;try {PdfFont font = PdfFontFactory.createFont("fons/AlibabaPuHuiTi-3-35-Thin.ttf",// font class pathPdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED);PdfWriter pdfWriter = new PdfWriter(dest);pdfWriter.setCompressionLevel(CompressionConstants.BEST_COMPRESSION);doc = new PdfDocument(template, pdfWriter);if (pageNo) {doc.addEventHandler(PdfDocumentEvent.END_PAGE, new PageNumberEvent(font, firstPageNo));}PdfAcroForm form = PdfAcroForm.getAcroForm(doc, true);for (Map.Entry<String, Object> entry : fillData.entrySet()) {String keyword = entry.getKey();PdfFormField templateFormField = form.getField(keyword);if (templateFormField == null) {continue;}Object value = entry.getValue();if (value instanceof byte[]) {PdfArray pos = templateFormField.getWidgets().get(0).getRectangle();float x = pos.getAsNumber(0).floatValue();float y = pos.getAsNumber(1).floatValue();float width = pos.getAsNumber(2).floatValue() - x;float height = pos.getAsNumber(3).floatValue() - y;Rectangle rectangle = new Rectangle(x, y, width, height);PdfFormField formField = PdfFormCreator.createFormField(new PdfWidgetAnnotation(rectangle), doc);PdfPage annotationPage = findAnnotationPage(doc, keyword);if (annotationPage != null) {doFillFieldImage(annotationPage, formField, (byte[]) value);}} else {if (value instanceof Boolean) {templateFormField.setValue(String.valueOf(value), true);} else {templateFormField.setValue(String.valueOf(value));}templateFormField.setFontAndSize(font, templateFormField.getFontSize());templateFormField.setReadOnly(true);}if (unfilledFormEditable) {form.partialFormFlattening(keyword);}}form.flattenFields();} finally {if (doc != null && !doc.isClosed()) {doc.close();}}return dest.toByteArray();}/*** 启用未填充的表单域可编辑*/public Pdf7TemplateFillUtil unfilledFormEditable() {this.unfilledFormEditable = true;return this;}/*** 启用页码*/public Pdf7TemplateFillUtil pageNumber() {this.pageNo = true;return this;}/*** 启用页码** @param firstPageNo - 首页显示页码*/public Pdf7TemplateFillUtil pageNumber(boolean firstPageNo) {this.pageNo = true;this.firstPageNo = firstPageNo;return this;}/*** 图片填充** @param newPage   - 当前页* @param formField - 表单文本域* @param imgBytes  - 图片文件字节数组*/private void doFillFieldImage(PdfPage newPage, PdfFormField formField, byte[] imgBytes) {Rectangle rtl = formField.getWidgets().get(0).getRectangle().toRectangle(); // 获取表单域的xy坐标PdfCanvas canvas = new PdfCanvas(newPage);ImageData img = ImageDataFactory.create(imgBytes);if (Float.compare(img.getWidth(), rtl.getWidth()) <= 0 && Float.compare(img.getHeight(), rtl.getHeight()) <= 0) {// 不处理canvas.addImageAt(img, rtl.getX(), rtl.getY(), true);} else {// 压缩图片。计算得到图片放缩的最大比例float scale = Math.max(img.getWidth() / rtl.getWidth(), img.getHeight() / rtl.getHeight());int imgWidth = Math.round(img.getWidth() / scale);int imgHeight = Math.round(img.getHeight() / scale);// 压缩图片byte[] compressImgBytes;try {compressImgBytes = ImageCompressUtils.resizeByThumbnails(imgBytes, imgWidth, imgHeight);} catch (IOException e) {throw new RuntimeException(e);}img = ImageDataFactory.create(compressImgBytes);canvas.addImageAt(img, rtl.getX(), rtl.getY(), true);}}/*** 根据表单域关键字查找当前关键字所在页对象(PdfPage)** @param keyword - 关键字* @return - page object*/private PdfPage findAnnotationPage(PdfDocument doc, String keyword) {int pages = doc.getNumberOfPages();for (int index = 1; index <= pages; index++) {PdfPage page = doc.getPage(index);for (PdfAnnotation annotation : page.getAnnotations()) {PdfString title = annotation.getPdfObject().getAsString(PdfName.T);if (title != null && keyword.equals(String.valueOf(title))) {return page;}}}return null;}/*** 获取模板文件表单域关键字位置信息*/private Map<Integer, Map<String, float[]>> getFormKeywordsPos(PdfDocument doc) {int pages = doc.getNumberOfPages();Map<Integer, Map<String, float[]>> maps = new HashMap<>(pages);for (int index = 1; index <= pages; index++) {maps.putIfAbsent(index, new HashMap<>());PdfPage page = doc.getPage(index);// 获取当前页的表单域int finalIndex = index;page.getAnnotations().forEach(anno -> {PdfString title = anno.getTitle();PdfArray rectangle = anno.getRectangle();float x = rectangle.getAsNumber(0).floatValue();float y = rectangle.getAsNumber(1).floatValue();float width = rectangle.getAsNumber(2).floatValue() - x;float height = rectangle.getAsNumber(3).floatValue() - y;maps.get(finalIndex).put(title.getValue(), new float[]{x, y, width, height});});}return maps;}private Pdf7TemplateFillUtil(PdfReader template) {this.template = template;}public static Pdf7TemplateFillUtil load(String template) throws IOException {return new Pdf7TemplateFillUtil(new PdfReader(template));}public static Pdf7TemplateFillUtil load(File template) throws IOException {return new Pdf7TemplateFillUtil(new PdfReader(template));}public static Pdf7TemplateFillUtil load(byte[] template) throws IOException {return new Pdf7TemplateFillUtil(new PdfReader(new ByteArrayInputStream(template)));}public static Pdf7TemplateFillUtil load(InputStream template) throws IOException {return new Pdf7TemplateFillUtil(new PdfReader(template));}static class PageNumberEvent implements IEventHandler {private transient final PdfFont font;private transient final boolean firstPageNo;PageNumberEvent(PdfFont font, boolean firstPageNo) {this.font = font;this.firstPageNo = firstPageNo;}@Overridepublic void handleEvent(Event event) {PdfDocumentEvent docEvent = (PdfDocumentEvent) event;PdfDocument pdf = docEvent.getDocument();PdfPage page = docEvent.getPage();int pageNumber = pdf.getPageNumber(page);pageNumber = firstPageNo ? pageNumber : pageNumber - 1;if (pageNumber > 0) {Rectangle pageSize = page.getPageSize();PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf);Canvas canvas = new Canvas(pdfCanvas, pageSize);float x = (pageSize.getLeft() + pageSize.getRight()) / 2;float y = pageSize.getBottom() + 10;Paragraph p = new Paragraph(String.valueOf(pageNumber)).setFontSize(10).setFont(font);canvas.showTextAligned(p, x, y, TextAlignment.CENTER);canvas.close();}}}
}

特别注意

字体加载有一个巨坑,一定要放在具体的方法中去加载,保证用完即销毁。

否则填充时会出错:Pdf indirect object belongs to other PDF document....

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com