如何借助PDFBox准确转换PDF图片与文本坐标以移除图片上的文字
解决PDFBox中图片坐标与文本坐标不匹配的问题
你的核心问题是图片坐标转换逻辑错误,导致和TextPosition返回的文本坐标无法匹配。以下是修正方案:
一、错误原因分析
原代码的图片坐标转换存在三个关键问题:
- 直接使用图片原始像素尺寸(
imageWidth/imageHeight),忽略了CTM(当前变换矩阵)的缩放、旋转、切变效果,实际显示尺寸由CTM决定而非原始像素。 - 错误手动引入切变参数(
shearX/shearY),CTM已经包含所有变换,无需额外加减。 - Y轴坐标系转换逻辑颠倒,PDF默认以左下角为原点(Y向上),如果你的文本坐标使用左上角为原点(Y向下),转换方式错误。
二、修正后的图片坐标获取代码
通过CTM变换图片的四个顶点,计算出图片在用户空间的准确包围盒,再根据需求转换到文本坐标系:
@Override protected void processOperator(Operator operator, List<COSBase> operands) throws IOException { String operation = operator.getName(); if ("Do".equals(operation)) { COSName objectName = (COSName) operands.get(0); PDXObject xobject = getResources().getXObject(objectName); if (xobject instanceof PDImageXObject) { PDImageXObject image = (PDImageXObject) xobject; float imgWidth = image.getWidth(); float imgHeight = image.getHeight(); Matrix ctm = getGraphicsState().getCurrentTransformationMatrix(); // 变换图片四个顶点到用户空间坐标(PDF默认左下角为原点) Point2D.Float p0 = ctm.transformPoint(0, 0); Point2D.Float p1 = ctm.transformPoint(imgWidth, 0); Point2D.Float p2 = ctm.transformPoint(imgWidth, imgHeight); Point2D.Float p3 = ctm.transformPoint(0, imgHeight); // 计算图片的最小/最大包围盒坐标 float minX = Math.min(Math.min(p0.x, p1.x), Math.min(p2.x, p3.x)); float maxX = Math.max(Math.max(p0.x, p1.x), Math.max(p2.x, p3.x)); float minY = Math.min(Math.min(p0.y, p1.y), Math.min(p2.y, p3.y)); float maxY = Math.max(Math.max(p0.y, p1.y), Math.max(p2.y, p3.y)); // 转换到文本坐标系(左上角为原点,Y轴向下) PDRectangle mediaBox = getCurrentPage().getMediaBox(); float pageHeight = mediaBox.getHeight(); float textMinY = pageHeight - maxY; float textMaxY = pageHeight - minY; // 存入坐标数组(根据你的Coordinate类结构调整参数) Coordinate coordinate = new Coordinate( (int) minX, (int) textMinY, (int) maxX, (int) textMaxY, pageNum ); coordinates.add(coordinate); } else if (xobject instanceof PDFormXObject) { PDFormXObject form = (PDFormXObject) xobject; showForm(form); } } }
三、修正文本坐标的匹配逻辑
TextPosition返回的x1/y1是字符基线的坐标,height是从基线到字符顶部的距离,需要确保和图片坐标使用同一坐标系,同时改用重叠检测(而非完全包含),避免遗漏部分覆盖的字符:
@Override protected void processTextPosition(TextPosition text) { PDPage pdpage = getCurrentPage(); int pageno = getCurrentPageNo(); super.processTextPosition(text); // ... 其他字符属性获取代码保持不变 ... float x1 = text.getX(); float y1 = text.getY(); float height = text.getHeight(); float width = text.getUnicode().trim().isEmpty() ? text.getWidthOfSpace() : text.getWidth(); // 转换字符坐标到文本坐标系(和图片坐标统一) float pageHeight = pdpage.getMediaBox().getHeight(); float charTextY1 = pageHeight - (y1 + height); float charTextY2 = pageHeight - y1; float charTextX2 = x1 + width; PCharacter pchar = new PCharacter(pageno, text.getDir(), x1, charTextY1, charTextX2, charTextY2, ...); // 判断字符是否与图片重叠 boolean isOnImage = PSVGReader.coordinates.stream().anyMatch(coordinate -> pageno == coordinate.getPageNo() && x1 < coordinate.getX2() // 字符左边界小于图片右边界 && charTextX2 > coordinate.getX1() // 字符右边界大于图片左边界 && charTextY1 < coordinate.getY2() // 字符上边界小于图片下边界 && charTextY2 > coordinate.getY1() // 字符下边界大于图片上边界 ); // 只保留不在图片上的字符 if (!isOnImage) { page.rawCharacters().add(pchar); } pages().put(pageno, page); }
关键注意事项
- 坐标系统一:必须确保图片坐标和文本坐标使用同一基准(要么都是PDF原生的左下角原点,要么都是左上角原点),否则必然匹配失败。
- CTM变换的重要性:所有图形元素的位置都受CTM影响,直接手动计算缩放、旋转会忽略复杂变换场景(比如斜切、旋转的图片)。
- 重叠检测更实用:实际场景中字符可能只部分覆盖图片,用重叠判断比完全包含更准确。
内容的提问来源于stack exchange,提问作者Abhijeet
相关产品推荐
相关产品推荐

