【问题标题】:Using PDFbox to determine the coordinates of words in a document使用 PDFbox 确定文档中单词的坐标
【发布时间】:2012-08-06 02:39:53
【问题描述】:

我正在使用 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 不够熟悉,即使还包含空格,也无法准确地将这些字符连接回单词以进行搜索。有没有其他人遇到过类似的情况,如果有,您是如何处理的?我真的只需要单词中第一个字符的坐标,以便简化部分,但至于我将如何将字符串与那种输出进行匹配是我无法做到的。

【问题讨论】:

    标签: java pdf pdfbox


    【解决方案1】:

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

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

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

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

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

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

    【讨论】:

    • 我喜欢这个。您是否有特定原因不只是寻找空格字符,而是在构建列表时将其用作分隔符?当提供标签等内容时,坐标比较似乎会更加严格,但由于逐字符计算添加了字符,它似乎也可能“更慢”。此外,Java 中的“列表”是否等同于数组或在某些方面具有优势?
    • PDFBox 可能会从某些文档中提取空格字符,但在其他文档中只会提取字母。因此,要构建一个不错的应用程序,您应该(我认为)遵循这些步骤,然后如果您找到一个等于“”(空格)的字形,那么您可以直接拆分行。这可能是一个想法。我真的不知道列表是否比数组更有利,因为列表的具体实现可能是 ArrayList,我会说它在某种程度上具有可比性。我主要使用列表,因为它易于操作。
    • @user1437888,根据我有限的经验,我处理的 PDF 文档中没有任何空格字符。文本被定位,并且必须从没有文本的空间大小的区域推断空间。布莱奇!
    • 关于单词之间的间隙...有时有空格字符有时只是向右跳转...这主要取决于PDF生成软件。
    • 根据个人经验,最好探索一下PDFBoxpdfbox.apache.org/download.cgi源代码中出现的原始示例,尤其是org.apache.pdfbox.examples.util.PrintTextLocations
    【解决方案2】:

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

    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, 字体框, Apache 通用日志接口。

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

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

    输出类似于:

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

    【讨论】:

    • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。
    • 此代码已过时。方法processStream(...) 不存在。
    • 此代码不适用于最新版本(pdfbox v2.0.8)
    【解决方案3】:

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

    【讨论】:

    • 问题:只保留单词最后位置的坐标。但总的来说,它很好地展示了搜索功能。
    • 这对我有很大帮助,只需要很少的改动
    • @MarinosAn 请考虑添加对您有用的示例。
    • @sura2k:你能帮我在上面做些什么改变来获得协调吗?
    • 如何获取此类 PDFTextLocation?
    【解决方案4】:

    我使用 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);
        }
    
    }
    

    【讨论】:

      【解决方案5】:

      对于那些仍然需要帮助的人,这是我在我的代码中使用的,应该很有用。它使用 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>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-01-10
        • 1970-01-01
        • 2018-02-15
        • 2012-01-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多