前言

需要实现PDF合同自动签字的功能,实现这个功能关键点在与怎么找到签字的位置,我想到的办法是在签字位置放一个关键字,然后程序找到这个关键字的位置,在上面放上用户的签字.

代码

首先先导入对应的包

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.13</version>
</dependency>

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-asian</artifactId>
    <version>5.2.0</version>
</dependency>

具体实现

/**
 *
 * PDF处理类
 *
 * @author GAS
 * @date 2021年09月13日 10:36
 */
public class PDFUtils {
    public static void Autograph() throws Exception {
        // 模板文件路径
        String templatePath = "D:\\测试.pdf";
        // 生成的文件  本地路径
        String targetPath = "D:\\target.pdf";
        //模拟用户传参
        HashMap<String, String> hashMap = new HashMap<>(5);
        hashMap.put("Aname", "D:\\A.jpg");
        hashMap.put("Bname", "D:\\B.jpg");
        hashMap.put("甲签章", "D:\\C.jpg");
        hashMap.put("乙签章", "D:\\D.jpg");

        // 读取模板文件
        // InputStream input = new FileInputStream(new File(templatePath));
        PdfReader reader = new PdfReader(templatePath);

        //新建一个PDF解析对象
        PdfReaderContentParser parser = new PdfReaderContentParser(reader);
        PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(targetPath));
        //循环PDF页
        for (int i = 1; i <= reader.getNumberOfPages(); i++) {
            //循环关键字
            for (String key : hashMap.keySet()) {
                //新建一个ImageRenderListener对象,该对象实现了RenderListener接口,作为处理PDF的主要类
                TestRenderListener listener = new TestRenderListener(i, key);
                //解析PDF,并处理里面的文字
                parser.processContent(i, listener);
                List<pdfHelpEntity> result = listener.result;
                for (pdfHelpEntity entity : result) {
                    // 获取操作的页面---
                    PdfContentByte under = stamper.getOverContent(entity.getPageNumber());
                    // 读图片
                    Image image = Image.getInstance(hashMap.get(key));
                    // url远程文件旋转需要使用setRotationDegrees   本地使用setRotation
                    image.setRotationDegrees(90f);
                    // 根据域的大小缩放图片
                    double width = Math.abs(entity.getStart().getMinX() - entity.getEnd().getMinX()) * entity.getSign().length();
                    double height = entity.getStart().getHeight();
//                    image.scaleToFit((float) height,(float) width);
                    image.scaleAbsolute((float) height * 2,(float) width);
                    // 添加图片
                    image.setAbsolutePosition((float) entity.getStart().getMinX(), (float) entity.getStart().getMinY() - 5);
                    under.addImage(image);
                }
            }
        }
        stamper.close();
        reader.close();
        System.exit(0);
    }
}
public class TestRenderListener implements RenderListener {

    // 关键字
    public String keyWord;
    // 关键字字符数组
    public char[] charArray;
    // PDF当前页数
    public int curPage;
    // 坐标信息集合
    public List<pdfHelpEntity> result = new ArrayList<>();
    //下标
    public int subscript = 0;
    //是否找到
    public boolean isFind = false;
    //一个实体类
    pdfHelpEntity entity;

    public TestRenderListener(int curPage, String keyWord) {
        this.curPage = curPage;
        this.keyWord = keyWord;
        this.charArray = keyWord.toCharArray();
    }

    /**
     * 文字主要处理方法
     */
    @Override
    public void renderText(TextRenderInfo textRenderInfo) {
        String text = textRenderInfo.getText();
        RectangularShape rectBase = textRenderInfo.getBaseline().getBoundingRectange();
        Rectangle2D.Float rectAscen = textRenderInfo.getAscentLine().getBoundingRectange();
        //查找是否匹配 关键字的第一个字符
        if (text.equals(keyWord.substring(0, 1))) {
            Rectangle2D.Float start = new Rectangle2D.Float();
            start.setRect((float) rectBase.getMinX()
                    , (float) rectBase.getMinY()
                    , (float) (rectAscen.getMaxX() - rectBase.getMinX())
                    , (float) (rectAscen.getMaxY() - rectBase.getMinY())
            );
            entity = new pdfHelpEntity();
            entity.setPageNumber(curPage);
            entity.setStart(start);

            //下标自增
            subscript++;
            //已找到
            isFind = true;

            return;
        }

        if (isFind) {
            //下标不能超过关键字的长度
            if (subscript < charArray.length) {
                //判断后续的字符是否符合
                if (text.equals(String.valueOf(charArray[subscript]))) {
                    //下标自增
                    subscript++;

                    if (subscript >= charArray.length){
                        Rectangle2D.Float end = new Rectangle2D.Float();

                        end.setRect((float) rectBase.getMinX()
                                , (float) rectBase.getMinY()
                                , (float) (rectAscen.getMaxX() - rectBase.getMinX())
                                , (float) (rectAscen.getMaxY() - rectBase.getMinY())
                        );
                        entity.setEnd(end);
                        entity.setSign(keyWord);
                        result.add(entity);
                        System.out.println("成功-" + keyWord + "-" + text + "-" + charArray[subscript-1] + "-" + subscript);
                        //这里是找完一组了 所以重置数据
                        isFind = false;
                        subscript = 0;
                        return;
                    }

                    return;
                }
                //如果后续字符不匹配
                else {
                    System.out.println("失败-" + keyWord + "-" + text + "-" + charArray[subscript] + "-" + subscript);
                    isFind = false;
                    subscript = 0;
                    return;
                }
            }
        }
    }

    @Override
    public void beginTextBlock() {
    }

    @Override
    public void endTextBlock() {
    }

    //step 1(图片处理方法)
    @Override
    public void renderImage(ImageRenderInfo renderInfo) {

    }
}
public class pdfHelpEntity {
    String sign;
    com.itextpdf.awt.geom.Rectangle2D.Float start;
    com.itextpdf.awt.geom.Rectangle2D.Float end;
    int pageNumber;

    public String getSign() {
        return sign;
    }

    public void setSign(String sign) {
        this.sign = sign;
    }

    public com.itextpdf.awt.geom.Rectangle2D.Float getStart() {
        return start;
    }

    public void setStart(com.itextpdf.awt.geom.Rectangle2D.Float start) {
        this.start = start;
    }

    public com.itextpdf.awt.geom.Rectangle2D.Float getEnd() {
        return end;
    }

    public void setEnd(com.itextpdf.awt.geom.Rectangle2D.Float end) {
        this.end = end;
    }

    public int getPageNumber() {
        return pageNumber;
    }

    public void setPageNumber(int pageNumber) {
        this.pageNumber = pageNumber;
    }
}

使用上面的代码运行就能实现功能了,但具体如何封装到自己的应用中,就更凭本事发挥,网上也有更多比我更好的实现方案,我这里的算是简陋版的,献丑了.

踩过的坑

网上百度的实现大多是告诉你renderText获取到一行之后,直接匹配关键字就行了,然而renderText获取到的内容不是一行,而是一个字,我翻阅了很多资料,发现renderText每次读取到的都是chunk(文本块)里的内容,属实乱复制粘贴,罪大恶极啊.

参考

文章1: https://cloud.tencent.com/developer/article/1502411

文章2: http://www.manongjc.com/article/54527.html

文章3: https://blog.csdn.net/qq_40707682/article/details/108347146

文章4: https://www.codeleading.com/article/61094200906/

官网: https://itextpdf.com/en/resources/api-documentation/itext-5-java