IText 无法正确读取 PDF 区域

IText won't read correctly PDF region

我正在使用 LocationTextExtractionStrategy 结合自定义 ITextExtractionStrategy class 阅读 PDF。使用此代码,我可以毫无问题地阅读基于坐标的文档部分。

现在我得到的 PDF 看起来和其他的一样,但如果我尝试阅读它,我会得到这样的文本:

2  D   80 D   8 1 M 13M2 R   V / 8  3B 3 3 710 022/F//0 R8 8        1 0 / 3

这是我正在使用的代码:

private static string ReadFilePart(string fileName,int pageNumber, int fromLeft, int fromBottom, int width, int height)
{
        var rect = new System.util.RectangleJ(fromLeft, fromBottom, width, height);
        var pdfReader = new PdfReader(fileName);

        var filters = new RenderFilter[1];
        filters[0] = new RegionTextRenderFilter(rect);

        var strategy = new FilteredTextRenderListener(new LocationTextExtractionStrategy(), filters);
        var pageText = PdfTextExtractor.GetTextFromPage(pdfReader, pageNumber, new LimitedTextStrategy(strategy));

        pdfReader.Close();

        return pageText;
}

private class LimitedTextStrategy : ITextExtractionStrategy
{

        public readonly ITextExtractionStrategy textextractionstrategy;

        public LimitedTextStrategy(ITextExtractionStrategy strategy)
        {
            textextractionstrategy = strategy;
        }

        public void RenderText(TextRenderInfo renderInfo)
        {
            foreach (TextRenderInfo info in renderInfo.GetCharacterRenderInfos())
            {
                textextractionstrategy.RenderText(info);
            }
        }
        public string GetResultantText()
        {
            return textextractionstrategy.GetResultantText();
        }

        public void BeginTextBlock()
        {
            textextractionstrategy.BeginTextBlock();

        }
        public void EndTextBlock()
        {
            textextractionstrategy.EndTextBlock();

        }
        public void RenderImage(ImageRenderInfo renderInfo)
        {
            textextractionstrategy.RenderImage(renderInfo);
        }
 }

由于数据敏感,我无法共享 PDF 文件。

更新

如果我将 LocationTextExtractionStrategy 更改为 SimpleTextExtractionStrategy,它会识别没有奇怪字符的整行(PDF 结构?)。

更新 2

我现在可以共享文件了!有问题的页面是 2° 和 3°

PDF file

Test solution to read the file

更新 3

mkl 为我指明了正确的方向,我修复了将 FistCharLastCharWidths 添加到所有缺少默认值属性的字体。

private static PdfReader FontFix(PdfReader pdfReader)
{
        for (var p = 1; p <= pdfReader.NumberOfPages; p++)
        {
            var dic = pdfReader.GetPageN(p);
            var resources = dic.GetAsDict(PdfName.RESOURCES);

            var fonts = resources?.GetAsDict(PdfName.FONT);
            if (fonts == null) continue;

            foreach (var key in fonts.Keys)
            {
                var font = fonts.GetAsDict(key);

                var firstChar = font.Get(PdfName.FIRSTCHAR);
                if(firstChar==null)
                    font.Put(PdfName.FIRSTCHAR, new PdfNumber(0));

                var lastChar = font.Get(PdfName.LASTCHAR);
                if (lastChar == null)
                    font.Put(PdfName.LASTCHAR, new PdfNumber(255));

                var widths= font.GetAsArray(PdfName.WIDTHS);

                if (widths == null)
                {
                    var array=new int[256];
                    array=Enumerable.Repeat(600, 256).ToArray();
                    font.Put(PdfName.WIDTHS, new PdfArray(array));
                }
            }
        }
        return pdfReader;
  } 

PDF 中的错误

导致此问题的原因是 PDF 包含一个不完整的字体词典。 PDF 中的大多数字体词典都是完整的,但有一个例外,对象 28 中的词典用于共享资源中的字体 Fo0,用于“填写”页面上的字段二和三:

<<
/Name       /Fo0
/Subtype    /TrueType
/BaseFont   /CourierNew
/Type       /Font
/Encoding   /WinAnsiEncoding
>>

特别是此字体字典不包含所需的 Widths 条目,其值将是字体字形宽度的数组。

因此,iTextSharp 不知道字形实际有多宽,并使用 0 作为默认值。

顺便说一句,对于一组非常有限的 Type 1 字体,即所谓的 标准 14 种字体,允许使用此类不完整的字体词典(尽管已弃用)。 TrueType字体“CourierNew”显然不在其中。但是创建软件的开发人员对上述不完整的结构负责,可能不关心查看 PDF 规范,而只是按照那些特殊的 Type 1 字体的示例。

对代码的影响

在您的 LimitedTextStrategy.RenderText 实施中

public void RenderText(TextRenderInfo renderInfo)
{
    foreach (TextRenderInfo info in renderInfo.GetCharacterRenderInfos())
    {
        textextractionstrategy.RenderText(info);
    }
}

您将 renderInfo(描述更长的字符串)拆分为多个 TextRenderInfo 实例(每个实例描述一个字形)。如果 renderInfo 的字体是关键 Fo0,所有这些 TextRenderInfo 实例都具有相同的位置,因为 iTextSharp 假定字形宽度为 0。

...使用 LocationTextExtractionStrategy

那些 TextRenderInfo 个实例然后被过滤并转发到 LocationTextExtractionStrategy,后者随后按位置对它们进行排序。由于位置重合,并且所使用的排序算法不会使元素按其原始顺序保持相同位置,因此 排序 有效地打乱了它们。最终你得到所有对应的字符,顺序混乱。

...使用 SimpleTextExtractionStrategy

在这种情况下,那些 TextRenderInfo 个实例然后被过滤并转发到 SimpleTextExtractionStrategy,后者 对它们进行排序,而是将相应的字符添加到结果字符串。如果在内容流中显示操作的文本按阅读顺序发生,则策略返回的结果也是按正确的阅读顺序。

为什么 Adob​​e Reader 以正确的顺序显示文本?

如果遇到损坏的 PDF,不同的程序可以尝试不同的策略来应对这种情况。

Adobe Reader 在手头的案例中很可能会在操作系统中搜索 CourierNew TrueType 字体程序并使用那里的宽度信息。这很可能是那个损坏的字体结构的创建者所希望的。