最近接了个需求,要往生成好的PDF格式文书上盖上电子签章。盖章是第三方盖,但盖到第几页以及盖在什么位置需要我这边提供出来。本来位置信息是根据文书模板量的,但由于文字填充后导致页数增加,位置也波动不定,所以就想到通过检索落款单位在文档中的位置来确认盖章的位置。

初次接触PDF处理,这也是自己网上查资料再加上自己思考后做出来的,为了后面类似需求可以方便些,在此小记一下。

maven引入相应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>

第三方签章需要的位置信息类【SignSetting】:

import lombok.Data;/*** 签章配置项*/
@Data
public class SignSetting {/*** 位置签章页码/骑缝签章页数*/private Integer page;/*** 签章X轴坐标*/private Integer posX;/*** 签章Y轴坐标*/private Integer posY;
}

PDF处理监听程序类【PdfRenderListener】 :

import com.itextpdf.awt.geom.Rectangle2D;
import com.itextpdf.text.pdf.parser.ImageRenderInfo;
import com.itextpdf.text.pdf.parser.RenderListener;
import com.itextpdf.text.pdf.parser.TextRenderInfo;
import lombok.Data;
import model.TextChunkRenderInfo;import java.util.ArrayList;
import java.util.List;/*** PDF处理监听程序,主要用于文本处理* Author:* Date:2022-08-25 00:56*/
@Data
public class PdfRenderListener implements RenderListener {/*** 文本块信息列表*/private List<TextChunkRenderInfo> textChunkRenderInfoList = new ArrayList<>();/*** 文本块处理开始*/@Overridepublic void beginTextBlock() {}/*** 文本小块处理** @param textRenderInfo 文本信息处理,此处包含一个文本块,该文本块内包含一个字符或多个字符*/@Overridepublic void renderText(TextRenderInfo textRenderInfo) {// 获取文本内容String text = textRenderInfo.getText();if (text != null) {// 获取文本矩形信息Rectangle2D.Float rectAscent = textRenderInfo.getAscentLine().getBoundingRectange();// 计算文字的边框矩形float minX = (float) rectAscent.getMinX(); // 文本行最左侧float minY = (float) rectAscent.getMinY() - 1; // 文本行最下方float maxX = (float) rectAscent.getMaxX(); // 文本最右侧float maxY = (float) rectAscent.getMaxY() + 1; // 文本最上方TextChunkRenderInfo textChunkRenderInfo = new TextChunkRenderInfo(text, minX, minY, maxX, maxY);this.textChunkRenderInfoList.add(textChunkRenderInfo);}}/*** 文本块处理结束*/@Overridepublic void endTextBlock() {}/*** 图片处理** @param imageRenderInfo 图片预处理信息*/@Overridepublic void renderImage(ImageRenderInfo imageRenderInfo) {}
}

从PDF文件读取到的文本块信息类【TextChunkRenderInfo】:

import lombok.Data;/*** 文本块信息* Author:* Date:2022-08-25 01:01*/
@Data
public class TextChunkRenderInfo {/*** 文本内容*/private String text;/*** 文本左下角横坐标*/private float minX;/*** 文本左下角纵坐标*/private float minY;/*** 文本右上角横坐标*/private float maxX;/*** 文本右上角纵坐标*/private float maxY;/*** 有参构造函数** @param text 文本内容* @param minX 文本左下角横坐标* @param minY 文本左下角纵坐标* @param maxX 文本右上角横坐标* @param maxY 文本右上角纵坐标*/public TextChunkRenderInfo(String text, float minX, float minY, float maxX, float maxY) {this.text = text;this.minX = minX;this.minY = minY;this.maxX = maxX;this.maxY = maxY;}/*** 获取文本的高度** @return 文本高度*/public float getWordHeight() {return this.maxY - this.minY;}/*** 获取文本文字宽度** @return 文本文字宽度*/public float getWordWidth() {float fullWidth = this.maxX - this.minX;return fullWidth / this.text.length();}/*** 获取单词在文本内容中的位置及匹配的长度** @param word      待匹配的单词* @param textStart 从文本的第几个字符开始匹配* @param wordStart 从单词第几个字符开始匹配* @return 匹配结果 0:从第几位开始匹配成功,1:word中从第一个字符开始连续匹配成功长度*/public int[] getTextContainWordInfo(String word, int textStart, int wordStart) {int firstMatchIndex = 0; // 第一次匹配成功索引int matchLength = 0; // 匹配长度char[] textChars = this.text.toCharArray();char[] wordChars = word.toCharArray();for (int i = textStart - 1; i < textChars.length; i++) {int textIndex = -1;for (int j = wordStart - 1; j < wordChars.length; j++) {textIndex = i + j - wordStart + 1;// 只要存在一个不匹配就匹配不上if (textChars[textIndex] != wordChars[j]) {textIndex = -1;break;}// j之前都匹配成功了,如果匹配长度超出了text文本长度,则跳出循环,说明只匹配成功了部分if (textIndex >= textChars.length - 1) {break;}}if (textIndex >= 0) { // 说明匹配成功了,不管text是否走完都跳出循环firstMatchIndex = i;matchLength = textIndex - i + 1;break;}}if (matchLength > 0) {return new int[]{firstMatchIndex + 1, matchLength};}return null;}
}

从PDF文件中匹配落款单位匹配结果信息类【TextChunkMatchResult】:

import lombok.Data;/*** PDF文本块关键词匹配结果* Author: * Date: 2022-08-25 13:56*/
@Data
public class TextChunkMatchResult {/*** 第一个匹配成功的文本块信息*/private TextChunkRenderInfo firstMatchRenderInfo;/*** 第一个匹配成功的文本块信息开始字符索引*/private int firstMatchStartIndex;/*** 第一个匹配成功的文本块信息匹配成功字符长度*/private int firstMatchLength;/*** 最后一个匹配成功的文本块信息*/private TextChunkRenderInfo lastMatchRenderInfo;
}

主要处理程序,PDFUtil类实现代码:

import com.alibaba.fastjson.JSONObject;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.parser.PdfReaderContentParser;
import entities.es.SignSetting;
import model.TextChunkMatchResult;
import model.TextChunkRenderInfo;import java.util.List;/*** PDF文件相关操作类* Author:* Date:2022-08-24 14:23*/
public class PdfUtil {/*** 根据落款单位获取该落款单位在PDF文件中的位置信息,用来进行电子签章* Author: * Date: 2022-08-24 14:32** @param fileName PDF文件路径* @param unitName 落款单位名称* @return 电子签章位置信息*/public static SignSetting getSignaturePositionBySignedUnit(String fileName, String unitName) {SignSetting rtnSetting = null;PdfReader pdfReader = null;try {// 初始化PDF文件pdfReader = new PdfReader(fileName);// 获取PDF文件总页数int pdfPage = pdfReader.getNumberOfPages();// PDF 内容PdfReaderContentParser parser = new PdfReaderContentParser(pdfReader);// 从最后一页开始查找落款单位名称,找到后直接跳出循环// region 获取KeyWordBean// 根据落款单位定位for (int i = pdfPage; i >= 1; i--) {SignSetting finalSignSetting = null;// PDF当前页文本处理PdfRenderListener listener = new PdfRenderListener();parser.processContent(i, listener);// PDF 获取关键词所在位置信息finalSignSetting = getKeyWordPosition(listener.getTextChunkRenderInfoList(), unitName);if (finalSignSetting != null) {finalSignSetting.setPage(i);rtnSetting = finalSignSetting;break;}}// 位置调整,章是从这里开始下方盖章,所以位置上移部分,可以盖在字的上面if (rtnSetting != null) {rtnSetting.setPosY(rtnSetting.getPosY() + 50);}// endregion// 如果未找到,就把章盖到最后一页if (rtnSetting == null) {rtnSetting = new SignSetting();rtnSetting.setPage(pdfPage);}} catch (Exception ex) {StringOutPrintlnUtil.writeInfo(ex.getMessage());} finally {if (pdfReader != null) {pdfReader.close();}}return rtnSetting;}/*** 获取关键词所在位置信息** @param textChunkRenderInfoList 文本块信息* @param keyWord                 关键词* @return 关键词位置信息*/private static SignSetting getKeyWordPosition(List<TextChunkRenderInfo> textChunkRenderInfoList, String keyWord) {SignSetting signSetting = null; // 返回盖章配置信息TextChunkMatchResult matchResult = null; // 最终匹配结果keyWord = keyWord.replace(" ", ""); // 去除中间空格// 第一个匹配到的文本信息TextChunkRenderInfo firstCharMatch = null;// 第一个匹配到的文本位置信息int firstMatchPos = 0;// 第一个匹配到的字符长度信息int firstMatchLength = 0;// 已经匹配成功的字符长度int matchLength = 0;for (TextChunkRenderInfo renderInfo : textChunkRenderInfoList) {int textStart = 1;while (textStart <= renderInfo.getText().length()) {int[] matchRes = renderInfo.getTextContainWordInfo(keyWord, textStart, matchLength + 1);if (matchRes == null) {firstCharMatch = null;if (matchLength == 0) {break;}matchLength = 0;continue;}if (matchLength == 0) {firstCharMatch = renderInfo;firstMatchPos = matchRes[0];firstMatchLength = matchRes[1];}matchLength += matchRes[1];if (matchLength >= keyWord.length()) { // 已经完全匹配成功// 初始化匹配结果matchResult = new TextChunkMatchResult();matchResult.setFirstMatchRenderInfo(firstCharMatch);matchResult.setFirstMatchLength(firstMatchLength);matchResult.setFirstMatchStartIndex(firstMatchPos - 1);matchResult.setLastMatchRenderInfo(renderInfo);firstCharMatch = null;firstMatchPos = 0;firstMatchLength = 0;matchLength = 0;}textStart = matchRes[0] + matchRes[1];}}if (matchResult != null) { // 成功匹配到了最后一个signSetting = new SignSetting();float posX = matchResult.getFirstMatchRenderInfo().getMinX();// 将左侧未匹配到的距离加上posX = posX + matchResult.getFirstMatchRenderInfo().getWordWidth() * matchResult.getFirstMatchStartIndex();signSetting.setPosX((int) posX);signSetting.setPosY((int) matchResult.getFirstMatchRenderInfo().getMinY());}return signSetting;}
}

测试如下:

