使用 iText 提取文本不起作用:编码或加密文本?

Posted

技术标签:

【中文标题】使用 iText 提取文本不起作用:编码或加密文本?【英文标题】:Extract text with iText not works: encoding or crypted text? 【发布时间】:2016-06-10 12:29:59 【问题描述】:

我有一个 pdf 文件,它具有以下安全属性:打印:允许;文件汇编:不允许;内容复制:允许;可访问性的内容副本:允许;页面提取:不允许;

我尝试获取带有示例代码的文本作为文档示例,如下所示:

pdftext.Text = null;
StringBuilder text = new StringBuilder();
PdfReader pdfReader = new PdfReader(filename);
for (int page = 1; page <= pdfReader.NumberOfPages; page++)

    ITextExtractionStrategy strategy = new SimpleTextExtractionStrategy();
    string currentText = PdfTextExtractor.GetTextFromPage(pdfReader, page, strategy);
    text.Append(System.Environment.NewLine);
    text.Append("\n Page Number:" + page);
    text.Append(System.Environment.NewLine);
    currentText = Encoding.UTF8.GetString(ASCIIEncoding.Convert(Encoding.Default, Encoding.UTF8, Encoding.Default.GetBytes(currentText)));
    text.Append(currentText);
    progressBar1.Value++;

    

pdftext.Text += text.ToString();
pdfReader.Close();

但输出文本是带有“”的行??? ? ???????\n?? ??? ? " 价值观;

似乎文件已加密或我们有编码问题...

请注意以下几行

var f = pdfReader.IsOpenedWithFullPermissions; -> FALSE
var f1 = pdfReader.IsEncrypted(); - > FALSE
var f2 = pdfReader.ComputeUserPassword(); - > NULL
var f3 = pdfReader.Is128Key(); - > FALSE
var f4 = pdfReader.HasUsageRights();

f, f1, f3, f4 return FALSE ...似乎文档没有加密, ...所以我不知道是编码问题还是与加密字符串相关的问题...

有人可以帮助我吗? 提前致谢。 G.G.

【问题讨论】:

经过调查,我发现问题是定制的。 pdfinfo 的结果是: 作者:用户创建者:Compart Docponent API 生产者:Compart MFFPDF I/O 过滤器 2013-03-09 00:51:11 CreationDate: 04/21/16 11:26:59 ModDate: 06/09/ 16 10:02:16 Tagged: no Form: none 页数: 6 Encrypted: no Page size: 595.2 x 841.92 pts (A4) (rotated 0°) 文件大小: 312703 bytes 优化: yes PDF 版本: 1.4 pdffont 的结果是(对于每种字体): name:[none] ;类型:类型 3 ; emb:是的;子:没有;单:是的;所以 pdf 不是 encryptde 并且 ToUnicode 表的存在有机会提取文本..但我不知道如何......?有人可以帮助我吗? 请分享有问题的 PDF。没有它,我们只能猜测。不过,前面要说一句:有许多 PDF,您无法使用通用文本提取代码从中提取文本。如果您的 PDF 是其中之一,那么可能会证明除了 OCR 之外什么都行不通。 您好,感谢您的回复。你可以在这里下载 pdf drive.google.com/file/d/0B0f6X4SAMh2KRDJTbm4tb3E1a1U/… 【参考方案1】:

当您在使用标准代码从文档中提取文本时遇到问题时,首先要做的是尝试使用 Adob​​e Acrobat Reader 从文档中复制和粘贴文本。 Adobe Reader 复制粘贴根据 PDF 规范的建议执行文本提取,如果失败,这通常意味着文档中提取文本所需的必要信息丢失或损坏(意外或设计)。要提取文本,需要专门针对特定 PDF 自定义代码或使用 OCR。

对于手头的文档,Adobe Reader 复制和粘贴也会导致垃圾,就像使用 iText 提取时一样。因此,文档中有一些可疑之处。

检查文档发现字体包含 ToUnicode 映射,如下所示:

