PDF文件替换内容(电子签章),依赖免费pdfbox
首先提前准备,压入如下依赖
<!-- https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.16</version>
</dependency>
正文开始
创建:
CoordinateDTO 坐标bean,用来存替换文字的坐标位置
PdfBoxKeyWordPosition 工具类,解析pdf文件,获取关键字坐标
ReportUtils 工具类,替换文件内容,可用于电子签章
源码如下:
(注:源码中的package...和import...需要修改为项目实际位置)
CoordinateDTO.java
/*** @FileName: CoordinateDTO.java* @creator yongzhizean* @date 2023年2月17日 下午5:16:52* @editor* @Description:* @version V1.0*/
package ...包位置;import lombok.Data;/*** @ClassName: CoordinateDTO * @Description: 坐标* @author yongzhizean* @date 2023年2月17日 下午5:16:52* @version V1.0*/
@Data
public class CoordinateDTO {/*** 关键字在PDF中的X坐标*/private Float x;/*** 关键字在PDF中的Y坐标*/private Float y;/*** 关键字在PDF中的页码*/private Integer pageNum;/*** 关键字在PDF中的显示出来的长度*/private Float length;
}
PdfBoxKeyWordPosition.java
/*** @FileName: PdfBoxKeyWordPosition.java* @creator yongzhizean* @date 2023年2月17日 下午5:16:52* @editor* @Description:* @version V1.0*/
package ...自己包位置;import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;import ...CoordinateDTO位置;import lombok.extern.slf4j.Slf4j;import java.io.*;
import java.util.ArrayList;
import java.util.List;/*** @ClassName: PdfBoxKeyWordPosition* @Description: 解析pdf文件,获取关键字坐标* @author yongzhizean* @date 2023年2月17日 下午5:16:52* @version V1.0*/
@Slf4j
public class PdfBoxKeyWordPosition extends PDFTextStripper {/*** 关键字字符数组*/private char[] key;/*** 关键字字符数组*/private boolean flag;/*** 坐标集合*/private List<CoordinateDTO> coordinates = new ArrayList<CoordinateDTO>();/*** 当前页坐标集合*/private List<CoordinateDTO> pageList = new ArrayList<CoordinateDTO>();/*** 使用字符流* * @param keyWords* @param document* @param flag* @throws IOException*/public PdfBoxKeyWordPosition(String keyWords, PDDocument document, boolean flag) throws IOException {super();super.setSortByPosition(true);this.document = document;this.flag = flag;char[] key = new char[keyWords.length()];for (int i = 0; i < keyWords.length(); i++) {key[i] = keyWords.charAt(i);}this.key = key;}/*** * 获取坐标信息* @date 2023年2月17日 下午5:22:34* @author yongzhizean* @return* @return List<CoordinateDTO>*/public List<CoordinateDTO> getCoordinate() {try {int pages = document.getNumberOfPages();for (int i = 1; i <= pages; i++) {super.setSortByPosition(true);super.setStartPage(i);super.setEndPage(i);Writer dummy = new OutputStreamWriter(new ByteArrayOutputStream());super.writeText(document, dummy);for (CoordinateDTO li : pageList) {li.setPageNum(i);}coordinates.addAll(pageList);pageList.clear();}} catch (Exception e) {log.error("获取pdf关键字坐标失败:{}", e);} finally {pageList.clear();if (flag) {try {if (document != null) {document.close();}} catch (IOException e) {log.error("关闭文件失败:{}", e);}} else {log.info("不关闭文件");}}return coordinates;}/*** 获取坐标信息*/@Overrideprotected void writeString(String string, List<TextPosition> textPositions) throws IOException {for (int i = 0; i < textPositions.size(); i++) {String str = textPositions.get(i).getUnicode();// 找到 key 中第一位所在位置if (str.equals(String.valueOf(key[0]))) {int count = 0;for (int j = 0; j < key.length; j++) {String s = "";try {s = textPositions.get(i + j).getUnicode();} catch (Exception e) {s = "";}// 判断key 中每一位是否和文本中顺序对应,一旦不等说明 关键字与本段落不等,则停止本次循环if (s.equals(String.valueOf(key[j]))) {count++;} else if (count > 0) {break;}}// 判断 key 中字 在文本是否连续,是则获取坐标if (count == key.length) {CoordinateDTO coordinate = new CoordinateDTO();TextPosition tp = textPositions.get(i);// X坐标 直接获取的字体位置 ,也可以加上了字体的长度 tp.getX()+ + tp.getFontSize()Float x = tp.getX();// Y坐标 减去的字体的长度 tp.getPageHeight() - tp.getY() - 4 * tp.getFontSize()Float y = tp.getPageHeight() - tp.getY();coordinate.setX(x);coordinate.setY(y);coordinate.setLength(tp.getFontSize());pageList.add(coordinate);}}}}
}
ReportUtils.java
/*** @FileName: ReportUtils.java* @creator yongzhizean* @date 2023年2月17日 下午5:16:52* @editor* @Description:* @version V1.0*/
package ...包位置;import ...CoordinateDTO位置;
import ...PdfBoxKeyWordPosition位置;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.pdfbox.contentstream.operator.Operator;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSString;
import org.apache.pdfbox.pdfparser.PDFStreamParser;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.springframework.stereotype.Service;import java.awt.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.List;/*** * @ClassName: ReportUtils * @Description: pdf电子签章* @author yongzhizean* @date 2023年2月17日 下午5:16:52* @version V2.0*/
@Service
@Slf4j
public class ReportUtils {/*** * 生成新的报表* * @date 2023年2月17日 下午4:59:15* @author yongzhizean* @param url* 报表、pdf文档的地址* @param imageUrl* 签章图片地址* @param key* 需要签章的文字* @return* @throws Exception* @return byte[]*/public byte[] getNewReport(String url, String imageUrl, String key) throws Exception {ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();try {log.info("访问报表Url:" + url);// 根据url获取文件流InputStream is = getFile(url);PDDocument doc = PDDocument.load(is);// 获取需要签章的文字坐标PdfBoxKeyWordPosition pdf = new PdfBoxKeyWordPosition(key, doc, false);List<CoordinateDTO> wordsPcoordinates = pdf.getCoordinate();log.info("获取新的textLocalDTO 带坐标:" + wordsPcoordinates.toString());// 替换坐标中的文字为空,并且赋值图片try {// 防止替换的文字盖住章或签字,先进行所有的替换,再进行所有的赋值for (CoordinateDTO coordinate : wordsPcoordinates) {// 获取需要替换的页面PDPage page = doc.getPage(coordinate.getPageNum() - 1);// 原本想根据key删除页面文本,但是deleteKey(page, textLocalDTO.getKey())未生效// 生成一个白色的矩形盖住需要替换的文本,实现替换功能PDPageContentStream contentStream = new PDPageContentStream(doc, page,PDPageContentStream.AppendMode.APPEND, true, true);contentStream.setNonStrokingColor(new Color(255, 255, 255));contentStream.addRect(coordinate.getX() - 1, coordinate.getY() - 1,coordinate.getLength() * key.length() + 1, coordinate.getLength() + 1);contentStream.fill();contentStream.close();}// 文件地址为空,签章文字坐标未找到返回原文件if (wordsPcoordinates.size() == 0 || StringUtils.isBlank(imageUrl)) {return toByteArray(is);}// 获取图片流InputStream imageInputStream = getFile(imageUrl);byte[] imageBtye = toByteArray(imageInputStream);// 指定页面,指定位置插入图片for (CoordinateDTO coordinate : wordsPcoordinates) {// 获取需要替换的页面PDPage page = doc.getPage(coordinate.getPageNum() - 1);PDPageContentStream contentStream = new PDPageContentStream(doc, page,PDPageContentStream.AppendMode.APPEND, true, true);PDImageXObject pdImage = PDImageXObject.createFromByteArray(doc, imageBtye, null);// 图片大小为80,找到替换位置x\y轴减去一半,保证在需要签章位置在中间contentStream.drawImage(pdImage, coordinate.getX() - 40, coordinate.getY() - 40, 80, 80);contentStream.close();}doc.save(byteArrayOutputStream);byte[] pdfBytes = byteArrayOutputStream.toByteArray();return pdfBytes;} catch (Exception e) {throw e;} finally {if (doc != null) {doc.close();}}} catch (Exception e) {throw e;}}/*** * 获取url后 下载文件* @date 2023年2月17日 下午5:16:28* @author yongzhizean* @param url* @return* @throws Exception* @return InputStream*/private InputStream getFile(String url) throws Exception {// 创建不同的 文件夹 目录InputStream inputStream = null;String fileName = url.substring(url.lastIndexOf("/") + 1);String newUrl = url.substring(0, url.lastIndexOf("/") + 1) + URLEncoder.encode(fileName, "utf-8");try {// 建立链接URL httpUrl = new URL(newUrl);HttpURLConnection conn = null;conn = (HttpURLConnection) httpUrl.openConnection();log.info("文件获取完毕,文件大小为:" + conn.getContentLength());// 获取网络输入流inputStream = httpUrl.openStream();} catch (Exception e) {log.error("文件获取失败:" + e.getMessage(), e);throw e;}return inputStream;}/**** InputStream 转换成byte[]* @date 2023年2月17日 下午5:17:53* @author yongzhizean* @param input* @return* @throws IOException* @return byte[]*/private byte[] toByteArray(InputStream input) throws IOException {ByteArrayOutputStream output = new ByteArrayOutputStream();byte[] buffer = new byte[1024 * 4];int n = 0;while (-1 != (n = input.read(buffer))) {output.write(buffer, 0, n);}return output.toByteArray();}/*** * 删除标记,未成功,不了解啥原因* @date 2023年2月17日 下午5:18:14* @author hushizhao* @param page* @param key* @throws IOException* @return void*/@SuppressWarnings("unused")private void deleteKey(PDPage page, String key) throws IOException {try {// 流对象来接收当前page的内容Iterator<PDStream> contents = page.getContentStreams();// PDF流对象剖析器(这将解析一个PDF字节流并提取操作数,等等)while (contents.hasNext()) {PDStream content = contents.next();// PDF流对象剖析器(这将解析一个PDF字节流并提取操作数,等等)PDFStreamParser parser = new PDFStreamParser(content.toByteArray());parser.parse();// 用list存流中的所有标记List<Object> tokens = parser.getTokens();for (int j = 0; j < tokens.size(); j++) {// 创建一个object对象去接收标记Object next = tokens.get(j);if (next instanceof Operator) {Operator op = (Operator) next;if (op.getName().equals("Tj")) {// COSString对象>>创建java字符串的一个新的文本字符串。COSString previous = (COSString) tokens.get(j - 1);// 将此字符串的内容作为PDF文本字符串返回。String string = previous.getString();// replaceAll>>替换字符string = string.replaceAll(key, "");System.out.println(string.getBytes("UTF-8"));// 重置COSString对象,设置字符编码格式previous.setValue(string.getBytes("UTF-8"));} else if (op.getName().equals("TJ")) {// COSArray是pdfbase对象数组,作为PDF文档的一部分COSArray previous = (COSArray) tokens.get(j - 1);// 循环previousfor (int k = 0; k < previous.size(); k++) {// 这将从数组中获取一个对象,这将取消引用该对象// 如果对象为cosnull,则返回nullObject arrElement = previous.getObject(k);if (arrElement instanceof COSString) {// COSString对象>>创建java字符串的一个新的文本字符串。COSString cosString = (COSString) arrElement;// 将此字符串的内容作为PDF文本字符串返回。String string = cosString.getString();// 替换string = string.replaceAll(key, "");log.info("替换字符1" + string);// 重置COSString对象cosString.setValue(string.getBytes("UTF-8"));}}}}}}} catch (Exception e) {log.error("pdf文本替换成空失败:" + e.getMessage(), e);throw e;}}
}
有大神解决了deleteKey方法不能用的话,欢迎评论指点,谢谢!
相关文章:
PDF文件替换内容(电子签章),依赖免费pdfbox
首先提前准备,压入如下依赖 <!-- https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox --> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId>…...

nvm 控制 node版本
nvm 官网 https://nvm.uihtm.com/ 1、卸掉nodejs,根据官网操作 2、如果之前安装过的nodejs,且安装的目录改变了,需重新配置系统环境 第一步:打开此电脑 > 右键属性 > 高级系统设置 > 环境变量 第二步: 在系统变量中选中…...

javaEE 初阶 — 传输层 TCP 协议中的异常情况与面向字节流的粘包问题
文章目录1 粘包问题1.1 什么是粘包问题1.2 如何解决粘包问题2 异常情况TCP 的十个特性:确认应答机制 超时重传机制 连接管理机制 滑动窗口 流量控制与拥塞控制 延迟应答与捎带应答 1 粘包问题 1.1 什么是粘包问题 面向字节流引入了一个比较麻烦的粘包问题。 …...

IP路由基础
——IP路由基础(IA)—— HCIA全套笔记已经上线(arpAAAvlanTrunk链路聚合vlan间通信ACL广域网技术以太网交换...........)_孤城286的博客-CSDN博客 目录 ——IP路由基础(IA)—— (1&#…...

12.centos7部署sonarqube9.6
12.centos7部署sonarqube9.6环境:sonarqube9.6Postgresql13JDK11sonarqube9.6下载地址:Postgresql13 rpm下载地址:JDK11下载地址:准备工作:修改文件句柄数(最大文件数)和用户最大进程数限制修改…...
大学四年自学Java编程,现在拿到28万年薪的offer,还是觉得挺值的
最近刚拿到美团的Java后端工程师的offer,(底薪、奖金、补贴、年终奖、五险一金)总包加在大概有28万的年薪,实际到手不会有这么多,但是我对于这个待遇还是非常满意的。说来还是非常的感慨,我属于那种从大一到…...

MySQL的日志详解
目录 一.介绍 日志分类 二.错误日志 三.二进制日志—binlog 概述 日志格式 操作 四.查询日志 五.慢查询日志 一.介绍 在任何一种数据库中,都会有各种各样的日志,记录着数据库工作的方方面面,以帮助数据库管理员追踪数据库曾经发生过的…...
输出该股票所有收盘比开盘上涨3%以上的日期
1:输出该股票所有收盘比开盘上涨3%以上的日期 #codingutf-8 import tushare as ts import pandas as pd import numpy as np#获取某支股票的历史行情数据 dfts.get_hist_data(code600519,start2001-01-01) #将互联网上的数据获取并且存储到本地 df.to_csv(./maotai…...

数值卡,让数据可视化玩出新花样丨三叠云
数值卡 路径 仪表盘 >> 仪表盘设计 功能简介 1. 数值卡增加「数值标题」、「图标」、「进度条」功能,使得应用场景更为广泛,实现数据可视化,让用户能够轻松地获取、处理信息。 2.「数据模型」支持0个维度1个指标、1个维度1个指标。…...

有这几个表现可能是认知障碍前兆
我国目前对于认知障碍的认知率、就诊率、诊断率很低,然而认知障碍如果能在早期发现,并及时治疗,生活质量会有效提高,缓解家属的精神和经济负担。所以,认知障碍的前兆一定要了解。1.记忆力减退,一周内的重要…...

java面试题-阿里真题详解
前言 大家好,我是局外人一枚,最近有不少粉丝去阿里巴巴面试了,回来之后总结不少难题给我,以下是面试的真题,跟大家一起来讨论怎么回答。 阿里一面 1、说⼀下ArrayList和LinkedList区别 ⾸先,他们的底层数…...
JSON格式解析关键词搜索API
为了进行此平台API的调用,首先我们需要做下面几件事情。 1、 获取一个KEY。 2、 参考API文档里的接入方式和示例。 3、查看测试工具是否有需要的接口,响应实例的返回字段是否符合参数要求。 4、利用平台的文档中心和API测试工具,对接口进…...

【Java基础】泛型(二)-泛型的难点:通配符
本文将尝试将通配符和泛型中的继承,多态一并讲解 关于泛型中继承的注意事项 因为Integer、Double继承了Number,根据多态性,以下语句是合法的 Number n new Integer(10); // OK, 父类引用变量可以指向子类对象 n 2.9 // OK,n实…...
黑马】后台管理-两个括号的坑
记录一下这两天的坑没想到后台管理系统上线这两天都没有搞明白1.首先第一个坑是使用node.js的express中间件框架创建一个微型服务器,然后将vue脚手架生成的dist文件夹的文件放入里面了 ,把项目加载到web服务器之后运行node .\app.js,页面显示…...
05:进阶篇 - 使用 CTKWidgets
作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 CTKWidgets 包含了一组 Qt 部件,用于生物医学成像应用程序。当然,即使你的程序与医学无关,很多部件也是很有参考意义的。 在 CTK 源码中,有很多选项开关,可以控制你想要编译的内容(详见:04:进阶篇 …...

【YOLO V5】代码复现过程
接上篇,讲到如何从mask转成YOLOv5训练需要的txt数据集格式,这篇就在此基础上进行模型训练预测和部署转换吧! 目录 1.环境准备 2.YOLO训练 2.1 数据集准备 2.2 data.yaml准备 2.3 yolov5.yaml准备 2.4 训练命令 3.YOLO预测 3.1OLOv5 P…...

汽车如何实现制动
汽车如何实现制动 汽车如何实现制动 难点答疑:汽车刹车时,四个车轮是如何制动的?制动机理是什么? 第一步:驾驶员踩下制动踏板,推动制动主缸 第二步:制动主缸将制动液的压力通过制动管道传递到四…...

cmake 引入第三方库(头文件目录、库目录、库文件)
程序的编写需要用到头文件,程序的编译需要lib文件,程序的运行需要dll文件,因此cmake引入第三方库其实就是将include目录、lib目录、bin目录引入工程。 目录 1、find_package(批量引入库文件和头文件) 2、include_dir…...

插件开发版|Authing 结合 APISIX 实现统一可配置 API 权限网关
当开发者在构建网站、移动设备或物联网应用程序时,API 网关作为微服务架构中不可或缺的控制组件,是流量的核心进出口。通过有效的权限管控,可以实现认证授权、监控分析等功能,提高 API 的安全性、可用性、拓展性以及优化 API 性能…...
deepinlinux v20安装rust和tauri并配置vscode开发工具过程
rust 很快进入linux内核开发,作为高效后台语言值得学习 tauri是代替electron的跨平台框架,不打包浏览器内核,所以打包出来体积小 安装rust 命令 curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh 安装后看版本 rustc -V 看构…...

基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...

K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表
##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...

通过MicroSip配置自己的freeswitch服务器进行调试记录
之前用docker安装的freeswitch的,启动是正常的, 但用下面的Microsip连接不上 主要原因有可能一下几个 1、通过下面命令可以看 [rootlocalhost default]# docker exec -it freeswitch fs_cli -x "sofia status profile internal"Name …...