    public static void main(String[] args) {SignSetting signaturePositionBySignedUnit = PdfUtil.getSignaturePositionBySignedUnit("F:\\PDF操作测试.pdf", "单位落款");System.out.println("查找结果:" + JSONObject.toJSONString(signaturePositionBySignedUnit));}

测试结果:查找结果:{"page":2,"posX":405,"posY":499}

JAVA 查找PDF中落款单位所在页码及位置信息相关推荐

  1. pdfparser java_如何使用java从PDF中提取内容?

    在Java编程中,如何使用java从PDF中提取内容? 项目的目录结构如下 - Tika的工具包可从以下网址下载:http://tika.apache.org/download.html ,只下载:t ...

  2. 用Java读取pdf中的数据

    用Java简单的读取pdf文件中的数据: 第一步:下载PDFBox-0.7.2.jar.提供一个下载地址:[url]http://pdfhome.hope.com.cn/Resource.aspx?C ...

  3. JAVA 替换pdf中文字

    java 读取PDF文件内容进行替换 需要使用到的包 监听类(对需要替换的内容关键词进行匹配) 实体类(保存关键字字体格式信息以及其位置) 工具类(对关键字进行替换) 测试类 需要使用到的包      ...

  4. Java 在PDF中添加工具提示|ToolTip

