Java 实现给pdf文件指定位置盖章功能

Java 实现给pdf文件指定位置盖章功能

开发中遇到一个需求, 需要给用户上传的的pdf文件, 指定位置上盖公章的功能, 经过调研和对比, 最终确定实现思路.

这里是使用pdf文件中的关键字进行章子的定位, 之所以这样考虑是因为如果直接写死坐标的话, 可能会出现因pdf大小, 缩放, 盖章位置变更的原因, 导致公章位置错位. 所以选择了根据关键字定位的方式.

下面列出具体的实现方式:

  1. 使用的是Java语言, 使用Maven管理依赖, 下面是操作pdf所依赖的库的坐标

    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>itextpdf</artifactId>
        <version>5.5.12</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>itext-asian</artifactId>
        <version>5.2.0</version>
    </dependency>
    
  2. 下面是具体的代码逻辑

    • CustomRenderListener, 自定义渲染监听器, 实现查找关键字自有逻辑

      import com.itextpdf.awt.geom.Rectangle2D.Float;
      import com.itextpdf.text.pdf.parser.ImageRenderInfo;
      import com.itextpdf.text.pdf.parser.RenderListener;
      import com.itextpdf.text.pdf.parser.TextRenderInfo;
      import lombok.Data;
      
      /**
       * pdf关键词帮助类
       *
       * @author wdhcr
       */
      @Data
      public class CustomRenderListener implements RenderListener {
      
        	// 坐标
          private float[] coordinate = null;
      
        	// 关键字
          private String keyWord;
      		
        	// pdf当前页
          private int page;
      
          @Override
          public void beginTextBlock() {
          }
      
          @Override
          public void endTextBlock() {
          }
      
          @Override
          public void renderImage(ImageRenderInfo arg0) {
          }
      
          @Override
          public void renderText(TextRenderInfo textRenderInfo) {
              String text = textRenderInfo.getText();
              if (null != text && text.contains(keyWord)) {
                  Float boundingRange = textRenderInfo.getBaseline().getBoundingRectange();
                  coordinate = new float[3];
                  coordinate[0] = boundingRange.x;
                  coordinate[1] = boundingRange.y;
                  coordinate[2] = page;
              }
          }
      }
      
    • 获取关键字坐标的工具类

      import com.itextpdf.text.Image;
      import com.itextpdf.text.pdf.PdfContentByte;
      import com.itextpdf.text.pdf.PdfGState;
      import com.itextpdf.text.pdf.PdfReader;
      import com.itextpdf.text.pdf.PdfStamper;
      import com.itextpdf.text.pdf.parser.PdfReaderContentParser;
      import com.jkwl.common.exception.BaseException;
      import lombok.SneakyThrows;
      import lombok.extern.slf4j.Slf4j;
      
      import java.io.*;
      
      /**
       * Pdf定位工具类
       * @author wdhcr
       */
      @Slf4j
      public class PdfUtils {
      
          /**
           * 获取关键字所在PDF坐标
           *
           * @param pdfReader 流
           * @param keyWords  关键词
           * @return float[] 坐标
           */
          public static float[] getKeyWords(PdfReader pdfReader, String keyWords) {
              float[] coordinate = null;
              int page = 0;
              try {
                  int pageNum = pdfReader.getNumberOfPages();
                  PdfReaderContentParser pdfReaderContentParser = new PdfReaderContentParser(pdfReader);
                  CustomRenderListener renderListener = new CustomRenderListener();
                  renderListener.setKeyWord(keyWords);
                  for (page = 1; page <= pageNum; page++) {
                      renderListener.setPage(page);
                      pdfReaderContentParser.processContent(page, renderListener);
                      coordinate = renderListener.getCoordinate();
                      if (coordinate != null) {
                          break;
                      }
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              }
              return coordinate;
          }
      
      
          @SneakyThrows
          public static void stampToPdf(InputStream pdfInputStream, String keyWords, InputStream sealInputStream, OutputStream outputStream) {
              byte[] sealBytes = sealInputStream.readAllBytes();
              PdfReader reader = new PdfReader(pdfInputStream);
              float[] xyz = PdfUtils.getKeyWords(reader, keyWords);
              if (xyz == null) {
                  throw new BaseException("未找到盖章位置");
              }
              PdfStamper stamper = new PdfStamper(reader, outputStream);
              // 将印章图片放在pdf文件的第?页
              PdfContentByte over = stamper.getOverContent((int) xyz[2]);
              // 需要插入的图片
              Image contractSealImg = Image.getInstance(sealBytes);
              // 保存状态
              over.saveState();
              // 图片处理
              PdfGState pdfGState = new PdfGState();
              // 给图片设置透明度,一般不需要
              pdfGState.setFillOpacity(0.8F);
              over.setGState(pdfGState);
              contractSealImg.setAbsolutePosition(xyz[0] + 50, xyz[1] - 40);
              // 设置图片大小
              contractSealImg.scaleAbsolute(100, 100);
              // 将图片添加到pdf文件
              over.addImage(contractSealImg);
              over.restoreState();
              stamper.setFormFlattening(true);
              // 关闭流
              stamper.close();
              reader.close();
          }
      
      }
      
    • 测试demo

          @SneakyThrows
          public static void main(String[] args) {
              FileInputStream pdfInputStream = new FileInputStream("/Users/Desktop/报告模版.pdf");
              FileInputStream sealInputStream = new FileInputStream("/Users/Desktop/dbd.png");
              FileOutputStream fileOutputStream = new FileOutputStream("/Users/Desktop/报告模版盖章.pdf");
              // 关键字为: 签发日期
              PdfUtils.stampToPdf(pdfInputStream, "签发日期", sealInputStream, fileOutputStream);
              fileOutputStream.close();
              pdfInputStream.close();
              sealInputStream.close();
              System.out.println("盖章完成");
      
          }
      
    • 公章如图所示

      seal

    • 报告模版如下:

      image-20241211145537180

    • 盖完章后

      image-20241211145618814

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vmware的网络适配器的NAT模式和桥接模式有何区别?如何给Uubunt系统添加桥接网卡?

Vmware的网络适配器的NAT模式和桥接模式有何区别&#xff1f; 如何给Uubunt系统添加桥接网卡? 步骤如下&#xff1a;

主机连不上CentOS7虚拟机Redis

CentOS7中的Redis连不上主机 是否ping通 先尝试主机是否能Ping通虚拟机 虚拟机中查看ens33对应的地址&#xff0c;使用ifconfig 再在主机上尝试Ping&#xff0c;如果无法Ping通&#xff0c;先排除是否是虚拟机NAT或者桥接模式配置的问题 redis.conf配置 我是按照黑马的教…

vue element 切换 select 下拉框的 单选多选报错

今天根据项目需求&#xff0c;需要对下拉框进行&#xff0c;单双选判断&#xff0c;当多选切换成多选&#xff0c;没有问题但是单选切换成多选报错如下 页面是要求 选择in或者notin时候 多选 经过好长时间摸索&#xff0c;解决了&#xff0c;最后使用select的失去焦点事件解决的…

VBA高级应用30例应用在Excel中的ListObject对象:向表中添加注释

《VBA高级应用30例》&#xff08;版权10178985&#xff09;&#xff0c;是我推出的第十套教程&#xff0c;教程是专门针对高级学员在学习VBA过程中提高路途上的案例展开&#xff0c;这套教程案例与理论结合&#xff0c;紧贴“实战”&#xff0c;并做“战术总结”&#xff0c;以…

BA和CS算法中的Levy飞行策略

Levy飞行策略通过模拟自然界中动物的长距离迁徙行为&#xff0c;指导粒子进行更大范围的搜索&#xff0c;有助于算法快速找到全局最优解。它是一种具有独特优势的随机行为策略&#xff0c;模拟随机游走或搜索过程中的步长和方向&#xff0c;其步长的概率分布为重尾分布&#xf…

JavaEE多线程案例之阻塞队列

上文我们了解了多线程案例中的单例模式&#xff0c;此文我们来探讨多线程案例之阻塞队列吧 1. 阻塞队列是什么&#xff1f; 阻塞队列是⼀种特殊的队列.也遵守"先进先出"的原则. 阻塞队列是⼀种线程安全的数据结构,并且具有以下特性: 当队列满的时候,继续⼊队列就会…

[Maven]下载安装、配置与简介

很多框架的下载使用的流程和思路是差不多的&#xff0c;这里以maven做详细介绍。 下载安装与配置变量 下载 首先&#xff0c;我们要使用maven&#xff0c;必须先下载它的相关文件。想要下载&#xff0c;我们可以直接搜索maven。找到它的官网。这里不绕弯子&#xff0c;直接给出…

centos部署SkyWalking以及在springcloud项目中搭配loki使用

文章目录 场景SkyWalking介绍部署部署Storage [单机版Elasticsearch]部署SkyWalking OAP [下载地址](https://skywalking.apache.org/downloads/#SkyWalkingAPM)部署SkyWalking Java Agent springCloud 使用举例追踪ID写入loki 场景 SkyWalking是应用性能监控平台&#xff0c;可…

FastAPI vs Flask 选择最适合您的 Python Web 框架

文章目录 1. 简介2. 安装和设置3. 路由和视图4. 自动文档生成5. 数据验证和序列化6. 性能和异步支持结论 在 Python Web 开发领域&#xff0c;FastAPI 和 Flask 是两个备受欢迎的选择。它们都提供了强大的工具和功能&#xff0c;但是在某些方面有所不同。本文将比较 FastAPI…

xshell连接虚拟机,更换网络模式:NAT->桥接模式

NAT模式&#xff1a;虚拟机通过宿主机的网络访问外网。优点在于不需要手动配置IP地址和子网掩码&#xff0c;只要宿主机能够访问网络&#xff0c;虚拟机也能够访问。对外部网络而言&#xff0c;它看到的是宿主机的IP地址&#xff0c;而不是虚拟机的IP。但是&#xff0c;宿主机可…

常见的网络攻击手段

IP 欺骗 IP 是什么? 在网络中&#xff0c;所有的设备都会分配一个地址。这个地址就仿佛小蓝的家地址「多少号多少室」&#xff0c;这个号就是分配给整个子网的&#xff0c;「室」对应的号码即分配给子网中计算机的&#xff0c;这就是网络中的地址。「号」对应的号码为网络号…

酒店/电影推荐系统里面如何应用深度学习如CNN?

【1】酒店推荐系统里面如何应用CNN&#xff1f;具体过程是什么 在酒店推荐系统中应用卷积神经网络&#xff08;CNN&#xff09;并不是一个常见的选择&#xff0c;因为 CNN 主要用于处理具有空间结构的数据&#xff0c;如图像、音频和某些类型的序列数据。然而&#xff0c;在某…

Qt-chart 画柱状图

记录下&#xff0c;记录下 效果图 直接上代码 // 创建柱状系列 QBarSeries *series new QBarSeries();// 创建数据集 QBarSet *setTar new QBarSet(("tar"));QBarSet *setReality new QBarSet(("reality"));//添加柱状数据*setTar << 1<<…

python图像处理

一、图像透视变化 1.1 实验原理 图像透视变换&#xff08;Perspective Transformation&#xff09;是一种通过数学方法将图像中的点集映射到一个新的点集上的技术。它能够对图像进行几何变换&#xff0c;常用于将不规则形状的区域转换为规则形状&#xff0c;或修正图像中的透视…

Android四大组件——Activity(二)

一、Activity之间传递消息 在&#xff08;一&#xff09;中&#xff0c;我们把数据作为独立的键值对进行传递&#xff0c;那么现在把多条数据打包成一个对象进行传递&#xff1a; 1.假设有一个User类的对象&#xff0c;我们先使用putExtra进行传递 activity_demo06.xml <…

【Lambda】java之lambda表达式stream流式编程操作集合

java之lambda表达式&stream流式编程操作集合 1 stream流概念1.1 中间操作1.1.1 无状态操作1.1.2 有状态操作 1.2 终端操作1.2.1 非短路操作1.2.2 短路操作 2 steam流的生成2.1 方式一&#xff1a;数组转为stream流2.2 方式二&#xff1a;集合转为steam流2.3 方式三&#xf…

springboot整合lua脚本在Redis实现商品库存扣减

1、目的 使用lua脚本&#xff0c;可以保证多条命令的操作原子性&#xff1b;同时可以减少操作IO&#xff08;比如说判断redis对应数据是否小于0&#xff0c;小于0就重置为100&#xff0c;这个场景一般是取出来再判断&#xff0c;再存放进行&#xff0c;就至少存在2次IO,用lua脚…

深入了解 Spring IOC,AOP 两大核心思想

文章目录 一、Spring 基础 - 控制反转&#xff08;IOC&#xff09;1.1. 引入1.2. 如何理解 IOCSpring Bean 是什么&#xff1f;IoC 是什么&#xff1f;IoC 能做什么&#xff1f;IoC 和 DI 是什么关系&#xff1f; 1.3. IoC 配置的三种方式xml 配置Java 配置注解配置 1.4. 依赖注…

HNU_多传感器(专选)_作业4(构建单层感知器实现分类)

1. (论述题)&#xff08;共1题&#xff0c;100分&#xff09; 假设平面坐标系上有四个点&#xff0c;要求构建单层感知器实现分类。 (3,3),(4,3) 两个点的标签为1&#xff1b; (1,1),(0,2) 两个点的标签为-1。 思路&#xff1a;要分类的数据是2维数据&#xff0c;需要2个输入…

dolphinscheduler服务RPC框架源码解析(二)RPC核心注解@RpcService和@RpcMethod设计实现

1.工程目录 从3.2.1版本之后这个dolphinscheduler中的RPC框架工程就从原来的dolphinscheduler-remote工程重构到了dolphinscheduler-extract工程。 dolphinscheduler 父项目 dolphinscheduler-extract RPC服务项目 dolphinscheduler-extract-alert 监控告警服务RPC接口定义、…