【问题标题】:how can we extract text from pdf using itextsharp with spaces?我们如何使用带空格的 itextsharp 从 pdf 中提取文本?
【发布时间】:2013-04-30 04:37:51
【问题描述】:

我正在使用以下方法逐行提取 pdf 文本。但问题是,它不是阅读单词和数字之间的空格。有什么办法可以解决这个问题??

我只想创建一个字符串列表,列表对象中的每个字符串都有一个来自 pdf 的文本行,就像它在 pdf 中一样,包括空格。

public void readtextlinebyline(string filename)   {


        List<string> strlist = new List<string>();
        PdfReader reader = new PdfReader(filename);
        string text = string.Empty;
        for (int page = 1; page <= 1; page++)
        {

            text += PdfTextExtractor.GetTextFromPage(reader, page ,new LocationTextExtractionStrategy())+" ";

        }
        reader.Close();
        string[] words = text.Split('\n');
        foreach (string word in words)
        {
            strlist.Add(word);
        }

        foreach (string st in strlist)
        {
            Response.Write(st +"<br/>");
        }

   }

我也通过将策略更改为 SimpleTextExtractionStrategy 来尝试此方法,但它也不适合我。

【问题讨论】:

  • 这个answer to "itext java pdf to text creation" 可以说明原因并提示解决方案:复制文本提取策略并调整内部参数,在您的情况下将间隙的最小宽度识别为空格,@默认为 987654323@;回问的人通过renderInfo.getSingleSpaceWidth()/4f 获得了改进的结果。
  • @Pengu 当你提供赏金时,你肯定会遇到这个问题。因此,您当然可以提供一个或多个示例 PDF 作为建议解决方案的测试用例。问题的当前状态使得回答纯粹是猜测。
  • @mkl 很抱歉回复晚了,我的连接中断了。我不喜欢的不是你的解决方案(它有效)——让我感到不满的是这个解决方案可能不可靠。 F.E:它适用于一个文件,但可能在另一个文件上会产生太多空格(因为该文档需要 renderInfo.getSingleSpaceWidth()/2f 或完全不同的分隔符)。我还没有一个例子,但我可以想象它会发生。所以我从“更”可靠的来源寻求答案。
  • @Pengu 不幸的是,您不会轻易获得通用的 100% 可靠的解决方案。我指出的答案中提到了一些难以获得它的问题。很难区分字距调整和紧密排列的单词。
  • @mkl 是的,我是这么想的。悲伤但不可改变。我也尝试了很多事情,比如尝试根据字体计算空间大小等,但没有什么比你已经发布的解决方案更好的了。如果您再次将您的解决方案发布为 answear,我可以给您声誉。

标签: c# pdf extract extraction pdf-reader


【解决方案1】:

为什么 iText(Sharp) 或其他 PDF 文本提取器有时无法正确识别单词之间的空格的背景,已在 this answer to "itext java pdf to text creation" 中解释:这些“空格”不一定使用空格字符创建,而是使用操作创造了一个小的差距。但是,这些操作也用于其他目的(不会断词),因此文本提取器必须使用启发式方法来确定这种间隙是否是断词......

这尤其意味着您永远无法获得 100% 安全的断字检测。

不过,您可以做的是改进所使用的启发式方法。

iText 和 iTextSharp 标准文本提取策略,例如如果

a) 有一个空格字符或

b) 至少有半个空格字符那么宽。

项目 a 肯定会成功,但项目 b 在密集设置文本的情况下可能经常失败。 answer referenced above 的问题的 OP 使用空格字符宽度的四分之一代替,得到了相当好的结果。

您可以通过复制和更改您选择的文本提取策略来调整这些标准。

SimpleTextExtractionStrategy 中,您会发现此标准嵌入在renderText 方法中:

if (spacing > renderInfo.GetSingleSpaceWidth()/2f){
    AppendTextChunk(' ');
}

LocationTextExtractionStrategy的情况下,这个标准同时被放入了它自己的方法中:

/**
 * Determines if a space character should be inserted between a previous chunk and the current chunk.
 * This method is exposed as a callback so subclasses can fine tune the algorithm for determining whether a space should be inserted or not.
 * By default, this method will insert a space if the there is a gap of more than half the font space character width between the end of the
 * previous chunk and the beginning of the current chunk.  It will also indicate that a space is needed if the starting point of the new chunk 
 * appears *before* the end of the previous chunk (i.e. overlapping text).
 * @param chunk the new chunk being evaluated
 * @param previousChunk the chunk that appeared immediately before the current chunk
 * @return true if the two chunks represent different words (i.e. should have a space between them).  False otherwise.
 */
protected bool IsChunkAtWordBoundary(TextChunk chunk, TextChunk previousChunk) {
    float dist = chunk.DistanceFromEndOf(previousChunk);
    if(dist < -chunk.CharSpaceWidth || dist > chunk.CharSpaceWidth / 2.0f)
        return true;
    return false;
}

