itext:如何调整文本提取?

itext: how to tweak text extraction?

我正在为 Java 使用 iText 5.5.8。 遵循默认的直接文本提取程序,即

PdfTextExtractor.getTextFromPage(reader, pageNumber)

我很惊讶地发现输出中有几个错误,特别是所有字母 ds 都变成了 os。

那么 iText 中的文本提取究竟是如何工作的呢?是某种 OCR 吗?

我看了一下引擎盖,试图了解 TextExtractionStrategy 是如何工作的,但我想不通。例如,SimpleTextExtractionStrategy 似乎只是确定行和空格的存在,而 TextRenderInfo 通过在 GraphicsState 上调用一些 decode 方法来提供文本的 font 领域,这是我在没有严重偏头痛的情况下所能做到的。

那么谁是我的男人?我应该覆盖哪个 class 或者我应该调整哪个参数才能告诉 iText "hey, you're reading all ds wrong!"

编辑:

示例 PDF 可在 http://www.fpozzi.com/stampastopper/download/ 处找到 文件名是 0116_LR.pdf 抱歉,无法直接分享 link。 这是文本提取的一些基本代码

import java.io.File;
import java.io.IOException;

import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.parser.PdfTextExtractor;

public class Import
{

    public static void importFromPdf(final File pdfFile) throws IOException
    {
        PdfReader reader = new PdfReader(pdfFile.getAbsolutePath());

        try
        {

            for (int i = 1; i <= reader.getNumberOfPages(); i++)
            {
                System.out.println(PdfTextExtractor.getTextFromPage(reader, i));
                System.out.println("----------------------------------");
            }

        }
        catch (IOException e)
        {
            throw e;
        }
        finally
        {
            reader.close();
        }
    }

    public static void main(String[] args)
    {
        try
        {
            importFromPdf(new File("0116_LR.pdf"));
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }
}

在@blagae 和@mkl 回答后编辑

在开始使用 iText fiddle 之前,我已经尝试从 Apache PDFBox(一个类似于我刚刚发现的 iText 的项目)提取文本,但它确实有同样的问题。

了解这些程序如何处理文本远远超出了我的专注范围,因此我编写了一个简单的方法来从原始页面内容中提取文本,即 BT 和 ET 标记之间的任何内容。

import java.io.File;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.itextpdf.text.io.RandomAccessSourceFactory;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.RandomAccessFileOrArray;
import com.itextpdf.text.pdf.parser.ContentByteUtils;
import com.itextpdf.text.pdf.parser.PdfTextExtractor;

public class Import
{

    private final static Pattern actualWordPattern = Pattern.compile("\((.*?)\)");

    public static void importFromPdf(final File pdfFile) throws IOException
    {
        PdfReader reader = new PdfReader(pdfFile.getAbsolutePath());

        Matcher matcher;
        String line, extractedText;
        boolean anyMatchFound;
        try
        {
            for (int i = 1; i <= 16; i++)
            {
                byte[] contentBytes = ContentByteUtils.getContentBytesForPage(reader, i);
                RandomAccessFileOrArray raf = new RandomAccessFileOrArray(new RandomAccessSourceFactory().createSource(contentBytes));
                while ((line = raf.readLine()) != null && !line.equals("BT"));

                extractedText = "";
                while ((line = raf.readLine()) != null && !line.equals("ET"))
                {
                    anyMatchFound = false;
                    matcher = actualWordPattern.matcher(line);
                    while (matcher.find())
                    {
                        anyMatchFound = true;
                        extractedText += matcher.group(1);
                    }
                    if (anyMatchFound)
                        extractedText += "\n";
                }
                System.out.println(extractedText);
                System.out.println("+++++++++++++++++++++++++++");
                String properlyExtractedText = PdfTextExtractor.getTextFromPage(reader, i);
                System.out.println(properlyExtractedText);
                System.out.println("---------------------------");
            }
        }
        catch (IOException e)
        {
            throw e;
        }
        finally
        {
            reader.close();
        }
    }

    public static void main(String[] args)
    {
        try
        {
            importFromPdf(new File("0116_LR.pdf"));
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }
}

至少在我看来,字符是正确的。但是单词甚至字母的顺序都是乱七八糟的,其实超级乱,所以这种方法也不能用。

真正让我吃惊的是,到目前为止我尝试过的所有从 PDF 中检索文本的方法,包括来自 Adob​​e Reader 的 copy/paste,搞砸了。

我得出的结论是,获得一些像样的文本提取的最可靠方法也可能是最出乎意料的:一些好的 OCR。 我现在正在尝试: 1) 将 pdf 转换为图像(PDFBox 擅长于此——甚至不用费心尝试 pdf-renderer) 2)OCR那张图片 几天后我会 post 结果。

这可能首先与带有 OCR 的 PDF 的方式有关,而不是与 iTextSharp 如何解析 PDF 的内容有关。尝试 copy/pasting 将 PDF 中的文本输入记事本,然后查看 "ds -> os" 转换是否仍然发生。如果是这种情况,您将必须在解析来自该特定 PDF 的文本时执行以下操作:

  1. 识别所有出现的字符串 "os"。
  2. 判断给定 "os" 个实例构成的单词是否是一个有效的 English/German/Spanish/ 单词。
  3. 如果它是一个有效的词,什么也不做。
  4. 如果它不是一个有效的词,执行反向 "os -> ds" 转换,并再次检查您选择的语言的字典。

您的输入文档是以一种奇怪的(但 'legal')方式创建的。资源中有一个 Unicode 映射,可将任意字形映射到 Unicode 点。特别是,字符编号 0x64,ASCII 中的 d,映射到具有 Unicode 点 0x6f (UTF-8) 的字形,在此字体中为 o。这本身不是问题 - 任何 PDF 查看器都可以处理它 - 但它很奇怪,因为使用的所有其他字形都不是 "cross-mapped"。例如字符 0x63 映射到 Unicode 点 0x63(即 c)等

现在由于Acrobat 提取文本正确(除了space),其他的都出错了。我们必须为此深入研究 PDF 语法:

[p, -17.9, e, -15.1, l, 1.4, l, 8.4, i, -20,  m, 5.8, i, 14, st, -17.5, e, 31.2, ,, -20.1,  a] TJ
<</ActualText <fffffffeffffffff00640064> >> BDC
5.102 0 Td
[d, -14.2, d] TJ
EMC

告诉 PDF 查看器在代码的第一行打印 p-e-l-l-i- -m-i-st-e- -a,然后在第四行打印 d-d。然而,d 映射到 o,这显然只是文本提取的问题。 Acrobat 确实正确地进行了文本提取,因为有一个内容标记 /ActualText 表示我们在 BDC 和 EMC 标记之间写入的任何内容都必须解析为 dd (0x64,0x64)。

所以回答你的问题:iText 与许多 well-respected 观众在同一水平上这样做,他们都忽略了 /ActualText 标记。除了 Acrobat,它尊重它并否决了 ToUnicode 映射。

为了真的回答你的问题:iText目前正在研究解析/ActualText标记,但可能需要一段时间才能正式发布.