前言
需要实现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