/CIDInit /ProcSet
findresource begin 12 dict begin begincmap /CIDSystemInfo<</Registry(Adobe)
/Ordering(Identity)
/Supplement 0
>>
def
/CMapName/F18 def
1 begincodespacerange <0000> <FFFF> endcodespacerange
44 beginbfrange
<20> <20> <0020>
<21> <21> <E0F9>
<22> <22> <E0F1>
<23> <23> <E0FA>
<24> <24> <E0F7>
<25> <25> <E0A3>
<26> <26> <E084>
<27> <27> <E097>
<28> <28> <E098>
<29> <29> <E09A>
<2A> <2A> <E08A>
<2B> <2B> <E099>
<2C> <2C> <E0A5>
<2D> <2D> <E086>
<2E> <2E> <E094>
<2F> <2F> <E0DE>
<30> <30> <E0A6>
<31> <31> <E096>
<32> <32> <E088>
<33> <33> <E082>
<34> <34> <E04C>
<35> <35> <E0A4>
<36> <36> <E0F6>
<37> <37> <E0F2>
<38> <38> <E0D8>
<39> <39> <E0AA>
<3A> <3A> <E06C>
<3B> <3B> <E087>
<3C> <3C> <E095>
<3D> <3D> <E0C4>
<3E> <3E> <E07E>
<3F> <3F> <E055>
<40> <40> <E089>
<41> <41> <E085>
<42> <42> <E083>
<43> <43> <E070>
<44> <44> <E0E6>
<45> <45> <E080>
<46> <46> <E0C8>
<47> <47> <E0F4>
<48> <48> <E062>
<49> <49> <E0F3>
<4A> <4A> <E04E>
<4B> <4B> <E05E>
endbfrange
endcmap CMapName currentdict /CMap defineresource pop end end 

也就是说,如果您对此不感兴趣,字体会声称它们的所有字形(除了 0x20 处的空格字形)都代表 Unicode 专用区域中的字符 U+E0xx。正如该区域的名称所示,具有这些值的字符没有共同的含义。

因此,根据 PDF 规范进行文本提取将返回具有未定义含义的字符串,其结果与您在 iText 中观察到的或我在 Adob​​e Reader 中看到的结果相同。


有时在这种情况下,仍然可以通过忽略 ToUnicode 映射并使用字体 Encoding 或嵌入字体程序中的信息来强制执行正确的文本提取。

不幸的是,这里的 Encoding 有效地包含与 ToUnicode 映射相同的信息,例如对于与上面相同的字体

/Differences [ 32 /space /uniE0F9 /uniE0F1 /uniE0FA /uniE0F7 /uniE0A3 /uniE084 /uniE097 /uniE098 
/uniE09A /uniE08A /uniE099 /uniE0A5 /uniE086 /uniE094 /uniE0DE /uniE0A6 /uniE096 
/uniE088 /uniE082 /uniE04C /uniE0A4 /uniE0F6 /uniE0F2 /uniE0D8 /uniE0AA /uniE06C 
/uniE087 /uniE095 /uniE0C4 /uniE07E /uniE055 /uniE089 /uniE085 /uniE083 /uniE070 
/uniE0E6 /uniE080 /uniE0C8 /uniE0F4 /uniE062 /uniE0F3 /uniE04E /uniE05E ] 

字体是 Type3 字体,即没有嵌入式字体程序,但每个字形都被定义为单独的 PDF 画布,没有更多的字符信息。

因此,这里也没有任何收获。

实际上,这些小的 PDF 画布包含相应字形的内联位图图形,这也是导致文档图形质量差的原因(如果您没有立即看到,只需放大一点即可会看到字形参差不齐的轮廓)。

顺便说一句,这样的结构通常意味着 PDF 的制作者明确希望阻止文本提取。


如果您碰巧必须从许多此类文档中提取文本,您可以尝试确定从它们的 U+E0xx 字符到实际可理解的 Unicode 字符的映射,并将该映射应用于您提取的文本。

如果所有这些文档中的所有字体碰巧对相同的实际字符使用相同的 U+E0xx 代码点,您将能够在投入一定数量后从这些文档中提取文本初步工作。

否则请尝试 OCR。


以下代码将页面添加到将 ToUnicode 值映射到显示的字符的文档中:

