如何在Java中从PDF文档中搜索特定字符串或单词及其坐标

4
我正在使用Pdfbox从pdf文件中搜索一个单词(或字符串),我还想知道该单词的坐标。 例如:在pdf文件中有一个类似于"${abc}"的字符串,我想知道这个字符串的坐标。 我尝试了一些例子,但是没有得到我想要的结果。结果显示的是字符的坐标。
以下是代码:
@Override
protected void writeString(String string, List<TextPosition> textPositions) throws IOException {
    for(TextPosition text : textPositions) {


        System.out.println( "String[" + text.getXDirAdj() + "," +
                text.getYDirAdj() + " fs=" + text.getFontSize() + " xscale=" +
                text.getXScale() + " height=" + text.getHeightDir() + " space=" +
                text.getWidthOfSpace() + " width=" +
                text.getWidthDirAdj() + "]" + text.getUnicode());

    }
}

我正在使用pdfbox 2.0


1
我尝试了一些例子,但没有得到我想要的结果。结果显示字符的坐标。但是,如果你有每个字符的坐标,你可以将它们组合起来得到你搜索的术语的坐标,不是吗? - mkl
@mkl,请问您能否解释一下如何将它们合并在一起? - Ashok Kanwal
首先,你的问题中的 writeString 代码片段是否按正确顺序输出字符?如果没有,你是否使用 setSortByPosition(true) 初始化了 PdfTextStripper - mkl
是的,@mkl,“writeString”代码片段按正确顺序输出字符,并且我已使用“setSortByPosition(true)”初始化了“PDFTextStripper”。 - Ashok Kanwal
好的,那么将这些“TextPosition”实例拼接在一起应该很容易。我稍后会研究一下。(我不知道周末是否有时间。) - mkl
显示剩余3条评论
4个回答

12
PDFBox的PDFTextStripper类中仍具有位置信息的文本(在变为纯文本之前)的最后一种方法是:
/**
 * Write a Java string to the output stream. The default implementation will ignore the <code>textPositions</code>
 * and just calls {@link #writeString(String)}.
 *
 * @param text The text to write to the stream.
 * @param textPositions The TextPositions belonging to the text.
 * @throws IOException If there is an error when writing the text.
 */
protected void writeString(String text, List<TextPosition> textPositions) throws IOException

应该在这里拦截,因为该方法接收预处理的、特别是已经排序TextPosition对象(如果一开始就要求排序)。

