PDF模板填充,基于IText5

前言

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/585430.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第5章-第7节-Java面向对象编程之接口

1、接口 概念&#xff1a;封装了一组规范、标准 生活层面&#xff1a; usb接口&#xff1a; 计算机厂商的角度&#xff1a;在设计计算机的过程中&#xff0c;如果多开辟几个usb口&#xff0c; 则可以让更多的外接设备在同一时间都连接上我的计算机 外接设…

YOLOv8改进 | 主干篇 | EfficientNetV1均衡缩放网络改进特征提取层

一、本文介绍 这次给大家带来的改进机制是EfficientNetV1主干&#xff0c;用其替换我们YOLOv8的特征提取网络&#xff0c;其主要思想是通过均衡地缩放网络的深度、宽度和分辨率&#xff0c;以提高卷积神经网络的性能。这种方法采用了一个简单但有效的复合系数&#xff0c;统一…

计算机网络【EPoll原理】

预备知识&#xff1a;内核poll钩子原理 内核函数poll_wait 把当前进程加入到驱动里自定义的等待队列上 &#xff1b; 当驱动事件就绪后&#xff0c;就可以在驱动里自定义的等待队列上唤醒调用poll的进程&#xff1b; 故poll_wait作用&#xff1a;可以让驱动知道事件就绪的时…

thinkphp操作mongo数据的三种方法

总结一下当前thinkphp开发的项目中需要操作mongo&#xff1a; 以下是三种tp中操作mongo数据的方法&#xff1a; 使用tp中的扩展&#xff0c;方法一 Db::connect(dataname)->table(dbname)->insertAll($list); $info $connection->getTableInfo(collection); 后面…

数据加密、端口管控、行为审计、终端安全、整体方案解决提供商

PC端访问地址&#xff1a; https://isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee 以下是关于这几个概念的解释&#xff1a; 数据加密&#xff1a;这是一种通过加密算法和密钥将明文转换为密文&#xff0c;以及通过解密算法和解密密钥将密文恢复为明文…

数据缓存(Redis, Spring Cache)——后端

场景&#xff1a;给用户端展示的数据都是通过查询数据库所得&#xff0c;因此数据库访问压力会随着用户访问量增大而增加&#xff0c;从而导致系统响应慢、用户体验差。 方法&#xff1a;通过Redis缓存数据&#xff0c;减少查询数据库操作。&#xff08;Redis的数据是存储在内存…

Qt 5.9.4 转 Qt 6.6.1 遇到的问题总结(一)

最近公司对大家的开发的硬件环境进行了升级&#xff0c;电脑主机的配置、显示器&#xff08;两台大屏显示器&#xff09;变得的逼格高多了。既然电脑上的开发环境都需要重装&#xff0c;就打算把开发环境也升级到最新版本&#xff0c;要用就用最新版本。下面对升级后的开发环境…

ramdump 中的memory统计

0. 前言 ramdump是指某个时刻系统或者子系统发生crash等异常&#xff0c;系统将内存中的数据通过一定的方式保存下来&#xff0c;相当于一个系统内存快照&#xff0c;用以开发者离线分析系统异常问题。 ramdump 工具中有很多内存统计的脚本&#xff0c;本文逐一剖析内存相关的…

需求分析 :不得不重新去面对的一关。

软件需求分析 背景 深入需求产生的背景明确项目目标了解用户群体 需求优先级 需求的分类与整理明确需求优先级让团队成员都参与到需求分析中来&#xff0c;增加团队合作能力与效率 编写需求文档 整理好的需求编写成详细的需求文档包括需求的描述、输入/输出格式、功能流程…

Leetcode算法系列| 10. 正则表达式匹配

目录 1.题目2.题解C# 解法一&#xff1a;分段匹配法C# 解法二&#xff1a;回溯法C# 解法三&#xff1a;动态规划 1.题目 给你一个字符串 s 和一个字符规律 p&#xff0c;请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。 1.‘.’ 匹配任意单个字符 2.‘.’ 匹配任意单个字…

网络运行状况监控工具

