使用 PDFbox 确定文档中单词的坐标

2022-09-02 01:40:47

我正在使用PDFbox来提取PDF文档中单词/字符串的坐标,到目前为止,我已经成功地确定了单个字符的位置。这是到目前为止的代码,来自PDFbox文档:

package printtextlocations;

import java.io.*;
import org.apache.pdfbox.exceptions.InvalidPasswordException;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.util.PDFTextStripper;
import org.apache.pdfbox.util.TextPosition;

import java.io.IOException;
import java.util.List;

public class PrintTextLocations extends PDFTextStripper {

    public PrintTextLocations() throws IOException {
        super.setSortByPosition(true);
    }

    public static void main(String[] args) throws Exception {

        PDDocument document = null;
        try {
            File input = new File("C:\\path\\to\\PDF.pdf");
            document = PDDocument.load(input);
            if (document.isEncrypted()) {
                try {
                    document.decrypt("");
                } catch (InvalidPasswordException e) {
                    System.err.println("Error: Document is encrypted with a password.");
                    System.exit(1);
                }
            }
            PrintTextLocations printer = new PrintTextLocations();
            List allPages = document.getDocumentCatalog().getAllPages();
            for (int i = 0; i < allPages.size(); i++) {
                PDPage page = (PDPage) allPages.get(i);
                System.out.println("Processing page: " + i);
                PDStream contents = page.getContents();
                if (contents != null) {
                    printer.processStream(page, page.findResources(), page.getContents().getStream());
                }
            }
        } finally {
            if (document != null) {
                document.close();
            }
        }
    }

    /**
     * @param text The text to be processed
     */
    @Override /* this is questionable, not sure if needed... */
    protected void processTextPosition(TextPosition text) {
        System.out.println("String[" + text.getXDirAdj() + ","
                + text.getYDirAdj() + " fs=" + text.getFontSize() + " xscale="
                + text.getXScale() + " height=" + text.getHeightDir() + " space="
                + text.getWidthOfSpace() + " width="
                + text.getWidthDirAdj() + "]" + text.getCharacter());
    }
}

这将生成一系列包含每个字符位置的行,包括空格,如下所示:

String[202.5604,41.880127 fs=1.0 xscale=13.98 height=9.68814 space=3.8864403 width=9.324661]P

其中“P”是字符。我无法在PDFbox中找到一个函数来查找单词,而且我对Java不够熟悉,无法将这些字符准确地连接回单词进行搜索,即使空格也包括在内。有没有其他人遇到过类似的情况,如果是这样,你是怎么做到的?我真的只需要单词中第一个字符的坐标,以便简化部分,但是至于我将如何使字符串与这种输出相匹配,这超出了我的范围。


答案 1

PDFBox中没有允许您自动提取单词的功能。我目前正在提取数据以将其收集到块中,这是我的过程:

  1. 我提取文档的所有字符(称为字形)并将其存储在列表中。

  2. 我对每个字形的坐标进行分析,在列表上循环。如果它们重叠(如果当前字形的顶部包含在前一个字形的顶部和底部之间/或者当前字形的底部包含在前一个字形的顶部和底部之间),则将其添加到同一行中。

  3. 此时,我已经提取了文档的不同行(请注意,如果您的文档是多列的,则表达式“lines”表示垂直重叠的所有字形,即具有相同垂直坐标的所有列的文本)。

  4. 然后,您可以将当前字形的左坐标与前一个字形的右坐标进行比较,以确定它们是否属于同一个单词(PDFTextStripper 类提供了一个 getSpacingTolerance() 方法,该方法根据试验和错误为您提供“正常”空间的值。如果右坐标和左坐标之间的差小于此值,则两个字形属于同一个单词。

我将这种方法应用于我的工作,效果很好。


答案 2

基于最初的想法,这里是PDFBox 2的文本搜索版本。代码本身很粗糙,但很简单。它应该让你相当快地开始。

import java.io.IOException;
import java.io.Writer;
import java.util.List;
import java.util.Set;
import lu.abac.pdfclient.data.PDFTextLocation;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;

public class PrintTextLocator extends PDFTextStripper {

    private final Set<PDFTextLocation> locations;

    public PrintTextLocator(PDDocument document, Set<PDFTextLocation> locations) throws IOException {
        super.setSortByPosition(true);
        this.document = document;
        this.locations = locations;
        this.output = new Writer() {
            @Override
            public void write(char[] cbuf, int off, int len) throws IOException {
            }
            @Override
            public void flush() throws IOException {
            }

            @Override
            public void close() throws IOException {
            }
        };
    }

    public Set<PDFTextLocation> doSearch() throws IOException {

        processPages(document.getDocumentCatalog().getPages());
        return locations;
    }

    @Override
    protected void writeString(String text, List<TextPosition> textPositions) throws IOException {
        super.writeString(text);

        String searchText = text.toLowerCase();
        for (PDFTextLocation textLoc:locations) {
            int start = searchText.indexOf(textLoc.getText().toLowerCase());
            if (start!=-1) {
                // found
                TextPosition pos = textPositions.get(start);
                textLoc.setFound(true);
                textLoc.setPage(getCurrentPageNo());
                textLoc.setX(pos.getXDirAdj());
                textLoc.setY(pos.getYDirAdj());
            }
        }

    }


}

推荐