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 看构…...
省下一个定时器!用STM32外部时钟模式驱动YF-S401流量计,资源占用更少
优化嵌入式系统资源占用:STM32外部时钟模式驱动YF-S401流量计实战 在资源受限的嵌入式系统中,每一个定时器和中断通道都显得弥足珍贵。想象一下这样的场景:你的STM32需要同时处理电机控制、无线通信、用户界面交互,现在还要实时监…...
免费Mac桌面歌词神器LyricsX:解锁音乐沉浸新体验
免费Mac桌面歌词神器LyricsX:解锁音乐沉浸新体验 【免费下载链接】Lyrics Swift-based iTunes plug-in to display lyrics on the desktop. 项目地址: https://gitcode.com/gh_mirrors/lyr/Lyrics 在Mac上听音乐时,你是否曾想过让歌词像电影字幕一…...
Visual C++运行库终极修复指南:5分钟解决Windows软件无法运行的完整教程
Visual C运行库终极修复指南:5分钟解决Windows软件无法运行的完整教程 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 还在为"找不到MSVCR140.d…...
给ADAS工程师的CIS相机选型避坑指南:CRA、QE、CFA这些参数到底怎么配?
给ADAS工程师的CIS相机选型避坑指南:CRA、QE、CFA这些参数到底怎么配? 在ADAS系统开发中,摄像头作为环境感知的核心传感器,其性能直接影响算法识别准确率。面对供应商琳琅满目的参数手册,工程师常陷入"参数陷阱&…...
大语言模型混合架构:显式记录与最大熵方法优化
1. 大语言模型的学习困境与人类启发在自然语言处理领域,大型语言模型(LLM)已经展现出惊人的能力,从代码生成到创意写作,从技术问答到逻辑推理。然而,当我们把这些模型部署到真实世界的专业场景时——比如诊断一台特定型号的工业设…...
风控规则灰度发布怎么做才稳?白名单、比例放量、效果观察、快速回滚全讲清
风控规则灰度发布怎么做才稳?白名单、比例放量、效果观察、快速回滚全讲清 这篇直接按线上风控发版来拆,不只讲“先小流量再全量”,而是把版本、白名单、观察指标、回滚链路讲具体。 目标是你看完后,能把风控规则灰度从一句流程话…...
快速体验GLM-4.7-Flash:用Ollama Web UI实现零门槛AI对话
快速体验GLM-4.7-Flash:用Ollama Web UI实现零门槛AI对话 1. GLM-4.7-Flash模型简介 1.1 模型架构与特点 GLM-4.7-Flash是一款30B参数规模的混合专家模型(MoE),采用A3B稀疏激活架构。这意味着: 总参数30B:具备接近GPT-4级别的…...
手把手教你为UniApp微信小程序项目配置安全的WSS WebSocket连接(Vue3版)
从零构建UniApp微信小程序的WSS WebSocket全链路配置指南 微信小程序作为日活超4亿的超级入口,其安全策略要求所有网络请求必须通过HTTPS加密传输,这对WebSocket连接提出了WSS的强制要求。许多全栈开发者在项目上线时,往往卡在如何从普通WS服…...
保姆级避坑指南:在Ubuntu 20.04上为UR5机械臂配置ROS Noetic和MoveIt(从仿真到实物)
UR5机械臂ROS开发避坑实战:从环境配置到实物联调全指南 如果你正在Ubuntu 20.04上为UR5机械臂配置ROS Noetic和MoveIt环境,大概率已经体会过依赖地狱、版本冲突和网络配置的折磨。这份指南不会重复官方文档的基础操作,而是聚焦那些让开发者彻…...
如何高效组织Meteor项目结构:从入门到精通的完整指南
如何高效组织Meteor项目结构:从入门到精通的完整指南 【免费下载链接】meteor Meteor, the JavaScript App Platform 项目地址: https://gitcode.com/gh_mirrors/me/meteor Meteor作为全栈JavaScript应用平台,其独特的文件结构和模块划分方式是开…...
