业务中遇到在pdf文件上盖章,添加文字信息(签名)的需求:包含单图片,多图片,多参数方法
老样子,直接上代码和注释,使用的方法类
引入jar包依赖
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.6</version>
</dependency>
<!-- itextpdf的亚洲字体支持 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
controller层代码:
@PostMapping("/print")
@ApiOperation(value = "打印结果")
public ResponseMessage print(@RequestBody PrintResultRequest request) {
OrganPrintSignature reviewer = null;
OrganPrintSignature verifier = null;
//这里是指定输出为流文件对象给前端,前端拿来使用“预览和下载”行为
HttpServletResponse response = HttpContextUtil.getHttpServletResponse();
try {
OutputStream os = response.getOutputStream();
// 设定输出文件头
response.setHeader(
"Content-disposition",
"attachment; filename="
+ new String(("打印结果结果.pdf")
.getBytes("GB2312"), "ISO8859-1"));
// 定义输出类型
response.setContentType("application/pdf");
//签名url的map集合 key:用来做枚举,获取指定类型的url在pdf上的位置
HashMap<String, String> imgMap = new HashMap<>();
//pdfUrl:一般pdf都是把通用模板上传到服务器,引用的是服务器绝对路径
InputStream docIn = new FileInputStream(new File(pdfUrl));
log.info("------------- 文件读取成功,准备写入pdf");
//审核签名url
if (ObjectUtil.isNotNull(reviewer)) {
imgMap.put("examineImg", reviewer.getUrl());
}
//本人签名url
if (ObjectUtil.isNotNull(verifier)) {
imgMap.put("checkImg", verifier.getUrl());
}
//印章图片url
if (ObjectUtil.isNotNull(organPrintConfig.getSignetUrl())) {
imgMap.put("sealImg", organPrintConfig.getSignetUrl());
}
//输出时间格式化
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
//需要印在pdf文件上的文本参数 参数根据业务获取,这里举两个例子
Map<String, String> map = new HashMap<>();
map.put("name", "奥特曼");
map.put("nowTime", df.format(LocalDateTime.now()));
//填充pdf文件
os.write(PDFUtil.signs(map, docIn, imgMap, 0, 1));
log.info("------------- 填充pdf文件完成");
} catch (Exception e) {
String result = "系统提示:下载失败,原因:" + e.toString();
log.error(result, e);
throw new ServiceException(ResultCode.SYSTEM_BUSY);
}
return ResponseMessage.ok();
}
pdfUtil方法类:
@Slf4j
@Configuration
public class PDFUtil {
/**
* 读取pdf中文字信息(全部)
*/
public static void READPDF(String inputFile) {
//创建文档对象
PDDocument doc = null;
String content = "";
PdfDomainDTO vo = new PdfDomainDTO();
try {
//加载一个pdf对象
doc = PDDocument.load(new File(inputFile));
//获取一个PDFTextStripper文本剥离对象
PDFTextStripper textStripper = new PDFTextStripper();
content = textStripper.getText(doc);
//vo.setContent(content);
System.out.println("内容:" + content);
System.out.println("全部页数" + doc.getNumberOfPages());
//关闭文档
doc.close();
} catch (Exception e) {
// TODO: handle exception
}
}
/**
* 将一张图片添加到PDF文件上,类似签名
*
* @param docInput PDF文件
* @param imgInput 图片文件,建议是无背景的PNG格式文件
* @param x 以左下角为原点的x坐标
* @param y 左下角为原点的y坐标
* @param imgHeight 带有签名文字的图片宽度
* @param imgWidth 带有签名文字的图片高度
* @param rotation 带有签名文字的图片的旋转角度,逆时针旋转,如果不旋转则为0
* @param pageNum 需要签名的页码数组,从1开始
* @return 返回签好名的PDF文件字节码
*/
public static byte[] addImgToDoc(Map<String, String> map, InputStream docInput, InputStream imgInput, int x, int y, float imgWidth, float imgHeight, float rotation, int[] pageNum) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
PdfReader reader = new PdfReader(docInput);
PdfStamper stamp = new PdfStamper(reader, baos);
stamp.setRotateContents(false);
//印章图片
byte[] imgBytes = new byte[imgInput.available()];
IOUtils.read(imgInput, imgBytes);
Image img = Image.getInstance(imgBytes);
img.scaleAbsoluteWidth(imgWidth);
img.scaleAbsoluteHeight(imgHeight);
img.setAbsolutePosition(x, y);
//这个字体是itext-asian.jar中自带的 所以不用考虑操作系统环境问题.
BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
//baseFont不支持字体样式设定.但是font字体要求操作系统支持此字体会带来移植问题.
Font font = new Font(bf, 30);
font.setStyle(Font.NORMAL);
font.getBaseFont();
for (int pn : pageNum) {
img.setRotationDegrees(rotation + reader.getPageRotation(pn)); // 旋转角度
PdfContentByte over = stamp.getOverContent(pn);
over.addImage(img);
//开始写入文本
over.beginText();
//设置字体和大小
over.setFontAndSize(font.getBaseFont(), 10);
//设置字体颜色
//over.setColorFill(new BaseColor(0,110,107,100));
com.itextpdf.text.pdf.PdfGState gState = new PdfGState();
gState.setStrokeOpacity(0.1f);
over.setGState(gState);
for (Map.Entry<String, String> entry : map.entrySet()) {
PdfAxisEnum anEnum = PdfAxisEnum.fetch(entry.getKey());
//要输出的text(对齐方式,写的字,设置字体的输出位置,字体是否旋转)
if (ObjectUtil.isNotNull(anEnum)) {
over.showTextAligned(0, entry.getValue(), anEnum.getXAxis(), anEnum.getYAxis(), 0);
if (anEnum.getParam().equals("result")) {
over.showTextAligned(0, entry.getValue(), anEnum.getXAxis() + 140, anEnum.getYAxis(), 0);
}
}
}
over.endText();
}
stamp.close();
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
return baos.toByteArray();
}
/**
* 将多张图片添加到PDF文件上,类似签名
*
* @param map 要写入的文本map
* @param docInput PDF文件
* @param imgMap 要添加的图片,输入流
* @param rotation 带有签名文字的图片的旋转角度,逆时针旋转,如果不旋转则为0
* @param pageNum 需要签名的页码数组,从1开始
* @return 返回签好名的PDF文件字节码
*/
public static byte[] addImgsToDoc(Map<String, String> map, InputStream docInput, Map<String, String> imgMap, float rotation, int[] pageNum) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
PdfReader reader = new PdfReader(docInput);
PdfStamper stamp = new PdfStamper(reader, baos);
stamp.setRotateContents(false);
//这个字体是itext-asian.jar中自带的 所以不用考虑操作系统环境问题.
BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
//baseFont不支持字体样式设定.但是font字体要求操作系统支持此字体会带来移植问题.
Font font = new Font(bf, 30);
font.setStyle(Font.NORMAL);
font.getBaseFont();
for (int pn : pageNum) {
PdfContentByte over = stamp.getOverContent(pn);
//循环写入图片,并按照枚举菜单取出在pdf中对应的位置
for (Map.Entry<String, String> entry : imgMap.entrySet()) {
log.info("------------- 开始写入图片");
// byte[] imgBytes = new byte[entry.getValue().available()];
// IOUtils.read(entry.getValue(), imgBytes);
Image img = Image.getInstance(entry.getValue());
PdfAxisEnum anEnum = PdfAxisEnum.fetch(entry.getKey());
//要添加的图片
if (ObjectUtil.isNotNull(anEnum)) {
//图片大小
int imgWidth = 0;
int imgHeight = 0;
if (anEnum.getParam().equals("sealImg")) {
//印章图片
imgWidth = 100;
imgHeight = 100;
} else if (anEnum.getParam().equals("checkImg") || anEnum.getParam().equals("examineImg")) {
//签名图片1
imgWidth = 40;
imgHeight = 20;
}
img.scaleAbsoluteWidth(imgWidth);
img.scaleAbsoluteHeight(imgHeight);
img.setAbsolutePosition(anEnum.getXAxis(), anEnum.getYAxis());
img.setRotationDegrees(rotation + reader.getPageRotation(pn)); // 旋转角度
over.addImage(img);
}
}
log.info("------------- 开始写入文本");
//开始写入文本
over.beginText();
//设置字体和大小
over.setFontAndSize(font.getBaseFont(), 10);
//设置字体颜色
//over.setColorFill(new BaseColor(0,110,107,100));
com.itextpdf.text.pdf.PdfGState gState = new PdfGState();
gState.setStrokeOpacity(0.1f);
over.setGState(gState);
for (Map.Entry<String, String> entry : map.entrySet()) {
PdfAxisEnum anEnum = PdfAxisEnum.fetch(entry.getKey());
//要输出的text(对齐方式,写的字,设置字体的输出位置,字体是否旋转)
if (ObjectUtil.isNotNull(anEnum)) {
if (StrUtil.isNotEmpty(entry.getValue())) {
over.showTextAligned(0, entry.getValue(), anEnum.getXAxis(), anEnum.getYAxis(), 0);
}
if (anEnum.getParam().equals("result")) {
over.showTextAligned(0, entry.getValue(), anEnum.getXAxis() + 140, anEnum.getYAxis(), 0);
}
}
}
over.endText();
}
stamp.close();
reader.close();
} catch (Exception e) {
log.error("写入pdf文件失败", e);
}
return baos.toByteArray();
}
/**
* 将一张图片添加到PDF文件上,类似签名
*
* @param doc PDF文件
* @param img 图片文件,建议是无背景的PNG格式文件
* @param x 以左下角为原点的x坐标
* @param y 左下角为原点的y坐标
* @param imgHeight 带有签名文字的图片宽度
* @param imgWidth 带有签名文字的图片高度
* @param rotation 带有签名文字的图片的旋转角度,逆时针旋转,如果不旋转则为0
* @param pageNum 需要签名的页码数组,从1开始
* @return 返回签好名的PDF文件字节码
*/
public static byte[] sign(Map<String, String> map, InputStream doc, InputStream img, int x, int y, float imgWidth, float imgHeight, float rotation, int... pageNum) {
if (pageNum == null || pageNum.length == 0) {
return null;
}
return addImgToDoc(map, doc, img, x, y, imgWidth, imgHeight, rotation, pageNum);
}
/**
* 将多张图片添加到PDF文件上,类似签名
*
* @param map 要写入的文本map集合
* @param doc PDF文件
* @param imgMap 要添加的图片,输入流集合
* @param rotation 带有签名文字的图片的旋转角度,逆时针旋转,如果不旋转则为0
* @param pageNum 需要签名的页码数组,从1开始
* @return 返回签好名的PDF文件字节码
*/
public static byte[] signs(Map<String, String> map, InputStream doc, Map<String, String> imgMap, float rotation, int... pageNum) {
if (pageNum == null || pageNum.length == 0) {
return null;
}
return addImgsToDoc(map, doc, imgMap, rotation, pageNum);
}
/**
* 根据url保存到指定路径
*
* @param url url地址
* @param saveAddress 文件新路径
*/
public static void downloadFile(String url, String saveAddress) {
try {
URL fileUrl = new URL(url);
InputStream is = fileUrl.openStream();
OutputStream os = new FileOutputStream(saveAddress);
byte bf[] = new byte[1024];
int length = 0;
while ((length = is.read(bf, 0, 1024)) != -1) {
os.write(bf, 0, length);
}
is.close();
os.close();
} catch (Exception e) {
log.error(e.toString());
}
}
}
枚举菜单PdfAxisEnum类代码:
@Getter
public enum PdfAxisEnum {
/**
* 姓名
*/
NAME("name", 60, 625),
/**
* 时间
*/
NOW_TIME("nowTime", 80, 562),
/**
* 签名图片
*/
CHECK_IMG("checkImg", 425, 400),
/**
* 签名图片
*/
EXAMINE_IMG("examineImg", 520, 400),
/**
* 印章图片
*/
SEAL_IMG("sealImg", 460, 700),
;
/**
* 参数名
*/
public String param;
/**
* x轴数值
*/
public Integer xAxis;
/**
* y轴数值
*/
public Integer yAxis;
PdfAxisEnum(String param, Integer xAxis, Integer yAxis) {
this.param = param;
this.xAxis = xAxis;
this.yAxis = yAxis;
}
public static PdfAxisEnum fetch(String param) {
for (PdfAxisEnum value : PdfAxisEnum.values()) {
if (value.param.equals(param)) {
return value;
}
}
return null;
}
}