(实际上,我更喜欢在调用方法writeLine中拦截,因为根据其参数和本地变量的名称,其所有TextPosition实例都有一个,并且每个word调用writeString一次;不幸的是,PDFBox开发人员已将此方法声明为私有...嗯,也许在最终的2.0.0版本中会有所改变...暗示,暗示更新:不幸的是,在发布中它没有改变...叹气

此外,使用一个帮助类来包装TextPosition实例序列,以使代码更清晰,也很有帮助。

有了这个想法,可以像这样搜索变量。

List<TextPositionSequence> findSubwords(PDDocument document, int page, String searchTerm) throws IOException
{
    final List<TextPositionSequence> hits = new ArrayList<TextPositionSequence>();
    PDFTextStripper stripper = new PDFTextStripper()
    {
        @Override
        protected void writeString(String text, List<TextPosition> textPositions) throws IOException
        {
            TextPositionSequence word = new TextPositionSequence(textPositions);
            String string = word.toString();

            int fromIndex = 0;
            int index;
            while ((index = string.indexOf(searchTerm, fromIndex)) > -1)
            {
                hits.add(word.subSequence(index, index + searchTerm.length()));
                fromIndex = index + 1;
            }
            super.writeString(text, textPositions);
        }
    };
    
    stripper.setSortByPosition(true);
    stripper.setStartPage(page);
    stripper.setEndPage(page);
    stripper.getText(document);
    return hits;
}
使用这个帮助类。
public class TextPositionSequence implements CharSequence
{
    public TextPositionSequence(List<TextPosition> textPositions)
    {
        this(textPositions, 0, textPositions.size());
    }

    public TextPositionSequence(List<TextPosition> textPositions, int start, int end)
    {
        this.textPositions = textPositions;
        this.start = start;
        this.end = end;
    }

    @Override
    public int length()
    {
        return end - start;
    }

    @Override
    public char charAt(int index)
    {
        TextPosition textPosition = textPositionAt(index);
        String text = textPosition.getUnicode();
        return text.charAt(0);
    }

    @Override
    public TextPositionSequence subSequence(int start, int end)
    {
        return new TextPositionSequence(textPositions, this.start + start, this.start + end);
    }

    @Override
    public String toString()
    {
        StringBuilder builder = new StringBuilder(length());
        for (int i = 0; i < length(); i++)
        {
            builder.append(charAt(i));
        }
        return builder.toString();
    }

    public TextPosition textPositionAt(int index)
    {
        return textPositions.get(start + index);
    }

    public float getX()
    {
        return textPositions.get(start).getXDirAdj();
    }

    public float getY()
    {
        return textPositions.get(start).getYDirAdj();
    }

    public float getWidth()
    {
        if (end == start)
            return 0;
        TextPosition first = textPositions.get(start);
        TextPosition last = textPositions.get(end - 1);
        return last.getWidthDirAdj() + last.getXDirAdj() - first.getXDirAdj();
    }

    final List<TextPosition> textPositions;
    final int start, end;
}

要仅输出它们的位置、宽度、最终字母和最终字母位置,您可以使用以下代码:

void printSubwords(PDDocument document, String searchTerm) throws IOException
{
    System.out.printf("* Looking for '%s'\n", searchTerm);
    for (int page = 1; page <= document.getNumberOfPages(); page++)
    {
        List<TextPositionSequence> hits = findSubwords(document, page, searchTerm);
        for (TextPositionSequence hit : hits)
        {
            TextPosition lastPosition = hit.textPositionAt(hit.length() - 1);
            System.out.printf("  Page %s at %s, %s with width %s and last letter '%s' at %s, %s\n",
                    page, hit.getX(), hit.getY(), hit.getWidth(),
                    lastPosition.getUnicode(), lastPosition.getXDirAdj(), lastPosition.getYDirAdj());
        }
    }
}

为了测试,我使用MS Word创建了一个小的测试文件:

Sample file with variables

这个测试的输出:

@Test
public void testVariables() throws IOException
{
    try (   InputStream resource = getClass().getResourceAsStream("Variables.pdf");
            PDDocument document = PDDocument.load(resource);    )
    {
        System.out.println("\nVariables.pdf\n-------------\n");
        printSubwords(document, "${var1}");
        printSubwords(document, "${var 2}");
    }
}
是什么意思?
Variables.pdf
-------------

* Looking for '${var1}'
  Page 1 at 164.39648, 158.06 with width 34.67856 and last letter '}' at 193.22, 158.06
  Page 1 at 188.75699, 174.13995 with width 34.58806 and last letter '}' at 217.49, 174.13995
  Page 1 at 167.49583, 190.21997 with width 38.000168 and last letter '}' at 196.22, 190.21997
  Page 1 at 176.67009, 206.18 with width 35.667114 and last letter '}' at 205.49, 206.18

* Looking for '${var 2}'
  Page 1 at 164.39648, 257.65997 with width 37.078552 and last letter '}' at 195.62, 257.65997
  Page 1 at 188.75699, 273.74 with width 37.108047 and last letter '}' at 220.01, 273.74
  Page 1 at 167.49583, 289.72998 with width 40.55017 and last letter '}' at 198.74, 289.72998
  Page 1 at 176.67778, 305.81 with width 38.059418 and last letter '}' at 207.89, 305.81

我有点惊讶,因为如果在单行上找到了${var 2};毕竟,PDFBox代码让我认为我重写的writeString方法只检索单词;看起来它检索比单词更长的行部分...

如果您需要来自已分组的TextPosition实例的其他数据,请相应地增强TextPosition序列


我需要每个搜索词的最后一个字符的坐标,例如如果我搜索 ${var 2},那么我只需要 } 的坐标。 - Ashok Kanwal
TextPositionSequence中添加对单个TextPosition实例的访问是微不足道的,详见我的编辑。 - mkl
1
@AshokKanwal 这个回答是否足够解答了你的问题? - mkl
代码无法正常工作,请在下方检查。 - Ashok Kanwal
检查下面什么? - mkl
显示剩余6条评论

0

如上所述,这不是对您问题的回答,但以下是一个骨架示例,展示了您如何在IText中完成此操作。这并不意味着在Pdfbox中不可能实现相同的功能。

基本上,您需要创建一个RenderListener,接受“解析事件”发生时的信息。将此侦听器传递给PdfReaderContentParser.processContent。在侦听器的renderText方法中,您可以获取重建布局所需的所有信息,包括x/y坐标以及组成内容的文本/图像/等。

