电子签章过程中存在着在网页上对签署文件进行预览、指定签署位置、文件签署等操作,由于图片在浏览器上的兼容性和友好性优于PDF文件,所以一般在网页上进行电子签章时,会先将PDF文件转换成图片,展示给用户。用户在页面上确定好签署位置,并进行签署时,后端服务会通过对电子印章/手写签名位置、大小以及PDF文件的大小进行计算,在PDF文件的准确位置上完成文件签署。以下代码是Java后端与前端交互签名位置计算的源代码,希望对大家有帮助。



更多电子签章前后端交互体验,可访问开源网站获取电子签章/电子合同工具源码:

https://gitee.com/kaifangqian

https://github.com/kaifangqian

关联工具包:itext-pdf;

​1、计算签署配置业务类;


import com.itextpdf.text.Document;
import com.itextpdf.text.pdf.PdfReader;
import com.resrun.service.pojo.RealPositionProperty;
import com.resrun.service.pojo.SelectKeywords;
import com.resrun.service.pojo.SourcePositionProperty;
import org.springframework.stereotype.Service; import java.io.IOException;
import java.util.ArrayList;
import java.util.List; /**
* @Description: 计算签署位置业务
* @Package: com.resrun.service.pdf
* @ClassName: CalculatePositionService
* @copyright 北京资源律动科技有限公司
*/
@Service
public class CalculatePositionService { /**
* @Description #批量计算真实签署位置
* @Param [sourcePositionProperties]
* @return java.util.List<com.resrun.modules.sign.service.tool.pojo.RealPositionProperty>
**/
public List<RealPositionProperty> calculatePositions(List<SourcePositionProperty> sourcePositionProperties, byte[] pdfFileByte){
List<RealPositionProperty> realPositionProperties = new ArrayList<>(); PdfReader reader = null ;
try {
//将pdf文件读入PdfReader工具类
reader = new PdfReader(pdfFileByte);
for(SourcePositionProperty sourcePositionProperty : sourcePositionProperties){
RealPositionProperty realPositionProperty = calculatePosition(sourcePositionProperty,pdfFileByte);
Document document = new Document(reader.getPageSize(sourcePositionProperty.getPage()));
//获取真实pdf文件指定页的真实文档宽高
float realPdfHeight = document.getPageSize().getHeight();
float realPdfWidth = document.getPageSize().getWidth();
//获取页面上文档的宽高
float sourcePageWidth = sourcePositionProperty.getPageWidth();
float sourcePageHeight = sourcePositionProperty.getPageHeight();
//计算真实文档的宽高和页面文档的宽高的比率
float rateHeight = realPdfHeight / sourcePageHeight;
float rateWidth = realPdfWidth / sourcePageWidth;
//计算页面上的横纵坐标,由于页面上给出的是左上角的坐标,所以需要再转换计算一下
//左下角
float pageStartX = sourcePositionProperty.getOffsetX();
float pageStartY = sourcePositionProperty.getOffsetY() + sourcePositionProperty.getHeight() ;
//右上角
float pageEndX = sourcePositionProperty.getOffsetX() + sourcePositionProperty.getWidth();
float pageEndY = sourcePositionProperty.getOffsetY();
//根据比率去计算真实文档上的坐标位置
float startX = pageStartX * rateWidth ;
float startY = pageStartY * rateHeight;
float endX = pageEndX * rateWidth ;
float endY = pageEndY * rateHeight ;
//由于页面的纵坐标和pdf的纵坐标是相反的,所以真实的pdf的纵坐标在计算的时候需要再反转一下
startY = realPdfHeight - startY ;
endY = realPdfHeight - endY ;
//封装返回数据
realPositionProperty.setStartx(startX);
realPositionProperty.setStarty(startY);
realPositionProperty.setEndx(endX);
realPositionProperty.setEndy(endY);
realPositionProperty.setPageNum(sourcePositionProperty.getPage());
document.close();
realPositionProperties.add(realPositionProperty);
} reader.close();
} catch (Exception e) {
e.printStackTrace();
}
return realPositionProperties ;
} /**
* @Description #单独计算真实签署位置
* @Param [sourcePositionProperty]
* @return com.resrun.modules.sign.service.tool.pojo.RealPositionProperty
**/
public RealPositionProperty calculatePosition(SourcePositionProperty sourcePositionProperty, byte[] pdfFileByte){
RealPositionProperty realPositionProperty = new RealPositionProperty();
PdfReader reader = null ;
Document document = null ;
try {
//将pdf文件读入PdfReader工具类
reader = new PdfReader(pdfFileByte);
document = new Document(reader.getPageSize(sourcePositionProperty.getPage()));
//获取真实pdf文件指定页的真实文档宽高
float realPdfHeight = document.getPageSize().getHeight();
float realPdfWidth = document.getPageSize().getWidth();
//获取页面上文档的宽高
float sourcePageWidth = sourcePositionProperty.getPageWidth();
float sourcePageHeight = sourcePositionProperty.getPageHeight();
//计算真实文档的宽高和页面文档的宽高的比率
float rateHeight = realPdfHeight / sourcePageHeight;
float rateWidth = realPdfWidth / sourcePageWidth;
//计算页面上的横纵坐标,由于页面上给出的是左上角的坐标,所以需要再转换计算一下
//左下角
float pageStartX = sourcePositionProperty.getOffsetX();
float pageStartY = sourcePositionProperty.getOffsetY() + sourcePositionProperty.getHeight() ;
//右上角
float pageEndX = sourcePositionProperty.getOffsetX() + sourcePositionProperty.getWidth();
float pageEndY = sourcePositionProperty.getOffsetY();
//根据比率去计算真实文档上的坐标位置
float startX = pageStartX * rateWidth ;
float startY = pageStartY * rateHeight;
float endX = pageEndX * rateWidth ;
float endY = pageEndY * rateHeight ;
//由于页面的纵坐标和pdf的纵坐标是相反的,所以真实的pdf的纵坐标在计算的时候需要再反转一下
startY = realPdfHeight - startY ;
endY = realPdfHeight - endY ;
//封装返回数据
realPositionProperty.setStartx(startX);
realPositionProperty.setStarty(startY);
realPositionProperty.setEndx(endX);
realPositionProperty.setEndy(endY);
realPositionProperty.setPageNum(sourcePositionProperty.getPage()); document.close();
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
return realPositionProperty ;
} public RealPositionProperty calculatePosition(SourcePositionProperty sourcePositionProperty){
RealPositionProperty realPositionProperty = new RealPositionProperty();
//获取真实pdf文件指定页的真实文档宽高
float realPdfHeight = sourcePositionProperty.getRealHeight();
float realPdfWidth = sourcePositionProperty.getRealWidth();
//获取页面上文档的宽高
float sourcePageWidth = sourcePositionProperty.getPageWidth();
float sourcePageHeight = sourcePositionProperty.getPageHeight();
//计算真实文档的宽高和页面文档的宽高的比率
float rateHeight = realPdfHeight / sourcePageHeight;
float rateWidth = realPdfWidth / sourcePageWidth;
//计算页面上的横纵坐标,由于页面上给出的是左上角的坐标,所以需要再转换计算一下
//左下角
float pageStartX = sourcePositionProperty.getOffsetX();
float pageStartY = sourcePositionProperty.getOffsetY() + sourcePositionProperty.getHeight() ;
//右上角
float pageEndX = sourcePositionProperty.getOffsetX() + sourcePositionProperty.getWidth();
float pageEndY = sourcePositionProperty.getOffsetY();
//根据比率去计算真实文档上的坐标位置
float startX = pageStartX * rateWidth ;
float startY = pageStartY * rateHeight;
float endX = pageEndX * rateWidth ;
float endY = pageEndY * rateHeight ;
//由于页面的纵坐标和pdf的纵坐标是相反的,所以真实的pdf的纵坐标在计算的时候需要再反转一下
startY = realPdfHeight - startY ;
endY = realPdfHeight - endY ;
//封装返回数据
realPositionProperty.setStartx(startX);
realPositionProperty.setStarty(startY);
realPositionProperty.setEndx(endX);
realPositionProperty.setEndy(endY);
realPositionProperty.setPageNum(sourcePositionProperty.getPage());
return realPositionProperty ;
} /**
* 通过查询关键字来获得签名位置信息
* @param pdfFile 签署源文件
* @param keyWords 关键字
* @param width 签章宽度
* @param height 签章高度
* @return 签署位置信息
* @throws IOException
*/
public RealPositionProperty getPositionByKeyWords(byte[] pdfFile, String keyWords, int width, int height) {
RealPositionProperty positionProperty = new RealPositionProperty();
//调用通过关键字查询位置的方法
float[] result = new float[0];
try {
result = new SelectKeywords().selectKeyword(pdfFile,keyWords);
} catch (Exception e) {
e.printStackTrace();
}
if(result !=null){ positionProperty.setStartx(result[0]);
positionProperty.setStarty(result[1]+height/4);
positionProperty.setPageNum((int)result[2]);
positionProperty.setEndx(result[0]+width/2);
positionProperty.setEndy(result[1]-height/4); }
return positionProperty;
} /**
* 通过查询关键字来获得签名位置信息<br/>
*
* 同一个关键字出现在多处会一次性全部找出
*
* @param pdfFile 签署源文件
* @param keyWords 关键字
* @param width 签章宽度
* @param height 签章高度
* @return 签署位置信息
* @throws IOException
*/
public List<RealPositionProperty> getAllPositionByKeyWords(byte[] pdfFile,String keyWords,int width,int height) {
List<RealPositionProperty> positions = new ArrayList<RealPositionProperty>();
//调用通过关键字查询位置的方法
List<float[]> results = null;
try {
results = new SelectKeywords().selectAllKeyword(pdfFile, keyWords);
} catch (Exception e) {
e.printStackTrace();
}
if(results !=null && results.size()>0){
for (float[] result : results) {
RealPositionProperty positionProperty = new RealPositionProperty(); positionProperty.setStartx(result[0]);
positionProperty.setStarty(result[1]+height/4);
positionProperty.setPageNum((int)result[2]);
positionProperty.setEndx(result[0]+width/2);
positionProperty.setEndy(result[1]-height/4); positions.add(positionProperty);
}
}
return positions;
} } ​2、计算后的签名位置信息类;
````plaintext

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor; import java.io.Serializable; /**
* @Description: 经过计算后的文件签署位置属性类
* @Package: com.resrun.service.pojo
* @ClassName: PositionProperty
* @copyright 北京资源律动科技有限公司
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class RealPositionProperty implements Serializable { private static final long serialVersionUID = 8586984409612483553L; /** 签章左下角x坐标 */
private float startx; /** 签章左下角y坐标*/
private float starty; /** 签章右上角x坐标*/
private float endx; /** 签章右上角x坐标*/
private float endy; private int pageNum; // 填写值,填写专用
private String value ;
//对齐方式
private String align ;
//字体
private String fontFamily ;
//文字大小
private Integer fontSize ;
} ​3、关键字位置计算类;
````plaintext

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition; import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List; /**
* @Description: 关键字计算位置
* @Package: com.resrun.service.pojo
* @ClassName: SelectKeywords
* @copyright 北京资源律动科技有限公司
*/
public class SelectKeywords extends PDFTextStripper { private static ThreadLocal<KeyWorkPair> keyWorkPair = new ThreadLocal<KeyWorkPair>(); private Log logger = LogFactory.getLog(SelectKeywords.class); public SelectKeywords() throws IOException {
super.setSortByPosition(true);
} // public static void main(String[] args) throws Exception {
// //selectKeyword
// File file = new File("e:/test/948ad938bab14f4e8a2d843f6dd81d57.pdf");
// float [] resus = new SelectKeywords().selectKeyword(IOUtils.toByteArray(file), "948ad938bab14f4e8a2d843f6dd81d57");//66 571
// System.out.println(resus[0]+"--"+resus[1]+"---"+resus[2]);
// }
/**
* 查出PDF里所有的关键字
* @param pdfFile
* @param KEY_WORD
* @return
*/
public List<float[]> selectAllKeyword(byte [] pdfFile, String KEY_WORD) {
keyWorkPair.set(new KeyWorkPair(KEY_WORD.split(",")));
ByteArrayInputStream in = null;
PDDocument document = null;
try {
in = new ByteArrayInputStream(pdfFile);
document = PDDocument.load(in);//加载pdf文件
this.getText(document);
List<float[]> allResu = getAllResult();
return allResu;
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if(in!=null) in.close();
if(document!=null) document.close();
} catch (IOException e) {
}
}
return null;
}
private List<float[]> getAllResult(){
KeyWorkPair pair = keyWorkPair.get();
if(pair!=null && pair.getResu()!=null){
keyWorkPair.set(null);
return pair.getAllResu();
}else{
keyWorkPair.set(null);
return null;
}
}
/**
* 查出PDF里最后一个关键字
* @param pdfFile
* @param KEY_WORD
* @return
*/
public float [] selectKeyword(byte [] pdfFile,String KEY_WORD) {
keyWorkPair.set(new KeyWorkPair(KEY_WORD.split(",")));
ByteArrayInputStream in = null;
PDDocument document = null;
try {
in = new ByteArrayInputStream(pdfFile);
document = PDDocument.load(in);//加载pdf文件
this.getText(document);
float[] resu = getResult();
return resu;
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if(in!=null) in.close();
if(document!=null) document.close();
} catch (IOException e) {
}
}
return null;
} private float[] getResult(){
KeyWorkPair pair = keyWorkPair.get();
if(pair!=null && pair.getResu()!=null){
keyWorkPair.set(null);
return pair.getResu();
}else{
keyWorkPair.set(null);
return null;
}
} @Override
protected void writeString(String string, List<TextPosition> textPositions) throws IOException {
for (TextPosition text : textPositions) {
String tChar = text.toString();
char c = tChar.charAt(0);
String REGEX = "[,.\\[\\](:;!?)/]";
lineMatch = matchCharLine(text);
if ((!tChar.matches(REGEX)) && (!Character.isWhitespace(c))) {
if ((!is1stChar) && (lineMatch == true)) {
appendChar(tChar);
} else if (is1stChar == true) {
setWordCoord(text, tChar);
}
} else {
endWord();
}
}
endWord();
}
protected void appendChar(String tChar) {
tWord.append(tChar);
is1stChar = false;
} /**
*
* %拼接字符串%。
*/
protected void setWordCoord(TextPosition text, String tChar) {
itext = text;
tWord.append("(").append(pageNo).append(")[").append(roundVal(Float.valueOf(text.getXDirAdj()))).append(" : ")
.append(roundVal(Float.valueOf(text.getYDirAdj()))).append("] ").append(tChar);
is1stChar = false;
} protected boolean matchCharLine(TextPosition text) { Double yVal = roundVal(Float.valueOf(text.getYDirAdj()));
if (yVal.doubleValue() == lastYVal) {
return true;
}
lastYVal = yVal.doubleValue();
endWord();
return false;
} protected Double roundVal(Float yVal) {
DecimalFormat rounded = new DecimalFormat("0.0'0'");
Double yValDub = new Double(rounded.format(yVal));
return yValDub;
} protected void endWord() {
// String newWord = tWord.toString().replaceAll("[^\\x00-\\x7F]",
// "");//为了检索速度 使用正则去掉中文
String newWord = tWord.toString();// 去掉正则 可以检索中文
KeyWorkPair pair = keyWorkPair.get(); try {
String[] seekA = pair.getSeekA();
float[] resu = new float[3];
String sWord = newWord.substring(newWord.lastIndexOf(' ') + 1);
if (!"".equals(sWord)) {
if (sWord.contains(seekA[0])) {
resu[2] = getCurrentPageNo();// (595,842)
resu[0] = (float) (roundVal(Float.valueOf(itext.getXDirAdj())) + 0.0F);
resu[1] = 842.0F - (float) (roundVal(Float.valueOf(itext.getYDirAdj())) + 0.0F);
logger.info("PDF关键字信息:[页数:" + resu[2] + "][X:" + resu[0] + "][Y:" + resu[1] + "]");
pair.setResu(resu);
pair.addResuList(resu);//把每一次找出的关键字放在一个集合里
keyWorkPair.set(pair);
}
}
} catch (Exception e) {
e.printStackTrace();
keyWorkPair.set(null);
throw new RuntimeException();
}
tWord.delete(0, tWord.length());
is1stChar = true;
} private StringBuilder tWord = new StringBuilder();
private boolean is1stChar = true;
private boolean lineMatch;
private int pageNo = 0;
private double lastYVal; private TextPosition itext; /**
* 关键字和返回的位置信息类
*/
class KeyWorkPair { public KeyWorkPair(String[] seekA) {
super();
this.seekA = seekA;
}
public KeyWorkPair(String[] seekA, float[] resu) {
super();
this.seekA = seekA;
this.resu = resu;
}
public KeyWorkPair() {
super();
}
public String[] getSeekA() {
return seekA;
}
public void setSeekA(String[] seekA) {
this.seekA = seekA;
}
public float[] getResu() {
return resu;
}
public void setResu(float[] resu) {
this.resu = resu;
} public void addResuList(float[] resu) {
resuAll.add(resu);
}
public List<float[]> getAllResu() {
return resuAll;
} private String[] seekA;
private float[] resu;
//所有的位置
private List<float[]> resuAll = new ArrayList<>();
}
} ​4、原始文件签署位置信息类;
````plaintext

