PDFBox 2.0.20如何检测page.getAnnotations()返回空时的PDF遮盖高亮文本
基于PDFBox 2.0.20检测黑块遮盖文本的解决方案
你调用page.getAnnotations()返回空列表,是因为该PDF中的黑色遮盖块不属于注释类对象,而是直接写在页面内容流中的矢量填充矩形,和页面文本属于同一层级的绘制内容,因此无法通过注释接口捕获。
可按照以下思路实现检测:
实现逻辑
- 第一步:自定义内容流处理器,提取目标页面所有绘制指令,筛选出填充色为纯黑的矩形对象,记录每个矩形的坐标、宽高参数
- 第二步:提取目标页面所有文本片段,记录每个文本片段的包围盒坐标、内容信息
- 第三步:做空间重叠校验,若文本片段的包围盒与黑色矩形的包围盒重合度超过80%,即可判定该文本为被黑块遮盖的目标文本
核心代码示例
import org.apache.pdfbox.contentstream.PDFStreamEngine; import org.apache.pdfbox.contentstream.operator.Operator; import org.apache.pdfbox.cos.COSBase; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.graphics.color.PDColor; import org.apache.pdfbox.text.PDFTextStripperByArea; import java.awt.*; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; // 自定义流处理器,提取黑矩形区域 public class BlackMaskExtractor extends PDFStreamEngine { private final List<Rectangle> blackMasks = new ArrayList<>(); private PDColor currentNonStrokingColor; @Override protected void processOperator(Operator operator, List<COSBase> operands) throws IOException { String opName = operator.getName(); // 捕获设置非描边颜色指令 if ("scn".equals(opName) || "sc".equals(opName)) { currentNonStrokingColor = getGraphicsState().getNonStrokingColor(); } // 捕获矩形绘制指令 else if ("re".equals(opName)) { float x = ((org.apache.pdfbox.cos.COSNumber) operands.get(0)).floatValue(); float y = ((org.apache.pdfbox.cos.COSNumber) operands.get(1)).floatValue(); float width = ((org.apache.pdfbox.cos.COSNumber) operands.get(2)).floatValue(); float height = ((org.apache.pdfbox.cos.COSNumber) operands.get(3)).floatValue(); // 判断是否为纯黑填充 if (currentNonStrokingColor != null && currentNonStrokingColor.toRGB() == Color.BLACK.getRGB()) { // 坐标系转换适配:PDF默认原点在左下角,适配文本提取常用的左上角坐标系 int pageHeight = (int) getCurrentPage().getMediaBox().getHeight(); Rectangle rect = new Rectangle((int)x, pageHeight - (int)y - (int)height, (int)width, (int)height); blackMasks.add(rect); } } super.processOperator(operator, operands); } public List<Rectangle> getBlackMasks() { return blackMasks; } } // 调用示例 public class MaskedTextDetector { public static void main(String[] args) throws IOException { PDDocument document = PDDocument.load(new File("Manafort-response.pdf")); // 目标页为5、6、7、9,PDFBox页码从0开始,对应索引为4、5、6、8 int[] targetPageIndexes = new int[]{4,5,6,8}; for (int pageIndex : targetPageIndexes) { PDPage page = document.getPage(pageIndex); // 提取当前页所有黑块区域 BlackMaskExtractor maskExtractor = new BlackMaskExtractor(); maskExtractor.processPage(page); List<Rectangle> masks = maskExtractor.getBlackMasks(); // 提取黑块覆盖区域的文本 PDFTextStripperByArea textStripper = new PDFTextStripperByArea(); for (int i = 0; i < masks.size(); i++) { textStripper.addRegion("mask_" + i, masks.get(i)); } textStripper.extractRegions(page); // 输出结果 for (int i = 0; i < masks.size(); i++) { String maskedText = textStripper.getTextForRegion("mask_" + i); if (!maskedText.trim().isEmpty()) { System.out.printf("第%d页,第%d个黑块下的文本:%s%n", pageIndex + 1, i + 1, maskedText.trim()); } } } document.close(); } }
注意事项
- 坐标适配:如果你的文本提取逻辑用的是原生PDF坐标系,可删除坐标转换的逻辑,避免坐标不匹配导致漏判
- 阈值调整:如果存在部分遮盖的场景,可根据需求调整重叠度判定的阈值,默认80%的重合度即可判定为被遮盖文本
- 图片遮盖适配:如果后续遇到用图片做遮盖的场景,需要额外提取页面中的图片区域,再和文本做重叠校验,本次目标PDF的遮盖为矢量矩形,无需额外处理图片逻辑
内容的提问来源于stack exchange,提问作者Ahmad AlMughrabi
相关产品推荐
相关产品推荐