RenderListener listener = new RenderListener() {
    @Override
    public void renderText(TextRenderInfo arg0) {
        LineSegment segment = arg0.getBaseline();
        int x = (int) segment.getStartPoint().get(Vector.I1);
        // smaller Y means closer to the BOTTOM of the page. So we negate the Y to get proper top-to-bottom ordering
        int y = -(int) segment.getStartPoint().get(Vector.I2);
        int endx = (int) segment.getEndPoint().get(Vector.I1);
        log.debug("renderText "+x+".."+endx+"/"+y+": "+arg0.getText());
        ...
    }

    ... // other overrides
};

PdfReaderContentParser p = new PdfReaderContentParser(reader);
for (int i = 1; i <= reader.getNumberOfPages(); i++) {
    log.info("handling page "+i);
    p.processContent(i, listener);
}

实际上,在PDF文件中,字符串看起来像____${datefield}_____________,但我需要${datefield}的坐标。有没有办法获取这些坐标? - Ashok Kanwal
1
请查看 TextRenderInfo.getCharacterRenderInfos()。如果侦听器需要访问文本呈现操作中每个单独字形的位置,则此方法提供有用的详细信息。 - geert3

0

我正在寻找在PDF文件中突出显示不同单词的方法。为此,我需要正确了解单词坐标,因此我正在从左上角的第一个字母获取(x,y)坐标,并从右上角的最后一个字母获取(x,y)坐标。

随后,将这些点保存在一个数组中。请记住,为了正确获取y坐标,您需要相对于页面大小的相对位置,因为给定的坐标可能与页面中的坐标不匹配。但是getYDirAdj()方法是绝对的,很多时候与页面中的方法不匹配。

protected void writeString(String string, List<TextPosition> textPositions) throws IOException {
    boolean isFound = false;
    float posXInit  = 0, 
          posXEnd   = 0, 
          posYInit  = 0,
          posYEnd   = 0,
          width     = 0, 
          height    = 0, 
          fontHeight = 0;
    String[] criteria = {"Word1", "Word2", "Word3", ....};

    for (int i = 0; i < criteria.length; i++) {
        if (string.contains(criteria[i])) {
            isFound = true;
        } 
    }
    if (isFound) {
        posXInit = textPositions.get(0).getXDirAdj();
        posXEnd  = textPositions.get(textPositions.size() - 1).getXDirAdj() + textPositions.get(textPositions.size() - 1).getWidth();
        posYInit = textPositions.get(0).getPageHeight() - textPositions.get(0).getYDirAdj();
        posYEnd  = textPositions.get(0).getPageHeight() - textPositions.get(textPositions.size() - 1).getYDirAdj();
        width    = textPositions.get(0).getWidthDirAdj();
        height   = textPositions.get(0).getHeightDir();

        System.out.println(string + "X-Init = " + posXInit + "; Y-Init = " + posYInit + "; X-End = " + posXEnd + "; Y-End = " + posYEnd + "; Font-Height = " + fontHeight);

        float quadPoints[] = {posXInit, posYEnd + height + 2, posXEnd, posYEnd + height + 2, posXInit, posYInit - 2, posXEnd, posYEnd - 2};

        List<PDAnnotation> annotations = document.getPage(this.getCurrentPageNo() - 1).getAnnotations();
        PDAnnotationTextMarkup highlight = new PDAnnotationTextMarkup(PDAnnotationTextMarkup.SUB_TYPE_HIGHLIGHT);

        PDRectangle position = new PDRectangle();
        position.setLowerLeftX(posXInit);
        position.setLowerLeftY(posYEnd);
        position.setUpperRightX(posXEnd);
        position.setUpperRightY(posYEnd + height);

        highlight.setRectangle(position);

        // quadPoints is array of x,y coordinates in Z-like order (top-left, top-right, bottom-left,bottom-right) 
        // of the area to be highlighted

        highlight.setQuadPoints(quadPoints);

        PDColor yellow = new PDColor(new float[]{1, 1, 1 / 255F}, PDDeviceRGB.INSTANCE);
        highlight.setColor(yellow);
        annotations.add(highlight);
    }
}

0

你可以试试这个

@Override
protected void writeString(String str, List<TextPosition> textPositions) throws IOException {
    TextPosition startPos = textPositions.get(0);
    TextPosition endPos = textPositions.get(textPositions.size() - 1);

    System.out.println(str + " [(" + startPos.getXDirAdj() + "," + startPos.getYDirAdj() + ") ,("
            + endPos.getXDirAdj() + "," + endPos.getYDirAdj() + ")]");

}

输出将类似于 'String [(54.0,746.08) ,(99.71,746.08)]'


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接