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

Extract text with iText not works: encoding or crypted text?

我有一个 pdf 文件,其安全属性如下:打印:允许;文件汇编:不允许;内容复制:允许;可访问性的内容副本:允许;页面 extraction:NOT 允许;

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

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.

每当您在使用标准代码从文档中提取文本时遇到问题,首先要做的就是尝试使用 Adob​​e Acrobat Reader 复制并粘贴其中的文本。 Adobe Reader copy&paste 根据 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 处的 space 字形)代表 Unicode private 中的字符 U+E0xx使用面积。正如该区域的名称所示,具有这些值的字符没有共同的含义。

因此,根据 PDF 规范的文本提取将 return 具有未定义含义的字符串,结果如您在 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 canvas,没有进一步的字符信息。

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

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

顺便说一句,这样的构造通常意味着 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);
}

附加页面如下所示:

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