使用的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, '\0', 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;
}

itext实现合同尾部签章部分自动添加,定位签名的更多相关文章

  1. 前端自动化工具gulp自动添加版本号

    之前,我介绍了学习安装并配置前端自动化工具Gulp,觉得gulp确实比grunt的配置简单很多,于是我决定再深入学习一下gulp,就去网上查了资料,发现gulp还可以自动添加版本号,这个功能就为我平时 ...

  2. Gulp自动添加版本号(转载)

    本文转载自: gulp自动添加版本号

  3. VS 自动添加注释

    现在大多数公司都规定程序员在程序文件的头部加上版权信息,这样每个人写的文件都可以区分开来,如果某个文件出现问题就可以快速的找到文件的创建人,用最短的时间来解决问题,常常是以下格式: //======= ...

  4. Zabbix网络自动发现规则和自动添加hosts及link模板

    Version: zabbix 3.0 一.配置网络发现规则 Device uniqueness criteria:选择主机名作为唯一标识(Configuation Hosts中显示的NAME) 二. ...

  5. 如何给wordpress外部链接自动添加nofollow

    wordpress多作者博客可以丰富网站的内容,但同时也会产生一些无关的链接,例如有些投机的人会考虑在文章中随意添加外部链接,如果你不想给这些外部链接传递权重,你需要给这些外部链接加上 rel=&qu ...

  6. EditText中输入手机号码时,自动添加空格

    输入手机号码时,自动添加空格,更容易辨别 public class PhoneWatcher implements TextWatcher { private EditText _text; publ ...

  7. Excel VBA自动添加证书

    ---恢复内容开始--- 在说这个话题之前,我先解释一下为什么要加数字证书签名,它有什么作用,后面再解释如何添加.首先解释下证书添加的位置,如下图所示: 1.单击左上角的Office 按钮,选择右下角 ...

  8. 为js和css文件自动添加版本号

    web应用必然要面对缓存问题,无论前台后台都会涉足缓存.特别是对于前端而言,缓存利用的是否得当直接关系到应用的性能. 通常情况下,我们会倾向于使用缓存,因为缓存一方面可以减少网络开销,一方面可以减轻服 ...

  9. 向linux内核版本号添加字符/为何有时会自动添加“+”号

    转载:http://blog.csdn.net/adaptiver/article/details/7225980 1.   引子 编译2.6.35.7 kernel版本的时候发现,“2.6.35.7 ...

随机推荐

  1. win10版office365激活序列码

    win10版office365激活序列码(在别的地方找到一个) : NKGG6-WBPCC-HXWMY-6DQGJ-CPQVG 1.在线安装Office2016预览版后它是不会自动激活的,需在Offi ...

  2. AS3中的单件(Singleton)模式

    单件(singleton)模式在c#中是最容易实现的模式,其主要用意就在于限制使用者用new来创建多个实例.但在as3中,构造函数必须是public的(语法本身要求的),而且也不能在构造函数中抛出异常 ...

  3. 整数中1出现的次数(从1到n整数中1出现的次数)(python)

    题目描述 求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1.10.11.12.13因此共出现6次,但是对于后面问题他就没辙了. ...

  4. 阅读【现代网络技术 SDN/NFV/QOE 物联网和云计算】 第一章

    本人打算阅读这本书来了解物联网和云计算的基础架构和设计原理.特作笔记如下: 作者: William  Stallings 本书解决的主要问题: 由单一厂商例如IBM向企业或者个人提供IT产品和服务,包 ...

  5. 异常解决 Unable to write generated Java files for schemas: null

    错误是在使用cxf调用其他系统接口时报出的.而且问题很诡异,只有服务器调用时才报错,本地直接写main方法直接调用却正常的.以下是异常的堆栈: ERROR c.k.p.webservice.WebSe ...

  6. VirtualBox 克隆后 IP 地址相同(DHCP 分配),如何变更MAC以获取不同的IP?

    由于需要做实验需要两个相同环境的虚拟机,在linux下使用virtualbox最小化安装centos6.0,并克隆了一个相同的,联网模式为桥接,修改配置文件之后重启网络发现二者的网络信息相同,所获取的 ...

  7. 模板学习实践二 pointer

    c++ template学习记录 使用模板将实际类型的指针进行封装 当变量退出作用域 自动delete // 1111.cpp : 定义控制台应用程序的入口点. // #include "s ...

  8. Django Model模型的实战操作笔记

    Model模型的实战操作笔记 1. 创建数据库和表 进入MySQL数据库创建数据库:mytest 进入数据库创建数据表:mytest_users CREATE TABLE `mytest_users` ...

  9. IOS xib和代码自定义UIView

    https://www.jianshu.com/p/1bcc29653085 总结的比较好 iOS开发中,我们常常将一块View封装起来,以便于统一管理内部的子控件. 下面就来说说自定义View的封装 ...

  10. 2019.02.15 codechef Favourite Numbers(二分+数位dp+ac自动机)

    传送门 题意: 给444个整数L,R,K,nL,R,K,nL,R,K,n,和nnn个数字串,L,R,K,数字串大小≤1e18,n≤65L,R,K,数字串大小\le1e18,n\le65L,R,K,数字 ...