电子签章Java后端与前端交互签名位置计算

电子签章过程中存在着在网页上对签署文件进行预览、指定签署位置、文件签署等操作,由于图片在浏览器上的兼容性和友好性优于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
     **/
    public List calculatePositions(List sourcePositionProperties, byte[] pdfFileByte){
        List 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;
    }

    /**
     * 通过查询关键字来获得签名位置信息
* * 同一个关键字出现在多处会一次性全部找出 * * @param pdfFile 签署源文件 * @param keyWords 关键字 * @param width 签章宽度 * @param height 签章高度 * @return 签署位置信息 * @throws IOException */ public List getAllPositionByKeyWords(byte[] pdfFile,String keyWords,int width,int height) { List positions = new ArrayList(); //调用通过关键字查询位置的方法 List 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、计算后的签名位置信息类;

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、关键字位置计算类;

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 = new ThreadLocal();

    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 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 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 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 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 getAllResu() {
            return resuAll;
        }

        private String[] seekA;
        private float[] resu;
        //所有的位置
        private List resuAll = new ArrayList<>();
    }
}

4、原始文件签署位置信息类;

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,电子签章位置计算,电子签名,电子合同)