java pdf数字签证(图片),根据关键字定位签证位置

发布时间 2023-11-20 11:27:56作者: 名字都有相同的

网上找了很多,最后还是结合了一下才能用。

 

用到的主要jar包:itextpdf-5.5.13.jar,bcprov-jdk15on-1.49.jar,bcpkix-jdk15on-1.50.jar

main测试:


import java.io.FileInputStream;
import java.io.FileOutputStream;

import static weaver.interfaces.workflow.action.hrm.Test.PdfUtils.PdfHelper.getKeyWordsByPath;

public class PdfTest {

public static void main(String[] args) throws Exception {
String KEYSTORE="签证";
char[] PASSWORD = "签证密码".toCharArray();//keystory密码
String SRC="C:\\Users\\65390\\Desktop\\HQTS-H23097034IN1-Certificate(Official).pdf" ;//原始pdf //H23111879IN
String DEST="C:\\Users\\65390\\Desktop\\HQTS-H23097034IN4-Certificate(Official).pdf" ;//第二个测试的pdf
String DEST2="C:\\Users\\65390\\Desktop\\H23076631IN.pdf" ;//签名完成的pdf
String chapterPath="C:\\Users\\65390\\Desktop\\szqz.png";//签章图片
String reason="数据不可更改";
String location="看你自己";

MatchItem matchItem = getKeyWordsByPath(DEST, "Signed:");

System.out.println("x:" + matchItem.getX() + "y:" + matchItem.getY() + "页数:" + matchItem.getPageNum());



PdfUtils.sign( new FileInputStream(DEST), new FileOutputStream(DEST2),
new FileInputStream(KEYSTORE), PASSWORD, reason, location, chapterPath,matchItem.getX(), matchItem.getY(),matchItem.getPageNum());
}

}

查找关键字:

import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.parser.PdfReaderContentParser;

import java.io.IOException;
import java.util.List;

