Java如何移除PDF文件中的特定文本并保存修改后的文件
实现方案说明
首先明确两种初步方案的可行性:
- 直接读取PDF字节数组删除目标文本的方案完全不可行:PDF是结构化二进制文档格式,可见文本的存储顺序和阅读顺序不一致,还嵌套了字体映射、内容流指令、偏移索引等结构,直接修改字节会直接导致文件损坏、内容错乱。
- 使用第三方PDF处理类库是唯一可行的路径,你当前代码已经在用Apache PDFBox,直接基于它实现即可,不需要额外引入iText(iText新版本为AGPL协议,商用需要购买授权,限制较多)。
具体实现逻辑
你之前用的PDFTextStripper只能做纯文本提取,拿不到文本在页面的位置、对应绘制指令,无法完成删除操作。需要通过PDFBox的文本位置解析能力定位目标行,再通过覆盖/移除内容流的方式完成删除,优先推荐同色矩形覆盖的方案,实现简单、兼容性强,90%以上的常规PDF场景都适用:
- 遍历PDF每一页
- 解析页面内所有文本块的坐标、内容,定位到需要删除的目标行的边界(X/Y轴起始、结束位置)
- 在对应位置绘制和页面背景色一致的矩形(默认白底用白色即可)覆盖目标文本
- 保存修改后的文档
如果需要彻底移除内容流里的文本绘制指令(而不是视觉覆盖),可以用PDFContentStreamEditor重写页面内容流,过滤掉对应文本的绘制指令,但是这个方案对复杂排版、分栏、嵌套表单的PDF兼容性差,容易出现内容错位,非必要不推荐。
可运行代码示例(基于PDFBox 2.x)
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.text.PDFTextStripper; import org.apache.pdfbox.text.TextPosition; import org.apache.pdfbox.util.Matrix; import java.io.File; import java.io.IOException; import java.util.List; public class PdfTextRemover { // 自定义文本解析器,收集目标文本的坐标范围 static class TargetTextLocator extends PDFTextStripper { private float minX = Float.MAX_VALUE; private float maxX = Float.MIN_VALUE; private float minY = Float.MAX_VALUE; private float maxY = Float.MIN_VALUE; private final String targetText; private boolean found = false; public TargetTextLocator(String targetText) throws IOException { super(); this.targetText = targetText; // 按位置排序文本,保证解析顺序和阅读顺序一致 setSortByPosition(true); } @Override protected void writeString(String text, List<TextPosition> textPositions) throws IOException { // 匹配到包含目标文本的行 if (text.trim().contains(targetText) && !found) { found = true; for (TextPosition pos : textPositions) { // 转换为PDF默认坐标系(原点在页面左下角) Matrix textMatrix = pos.getTextMatrix(); float x = textMatrix.getTranslateX(); float y = textMatrix.getTranslateY(); float width = pos.getWidth(); float height = pos.getHeight(); // 更新文本边界,留2px冗余避免覆盖不全 minX = Math.min(minX, x) - 2; maxX = Math.max(maxX, x + width) + 2; minY = Math.min(minY, y) - 2; maxY = Math.max(maxY, y + height) + 2; } } super.writeString(text, textPositions); } public boolean isFound() { return found; } public float getRectX() { return minX; } public float getRectY() { return minY; } public float getRectWidth() { return maxX - minX; } public float getRectHeight() { return maxY - minY; } } public static void removeTargetText(String inputPath, String outputPath, String targetTextPerPage) throws IOException { try (PDDocument document = PDDocument.load(new File(inputPath))) { int pageCount = document.getNumberOfPages(); // 遍历所有页面 for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) { PDPage page = document.getPage(pageIndex); // 定位当前页目标文本位置 TargetTextLocator locator = new TargetTextLocator(targetTextPerPage); locator.setStartPage(pageIndex + 1); locator.setEndPage(pageIndex + 1); locator.getText(document); if (locator.isFound()) { // 绘制同色矩形覆盖目标文本 try (PDPageContentStream contentStream = new PDPageContentStream( document, page, PDPageContentStream.AppendMode.APPEND, true, true)) { // 默认用白色填充,非白底PDF替换为对应背景RGB值即可 contentStream.setNonStrokingColor(255, 255, 255); contentStream.addRect(locator.getRectX(), locator.getRectY(), locator.getRectWidth(), locator.getRectHeight()); contentStream.fill(); } } } // 保存修改后的文件 document.save(outputPath); } } public static void main(String[] args) { try { // 传入输入文件路径、输出文件路径、每页要删除的目标文本 removeTargetText("input.pdf", "output.pdf", "需要删除的那行文本内容"); } catch (IOException e) { e.printStackTrace(); } } }
注意事项
- 如果要删除的行是固定位置(比如页面顶部的页眉、底部的页脚),不需要匹配文本内容,可以直接跳过文本解析步骤,在固定坐标位置绘制覆盖矩形即可,处理效率更高。
- 不推荐使用PDF转纯文本修改后再转回PDF的方案,会丢失原PDF的格式、图片、书签、签名等结构。
内容的提问来源于stack exchange,提问作者marcss
相关产品推荐
相关产品推荐

