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

时间:2012-08-08 21:56:29

标签: java pdf pdfbox

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

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不熟悉,能够准确地将这些字符连接成单词以进行搜索,即使这些空格也包含在内。有没有其他人处于类似的情况,如果是这样,你是如何接近它的?我真的只需要单词中第一个字符的坐标,以便简化部分,但是我将如何匹配字符串与这种输出相匹配是超出我的。

5 个答案:

答案 0 :(得分:8)

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

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

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

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

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

    < / LI>

    我将这种方法应用到我的工作中并且效果很好。

答案 1 :(得分:5)

基于最初的想法,这里是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());
            }
        }

    }


}

答案 2 :(得分:1)

看看这个,我认为这就是你需要的。

https://jackson-brain.com/using-pdfbox-to-locate-text-coordinates-within-a-pdf-in-java/

以下是代码:

import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

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;

public class PrintTextLocations extends PDFTextStripper {

public static StringBuilder tWord = new StringBuilder();
public static String seek;
public static String[] seekA;
public static List wordList = new ArrayList();
public static boolean is1stChar = true;
public static boolean lineMatch;
public static int pageNo = 1;
public static double lastYVal;

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

public static void main(String[] args)
        throws Exception {
    PDDocument document = null;
    seekA = args[1].split(",");
    seek = args[1];
    try {
        File input = new File(args[0]);
        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);
            PDStream contents = page.getContents();

            if (contents != null) {
                printer.processStream(page, page.findResources(), page.getContents().getStream());
            }
            pageNo += 1;
        }
    } finally {
        if (document != null) {
            System.out.println(wordList);
            document.close();
        }
    }
}

@Override
protected void processTextPosition(TextPosition text) {
    String tChar = text.getCharacter();
    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 REGEX = "[,.\\[\\](:;!?)/]";
    char c = tChar.charAt(0);
    lineMatch = matchCharLine(text);
    if ((!tChar.matches(REGEX)) && (!Character.isWhitespace(c))) {
        if ((!is1stChar) && (lineMatch == true)) {
            appendChar(tChar);
        } else if (is1stChar == true) {
            setWordCoord(text, tChar);
        }
    } else {
        endWord();
    }
}

protected void appendChar(String tChar) {
    tWord.append(tChar);
    is1stChar = false;
}

protected void setWordCoord(TextPosition text, String tChar) {
    tWord.append("(").append(pageNo).append(")[").append(roundVal(Float.valueOf(text.getXDirAdj()))).append(" : ").append(roundVal(Float.valueOf(text.getYDirAdj()))).append("] ").append(tChar);
    is1stChar = false;
}

protected void endWord() {
    String newWord = tWord.toString().replaceAll("[^\\x00-\\x7F]", "");
    String sWord = newWord.substring(newWord.lastIndexOf(' ') + 1);
    if (!"".equals(sWord)) {
        if (Arrays.asList(seekA).contains(sWord)) {
            wordList.add(newWord);
        } else if ("SHOWMETHEMONEY".equals(seek)) {
            wordList.add(newWord);
        }
    }
    tWord.delete(0, tWord.length());
    is1stChar = true;
}

protected boolean matchCharLine(TextPosition text) {
    Double yVal = roundVal(Float.valueOf(text.getYDirAdj()));
    if (yVal.doubleValue() == lastYVal) {
        return true;
    }
    lastYVal = yVal.doubleValue();
    endWord();
    return false;
}

protected Double roundVal(Float yVal) {
    DecimalFormat rounded = new DecimalFormat("0.0'0'");
    Double yValDub = new Double(rounded.format(yVal));
    return yValDub;
}
}

依赖关系:

  

PDFBox的,   FontBox,   Apache通用日志记录接口。

您可以通过在命令行上输入来运行它:

javac PrintTextLocations.java 
sudo java PrintTextLocations file.pdf WORD1,WORD2,....

输出类似于:

[(1)[190.3 : 286.8] WORD1, (1)[283.3 : 286.8] WORD2, ...]

答案 3 :(得分:0)

我使用IKVM转换PDFBox.NET 1.8.9进行了此工作。在C#和.NET中。

我终于弄清楚了字符(字形)坐标是.NET程序集专用的,但是可以使用System.Reflection进行访问。

我在此处发布了一个完整的示例,该示例获取WORDS的坐标并将其使用SVG和HTML绘制到PDF的图像上:https://github.com/tsamop/PDF_Interpreter

对于下面的示例,您需要PDFbox.NET:http://www.squarepdf.net/pdfbox-in-net,并在项目中包括对其的引用。

我花了很长时间才弄清楚,所以我真的希望它可以节省其他人的时间!

如果您只需要知道在哪里查找字符和坐标,则可以使用以下缩写:

    using System;
using System.Reflection;

using org.apache.pdfbox.pdmodel;
using org.apache.pdfbox.util;

  // to test run pdfTest.RunTest(@"C:\temp\test_2.pdf");