网络运行状况是网络在其操作和环境约束范围内按预期运行的能力&#xff0c;但是&#xff0c;随着云和人工智能等技术的出现&#xff0c;网络变得越来越复杂&#xff0c;维护其 IT 基础设施是一项越来越繁琐的任务。为了确保网络可靠性&#xff0c;组织需要了解每个端点的运行状…

使用Ubuntu编译FFmpeg生成Android动态库/静态库

环境 我这里使用windows里的wsl2的ubuntu&#xff0c;使用物理机或者vmware&#xff0c;vbox之类的安装的ubuntu理论上也可以. gcc编译使用的环境如下: Ndk使用17 FFmpeg使用4.0.2. clang编译使用的环境如下: Ndk使用21.4 FFmpeg使用6.1 FFmpeg下载地址:https://ffmpeg.org/…

旗鼓相当的对手 - 加强版#洛谷

题目描述 现有 N ( N ≤ 1000 ) N(N\le 1000) N(N≤1000) 名同学参加了期末考试&#xff0c;并且获得了每名同学的信息&#xff1a;姓名&#xff08;不超过 8 8 8 个字符的字符串&#xff0c;没有空格&#xff09;、语文、数学、英语成绩&#xff08;均为不超过 150 150 15…

Spring Boot学习:Redis发布订阅

Redis发布订阅 Redis 2.8 及以上版本实现了发布订阅的功能&#xff0c;发送者可以通过 PUBLISH发布消息&#xff0c;订阅者通过 SUBSCRIBE 订阅接收消息或通过UNSUBSCRIBE 取消订阅。当发布者发布消息到指定频道时&#xff0c;所有订阅该频道的订阅者都能够接收到消息。这对于…

Halcon颜色通道的处理decompose3/image_to_channels/channels _to _image

Halcon颜色通道的处理 文章目录 Halcon颜色通道的处理一. 图像的通道二. 访问通道1.访问通道2.获取通道的数量 三. 通道分离与合并1. decompose3算子2. image_to_channels 算子3. compose3算子4. channels_to_image算子 四. 处理RGB信息 由于彩色图像通常包含不止一个通道&…

《基于 Vue 组件库 的 Webpack5 配置》- 总结

前言 Vue2 项目升级到 Webpack5 后&#xff0c;相关的配置也有所变化&#xff01;此篇以记录和总结&#xff0c;共同学习 Webpack ~ 推荐相关文章&#xff1a; 《Vue2.x 组件库 Webpack3 升 5》《Vue2.x 项目 Webpack 4 升级 5&#xff08;半自动升级&#xff09;》 配置 1…

LabVIEW开发LED驱动电源测试系统

LabVIEW开发LED驱动电源测试系统 本项建立一个二维激光振镜扫描控制系统&#xff0c;涵盖了光学系统和激光器的选择以及振镜驱动器的设计。项目的核心工作包括振镜驱动器的硬件电路设计、上位机控制软件的编写以及驱动器底层驱动软件的开发。此外&#xff0c;还对扫描图形的几何…

拍照就能建模!手机就能访问! 这个技术正成为宣传新手段!

随着人工智能技术的不断进步&#xff0c;现在可以通过拍摄照片结合AI技术来实现3D模型生成。这种技术的出现&#xff0c; 不仅能更加方便快捷地创建3D模型&#xff0c;而且还能真实复原现实中物件的质感、纹理等。同时&#xff0c;极大地降低了各行业对3D技术的应用门槛&#x…

中科院1区TOP,Elsevier出版社,均1-2个月录用!检索超稳!

【SciencePub学术】本期&#xff0c;小编给大家推荐的是一本Elsevier旗下、工程技术领域、影响因子为6.0的中科院1区TOP。其详情如下&#xff1a; 期刊简介 TRIBOLOGY INTERNATIONAL ISSN&#xff1a;0301-679X E-ISSN&#xff1a;1879-2464 IF&#xff08;2022&#x…

ES6+ 面试常问题

一、let const var 的区别 1. var&#xff1a; 没有块级作用域的概念&#xff0c;有函数作用域和全局作用域的概念全局作用域性下创建变量会被挂在到 windows 上存在变量提升同一作用域下&#xff0c;可以重复赋值创建未初始化&#xff0c;值为 undefined 2. let&#xff1a…