一 快速开始
PDFBox 3.0 至少需要 JDK 8,官方最高已对 JDK 19 进行了测试。
1 官网地址
Apache PDFBox | Getting StartedThe Apache PDFBox™ library is an open source Java tool for working with PDF documents. This project allows creation of new PDF documents, manipulation of existing documents and the ability to extract content from documents. Apache PDFBox also includes several command-line utilities. Apache PDFBox is published under the Apache License v2.0.https://pdfbox.apache.org/3.0/getting-started.html
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到1COSArray 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() 恢复之前保存的图形状态。