java操作pdf添加骑缝章
java操作pdf添加骑缝章
概述
印章是我国特有的历史文化产物,古代主要用作身份凭证和行驶职权的工具。它的起源是由于社会生活的实际需要。早在商周时代,印章就已经产生。如今的印章已成为一种独特的,融实用性和艺术性为一体的艺术瑰宝。传统的印章容易被坏人、小人私刻;从而新闻鲜有报道某某私刻公章,侵吞国家财产。随着计算机技术、加密技术及图像处理技术的发展,出现了电子签章。电子签章是电子签名的一种表现形式,利用图像处理技术、数字加密技术将电子签名操作转化为与纸质文件盖章操作相同的可视效果,同时利用电子签名技术保障电子信息的真实性和完整性以及签名人的不可否认性。
电子签章与数字证书一样是身份验证的一种手段,泛指所有以电子形式存在,依附在电子文件并与其逻辑关联,可用以辨识电子文件签署者身份,保证文件的完整性,并表示签署者同意电子文件所陈述事实的内容。一般来说对电子签章的认定都是从技术角度而言的。主要是指通过特定的技术方案来鉴别当事人的身份及确保电子资料内容不被篡改的安全保障措施。电子签章常于发送安全电子邮件、访问安全站点、网上招标投标、网上签约、安全网上公文传送、公司合同、电子处方笺等。
电子签章是一个很复杂的问题,大到有相关的电子签章系统;今天分享一下如何把电子签章应用到电子处方笺的PDF文件里。
目前做的项目甲方要求出具报告的时候提供骑缝章,所以在老旧的项目上加了这样一个功能
lib依赖
我由于是老项目,没有maven,所以需要手动导入以下jar包
bcpkix-jdk15on-1.50.jar
bcprov-jdk15on-1.49.jar
itextpdf-5.5.10.jar
若是maven项目,则加一下配置
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
代码实现
生成印章图片
package com.sign;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import sun.font.FontDesignMetrics;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
public class SignImage {
/**
* @param companyName
* String 公司名字
* @param bgName
* String 报告名称
* @param date
* String 签名日期
* 图片高度
* @param jpgname
* String jpg图片名
* @return
*/
public static boolean createSignTextImg(
String companyName, //
String bgName, //
String date,
String jpgname) {
int width = 255;
int height = 100;
FileOutputStream out = null;
//背景色
Color bgcolor = Color.WHITE;
//字色
Color fontcolor = Color.RED;
Font doctorNameFont = new Font(null, Font.BOLD, 20);
Font othorTextFont = new Font(null, Font.BOLD, 18);
try { // 宽度 高度
BufferedImage bimage = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g = bimage.createGraphics();
g.setColor(bgcolor); // 背景色
g.fillRect(0, 0, width, height); // 画一个矩形
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON); // 去除锯齿(当设置的字体过大的时候,会出现锯齿)
g.setColor(Color.RED);
g.fillRect(0, 0, 8, height);
g.fillRect(0, 0, width, 8);
g.fillRect(0, height - 8, width, height);
g.fillRect(width - 8, 0, width, height);
g.setColor(fontcolor); // 字的颜色
g.setFont(doctorNameFont); // 字体字形字号
FontMetrics fm = FontDesignMetrics.getMetrics(doctorNameFont);
int font1_Hight = fm.getHeight();
int strWidth = fm.stringWidth(companyName);
int y = 35;
int x = (width - strWidth) / 2;
g.drawString(companyName, x, y); // 在指定坐标除添加文字
g.setFont(othorTextFont); // 字体字形字号
fm = FontDesignMetrics.getMetrics(othorTextFont);
int font2_Hight = fm.getHeight();
strWidth = fm.stringWidth(bgName);
x = (width - strWidth) / 2;
g.drawString(bgName, x, y + font1_Hight); // 在指定坐标除添加文字
strWidth = fm.stringWidth(date);
x = (width - strWidth) / 2;
g.drawString(date, x, y + font1_Hight + font2_Hight); // 在指定坐标除添加文字
g.dispose();
out = new FileOutputStream(jpgname); // 指定输出文件
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(bimage);
param.setQuality(50f, true);
encoder.encode(bimage, param); // 存盘
out.flush();
return true;
} catch (Exception e) {
return false;
}finally{
if(out!=null){
try {
out.close();
} catch (IOException e) {
}
}
}
}
public static void main(String[] args) {
createSignTextImg("天禾软件", "委托报告", "2023.09.19", "委托01.jpg");
}
}
生成骑缝章
package com.sign;
import com.itextpdf.awt.geom.Rectangle2D;
import com.itextpdf.text.BadElementException;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.parser.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
/**
* @Description: TODO
* @Author: wanping
* @Date: 9/19/23
**/
public class Qifzhang {
public static void main(String[] args) throws DocumentException, IOException {
// 输出文件
String outfilePath = "/Users/wanping/IdeaProjects/thrj/Sign-master/file/卷烟委托检验报告模板-批样品-判定5.pdf";
// 图片路径
String picPath = "/Users/wanping/IdeaProjects/thrj/Sign-master/img.png";
stamperCheckMarkPdf("/Users/wanping/IdeaProjects/thrj/Sign-master/file/卷烟委托检验报告模板-批样品-判定.pdf", outfilePath, picPath, "");
}
/**
* 盖骑缝章
*
* @param infilePath 原PDF路径
* @param outFilePath 输出PDF路径
* @param picPath 章图片路径
* @param keyWord 盖章关键字位置
*/
public static void stamperCheckMarkPdf(String infilePath, String outFilePath, String picPath, String keyWord) {
// 选择需要印章的pdf
PdfReader reader = null;
// 加完印章后的pdf
PdfStamper stamp = null;
try {
reader = new PdfReader(infilePath);
stamp = new PdfStamper(reader, Files.newOutputStream(Paths.get(outFilePath)));
// 获得第一页
// Rectangle pageSize = reader.getPageSize(1);
// float height = pageSize.getHeight();
// float width = pageSize.getWidth();
// 获取pdf文档中的页数
int nums = reader.getNumberOfPages();
// 生成骑缝章切割图片
Image[] nImage = subImages(picPath, nums);
for(int n = 1; n <= nums; n++){
// 获得第一页
Rectangle pageSize = reader.getPageSize(n);
float height = pageSize.getHeight();
float width = pageSize.getWidth();
// 设置在第几页打印印章
PdfContentByte over = stamp.getOverContent(n);
// 选择图片
Image img = nImage[n-1];
// 控制图片位置
img.setAbsolutePosition(width-img.getWidth(),height/2-img.getHeight()/2);
over.addImage(img);
// 最后一页,查询盖章处,盖章
// 存在的问题:这边我控制的是当最后一页才去根据关键字查找盖章,如果关键字出现在前两页自然找不到
if (n == nums) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
InputStream is = null;
try {
is = Files.newInputStream(Paths.get(infilePath));
byte[] buffer = new byte[is.available()];
int m;
while ((m = is.read(buffer)) != -1) {
bos.write(buffer, 0, m);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bos.close();
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 获取输出流
byte[] bytes = bos.toByteArray();
// 获取关键字位置
List<Map<String, Object>> wordsCoordinates = getWordsCoordinate(bytes, keyWord);
if (wordsCoordinates!=null && wordsCoordinates.size()>0) {
Map<String, Object> map = wordsCoordinates.get(wordsCoordinates.size() - 1);
float x = (float) map.get("x");
float y = (float) map.get("y");
float w = (float) map.get("fontWidth") * 6;
float h = (float) map.get("fontHeight");
Image[] nImage2 = subImages(picPath, 1);
Image img2 = nImage2[0];
float xx = x + w - img2.getWidth() / 2;
float yy = y +h - img2.getHeight() / 2;
// 控制图片位置
img2.setAbsolutePosition(xx, yy);
over.addImage(img2);
}
}
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (null != stamp) {
stamp.close();
}
if (null != reader) {
reader.close();
}
} catch (Exception ignored) {}
}
}
/**
* 切割图片
*
* @param imgPath 原始图片路径
* @param n 切割份数
* @return itextPdf的Image[]
*/
public static Image[] subImages(String imgPath, int n) throws IOException, BadElementException {
Image[] nImage = new Image[n];
ByteArrayOutputStream out = new ByteArrayOutputStream();
// 服务器测试和resource下测试
// Resource resource = new ClassPathResource(imgPath);
// InputStream inputStream = resource.getInputStream();
// 使用本地图片测试方法
File resource = new File(imgPath);
InputStream inputStream = Files.newInputStream(resource.toPath());
BufferedImage img = ImageIO.read(inputStream);
// 图片高
int h = img.getHeight();
// 图片宽
int w = img.getWidth();
int sw = w / n;
for(int i = 0; i < n; i++){
BufferedImage subImg;
if( i == n - 1){
// 最后剩余部分
subImg = img.getSubimage(i * sw, 0, w-i*sw, h);
}else {
// 前n-1块均匀切
subImg = img.getSubimage(i * sw, 0, sw, h);
}
ImageIO.write(subImg, imgPath.substring(imgPath.lastIndexOf('.')+1),out);
nImage[i] = Image.getInstance(out.toByteArray());
out.flush();
out.reset();
}
return nImage;
}
/**
* 获取关键词位置
*
* @param pdfData pdf二进制字节流
* @param keyWord 关键字
* @return List<Map<String, Object>>
*/
public static List<Map<String, Object>> getWordsCoordinate(byte[] pdfData, String keyWord) {
if (null == keyWord || "".equals(keyWord)) {
return null;
}
List<Map<String, Object>> result = new ArrayList<>();
PdfReader reader = null;
try {
// pdfData :可以是二进制,也可以是文件路径,两种方式选择一种
reader = new PdfReader(pdfData);
// 获取pdf页数
int pages = reader.getNumberOfPages();
for (int pageNum = 1; pageNum <= pages; pageNum++) {
// 每页的宽度
float width = reader.getPageSize(pageNum).getWidth();
// 每页的高度
float height = reader.getPageSize(pageNum).getHeight();
RenderListenerHelper renderListenerHelper = new RenderListenerHelper(pageNum, width, height);
// 解析pdf,定位位置
PdfContentStreamProcessor processor = new PdfContentStreamProcessor(renderListenerHelper);
PdfDictionary pageDic = reader.getPageN(pageNum);
PdfDictionary resourcesDic = pageDic.getAsDict(PdfName.RESOURCES);
processor.processContent(ContentByteUtils.getContentBytesForPage(reader, pageNum), resourcesDic);
// 文本内容
String content = renderListenerHelper.getContent();
// 文本每个字对应的坐标
List<Map<String, Object>> charPositions = renderListenerHelper.getCharPositions();
for (int i = 0; i < content.length(); i++) {
// 获取关键字所在位置
int keyIndex = content.indexOf(keyWord, i);
if (keyIndex == -1) {
break;
}
result.add(charPositions.get(keyIndex));
i = keyIndex + 1;
}
}
} catch (Exception e) {
System.out.println("获取pdf关键字坐标失败:");
e.printStackTrace();
} finally {
reader.close();
}
return result;
}
static class RenderListenerHelper implements RenderListener {
private final int pageNum;
private final float pageWidth;
private final float pageHeight;
private final StringBuilder contentBuilder = new StringBuilder();
private final List<Map<String, Object>> charPositions = new ArrayList<>();
public RenderListenerHelper(int pageNum, float pageWidth, float pageHeight) {
this.pageNum = pageNum;
this.pageWidth = pageWidth;
this.pageHeight = pageHeight;
}
public String getContent() {
return contentBuilder.toString();
}
public List<Map<String, Object>> getCharPositions() {
return charPositions;
}
// step 2 遇到"BT"执行
@Override
public void beginTextBlock() {
}
// step 3 文字主要处理方法
@Override
public void renderText(TextRenderInfo renderInfo) {
//获取文本内容每个字信息集合
List<TextRenderInfo> characterRenderInfos = renderInfo.getCharacterRenderInfos();
for (TextRenderInfo textRenderInfo : characterRenderInfos) {
String word = textRenderInfo.getText();
if (word.length() > 1) {
word = word.substring(word.length() - 1);
}
// 关键字上边缘坐标
// Rectangle2D.Float boundingRectange = textRenderInfo.getAscentLine().getBoundingRectange();
// 关键字标准坐标(中间)
Rectangle2D.Float boundingRectange = textRenderInfo.getBaseline().getBoundingRectange();
// 关键字下边缘坐标
// Rectangle2D.Float boundingRectange = textRenderInfo.getDescentLine().getBoundingRectange();
// 正常坐标
Float x = boundingRectange.x;
Float y = boundingRectange.y;
Map<String, Object> coordinate = new HashMap<>(8);
coordinate.put("x", x);
coordinate.put("y", y);
// 页数
coordinate.put("pageNum", pageNum);
// 字体长度
coordinate.put("fontWidth", boundingRectange.width);
// 字段高度
coordinate.put("fontHeight", boundingRectange.height);
charPositions.add(coordinate);
contentBuilder.append(word);
}
}
// step 4(最后执行的,只执行一次),遇到“ET”执行
@Override
public void endTextBlock() {
}
// step 1(图片处理方法)
@Override
public void renderImage(ImageRenderInfo renderInfo) {
}
}
}
懒得截图展示了,太忙了