apache pdfbox 是一个开源的 java 库,用于处理 pdf 文档。它提供了一系列强大的功能,包括创建、渲染、拆分、合并、加密、解密 pdf 文件,以及从 pdf 中提取文本和元数据等。pdfbox 支持 pdf 1.7 标准,并且兼容大多数现代 pdf 格式和特性。
1、使用 maven 集成 pdfbox
在 pom.xml 文件中引入依赖
<dependency>
<groupid>org.apache.pdfbox</groupid>
<artifactid>pdfbox</artifactid>
<version>2.0.24</version> <!-- 请检查最新的版本 -->
</dependency>2、编写工具类
package cn.iocoder.yudao.module.contract.service.content;
import com.fasterxml.jackson.databind.objectmapper;
import org.apache.pdfbox.pdmodel.pddocument;
import org.apache.pdfbox.pdmodel.pdpage;
import org.apache.pdfbox.pdmodel.pdpagecontentstream;
import org.apache.pdfbox.pdmodel.common.pdrectangle;
import org.apache.pdfbox.pdmodel.graphics.image.pdimagexobject;
import org.apache.pdfbox.rendering.pdfrenderer;
import org.springframework.http.responseentity;
import javax.imageio.imageio;
import java.awt.image.bufferedimage;
import java.io.bytearrayinputstream;
import java.io.bytearrayoutputstream;
import java.io.ioexception;
import java.util.arraylist;
import java.util.base64;
import java.util.list;
public class pdfboxutil {
/**
* 加载 pdf 文档
*/
public static pddocument loadpdf(byte[] input) throws ioexception {
return pddocument.load(input);
}
/**
* 添加印章到 pdf 文档中
*
* @param document pdf 文档对象
* @param imagebytearray 印章图像的二进制数据
* @param x 横坐标
* @param y 纵坐标
* @param h 高度
* @param pageidx 页码
* @throws ioexception 异常
*/
public static void addstamptopdf(pddocument document, byte[] imagebytearray, int x, int y, int h, int pageidx) throws ioexception {
// 加载签章图像
pdimagexobject pdimage = pdimagexobject.createfrombytearray(document, imagebytearray, "签章");
// 获取 pdf 文档的第一个页面
pdpage page = document.getpage(pageidx);
// 计算签章图像的尺寸
float desiredheight = h; // 目标高度
float scale = desiredheight / pdimage.getheight();
// 创建一个内容流以添加签章
try (pdpagecontentstream contentstream = new pdpagecontentstream(document, page, pdpagecontentstream.appendmode.append, true, true)) {
// 在 pdf 页面上绘制签章图像
contentstream.drawimage(pdimage, x, y, pdimage.getwidth() * scale, pdimage.getheight() * scale);
}
// 可选:也可以向 pdf 添加一个签名字段
// addsignaturefield(document);
}
/**
* 将 bufferedimage 转换为字节数组
*
* @param image 要转换的图像
* @return 字节数组
*/
private static byte[] imagetobytes(bufferedimage image) {
try (bytearrayoutputstream os = new bytearrayoutputstream()) {
imageio.write(image, "png", os);
return os.tobytearray();
} catch (ioexception e) {
throw new runtimeexception("failed to convert image to bytes", e);
}
}
/**
* 裁剪图像
*
* @param image 要裁剪的图像
* @param page pdf 页面
* @param x 开始裁剪的横坐标
* @param y 开始裁剪的纵坐标
* @param w 需要裁剪的宽度
* @param h 需要裁剪的高度
* @return 裁剪后的图片
*/
private static bufferedimage cropimage(bufferedimage image, pdpage page, int x, int y, int w, int h) {
pdrectangle mediabox = pdrectangle.a4; // 使用默认的 a4 大小
// 将 pdf 单位转换为图像坐标
int width = (int) (mediabox.getwidth() * (image.getwidth() / page.getmediabox().getwidth()));
int height = (int) (mediabox.getheight() * (image.getheight() / page.getmediabox().getheight()));
// 裁剪图像
return image.getsubimage(x, y, width - w, height - h);
}
/**
* 将 pdf 转换为多个图片
*
* @param pdfbytes pdf 二进制数据
* @param dpi dpi 值
* @return 裁剪后的图片列表
* @throws ioexception 异常
*/
public static list<byte[]> convertpdftoimages(byte[] pdfbytes, int numberofpages, int dpi, int x, int y, int w, int h) throws ioexception {
list<byte[]> croppedimages = new arraylist<>();
try (pddocument document = pddocument.load(new bytearrayinputstream(pdfbytes))) {
pdfrenderer renderer = new pdfrenderer(document);
if (numberofpages == 0) {
numberofpages = document.getnumberofpages();
}
for (int i = 0; i < numberofpages; i++) {
// 渲染页面
bufferedimage image = renderer.renderimagewithdpi(i, dpi); // 300 dpi
// 裁剪图像
bufferedimage croppedimage = cropimage(image, document.getpage(i), x, y, w, h);
byte[] croppedimagebytes = imagetobytes(croppedimage);
croppedimages.add(croppedimagebytes);
}
}
return croppedimages;
}
/**
* 将 pdf 转换为 base64 编码的 json
*
* @param filecontent pdf 二进制数据
* @param x 开始裁剪的横坐标
* @param y 开始裁剪的纵坐标
* @param w 需要裁剪的宽度
* @param h 需要裁剪的高度
* @return base64 编码的 json
* @throws exception 异常
*/
public static responseentity<string> convertpdftobase64(byte[] filecontent, int x, int y, int w, int h) throws exception {
list<byte[]> imagebyteslist = convertpdftoimages(filecontent, 0, 300, x, y, w, h);
list<string> base64images = new arraylist<>();
for (byte[] imagebytes : imagebyteslist) {
string base64image = base64.getencoder().encodetostring(imagebytes);
base64images.add(base64image);
}
objectmapper mapper = new objectmapper();
string jsonresult = mapper.writevalueasstring(base64images);
return responseentity.ok().body(jsonresult);
}
}3、编写控制器用于浏览器直接打开
第五步会编写控制器用于在 vue 前端预览 pdf 文件
/**
* 测试添加数字签名
*
* @param filename 文件名
* @param x x坐标
* @param y y坐标
* @param h 高度
* @param i 宽度
*/
@getmapping("/stamp/{filename}/p")
@parameter(name = "x", description = "添加签名的 x 坐标", required = true, example = "x")
@parameter(name = "y", description = "添加签名的 y 坐标", required = true, example = "y")
@parameter(name = "h", description = "签名的显示高度", required = true, example = "h")
@parameter(name = "i", description = "签名所在页数下标", required = true, example = "i")
public responseentity<bytearrayresource> stamptest(@pathvariable string filename, @requestparam("x") integer x, @requestparam("y") integer y,
@requestparam("h") integer h, @requestparam("i") integer i) throws exception {
// 从数据库中获取文件内容,这里需要修改为你们自己的获取方式来获取源 pdf 文件的字节数组
byte[] filecontent = fileapi.getfilecontent(4l, filename);
bytearrayoutputstream out = new bytearrayoutputstream();
// 添加数字签名
try (pddocument document = pdfboxutil.loadpdf(filecontent)) {
// 这里需要修改为你们自己的获取方式来获取签名文件的字节数组
byte[] imagebytearray = fileapi.getfilecontent(4l, "2c095928083c5ee82e6e229089892191d7790a3a42616dfd5a49daae68c27f41.png");
pdfboxutil.addstamptopdf(document, imagebytearray, x, y, h, i);
document.save(out);
} catch (ioexception e) {
e.printstacktrace();
}
// 创建 bytearrayresource
bytearrayresource resource = new bytearrayresource(out.tobytearray());
return responseentity.ok()
.header(httpheaders.content_disposition, "inline; filename=\"" + filename + "\"")
.contenttype(mediatype.application_pdf)
.body(resource);
}4、浏览器测试
直接打开连接http://ip:端口/你们自己的控制器前缀/stamp/文件名/p?x=100&y=200&h=80&i=1进行测试

