首页 > 解决方案 > PDFBox 在特定的 pdf 文档中获取错误的 TextPositions

问题描述

上下文

我一直在开发一个获取 pdf 的程序,突出显示一些单词(通过 pdfbox Mark Annotation)并保存新的 pdf。

为此,我扩展了PDFTextStripper类,以覆盖writeString()方法并获取每个单词(框)的TextPositions,以便我确切地知道文本在 PDF 文档中的坐标位置(TextPosition 对象为我提供每个词框的坐标)。然后,基于此,我绘制了一个PDRectangle突出显示我想要的单词。

问题

它适用于我迄今为止尝试过的所有文档,除了我从 TextPostions 获得的位置似乎是错误的,导致错误的高亮显示。

这是原始文件:
https ://pdfhost.io/v/b1Mcpoy~s_Thomson.pdf

这是在 writeString() 提供给我的第一个单词框中突出显示的文档,其中setSortByPosition(false)MicroRNA
https
://pdfhost.io/v/V6INb4Xet_Thomson.pdf 它应该突出显示MicroRNA,但它是突出显示其上方的空白区域(粉红色 HL 矩形)。

这是在 writeString() 提供给我的第一个单词框中突出显示的文档,其中setSortByPosition(true)Original
https
://pdfhost.io/v/Lndh.j6ji_Thomson.pdf 它应该突出显示Original,但是它突出显示 PDF 文档开头的空白区域(粉红色 HL 矩形)。

我想,这个 PDF 可能包含 PDFBox 难以获得正确位置的东西,或者这可能是 PDFBox 中的一种错误。

技术规格:

PDFBox 2.0.17
Java 11.0.6+10, AdoptOpenJDK
MacOS Catalina 10.15.4, 16gb, x86_64

坐标值

因此,例如对于 MicroRNA 单词框的开始和结束,TextPosition 坐标 writeString() 给我的是:

M 字母

endX = 59.533783
endY = 682.696
maxHeight = 13.688589
rotation = 0
x = 35.886597
y = 99.26935
pageHeight = 781.96533
pageWidth = 586.97034
widthOfSpace = 11.9551
font = PDType1CFont JCFHGD+AdvT108
fontSize = 1.0
unicode = M
direction = -1.0

一封信

endX = 146.34933
endY = 682.696
maxHeight = 13.688589
rotation = 0
x = 129.18181
y = 99.26935
pageHeight = 781.96533
pageWidth = 586.97034
widthOfSpace = 11.9551
font = PDType1CFont JCFHGD+AdvT108
fontSize = 1.0
fontSizePt = 23
unicode = A
direction = -1.0

它会导致我在上面共享的错误 HL 注释,而对于所有其他 PDF 文档,这非常精确,而且我已经测试了许多不同的注释。我在这里一无所知,我也不是 PDF 定位方面的专家。我尝试使用 PDFbox 调试器工具,但无法正确阅读。这里的任何帮助将不胜感激。让我知道我是否可以提供更多证据。谢谢。

编辑

请注意,文本提取工作正常。

我的代码

首先,我创建一个坐标数组,其中包含我想要 HL 的第一个和最后一个字符的TextPosition对象的一些值:

private void extractHLCoordinates(TextPosition firstPosition, TextPosition lastPosition, int pageNumber) {
    double firstPositionX = firstPosition.getX();
    double firstPositionY = firstPosition.getY();
    double lastPositionEndX = lastPosition.getEndX();
    double lastPositionY = lastPosition.getY();

    double height = firstPosition.getHeight();
    double width = firstPosition.getWidth();
    int rotation = firstPosition.getRotation();

    double[] wordCoordinates = {firstPositionX, firstPositionY, lastPositionEndX, lastPositionY, pageNumber, 
    height, width, rotation};

    
    ...
}

现在是根据提取的坐标绘制时间:

for (int pageIndex = 0; pageIndex < pdDocument.getNumberOfPages(); pageIndex++) {

    DPage page = pdDocument.getPage(pageIndex);
    List<PDAnnotation> annotations = page.getAnnotations();

    int rotation;
    double pageHeight = page.getMediaBox().getHeight();
    double pageWidth  = page.getMediaBox().getWidth();
    
    // each CoordinatePoint obj holds the double array with the 
    // coordinates of each word I want to HL - see the previous method
    for (CoordinatePoint coordinate : coordinates) {
        double[] wordCoordinates = coordinate.getCoordinates();
        
        int pageNumber = (int) wordCoordinates[4];

        // if the current coordinates are not related to the current page, 
        //ignore them
        if ((int) pageNumber == (pageIndex + 1)) {
            // getting rotation of the page: portrait, landscape...
            rotation = (int) wordCoordinates[7];

            firstPositionX = wordCoordinates[0];
            firstPositionY = wordCoordinates[1];
            lastPositionEndX = wordCoordinates[2];
            lastPositionY = wordCoordinates[3];
            height = wordCoordinates[5];

            double height;
            double minX;
            double maxX;
            double minY;
            double maxY;
            
            if (rotation == 90) {

                double width = wordCoordinates[6];
                width = (pageHeight * width) / pageWidth;

                //defining coordinates of a rectangle
                maxX = firstPositionY;
                minX = firstPositionY - height;
                minY = firstPositionX;
                maxY = firstPositionX + width;
            } else {
                minX = firstPositionX;
                maxX = lastPositionEndX;
                minY = pageHeight - firstPositionY;
                maxY = pageHeight - lastPositionY + height;
            }
                    
            // Finally I draw the Rectangle
            PDAnnotationTextMarkup txtMark = new PDAnnotationTextMarkup(PDAnnotationTextMarkup.SUB_TYPE_HIGHLIGHT);

            PDRectangle pdRectangle = new PDRectangle();
            pdRectangle.setLowerLeftX((float) minX);
            pdRectangle.setLowerLeftY((float) minY);
            pdRectangle.setUpperRightX((float) maxX);
            pdRectangle.setUpperRightY((float) ((float) maxY + height));

            txtMark.setRectangle(pdRectangle);

            // And the QuadPoints
            float[] quads = new float[8];
            quads[0] = pdRectangle.getLowerLeftX();  // x1
            quads[1] = pdRectangle.getUpperRightY() - 2; // y1
            quads[2] = pdRectangle.getUpperRightX(); // x2
            quads[3] = quads[1]; // y2
            quads[4] = quads[0];  // x3
            quads[5] = pdRectangle.getLowerLeftY() - 2; // y3
            quads[6] = quads[2]; // x4
            quads[7] = quads[5]; // y5

            txtMark.setQuadPoints(quads);
            ...
        }
    }

标签: javapdfpdfbox

解决方案


您的 Quadpoints 坐标是相对于 CropBox 计算的,但它们需要相对于 MediaBox。对于本文档,CropBox 比 MediaBox 小,因此突出显示的位置不正确。用 CropBox.LLX - MediaBox.LLY 调整 x 和用 MediaBox.URY - CropBox.URY 调整 y,高光将在正确的位置。
上述调整适用于 Rotate = 0 的页面。如果 Rotate != 0 则可能需要进一步调整,具体取决于 PDFBox 返回坐标的方式(我对 PDFBox API 不是很熟悉)。

操作编辑

在此处发布我对代码所做的更改,以便对其他人有所帮助。请注意,我还没有尝试过旋转 == 90 的任何操作。一旦我有了这篇文章,我会在这里更新。

...
if (rotation == 90) {

    double width = wordCoordinates[6];
    width = (pageHeight * width) / pageWidth;

    //defining coordinates of a rectangle
    maxX = firstPositionY;
    minX = firstPositionY - height;
    minY = firstPositionX;
    maxY = firstPositionX + width;
} else {
    minX = firstPositionX;
    maxX = lastPositionEndX;
    minY = pageHeight - firstPositionY;
    maxY = pageHeight - lastPositionY + height;
}
...

...

PDRectangle mediaBox = page.getMediaBox();
PDRectangle cropBox = page.getCropBox();

if (rotation == 90) {

    double width = wordCoordinates[6];
    width = (pageHeight * width) / pageWidth;

    //defining coordinates of a rectangle
    maxX = firstPositionY;
    minX = firstPositionY - height;
    minY = firstPositionX;
    maxY = firstPositionX + width;
} else {
    minX = firstPositionX + cropBox.getLowerLeftX() - mediaBox.getLowerLeftY();
    maxX = lastPositionEndX + cropBox.getLowerLeftX() - mediaBox.getLowerLeftY();
    minY = pageHeight - firstPositionY - (mediaBox.getUpperRightY() - cropBox.getUpperRightY());
    maxY = pageHeight - lastPositionY + height - (mediaBox.getUpperRightY() - cropBox.getUpperRightY());
}
...

推荐阅读