import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor; import java.io.Serializable; /**
* @Description: 原始文件签署位置属性
* @Package: com.resrun.service.pojo
* @ClassName: SourcePositionProperty
* @copyright 北京资源律动科技有限公司
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SourcePositionProperty implements Serializable { private static final long serialVersionUID = 725976764583634367L; @ApiModelProperty("控件X坐标(左上角)")
private Float offsetX ;
@ApiModelProperty("控件Y坐标(左上角)")
private Float offsetY ;
@ApiModelProperty("控件宽度")
private Float width ;
@ApiModelProperty("控件高度")
private Float height ;
@ApiModelProperty("当前文件页面宽度")
private Float pageWidth ;
@ApiModelProperty("当前文件页面高度")
private Float pageHeight ;
@ApiModelProperty("控件所属页码")
private Integer page ; @ApiModelProperty("当前文件页面宽度")
private Float realWidth ;
@ApiModelProperty("当前文件页面高度")
private Float realHeight ; }

电子签章Java后端与前端交互签名位置计算的更多相关文章

  1. java 后端与前端Date类型与String类型互相转换(使用注解)

    后端返回的类型中,直接定义Date类型,加上此注解,直接将Date类型转成自定义的格式给前端 class TestDateOutput{ @JsonFormat(pattern = "yyy ...

  2. java后端接收前端传来的复杂对象(包含List对象集合)

    最近在和安卓对接口的时候发现往java后端传数据的时候,后台对象无法接收. 说明:后台对象为 类似结构 ObjectA{ private String  a; private String b; pr ...

  3. openssl+前端jsrsa签名+后端nodejs验签

    内容如标题所示,总体分为三个部分: 一.win10下安装openssl,然后通过openssl工具生成RSA的公钥和私钥 (1)win10下安装openssl需要的工具有:VS2013,Perl,na ...

  4. java后端无法接收到前端传递的json对象

    java后端无法接收到前端传递的json对象 一·可能是因为未使用@RequestBody 在Controller层中,要么使用@RestController要么使用@Controller+@@Req ...

  5. 招聘前端、Java后端开发、测试、Mysql DBA

    公司介绍: http://www.lagou.com/gongsi/43095.html http://www.yamichu.com 简历发到: zhuye@yamichu.com 招聘职位: JA ...

  6. JavaScript前端和Java后端的AES加密和解密

    在实际开发项目中,有些数据在前后端的传输过程中需要进行加密,那就需要保证前端和后端的加解密需要统一.这里给大家简单演示AES在JavaScript前端和Java后端是如何实现加密和解密的. 直接上代码 ...

  7. 前端与后端的数据交互(jquery ajax+python flask)

    前端与后端的数据交互,最常用的就是GET.POST,比较常用的用法是:提交表单数据到后端,后端返回json 前端的数据发送与接收 1)提交表单数据 2)提交JSON数据 后端的数据接收与响应 1)接收 ...

  8. vue前端+java后端 vue + vuex + koa2开发环境搭建及示例开发

    vue + vuex + koa2开发环境搭建及示例开发 https://segmentfault.com/a/1190000012918518 vue前端+java后端 https://blog.c ...

  9. Java技巧——将前端的对象数组通过Json字符串传到后端并转换为对象集合

    Java技巧——将前端的对象数组通过Json字符串传到后端并转换为对象集合 摘要:本文主要记录了如何将将前端的对象数组通过Json字符串传到后端,并在后端将Json字符串转换为对象集合. 前端代码 前 ...

  10. JavaScript前端和Java后端的AES加密和解密(转)

    在实际开发项目中,有些数据在前后端的传输过程中需要进行加密,那就需要保证前端和后端的加解密需要统一.这里给大家简单演示AES在JavaScript前端和Java后端是如何实现加密和解密的. java端 ...

随机推荐

  1. 《最新出炉》系列初窥篇-Python+Playwright自动化测试-26-处理单选和多选按钮-下篇

    1.简介 今天这一篇宏哥主要是讲解一下,如何使用Playwright来遍历单选和多选按钮.大致两部分内容:一部分是宏哥在本地弄的一个小demo,另一部分,宏哥是利用JQueryUI网站里的单选和多选按 ...

  2. 虚拟机centos7上安装docker+jenkins

    虚拟机centos7上安装docker+jenkins 学习某册子的CICD时,安装了docker和jenkins,记录的安装过程和中间碰到的问题. 使用的虚拟机为Parallels Desktop, ...

  3. 一键整合,万用万灵,Python3.10项目嵌入式一键整合包的制作(Embed)

    我们知道Python是一门解释型语言,项目运行时需要依赖Python解释器,并且有时候需要安装项目中对应的三方依赖库.对于专业的Python开发者来说,可以直接通过pip命令进行安装即可.但是如果是分 ...

  4. LabVIEW基于机器视觉的实验室设备管理系统(5)

    目录 行动计划 设备借用 判断设备ID是否正确.设备是否在库 判断是否为已注册用户.电话是否正确 借出设备 设备归还 信息查询 ​判断ID是否正确.选择设备状态 效果演示 今天这一期,我们就来完成实验 ...

  5. Python 中的单下划线和双下划线

    哈喽大家好,我是咸鱼 当我们在学习 Python 的时候,可能会经常遇到单下划线 _ 和双下划线 __ 这两种命名方式 单下划线 _ 和双下划线 __ 不仅仅是只是一种简单的命名习惯,它们在 Pyth ...

  6. [ABC261F] Sorting Color Balls

    Problem Statement There are $N$ balls arranged from left to right. The color of the $i$-th ball from ...

  7. Redis 学习笔记1:数据类型

    Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及 zset(sorted set:有序集合). 一.Redis 数据类型-STRING 1.常用 ...

  8. 安卓之各种Adapter优劣分析

    文章摘要 在 Android 开发中,适配器(Adapter)是一种非常重要的设计模式,它用于将数据与视图组件进行绑定.适配器可以帮助我们在不同的视图组件(如 ListView.GridView.Re ...

  9. TCP/IP协议---三次握手和四次挥手

    TCP首部的数据格式 其中, 源端口号和目的端口号各占16位,端口范围1~65535.1024以下为知名端口,1024~65535是供用户使用.源端口,目的端口,源ip,目的ip这四个值唯一确定一个T ...

  10. Python 中如何编写类型提示

    哈喽大家好,我是咸鱼 我们知道 Python 是一门具有动态特性的语言,在编写 Python 代码的时候不需要显式地指定变量的类型 这样做虽然方便,但是降低了代码的可阅读性,在后期 review 代码 ...