PDFBox 在特定的 pdf 文档中得到错误的 TextPositions
PDFBox getting wrong TextPositions in a specific pdf document
背景
我一直在开发一个获取 pdf 的程序,突出显示一些单词(通过 pdfbox 标记注释)并保存新的 pdf。
为此,我扩展了 PDFTextStripper class, in order to override the writeString() method and get the TextPositions of each word (box), so that I know exactly where the text is in the PDF doc in terms of coordinates (TextPosition object provides me the coordinates of each word box). Then, based on that, I draw a PDRectangle 突出显示我想要的词。
问题
它适用于我迄今为止尝试过的所有文档,除了一个我从 TextPostions 获得的位置似乎是错误的,导致错误的突出显示。
这是原始文档:
https://pdfhost.io/v/b1Mcpoy~s_Thomson.pdf
这是 writeString() 提供给我的第一个单词框中突出显示的文档,带有 setSortByPosition(false),即 MicroRNA:
https://pdfhost.io/v/V6INb4Xet_Thomson.pdf
它应该突出显示 MicroRNA,但它突出显示其上方的空白 space(粉红色 HL 矩形)。
这是 writeString() 提供给我的第一个单词框中突出显示的文档,带有 setSortByPosition(true),即 Original:
https://pdfhost.io/v/Lndh.j6ji_Thomson.pdf
它应该突出显示 Original,但它突出显示了 PDF 文档最开头的空白 space(粉红色 HL 矩形)。
我想,此 PDF 可能包含 PDFBox 难以获得正确位置的内容,或者这可能是 PDFBox 中的一种错误。
技术规格:
PDFBox 2.0.17
Java 11.0.6+10,采用OpenJDK
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);
...
}
}
您的 Quadpoints 坐标是相对于 CropBox 计算的,但它们需要相对于 MediaBox。对于此文档,CropBox 比 MediaBox 小,因此突出显示位置不正确。用 CropBox.LLX - MediaBox.LLY 调整 x,用 MediaBox.URY - CropBox.URY 调整 y,突出显示将在正确的位置。
上面的调整适用于 Rotate = 0 的页面。如果 Rotate != 0,则可能需要进一步调整,具体取决于 PDFBox 返回坐标的方式(我对 PDFBox API 不是很熟悉)。
OP 编辑
在此处发布我对代码所做的更改,以便对其他人有所帮助。
请注意,我还没有为 rotate == 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());
}
...
背景
我一直在开发一个获取 pdf 的程序,突出显示一些单词(通过 pdfbox 标记注释)并保存新的 pdf。
为此,我扩展了 PDFTextStripper class, in order to override the writeString() method and get the TextPositions of each word (box), so that I know exactly where the text is in the PDF doc in terms of coordinates (TextPosition object provides me the coordinates of each word box). Then, based on that, I draw a PDRectangle 突出显示我想要的词。
问题
它适用于我迄今为止尝试过的所有文档,除了一个我从 TextPostions 获得的位置似乎是错误的,导致错误的突出显示。
这是原始文档:
https://pdfhost.io/v/b1Mcpoy~s_Thomson.pdf
这是 writeString() 提供给我的第一个单词框中突出显示的文档,带有 setSortByPosition(false),即 MicroRNA:
https://pdfhost.io/v/V6INb4Xet_Thomson.pdf
它应该突出显示 MicroRNA,但它突出显示其上方的空白 space(粉红色 HL 矩形)。
这是 writeString() 提供给我的第一个单词框中突出显示的文档,带有 setSortByPosition(true),即 Original:
https://pdfhost.io/v/Lndh.j6ji_Thomson.pdf
它应该突出显示 Original,但它突出显示了 PDF 文档最开头的空白 space(粉红色 HL 矩形)。
我想,此 PDF 可能包含 PDFBox 难以获得正确位置的内容,或者这可能是 PDFBox 中的一种错误。
技术规格:
PDFBox 2.0.17
Java 11.0.6+10,采用OpenJDK
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);
...
}
}
您的 Quadpoints 坐标是相对于 CropBox 计算的,但它们需要相对于 MediaBox。对于此文档,CropBox 比 MediaBox 小,因此突出显示位置不正确。用 CropBox.LLX - MediaBox.LLY 调整 x,用 MediaBox.URY - CropBox.URY 调整 y,突出显示将在正确的位置。
上面的调整适用于 Rotate = 0 的页面。如果 Rotate != 0,则可能需要进一步调整,具体取决于 PDFBox 返回坐标的方式(我对 PDFBox API 不是很熟悉)。
OP 编辑
在此处发布我对代码所做的更改,以便对其他人有所帮助。 请注意,我还没有为 rotate == 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());
}
...