如何使用PDFBOX移除PDF文档中的文本?
使用PDFBOX移除PDF中的指定文本
PDFBOX的PDPageContentStream确实没有直接的get/remove文本方法,因为PDF的内容是由一系列绘图指令组成的,而非结构化的文本块。要移除特定文本,核心思路是解析页面内容流,过滤掉目标文本对应的绘制指令,再重新生成内容流。以下是具体实现步骤和注意事项:
核心步骤
- 解析内容流:提取页面所有的操作指令(操作符+操作数)
- 过滤目标指令:识别绘制目标文本的
Tj/TJ指令,将其从指令列表中移除 - 重建内容流:将过滤后的指令重新写入页面,替换原有内容流
示例代码
import org.apache.pdfbox.cos.COSArray; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.PDStream; import org.apache.pdfbox.pdmodel.common.PDString; import org.apache.pdfbox.contentstream.operator.Operator; import org.apache.pdfbox.contentstream.PDFStreamParser; import org.apache.pdfbox.contentstream.ContentStreamWriter; import java.io.File; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; public class RemoveTextFromPDF { public static void main(String[] args) throws Exception { String targetText = "要移除的目标文本"; PDDocument document = PDDocument.load(new File("input.pdf")); for (PDPage page : document.getPages()) { // 解析页面内容流,获取所有指令 PDFStreamParser parser = new PDFStreamParser(page); parser.parse(); List<Object> tokens = parser.getTokens(); List<Object> filteredTokens = new ArrayList<>(); for (int i = 0; i < tokens.size(); i++) { Object token = tokens.get(i); if (token instanceof Operator) { Operator op = (Operator) token; String opName = op.getName(); // 处理单行文本指令 Tj if ("Tj".equals(opName)) { PDString textStr = (PDString) tokens.get(i - 1); String content = textStr.getString(); // 仅保留非目标文本的指令 if (!content.equals(targetText)) { filteredTokens.add(tokens.get(i - 1)); filteredTokens.add(op); } i++; // 跳过已处理的操作数 } // 处理多行/带间距的文本指令 TJ else if ("TJ".equals(opName)) { COSArray textArray = (COSArray) tokens.get(i - 1); COSArray filteredArray = new COSArray(); boolean hasValidContent = false; for (Object obj : textArray) { if (obj instanceof PDString) { PDString str = (PDString) obj; String content = str.getString(); if (!content.equals(targetText)) { filteredArray.add(obj); hasValidContent = true; } } else { // 保留文本间距调整参数 filteredArray.add(obj); hasValidContent = true; } } if (hasValidContent) { filteredTokens.add(filteredArray); filteredTokens.add(op); } i++; // 跳过已处理的操作数 } // 其他指令直接保留 else { filteredTokens.add(op); } } else { // 非操作符的操作数,直接保留(除非前面是被过滤的文本指令) filteredTokens.add(token); } } // 生成新的内容流并替换原页面内容 PDStream newStream = new PDStream(document); try (OutputStream out = newStream.createOutputStream(COSName.FLATE_DECODE)) { ContentStreamWriter writer = new ContentStreamWriter(out); writer.writeTokens(filteredTokens); } page.setContents(newStream); } document.save(new File("output.pdf")); document.close(); } }
关键注意事项
- 文本编码问题:如果PDF使用自定义字体编码,
PDString.getString()可能无法正确解析文本。此时需要跟踪当前文本状态的字体(通过Tf指令获取),使用font.decode(string.getBytes())进行解码。 - 文本拆分问题:目标文本可能被拆分为多个
Tj/TJ指令,需要拼接连续的文本内容进行匹配,而非单独判断每个指令的文本。 - 特殊元素处理:如果文本位于表单域、注释或水印中,需要单独处理这些元素(比如遍历页面的
PDAnnotation或PDField进行移除),而非仅处理页面内容流。 - 内容流完整性:过滤指令时要确保保留文本块的边界指令(
BT/ET),避免破坏PDF的语法结构。
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