    本文,将介绍如何通过Java后端程序代码在PDF中创建工具提示.添加工具提示后,当鼠标悬停在页面上的元素时,将显示工具提示内容. 导入jar包 本次程序中使用的是Free Spire.PDF for ...

  5. java 获取文件大小_利用百度AI OCR图片识别,Java实现PDF中的图片转换成文字

    序言:我们在读一些PDF版书籍的时候,如果PDF中不是图片,做起读书笔记的还好:如果PDF中的是图片的话,根本无法编辑,做起笔记来,还是很痛苦的.我是遇到过了.我们搞技术的,当然得自己学着解决现在的痛 ...

  6. Java 读取PDF中表格的工具

    目录 1.方法1:Spire.PDF 1.1 Maven仓库下载导入 1.2 读取PDF中的表格 1.2.1 代码 1.2.2 表格内容 1.2.3 读取结果 2.方法2:Tabula 2.1 Mav ...

  7. java 替换pdf中的文字

    1.引入依赖 <dependency><groupId>com.itextpdf</groupId><artifactId>itext-asian< ...

  8. linux java 查找进程中的线程

    这里对linux下.sun(oracle) JDK的线程资源占用问题的查找步骤做一个小结: linux环境下,当发现java进程占用CPU资源很高,且又要想更进一步查出哪一个java线程占用了CPU资 ...