class pdfTest
{
    //simple example for getting character (gliph) coordinates out of a pdf doc.
    // a more complete example is here:   https://github.com/tsamop/PDF_Interpreter
    public static void RunTest(string sFilename)
    {
    //probably a better way to get page count, but I cut this out of a bigger project.
    PDDocument oDoc = PDDocument.load(sFilename);
        object[] oPages = oDoc.getDocumentCatalog().getAllPages().toArray();

        int iPageNo = 0; //1's based!!
        foreach (object oPage in oPages)
        {
            iPageNo++;

            //feed the stripper a page.
            PDFTextStripper tStripper = new PDFTextStripper();
            tStripper.setStartPage(iPageNo);
            tStripper.setEndPage(iPageNo);
            tStripper.getText(oDoc);

            //This gets the "charactersByArticle" private object in PDF Box.
            FieldInfo charactersByArticleInfo = typeof(PDFTextStripper).GetField("charactersByArticle", BIndingFlags.NonPublic | BindingFlags.Instance);
            object charactersByArticle = charactersByArticleInfo.GetValue(tStripper);

            object[] aoArticles = (object[])charactersByArticle.GetField("elementData");

            foreach (object oArticle in aoArticles)
            {
                if (oArticle != null)
                {

                    //THE CHARACTERS within the article
                    object[] aoCharacters = (object[])oArticle.GetField("elementData");

                    foreach (object oChar in aoCharacters)
                    {

                        /*properties I caulght using reflection:
                         * endX, endY, font, fontSize, fontSizePt, maxTextHeight, pageHeight, pageWidth, rot, str textPos, unicodCP, widthOfSpace, widths, wordSpacing, x, y
                         * 
                         */
                        if (oChar != null)
                        {
                            //this is a really quick test.  
                            // for a more complete solution that pulls the characters into words and displays the word positions on the page, try this:  https://github.com/tsamop/PDF_Interpreter
                            //the Y's appear to be the bottom of the char?
                            double mfMaxTextHeight = Convert.ToDouble(oChar.GetField("maxTextHeight")); //I think this is the height of the character/word

                            char mcThisChar = oChar.GetField("str").ToString().ToCharArray()[0];
                            double mfX = Convert.ToDouble(oChar.GetField("x"));
                            double mfY = Convert.ToDouble(oChar.GetField("y")) - mfMaxTextHeight;

                            //CALCULATE THE OTHER SIDE OF THE GLIPH
                            double mfWidth0 = ((Single[])oChar.GetField("widths"))[0];
                            double mfXend = mfX + mfWidth0; // Convert.ToDouble(oChar.GetField("endX"));

                            //CALCULATE THE BOTTOM OF THE GLIPH.
                            double mfYend = mfY + mfMaxTextHeight; //  Convert.ToDouble(oChar.GetField("endY"));

                            double mfPageHeight = Convert.ToDouble(oChar.GetField("pageHeight"));
                            double mfPageWidth = Convert.ToDouble(oChar.GetField("pageWidth"));

                            System.Diagnostics.Debug.Print(@"add some stuff to test {0}, {1}, {2}", mcThisChar, mfX, mfY);

                        }
                    }

                }
            }

        }
    }
}



using System.Reflection;

/// <summary>
/// To deal with the Java interface hiding necessary properties! ~mwr
/// </summary>
public static class GetField_Extension
{
    public static object GetField(this object randomPDFboxObject, string sFieldName)
    {
            FieldInfo itemInfo = randomPDFboxObject.GetType().GetField(sFieldName, BindingFlags.NonPublic | BindingFlags.Instance);
            return itemInfo.GetValue(randomPDFboxObject);
    }

}

答案 4 :(得分:0)

对于仍然需要帮助的人,这是我在代码中使用的内容,一开始应该很有用。它使用PDFBox 2.0.16

public class PDFTextLocator extends PDFTextStripper {
    
    private static String key_string;
    private static float x;
    private static float y;
    
    public PDFTextLocator() throws IOException {
        x = -1;
        y = -1;
    }
    
    /**
     * Takes in a PDF Document, phrase to find, and page to search and returns the x,y in float array
     * @param document
     * @param phrase
     * @param page
     * @return
     * @throws IOException
     */
    public static float[] getCoordiantes(PDDocument document, String phrase, int page) throws IOException {
        key_string = phrase;
        PDFTextStripper stripper = new PDFTextLocator();
        stripper.setSortByPosition(true);
        stripper.setStartPage(page);
        stripper.setEndPage(page);
        stripper.writeText(document, new OutputStreamWriter(new ByteArrayOutputStream()));
        y = document.getPage(page).getMediaBox().getHeight()-y;
        
        return new float[]{x,y};
    }
    
    /**
     * Override the default functionality of PDFTextStripper.writeString()
     */
    @Override
    protected void writeString(String string, List<TextPosition> textPositions) throws IOException {
        if(string.contains(key_string)) {
            TextPosition text = textPositions.get(0);
            if(x == -1) {
                x = text.getXDirAdj();
                y = text.getYDirAdj();
            }        
        }
    }
}

下面是Maven依赖项详细信息,

<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.16</version>
</dependency>