一 快速开始
PDFBox 3.0 至少需要 JDK 8,官方最高已对 JDK 19 进行了测试。
1 官网地址
2 添加依赖
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>3.0.3</version>
</dependency>
二 基础操作
1 文件操作
1.1 加载PDF
原有加载方法都已从 org.apache.pdfbox.pdmodel.PDDocument 中删除,新的类 org.apache.pdfbox.Loader 用于加载 PDF,它提供了几种使用不同类型的源加载 PDF 的方法。
PDDocument pdDocument = Loader.loadPDF(new File("/TEST.PDF"));
1.2 加载TTF
1.2.1 内部加载
原有 org.apache.pdfbox.pdmodel.font.PDType1Font 标准 14 种字体的静态实例被删除,因为底层的 COSDictionary 不应该是不可变的。
引入了一个新的构造函数 org.apache.pdfbox.pdmodel.font.PDType1Font 来创建标准 14 字体,使用新的枚举 org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName 作为参数,其中定义了标准 14 种字体的名称。
PDType1Font pdType1Font = new PDType1Font(Standard14Fonts.FontName.COURIER);
1.2.2 外部引入
(1)TTF
PDDocument pdDocument = Loader.loadPDF(new File("./TEST.PDF"));
File fontFile = new File("./FONT.TTF");
PDType1Font pdType0Font = PDType0Font.load(pdDocument, fontFile);
(2)TTC
TTC 文件是一个字体集合,包含了多个 TrueType 字体。TTC 主要用于将多个相关字体打包在一个文件中,以节省磁盘空间。
PDDocument pdDocument = Loader.loadPDF(new File("./TEST.PDF"));
File fontFile = new File("./FONT.TTF");
TrueTypeCollection ttc = new TrueTypeCollection(fontFile);
ttc.processAllFonts(font -> {
PDType0Font pdType0Font = PDType0Font.load(pdDocument, font, true);
});
1.3 保存PDF
输入文件不能用作保存操作的输出。这会损坏文件并引发异常,因为在保存文件时第一次读取文件的部分内容。
PDDocument pdDocument = Loader.loadPDF(new File("./TEST.PDF"));
pdDocument.save("./TEST_2.PDF");
pdDocument.close();
默认将以压缩模式保存,可以使用无损模式覆盖。
PDDocument pdDocument = Loader.loadPDF(new File("./TEST.PDF"));
pdDocument.save("./TEST_2.PDF", CompressParameters.NO_COMPRESSION);
pdDocument.close();
2 文本操作
2.1 新增文本
2.1.1 默认
// 加载文件
PDDocument pdDocument = Loader.loadPDF(new File("./TEST.PDF"));
File fontFile = new File("./FONT.TTF");
PDType1Font font = PDType0Font.load(pdDocument, fontFile);
// 读取首页内容流
pdDocument.getPages().get(0);
PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, PDPageContentStream.AppendMode.APPEND, true);
// 执行文本操作
contentStream.beginText();
contentStream.setFont(font, 10f);
contentStream.setNonStrokingColor(0f / 255f, 0f / 255f, 0f / 255f);
contentStream.showText("测试文本");
contentStream.endText();
操作解释:
(1)设置字体与字号
contentStream.setFont(font, 10f);
(2)设置字体颜色(RGB)
contentStream.setNonStrokingColor(0f / 255f, 0f / 255f, 0f / 255f)
(3)输出文本
contentStream.showText("测试文本");
2.1.2 加粗
在使用 RenderingMode.FILL_STROKE 方法来加粗文本时,会导致后续所有的文本都继承这个渲染模式,并且表现为加粗的效果。所以需要在使用完 RenderingMode.FILL_STROKE 之后,将渲染模式重置回 RenderingMode.FILL。
// 加载文件
PDDocument pdDocument = Loader.loadPDF(new File("./TEST.PDF"));
File fontFile = new File("./FONT.TTF");
PDType1Font font = PDType0Font.load(pdDocument, fontFile);
// 读取首页内容流
pdDocument.getPages().get(0);
PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, PDPageContentStream.AppendMode.APPEND, true);
// 执行文本操作
contentStream.beginText();
contentStream.setFont(font, 10f);
contentStream.setNonStrokingColor(0f / 255f, 0f / 255f, 0f / 255f);
// 设置加粗渲染模式
contentStream.setRenderingMode(RenderingMode.FILL_STROKE);
contentStream.setLineWidth(0.1f);
contentStream.setStrokingColor(0f / 255f, 0f / 255f, 0f / 255f);
contentStream.showText("测试文本");
contentStream.endText();
// 重置渲染模式
contentStream.setRenderingMode(RenderingMode.FILL);
操作解释:
(1)设置加粗颜色(RGB)
此处应与字体颜色保持一致。
contentStream.setStrokingColor(0f / 255f, 0f / 255f, 0f / 255f);
(2)设置加粗宽度
contentStream.setLineWidth(0.1f);
2.2 修改文本
3 形状操作
3.1 路径绘制与填充
// 加载文件
PDDocument pdDocument = Loader.loadPDF(new File("./TEST.PDF"));
File fontFile = new File("./FONT.TTF");
PDType1Font font = PDType0Font.load(pdDocument, fontFile);
// 读取首页内容流
pdDocument.getPages().get(0);
PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, PDPageContentStream.AppendMode.APPEND, true);
// 绘制
contentStream.setNonStrokingColor(0 / 255f, 0 / 255f, 0 / 255f);
contentStream.moveTo(0, 0);
contentStream.lineTo(10, 0);
contentStream.curveTo(20, 30, 40, 50, 60, 70);
contentStream.closePath();
contentStream.fill();
操作解释:
(1)设置填充颜色(RGB)
contentStream.setNonStrokingColor(0 / 255f, 0 / 255f, 0 / 255f);
(2)移动笔而不绘制路径
contentStream.moveTo(0, 0);
(3)绘制直线路径
contentStream.lineTo(10, 0);
(4)绘制贝塞尔曲线
curveTo方法用于绘制从当前点到指定控制点和终点的三次贝塞尔曲线。下面是对这个代码的解释:
- param1 和 param2:这是贝塞尔曲线的第一个控制点的坐标。
- param3 和 param4:这是贝塞尔曲线的第二个控制点的坐标。
- param5 和 param5:这是贝塞尔曲线的终点坐标。
contentStream.curveTo(20, 30, 40, 50, 60, 70);
(5)闭合路径
是PDFBox中用于闭合当前路径的命令。它会将当前路径的最后一个点与路径的起点相连,形成一个封闭的形状。如果路径已经闭合(起点和终点相同),这个方法不会再添加额外的线条。
这个命令非常重要,用于定义封闭的形状,使其可以被填充或描边。
contentStream.closePath();
(6)填充
contentStream.fill();
3.2 路径绘制与描边
// 加载文件
PDDocument pdDocument = Loader.loadPDF(new File("./TEST.PDF"));
File fontFile = new File("./FONT.TTF");
PDType1Font font = PDType0Font.load(pdDocument, fontFile);
// 读取首页内容流
pdDocument.getPages().get(0);
PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, PDPageContentStream.AppendMode.APPEND, true);
// 绘制
contentStream.setLineWidth(0.5f);
contentStream.setStrokingColor(0 / 255f, 0 / 255f, 0 / 255f);
contentStream.moveTo(0, 0);
contentStream.lineTo(10, 0);
contentStream.curveTo(20, 30, 40, 50, 60, 70);
contentStream.closePath();
contentStream.stroke();
操作解释:
(1)设置描边颜色(RGB)
contentStream.setStrokingColor(0 / 255f, 0 / 255f, 0 / 255f);
(2)设置线宽
contentStream.setLineWidth(0.5f);
(3)描边
contentStream.stroke();
进阶操作:
(1)填充并描边
contentStream.fillAndStroke();
3.3 设置透明度
// 加载文件
PDDocument pdDocument = Loader.loadPDF(new File("./TEST.PDF"));
File fontFile = new File("./FONT.TTF");
PDType1Font font = PDType0Font.load(pdDocument, fontFile);
// 读取首页内容流
pdDocument.getPages().get(0);
PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, PDPageContentStream.AppendMode.APPEND, true);
// 设置透明度
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
graphicsState.setNonStrokingAlphaConstant(0.5f);
contentStream.setGraphicsStateParameters(graphicsState);
// 绘制
contentStream.setNonStrokingColor(0 / 255f, 0 / 255f, 0 / 255f);
contentStream.moveTo(0, 0);
contentStream.lineTo(10, 0);
contentStream.curveTo(20, 30, 40, 50, 60, 70);
contentStream.closePath();
contentStream.fill();
// 重置
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
graphicsState.setNonStrokingAlphaConstant(1.0f);
contentStream.setGraphicsStateParameters(graphicsState);
操作解释:
(1)设置透明度
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
graphicsState.setNonStrokingAlphaConstant(0.5f);
contentStream.setGraphicsStateParameters(graphicsState);
(2)重置透明度为不透明
PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
graphicsState.setNonStrokingAlphaConstant(1.0f);
contentStream.setGraphicsStateParameters(graphicsState);
4 图片操作
三 进阶操作
1 文本操作
核心功能在于解析文本宽度与文本高度,如此即可轻松完成基于右下角定位或自动换行等进阶操作。
1.1 解析文本宽度
在 PDF 文档中,字体的宽度信息通常使用字体单元来表示。TrueType 和 OpenType 字体在 PDF 中的度量单位是以 “EM 方框”(EM square)为基础的,该方框的默认大小是 1000 个单位。因此,font.getStringWidth(content) 返回的宽度值是基于 1000 个单位的。
PDDocument pdDocument = Loader.loadPDF(new File("./TEST.PDF"));
File fontFile = new File("./FONT.TTF");
// 真实文本宽度 = EM宽度 / 1000 * 字号
float weight = font.getStringWidth(content) / 1000 * 10f;
1.2 解析字体高度
font.getFontDescriptor() 返回与该字体相关的描述符对象。该描述符对象包含了字体的各种元数据,例如上升、下降、字体重量、斜体角度等信息。
ascent 是字体中最高字符的顶部到基线之间的距离,descent 是字体中最低字符的底部到基线之间的距离,两值相减即能获取到字体的总高度(同样也是以 “EM方框” 为基础)。
PDFontDescriptor fontDescriptor = font.getFontDescriptor();
float ascent = fontDescriptor.getAscent();
float descent = fontDescriptor.getDescent();
// 真实文本高度 = EM高度 / 1000 * 字号
float height = (ascent - descent) / 1000 * 10f;
2 渐变
2.1 渐变矩形
// 创建一个COSDictionary来存储FunctionType2的属性
COSDictionary dictionary = new COSDictionary();
dictionary.setInt(COSName.FUNCTION_TYPE, 2);
// 设置domain域,从0到1
COSArray domain = new COSArray();
domain.add(COSInteger.get(0));
domain.add(COSInteger.get(1));
// 设置C0(左边颜色)
COSArray c0 = new COSArray();
c0.add(new COSFloat(255 / 255f));
c0.add(new COSFloat(255 / 255f));
c0.add(new COSFloat(255 / 255f));
// 设置C1(右边颜色)
COSArray c1 = new COSArray();
c1.add(new COSFloat(0 / 255f));
c1.add(new COSFloat(0 / 255f));
c1.add(new COSFloat(0 / 255f));
// 将域和值设置到字典中
dictionary.setItem(COSName.DOMAIN, domain);
dictionary.setItem(COSName.C0, c0);
dictionary.setItem(COSName.C1, c1);
dictionary.setInt(COSName.N, 1);
// 创建PDFunctionType2对象
PDFunctionType2 func = new PDFunctionType2(dictionary);
// 创建PDShadingType2(线性渐变)
PDShadingType2 axialShading = new PDShadingType2(new COSDictionary());
axialShading.setColorSpace(PDDeviceRGB.INSTANCE);
axialShading.setShadingType(PDShading.SHADING_TYPE2);
// 设置渐变的起始和结束坐标(水平渐变,从左到右)
COSArray coords = new COSArray();
coords.add(new COSFloat(0));
coords.add(new COSFloat(100));
coords.add(new COSFloat(100));
coords.add(new COSFloat(100));
axialShading.setCoords(coords);
// 将函数设置到Shading中
axialShading.setFunction(func);
// 剪切
contentStream.saveGraphicsState();
contentStream.addRect(0, 100, 100, 100);
contentStream.clip();
// 使用shadingFill填充渐变矩形
contentStream.shadingFill(axialShading);
// 恢复
contentStream.restoreGraphicsState();
- COSDictionary 是一个字典对象,用于存储PDF对象的属性和数据。在这里,它用于存储函数的属性。
- dictionary.setInt(COSName.FUNCTION_TYPE, 2) 指定函数类型为Type 2,即指数插值函数。这种函数通过给定的参数在两个颜色之间进行线性插值。
- domain 定义了输入值的范围,在这里是从0到1。这意味着函数的输入值会在这个范围内被插值计算。
- coords 定义了渐变的起始和结束坐标。在这里,渐变从 (0, 100) 到 (100, 100),即一个从左到右的水平渐变。
- saveGraphicsState() 保存当前的图形状态(如剪切路径)。
- addRect(0, 100, 100, 100) 定义一个矩形路径,这里是一个从 (0, 100) 到 (100, 200) 的矩形区域,参数分别为:x轴坐标、y轴坐标、宽度、高度。
- clip() 将剪切路径设置为之前定义的矩形区域,这样填充只会影响这个区域内的内容。
- shadingFill(axialShading) 使用之前定义的线性渐变填充这个矩形区域。
- restoreGraphicsState() 恢复之前保存的图形状态。