【问题标题】:How do I extract actual font names from a PDF with iTextSharp?如何使用 iTextSharp 从 PDF 中提取实际字体名称?
【发布时间】:2019-01-18 18:03:56
【问题描述】:

我正在使用 iTextSharp 进行 PDF 处理,我需要从现有 PDF 中提取所有以某种字体编写的文本。

A way to do thatinherit from a RenderFilter 并且只允许具有特定PostscriptFontName 的文本。问题是当我这样做时,我在 PDF 中看到以下字体名称:

CIDF字体+F1 CIDF字体+F2 CIDF字体+F3 CIDF字体+F4 CIDF字体+F5

这与我正在寻找的实际字体名称完全不同。

也就是说,我无法在文档结构中的任何位置看到实际的字体名称。

然而,当我在文档画布上选择各种文本框(例如 Arial、Courier New、Roboto)时,Adobe Acrobat DC 确实在“格式”窗格中显示了正确的字体名称,因此信息必须存储在某处。

使用 iTextSharp 解析 PDF 时如何获取这些真实字体名称?

【问题讨论】:

  • 可以分享一下pdf吗?我假设名称仅存在于实际的嵌入式字体程序中,而不存在于它的 pdf 元数据中。 Itext(就像 adobe acrobat 一样,除非您做一些强制它查看实际字体程序的操作)只查看字体元数据,因此只查看匿名名称。顺便说一句,这严格来说是pdf中的错误。但这是一个通常没人关心的错误。
  • 确实,嵌入式字体程序(TTF 等)确实包含您要查找的字体名称。 iText 不会查看它们的名称,严格来说,iText 不需要这样做,因为 PDF 规范要求您访问的 BaseName 必须从字体程序中获取,所以严格来说,您的 PDF 已损坏(即使在几乎没有任何软件会抱怨)。尽管如此,您还是可以查看代码中的字体程序。

标签: c# pdf fonts itext


【解决方案1】:

在问题的 cmets 过程中确定,字体名称在字体的所有 PDF 元数据中都是匿名的,但嵌入的字体程序本身包含实际的字体名称。

(因此严格来说,PDF 已损坏,尽管在某种程度上几乎没有任何软件会抱怨。)

因此,如果我们想要检索这些名称,我们必须查看这些字体程序的内部。

这里是一个概念证明,遵循您引用的this answer 中使用的架构,即使用RenderFilter

class FontProgramRenderFilter : RenderFilter
{
    public override bool AllowText(TextRenderInfo renderInfo)
    {
        DocumentFont font = renderInfo.GetFont();
        PdfDictionary fontDict = font.FontDictionary;
        PdfName subType = fontDict.GetAsName(PdfName.SUBTYPE);
        if (PdfName.TYPE0.Equals(subType))
        {
            PdfArray descendantFonts = fontDict.GetAsArray(PdfName.DESCENDANTFONTS);
            PdfDictionary descendantFont = descendantFonts[0] as PdfDictionary;
            PdfDictionary fontDescriptor = descendantFont.GetAsDict(PdfName.FONTDESCRIPTOR);
            PdfStream fontStream = fontDescriptor.GetAsStream(PdfName.FONTFILE2);
            byte[] fontData = PdfReader.GetStreamBytes((PRStream)fontStream);
            MemoryStream dataStream = new MemoryStream(fontData);
            dataStream.Position = 0;
            MemoryPackage memoryPackage = new MemoryPackage();
            Uri uri = memoryPackage.CreatePart(dataStream);
            GlyphTypeface glyphTypeface = new GlyphTypeface(uri);
            memoryPackage.DeletePart(uri);
            ICollection<string> names = glyphTypeface.FamilyNames.Values;
            return names.Where(name => name.Contains("Arial")).Count() > 0;
        }
        else
        {
            // analogous code for other font subtypes
            return false;
        }
    }
}

MemoryPackage 类来自this answer,这是我第一次发现如何使用 .Net 从内存中的字体读取信息。

像这样应用于您的 PDF 文件:

using (PdfReader pdfReader = new PdfReader(SOURCE))
{
    FontProgramRenderFilter fontFilter = new FontProgramRenderFilter();
    ITextExtractionStrategy strategy = new FilteredTextRenderListener(
            new LocationTextExtractionStrategy(), fontFilter);
    Console.WriteLine(PdfTextExtractor.GetTextFromPage(pdfReader, 1, strategy));
}

结果是

This is Arial.

注意:这只是概念验证。

一方面,您肯定还需要实现上面注释为analogous code for other font subtypes 的部分;甚至TYPE0 部分还没有准备好用于生产,因为它只考虑FONTFILE2 并且不能优雅地处理null 值。

另一方面,您需要缓存已检查字体的名称。

【讨论】:

  • 非常感谢,这是一个非常有用的例子。我不是 PDF 如何在二进制流中存储字体的专家,根据您的评论,我假设提取 FamilyNames 或类似代码的代码在 Type 0Type 2Type 3、@987654336 之间完全不同@ 和 OpenType?
  • “在...之间会完全不同” - 至少位置不同。对于带有 TTF 的类型 0,请参阅上面的代码,对于带有 CFF 的类型 0,您必须检查 FontFile3 而不是 FontFile2。对于 Type 1 或 TrueType 简单字体,没有 DescendantFonts 字典,FontDescriptor 条目在字体字典本身中,Type 1 字体使用 FontFileFontFile3 键。对于 Type 3 字体,显然没有要检查的字体程序。而且我还没有检查GlyphTypeface 是否知道处理 Adob​​e Type 1 字体和 CFF 字体。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-08-07
  • 2020-07-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-24
  • 2011-08-22
相关资源
最近更新 更多