  9. Java 在PDF中添加骑缝章

    骑缝章是用于往来业务合同,以确保合同真实.有效的印章加盖方法,是一种防范风险的重要方式.在Java程序中,可以通过使用工具来辅助加盖这种骑缝章. 工具:Free Spire.PDF for Java ...

  10. 如何用关键字查找PDF中的文字内容?

    我们都知道PDF格式最大的特点就是稳定,如果需要通过关键字查找快速定位和查看PDF文档是否可以实现呢?需要用到什么软件,如何操作呢? 有关PDF的修改必须用到PDF编辑器,但是如果仅是查找内容,在PD ...

最新文章

  1. 员工培训案例分析答案_在职员工培训管理办法案例
  2. 轻松使用make menuconfig达到内核的升级!
  3. 20145240 《Java程序设计》第四次实验报告
  4. 国家有线网挂牌时间再度推迟 预计为2012年底
  5. 葬身李刚儿子车轮下的漂亮女孩
  6. 了解下JavaScript中的prototype
  7. Joseph_Circle(约瑟夫环)
  8. CorePlot-饼状体
  9. java future 返回值_Java--Callable与返回值future
  10. html浮动div同行显示,div已经设了over-flow:auto;为什么没有滚动条浮动元素不能同行显示了,怎么办...
  11. java后端开发简历模板,最全Java知识总结
  12. hadoop部分架构图
  13. 东南亚电商lazadashopee平台怎么开店,需要什么条件?
  14. mysql查询出现ambiguous的问题
  15. MicroExpSTCNN and MicroExpFuseNet-基于三维时空卷积神经网络的自发面部微表情识别
  16. hdwiki的php架构,关于HDWiki的安装踩坑
  17. 论文阅读中经常出现的“消融研究/实验”
  18. 计算机主机配置一般有机箱主板cpu,1500元电脑主机配置有哪些 1500电脑主机配置推荐【图文】...
  19. 锐龙7000PBO温度墙设置
  20. Application Cache is a Douchebag

热门文章

  1. Bootstrap系列之表单(Forms)
  2. 多种代码生成炫酷代码雨(推荐)
  3. android 跨进程通信 binder
  4. python中keys函数怎么用_Python keys()函数
  5. (一)计网五层模型概述
  6. Mysql的分组函数
  7. idea从零到精通目录导航
  8. 从零开始搭建一个前端框架(一)环境准备并完成简单打包
  9. 自己动手编写CSDN博客备份工具-blogspider之源码分析(2)
  10. 安全是我们的生命线,将时刻保持敬畏心