void AddFontsTo(PdfReader reader, PdfStamper stamper)

    int documentPages = reader.NumberOfPages;
    for (int page = 1; page <= documentPages; page++)
    
        // ignore inherited resources for now
        PdfDictionary pageResources = reader.GetPageResources(page);
        if (pageResources == null)
            continue;
        PdfDictionary pageFonts = pageResources.GetAsDict(PdfName.FONT);
        if (pageFonts == null || pageFonts.Size == 0)
            continue;

        List<BaseFont> fonts = new List<BaseFont>();
        List<string> fontNames = new List<string>();
        HashSet<char> chars = new HashSet<char>();
        foreach (PdfName key in pageFonts.Keys)
        
            PdfIndirectReference fontReference = pageFonts.GetAsIndirectObject(key);
            if (fontReference == null)
                continue;
            DocumentFont font = (DocumentFont) BaseFont.CreateFont((PRIndirectReference)fontReference);
            if (font == null)
                continue;

            PdfObject toUni = PdfReader.GetPdfObjectRelease(font.FontDictionary.Get(PdfName.TOUNICODE));
            CMapToUnicode toUnicodeCmap = null; 
            if (toUni is PRStream)
            
                try
                
                    byte[] touni = PdfReader.GetStreamBytes((PRStream)toUni);
                    CidLocationFromByte lb = new CidLocationFromByte(touni);
                    toUnicodeCmap = new CMapToUnicode();
                    CMapParserEx.ParseCid("", toUnicodeCmap, lb);
                
                catch
                
                    toUnicodeCmap = null;
                
            
            if (toUnicodeCmap == null)
                continue;
            ICollection<int> mapValues = toUnicodeCmap.CreateDirectMapping().Values;
            if (mapValues.Count == 0)
                continue;

            fonts.Add(font);
            fontNames.Add(key.ToString());

            foreach (int value in mapValues)
                chars.Add((char)value);
        
        if (fonts.Count == 0 || chars.Count == 0)
            continue;

        Rectangle size = (fonts.Count > 10) ? PageSize.A4.Rotate() : PageSize.A4;

        PdfPTable table = new PdfPTable(fonts.Count + 1);
        table.AddCell("Page " + page);
        foreach (String name in fontNames)
        
            table.AddCell(name);
        
        table.HeaderRows = 1;
        float[] widths = new float[fonts.Count + 1];
        widths[0] = 2;
        for (int i = 1; i <= fonts.Count; i++)
            widths[i] = 1;
        table.SetWidths(widths);
        table.WidthPercentage = 100;

        List<char> charList = new List<char>(chars);
        charList.Sort();
        foreach (char character in charList)
        
            table.AddCell(((int)character).ToString("X4"));
            foreach (BaseFont font in fonts)
            
                table.AddCell(new PdfPCell(new Phrase(character.ToString(), new Font(font))));
            
        

        stamper.InsertPage(reader.NumberOfPages + 1, size);
        ColumnText columnText = new ColumnText(stamper.GetUnderContent(reader.NumberOfPages));
        columnText.AddElement(table);
        columnText.SetSimpleColumn(size);
        while ((ColumnText.NO_MORE_TEXT & columnText.Go(false)) == 0)
        
            stamper.InsertPage(reader.NumberOfPages + 1, size);
            columnText.Canvas = stamper.GetUnderContent(reader.NumberOfPages);
            columnText.SetSimpleColumn(size);
        
    

我将它应用到您的文档中,如下所示:

string input = @"4700198773.pdf";
string output = @"4700198773-fonts.pdf";

using (PdfReader reader = new PdfReader(input))
using (FileStream stream = new FileStream(output, FileMode.Create, FileAccess.Write))
using (PdfStamper stamper = new PdfStamper(reader, stream))

    AddFontsTo(reader, stamper);

附加页面如下所示:

现在您必须将本文档的不同字体和页面的输出相互比较,并与代表性文件选择的输出进行比较。如果你找到足够好的模式,你可以尝试这种替换方式。

【讨论】:

抱歉回复晚了,非常感谢您的回答。您能否帮助我了解如何尝试确定从其 U+E0xx 字符到实际合理的 Unicode 字符的映射,并将该映射应用于提取的文本?提前致谢 这不是微不足道的。我会尝试创造一些东西,但这需要一些时间。 非常感谢您的耐心 mkl... 我将该代码运行到其他“损坏”的 pdf 中,结果是没有添加额外的页面...因为 pdf 没有字体映射。 .(我在 Adob​​e -> 属性 -> 字体中打开了该文档,但没有显示任何字体信息)..所以我想我不能从这个洞里得到任何东西...... @sschuberth 在我回答的示例代码中,您会看到使用font.FontDictionary.Get(PdfName.TOUNICODE) 检索的 ToUnicode 表。您应该可以使用 font.FontDictionary.Put(PdfName.TOUNICODE, null) 将其删除。 @sschuberth 实际上,如果您的 PDF 文本提取成功,忽略 ToUnicode 表,请参阅。我的 Java 测试方法TextExtraction.testPbNoToUnicode()。但我认为始终忽略非嵌入字体的 ToUnicode 表并不是一个好主意,在其他情况下,这些表可能是您正确提取文本的唯一机会。

以上是关于使用 iText 提取文本不起作用:编码或加密文本?的主要内容,如果未能解决你的问题,请参考以下文章

如果我使用数字或特殊字符,文本到二进制转换器不起作用

如何使用 RegEx 提取单词之间的文本?

iText如何提取PDF中的数据——1. 总览

iText如何提取PDF中的数据——1. 总览

如何在文本框中验证邮政编码并将相应的州/城市输出与其各自的标签相对应

使用 itext 进行数字签名验证不起作用