public class PdfHelper {

/**
* @Description 用于供外部类调用获取关键字所在PDF文件坐标
* @param filepath
* @param keyWords
* @return float[]
*/
public static MatchItem getKeyWordsByPath(String filepath, String keyWords) {
MatchItem matchItem = null;
try{
PdfReader pdfReader = new PdfReader(filepath);
matchItem = getKeyWords(pdfReader, keyWords);
} catch (IOException e) {
e.printStackTrace();
}
return matchItem;
}

/**
* @Description 获取关键字所在PDF坐标
* @param pdfReader
* @param keyWords
* @return float[]
*/
private static MatchItem getKeyWords(PdfReader pdfReader, String keyWords) {
int page = 0;
try{
int pageNum = pdfReader.getNumberOfPages();
PdfReaderContentParser pdfReaderContentParser = new PdfReaderContentParser(pdfReader);
CustomRenderListener renderListener = new CustomRenderListener();
renderListener.setKeyWord(keyWords);
StringBuilder allText = null;
for (page = 1; page <= pageNum; page++) {
renderListener.setPage(page);
pdfReaderContentParser.processContent(page, renderListener);
List<MatchItem> matchItems = renderListener.getMatchItems();
if(matchItems != null && matchItems.size() > 0) {
//完全匹配
return matchItems.get(0);
}
List<MatchItem> allItems = renderListener.getAllItems();
allText = new StringBuilder();
for (MatchItem item : allItems) {
allText.append(item.getContent());
//关键字存在连续多个块中
if(allText.indexOf(keyWords) != -1) {
return item;
}
}


}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

}



import com.itextpdf.awt.geom.Rectangle2D;
import com.itextpdf.text.pdf.parser.ImageRenderInfo;
import com.itextpdf.text.pdf.parser.RenderListener;
import com.itextpdf.text.pdf.parser.TextRenderInfo;

import java.util.ArrayList;
import java.util.List;

public class CustomRenderListener implements RenderListener {

/**定位坐标的关键字*/
private String keyWord;
/**关键字所在的页数*/
private int page;
//所有匹配的项
private List<MatchItem> matchItems = new ArrayList<>();
//所有项
private List<MatchItem> allItems = new ArrayList<>();

public String getKeyWord() {
return keyWord;
}

public void setKeyWord(String keyWord) {
this.keyWord = keyWord;
}

public int getPage() {
return page;
}

public void setPage(int page) {
this.page = page;
}

public List<MatchItem> getMatchItems() {
return matchItems;
}

public void setMatchItems(List<MatchItem> matchItems) {
this.matchItems = matchItems;
}

public List<MatchItem> getAllItems() {
return allItems;
}

public void setAllItems(List<MatchItem> allItems) {
this.allItems = allItems;
}

@Override
public void beginTextBlock() {

}

@Override
public void renderText(TextRenderInfo textRenderInfo) {
String text = textRenderInfo.getText();
Rectangle2D.Float boundingRectange = textRenderInfo.getBaseline().getBoundingRectange();
MatchItem matchItem = new MatchItem();
matchItem.setContent(text);
matchItem.setPageNum(page);
matchItem.setX(boundingRectange.x);
matchItem.setY(boundingRectange.y);
if (null != text && !" ".equals(text)) {
if(text.equalsIgnoreCase(keyWord)) {
matchItems.add(matchItem);
}
}
allItems.add(matchItem);
}

@Override
public void endTextBlock() {

}

@Override
public void renderImage(ImageRenderInfo imageRenderInfo) {

}
}

签证代码:

import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;


import com.itextpdf.text.pdf.security.*;

public class PdfUtils {

public static void sign(InputStream src //需要签章的pdf文件路径
, OutputStream dest // 签完章的pdf文件路径
, InputStream p12Stream, //p12 路径
char[] password
, String reason //签名的原因,显示在pdf签名属性中,随便填
, String location, String chapterPath, float width, float height, int page) //签名的地点,显示在pdf签名属性中,随便填
throws GeneralSecurityException, IOException, DocumentException, java.io.IOException {
//读取keystore ,获得私钥和证书链
//KeyStore ks = KeyStore.getInstance("PKCS12");
KeyStore ks = KeyStore.getInstance("jks");
ks.load(p12Stream, password);
String alias = (String)ks.aliases().nextElement();
PrivateKey pk = (PrivateKey) ks.getKey(alias, password);
Certificate[] chain = ks.getCertificateChain(alias);

//下边的步骤都是固定的,照着写就行了,没啥要解释的
// Creating the reader and the stamper,开始pdfreader
PdfReader reader = new PdfReader(src);
//目标文件输出流
//创建签章工具PdfStamper ,最后一个boolean参数
//false的话,pdf文件只允许被签名一次,多次签名,最后一次有效
//true的话,pdf可以被追加签名,验签工具可以识别出每次签名之后文档是否被修改
PdfStamper stamper = PdfStamper.createSignature(reader, dest, '\0', null, false);
// 获取数字签章属性对象,设定数字签章的属性
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason(reason);
appearance.setLocation(location);
//设置签名的位置,页码,签名域名称,多次追加签名的时候,签名预名称不能一样
int pageCount = reader.getNumberOfPages();
//签名的位置,是图章相对于pdf页面的位置坐标,原点为pdf页面左下角
//四个参数的分别是,图章左下角x,图章左下角y,图章右上角x,图章右上角y
appearance.setVisibleSignature(new Rectangle(width+650, height+13, 90, 90), page, "sig1");
//读取图章图片,这个image是itext包的image
Image image = Image.getInstance(chapterPath);
appearance.setSignatureGraphic(image);
appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
//设置图章的显示方式,如下选择的是只显示图章(还有其他的模式,可以图章和签名描述一同显示)
appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);

// 这里的itext提供了2个用于签名的接口,可以自己实现,后边着重说这个实现
// 摘要算法
ExternalDigest digest = new BouncyCastleDigest();
// 签名算法
ExternalSignature signature = new PrivateKeySignature(pk, DigestAlgorithms.SHA1, null);
// 调用itext签名方法完成pdf签章CryptoStandard.CMS 签名方式,建议采用这种
MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, MakeSignature.CryptoStandard.CMS);
}