使用的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-->
效果圖
原理
- 通過itext中List添加固定文本
- 添加指定標記比如★☆用以之后替換成其他文本或圖片
- 可以不用標記,反正就是算好位置
- 至於簽章這塊位置的選定,根據文檔最后一行位置判定,我的判定方法就是文檔最后一頁最后一行離尾部距離小於一定值,簽章的整塊內容移到新的一頁
過程
- 核心利用了com.itextpdf.text.pdf.parser.RenderListener這個類,它會遍歷這個文檔的內容
- 寫個繼承這個類的方法,實現方法如下
@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);
}
}
- 這里我進行了很多微調,此方法肯定存在很多改進的地方,由於時間緊急,我對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;
}
}
...
- 通過上面的步驟找到最后一行位置,找到指定特殊字符的位置
- 添加尾部簽章部分通過
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();
-
整個過程會出現很多中間臨時文件,所以說還是可以有很多改進的地方。
-
替換方法,用來替換日期,和覆蓋特殊符號
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);
- 簽章方法
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;
}