Я использую iTextSharp для обработки PDF, и мне нужно извлечь весь текст из существующего PDF, который написан определенным шрифтом.
Способ сделать это заключается в том, чтобы наследоваться от RenderFilter
и разрешить только тот текст, который имеет определенное PostscriptFontName
. Проблема в том, что когда я делаю это, я вижу следующие названия шрифтов в PDF:
CIDFont+F1 CIDFont+F2 CIDFont+F3 CIDFont+F4 CIDFont+F5
что совсем не похоже на реальные названия шрифтов, которые я ищу.
Я попробовал перечислить ресурсы шрифтов, и он показывает тот же результат.
Я попробовал открыть PDF в полном Adobe Acrobat. Он также показывает искаженные названия шрифтов:
То есть я не смог увидеть фактические названия шрифтов нигде в структуре документа.
Тем не менее, Adobe Acrobat DC показывает правильные названия шрифтов в панели Format, когда я выбираю различные текстовые поля на холсте документа (например, Arial, Courier New, Roboto), так что эта информация должна где-то храниться.
Как получить эти реальные названия шрифтов при разборе PDF с помощью iTextSharp?
Как было определено в ходе комментариев к вопросу, названия шрифтов анонимизированы во всех метаданных PDF для шрифта, но сама встроенная программа шрифта содержит фактическое название шрифта.
(Таким образом, PDF, строго говоря, нарушен, хотя вряд ли какая-либо программа будет жаловаться на это).
Поэтому, если мы хотим получить эти имена, мы должны заглянуть в эти программы шрифтов.
Вот пример концепции, основанной на архитектуре, использованной в этом ответе, на которую вы ссылались, т.е. с использованием 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
взят из этого ответа, который был моей первой находкой при поиске того, как считать информацию из шрифта в памяти с помощью .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.
Остерегайтесь: Это всего лишь доказательство концепции.
С одной стороны, вам, конечно же, потребуется реализовать часть, закомментированную как аналогичный код для других подтипов шрифтов
выше; и даже часть TYPE0
не готова к использованию в производстве, поскольку она учитывает только FONTFILE2
и не обрабатывает значения null
изящно.
С другой стороны, вы захотите кэшировать имена для уже проверенных шрифтов.