将其放入自己的方法中的目的是仅需要对策略进行简单的子类化并覆盖此方法以调整启发式标准。这在等效的 iText Java 类的情况下工作正常,但在移植到 iTextSharp 期间,不幸的是,声明中没有添加 virtual(从 5.4.4 版开始)。因此,目前 iTextSharp 仍然需要复制整个策略。

@Bruno 你可能想告诉 iText -> iTextSharp 移植团队这件事。

虽然您可以在这些代码位置微调文本提取,但您应该注意,您不会在此处找到 100% 的标准。一些原因是:

  • 密集文本中单词之间的间隙可以小于字距调整或其他间隙,以实现单词内部的某些视觉效果。因此,这里没有一刀切的因素。
  • 在根本不使用空格字符的 PDF 中(因为您始终可以使用间隙,这是可能的),“空格字符的宽度”可能是某个随机值或根本无法确定!
  • 有一些有趣的 PDF 滥用空格字符宽度(可以在任何时候单独拉伸以进行操作)在使用间隙进行分词时进行一些表格格式设置。在这样的 PDF 中,空格字符的当前宽度值不能认真地用于确定分词。
  • 有时您会在一行中发现为了强调而间隔打印的单字。这些可能会被大多数启发式方法解析为单字母单词的集合。

通过考虑所有字符之间的实际可视可用空间(使用 PDF 渲染或字体信息分析机制),您可以获得比 iText 启发式和使用其他常量派生的更好的启发式方法,但要获得可感知的改进,您必须投入大量时间。

【讨论】:

  • 优秀的文章。 @mkl,您可能想在 iText 错误跟踪器中打开一个关于 iTextSharp 端口的问题(不确定布鲁诺是否会看到这个)。
  • 据我所知,iTextSharp Port 同时已将virtual 添加到此LocationTextExtractionStrategy 方法中。实际上不仅是这种方法,而且几乎每个public 方法。
  • 出色的答案。正是我需要的信息,并且写得非常完整和清晰。非常感谢。
【解决方案2】:

我有自己的实现,效果很好。

    /// <summary>
    /// Read a PDF file and returns the string content.
    /// </summary>
    /// <param name="par">ByteArray, MemoryStream or URI</param>
    /// <returns>FileContent.</returns>
    public static string ReadPdfFile(object par)
    {
        if (par == null) throw new ArgumentNullException("par");

        PdfReader pdfReader = null;
        var text = new StringBuilder();

        if (par is MemoryStream)
            pdfReader = new PdfReader((MemoryStream)par);
        else if (par is byte[])
            pdfReader = new PdfReader((byte[])par);
        else if (par is Uri)
            pdfReader = new PdfReader((Uri)par);

        if (pdfReader == null)
            throw new InvalidOperationException("Unable to read the file.");

        for (var page = 1; page <= pdfReader.NumberOfPages; page++)
        {
            var strategy = new SimpleTextExtractionStrategy();
            var currentText = PdfTextExtractor.GetTextFromPage(pdfReader, page, strategy);
            currentText = Encoding.UTF8.GetString(Encoding.Convert(Encoding.Default, Encoding.UTF8, Encoding.Default.GetBytes(currentText)));
            text.Append(currentText);
        }

        pdfReader.Close();

        return text.ToString();
    }

【讨论】:

  • 您将标准 iText(Sharp) 文本提取机制与 SimpleTextExtractionStrategy 一起使用,而 OP 将其与 LocationTextExtractionStrategy 一起使用。虽然这肯定是不同的,但它们使用基本相同的启发式方法来确定分词(空格字符或间隔至少是空格字符宽度的一半)。因此,这几乎不会比原始代码做得更好。
  • mkl 是对的。这在某些情况下可能有效,但在像我这样的其他情况下会失败。 (由于编码不同,我得到了无法识别的字符)此外,SimpleTextExtractionStrategy 在我的情况下也没有正确插入 '\n' 所以我必须构建我的自定义 RenderListener (因为我也需要提取图像)并调整代码以满足我的要求,例如更改从orientationMagnitude == other 检测新行的条件。 OrientationMagnitude 到 Math.Abs​​(orientationMagnitude - other.OrientationMagnitude)
【解决方案3】:
using (PdfReader reader = new PdfReader(path))
            {
                StringBuilder text = new StringBuilder();
                StringBuilder textfinal = new StringBuilder();
                String page = "";
                for (int i = 1; i <= reader.NumberOfPages; i++)
                {
                    text.Append(PdfTextExtractor.GetTextFromPage(reader, i));
                    page = PdfTextExtractor.GetTextFromPage(reader, i);
                    string[] lines = page.Split('\n');
                    foreach (string line in lines)
                    {
                        string[] words = line.Split('\n');
                        foreach (string wrd in words)
                        {

                        }
                        textfinal.Append(line);
                        textfinal.Append(Environment.NewLine); 
                    }
                    page = "";
                }
           }

【讨论】:

  • 你想用这段代码表达什么?
猜你喜欢
  • 2014-06-06
  • 1970-01-01
  • 2014-12-26
  • 1970-01-01
  • 2017-09-19
  • 2012-08-28
  • 1970-01-01
  • 1970-01-01
  • 2013-03-18
相关资源
最近更新 更多