5、编写控制器用于在 vue 前端预览 pdf 文件
我这边在预览的时候不想保留边距、页眉、页脚的数据,所以有裁剪参数,不需要的话需要自行修改
/**
* 根据合约名称获取合约 pdf 文件,并返回图片的 base64 编码
*
* @param filename合约标识
* @return 图片的 base64 编码
*/
@getmapping(value = "/get/{filename}", produces = mediatype.image_png_value)
@parameter(name = "x", description = "每一页开始裁剪的 x 横坐标", required = true, example = "x")
@parameter(name = "y", description = "每一页开始裁剪的 y 纵坐标", required = true, example = "y")
@parameter(name = "h", description = "每一页需要裁剪掉的高度 h", required = true, example = "h")
@parameter(name = "w", description = "每一个需要裁剪掉的宽度 w", required = true, example = "w")
public responseentity<string> getpageimage(@pathvariable string filename, @requestparam("x") int x, @requestparam("y") int y,
@requestparam("h") int h, @requestparam("w") int w) {
// 从数据库中获取文件内容,这里需要修改为你们自己的获取方式来获取源 pdf 文件的字节数组
byte[] filecontent = fileapi.getfilecontent(4l, filename);
try {
return pdfboxutil.convertpdftobase64(filecontent, x, y, w, h);
} catch (ioexception e) {
throw new runtimeexception("获取 pdf 文件截图异常", e);
} catch (exception e) {
throw new runtimeexception("读取 pdf 文件异常", e);
}
}6、编写 vue 代码
<template>
<dialog :title="dialogtitle" v-model="dialogvisible">
<div v-if="formloading">{{message}}</div>
<div id="pdf-container">
</div>
</dialog>
</template>
<script setup lang="ts">
defineoptions({ name: 'contentwxpreview' })
const dialogvisible = ref(false) // 弹窗的是否展示
const dialogtitle = ref('') // 弹窗的标题
const formloading = ref(false) // 表单的加载中
const message = ref('数据正在加载请稍后 ... ...')
/** 打开弹窗 */
const open = async (title: string, code: string) => {
dialogvisible.value = true
dialogtitle.value = title + '_预览'
formloading.value = true
try {
fetch('http://ip:端口/你们自己的控制器前缀/stamp/文件名/p?x=250&y=188&w=520&h=385', {
method: 'get',
headers: {
'content-type': 'application/octet-stream'
}
})
.then(response => response.text())
.then(base64images => {
const container = document.getelementbyid('pdf-container')
if (container) {
container.innerhtml = '' // 清空容器
const images = json.parse(base64images)
images.foreach(base64image => {
let img = document.createelement('img')
img.src = `data:image/png;base64,${base64image}`
container.appendchild(img)
})
}
formloading.value = false
})
} finally {
formloading.value = false
}
}
defineexpose({ open }) // 提供 open 方法,用于打开弹窗
</script>
<style lang="scss">
#pdf-container {
display: flex;
flex-direction: column;
align-items: center;
}
#pdf-container > img {
max-width: 100%;
}
</style>7、预览显示

扩展:虽然 pdfbox 很强大,但是在读取文件、文件识别、文字替换等方面使用起来不是特别方便,需要有一定的学习成本。对于我这边偶尔开发 pdf 文档处理半路子来说太难了,所以会在springboot集成spirepdf实现文本替换功能_java_代码网 (jb51.net)说明如何使用 spider.pdf 进行文本替换
以上就是springboot集成pdfbox实现电子签章的代码详解的详细内容,更多关于springboot pdfbox电子签章的资料请关注代码网其它相关文章!
发表评论