zoukankan      html  css  js  c++  java
  • itext实现合同尾部签章部分自动添加,定位签名

    使用的pom

    <!-- pdf处理 start-->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>itext-asian</artifactId>
        <version>5.2.0</version>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>itextpdf</artifactId>
        <version>5.5.4</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.itextpdf/kernel -->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>kernel</artifactId>
        <version>7.1.2</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.itextpdf/layout -->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>layout</artifactId>
        <version>7.1.2</version>
    </dependency>
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcprov-jdk15on</artifactId>
        <version>1.49</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.itextpdf/forms -->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>forms</artifactId>
        <version>7.1.4</version>
    </dependency>
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcpkix-jdk15on</artifactId>
        <version>1.49</version>
    </dependency>
    <!-- pdf处理 end-->
    

    效果图

    原理

    1. 通过itext中List添加固定文本
    2. 添加指定标记比如★☆用以之后替换成其他文本或图片
    3. 可以不用标记,反正就是算好位置
    4. 至于签章这块位置的选定,根据文档最后一行位置判定,我的判定方法就是文档最后一页最后一行离尾部距离小于一定值,签章的整块内容移到新的一页

    过程

    1. 核心利用了com.itextpdf.text.pdf.parser.RenderListener这个类,它会遍历这个文档的内容
    2. 写个继承这个类的方法,实现方法如下
    @Override
    public void renderText(TextRenderInfo textInfo) {
        Float bound = textInfo.getBaseline().getBoundingRectange();
        String text = textInfo.getText();
        float y = bound.y - this.fixHeight;
        for (String keyWord : findText) {
            if (null != text && text.contains(keyWord)) {
                ReplaceRegion region = new ReplaceRegion(keyWord);
                region.setH(bound.height == 0 ? defaultH : bound.height);
                if ((text.contains("☆") && keyWord.equals("☆"))) {
                    region.setW(20f);
                    int i = text.indexOf("☆");
                    region.setX(bound.x + 13 * i);
                } else if (text.contains("★") && keyWord.equals("★")) {
                    region.setW(15f);
                    region.setX(bound.x);
                } else if ((text.contains("△") && keyWord.equals("△"))) {
                    region.setW(15f);
                    int i = text.indexOf("△");
                    region.setX(bound.x + 11 * i);
                } else if ((text.contains("▲") && keyWord.equals("▲"))) {
                    region.setW(15f);
                    region.setX(bound.width + 15 * 4.5f);
                } else {
                    region.setW(bound.width);
                    region.setX(bound.x);
                }
                region.setY(y);
                result.put(keyWord, region);
            }
        }
        //判断最后一行是否小于某个值
        if (y < heightSign) {
            signY.put("endY", 0f);
        } else {
            signY.put("endY", y);
        }
    }
    
    1. 这里我进行了很多微调,此方法肯定存在很多改进的地方,由于时间紧急,我对itext的研究也不深,勉强实现需求
    ...
    PdfReader reader = new PdfReader(pdfBytes);
    //内容解析器
    PdfReaderContentParser parser = new PdfReaderContentParser(reader);
    for (int i = 1; i <= numberOfPages; i++) {
        parser.processContent(i, listener);
        Map<String, ReplaceRegion> res = listener.getResult(i);
        if (res.size() > 0) {
            pdfReplacer.setPageNum(i);
            result = res;
        }
    }
    ...
    
    1. 通过上面的步骤找到最后一行位置,找到指定特殊字符的位置
    2. 添加尾部签章部分通过
    PdfReader reader = new PdfReader(basePath + "_temp2.pdf");
    PdfWriter writer = new PdfWriter(basePath + "_temp3.pdf");
    PdfDocument pdf = new PdfDocument(reader, writer);
    int numberOfPages = pdf.getNumberOfPages();
    float height = pdf.getDefaultPageSize().getHeight();
    if (endY == 0f) {
        numberOfPages++;
        endY = height - 140f;
        pdf.addNewPage();
    }else{
        endY = endY - 60f;
    }
    
    Document doc = new Document(pdf);
    PdfFont font = PdfFontFactory.createFont("C:/Windows/Fonts/simsun.ttc,1", PdfEncodings.IDENTITY_H,true);
    com.itextpdf.layout.element.List list = new com.itextpdf.layout.element.List().setPageNumber(numberOfPages).setFixedPosition(80,endY,400f).setListSymbol("").setSymbolIndent(22f).setFont(font).setFontSize(14);
    list.add(new ListItem("甲方法定代表人:☆            乙方法定代表人: △"))
        .add(new ListItem("联系电话:                    联系电话:"))
        .add(new ListItem("身份证号码:                  身份证号码:"))
        .add(new ListItem("★                            ▲"));
    doc.add(list);
    
    pdf.close();
    
    1. 整个过程会出现很多中间临时文件,所以说还是可以有很多改进的地方。

    2. 替换方法,用来替换日期,和覆盖特殊符号

    textReplacer = new PdfReplacer(basePath + "_temp3.pdf");
    textReplacer.replaceText(replaceStr, destStr.toString());
    replaceRegion = textReplacer.toPdf(basePath + "_temp4.pdf");
    PdfReplacer textReplacer2 = new PdfReplacer(basePath + "_temp4.pdf");
    String dateRecord = sysconfig.getProperties().getProperty(dateSign);
    String text = DateUtil.format2str("yyyy 年  MM 月  dd 日");
    textReplacer2.setFont(14);
    String dateFontPath = sysconfig.getProperties().getProperty("date_font_path");
    if (dateFontPath.lastIndexOf("ttf") > 0) {
        textReplacer2.setFont(new com.itextpdf.text.Font(BaseFont.createFont(dateFontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED)));
    } else {
        dateFontPath = dateFontPath + ",1";
        textReplacer2.setFont(new com.itextpdf.text.Font(BaseFont.createFont(dateFontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED)));
    }
    log.info("字体路径{}", dateFontPath);
    textReplacer2.replaceText(dateRecord, text);
    
    1. 签章方法
    public static byte[] sign(String password, InputStream inputStream, String signPdfSrc, String signImage,
                              float x, float y,int page) {
        File signPdfSrcFile = new File(signPdfSrc);
        PdfReader reader = null;
        ByteArrayOutputStream signPDFData = null;
        PdfStamper stp = null;
        try {
            BouncyCastleProvider provider = new BouncyCastleProvider();
            Security.addProvider(provider);
            KeyStore ks = KeyStore.getInstance("PKCS12", new BouncyCastleProvider());
            // 私钥密码 为Pkcs生成证书是的私钥密码 123456
            ks.load(inputStream, password.toCharArray());
            String alias = (String) ks.aliases().nextElement();
            PrivateKey key = (PrivateKey) ks.getKey(alias, password.toCharArray());
            Certificate[] chain = ks.getCertificateChain(alias);
            reader = new PdfReader(signPdfSrc);
            signPDFData = new ByteArrayOutputStream();
            // 临时pdf文件
            File temp = new File(signPdfSrcFile.getParent(), System.currentTimeMillis() + ".pdf");
            stp = PdfStamper.createSignature(reader, signPDFData, '', temp, true);
            stp.setFullCompression();
            PdfSignatureAppearance sap = stp.getSignatureAppearance();
            sap.setReason("数字签名,不可改变");
            // 使用png格式透明图片
            Image image = Image.getInstance(signImage);
            sap.setImageScale(0);
            sap.setSignatureGraphic(image);
            sap.setRenderingMode(RenderingMode.GRAPHIC);
            int size = 120;
            // 是对应x轴和y轴坐标
            float lly = y - 50;
            sap.setVisibleSignature(new Rectangle(x, lly, x + size, lly+size), page,
                    UUID.randomUUID().toString().replaceAll("-", ""));
            stp.getWriter().setCompressionLevel(5);
            ExternalDigest digest = new BouncyCastleDigest();
            ExternalSignature signature = new PrivateKeySignature(key, DigestAlgorithms.SHA512, provider.getName());
            MakeSignature.signDetached(sap, digest, signature, chain, null, null, null, 0, CryptoStandard.CADES);
            stp.close();
            reader.close();
            return signPDFData.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
    
            if (signPDFData != null) {
                try {
                    signPDFData.close();
                } catch (IOException e) {
                }
            }
    
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                }
            }
        }
        return null;
    }
    
  • 相关阅读:
    Android复习(五)设备兼容—>多apk支持
    Android复习(五)设备兼容—>支持刘海屏
    KVC给只读属性进行赋值..
    pods 终端安装 第三方框架的一些命令
    使用SnakeKit怎么设置动画效果 以及 UIView的弹簧动画效果
    Cocoa、Foundation、UIKit、Objective-c、XCode、Interface Builder的概念
    仿写SDWebImage中的一个网络加载图片的框架
    TCP/IP 短链接 长链接 scoket
    init 和 initWithFrame 区别
    JSON XML 数据解析
  • 原文地址:https://www.cnblogs.com/sky-chen/p/9996121.html
Copyright © 2011-2022 走看看