losLab PDF Library может внедрить недостающие программы шрифтов уже загруженного PDF-файла с помощью одного вызова: EmbedMissingFonts обходит каждый словарь шрифтов в документе, находит соответствующий установленный системный шрифт по его имени BaseFont и записывает программу шрифта обратно в файл. Для команд, исправляющих сторонние документы, которые не проходят проверку соответствия PDF/A из-за отсутствия внедренных шрифтов, это решение устраняет ошибку предварительной проверки 00030
Подобный сценарий встречается удручающе часто. Конвейер архивации получает PDF-файлы от поставщиков, клиентов или сканирующего бюро; документы отлично отображаются на любом компьютере в офисе; а затем валидатор PDF/A отклоняет всю партию с одной и той же жалобой, повторяющейся для каждого файла: как минимум один шрифт не внедрен. Никто на предыдущих этапах не будет пересоздавать эти файлы, поэтому конвейеру приходится исправлять их самостоятельно. В этой статье рассматривается этот путь восстановления. Она является дополнением к статье о предварительной проверке (preflight), в которой описывается обнаружение нарушений PDF/A и PDF/UA: тот материал расскажет вам, какие документы повреждены, а этот поможет устранить наиболее частую причину их повреждения
Почему PDF/A требует внедрения каждого шрифта?
Стандарт ISO 19005-1 §6.3.4 требует, чтобы каждый шрифт, используемый в соответствующем документе, содержал свою программу шрифта внутри файла. Это связано с тем, что главным обещанием PDF/A является воспроизводимость: документ должен отображаться абсолютно одинаково на компьютере через пятьдесят лет, даже если на нем нет ни одного общего шрифта с системой, на которой документ был создан. Невнедренный шрифт — это указание найти Arial где-то в системе просмотра, и позиция стандарта такова, что поиск «где-то в системе просмотра» не является архивной гарантией. Какие бы глифы, метрики и покрытие ни имел замещающий шрифт, именно это и увидит читатель, и это может отличаться от того, что видел автор
Исторической причиной этого является соглашение о 14 стандартных шрифтах (Standard 14). В спецификации PDF 1.0 гарантировалось, что любая программа просмотра поставляется с Helvetica, Times, Courier, Symbol и ZapfDingbats. Поэтому генераторы документов научились ссылаться на эти шрифты по имени и ничего не внедрять, и за тридцать лет средства разработки до сих пор делают именно это. losLab PDF Library относится к этому требованию настолько серьезно, что в режиме создания PDF/A вызов AddStandardFont намеренно является пустой операцией: библиотека не поставляет программы стандартных 14 шрифтов, не может внедрить то, чего у нее нет, и отказывается записывать невнедренную ссылку в документ, претендующий на соответствие стандарту. Метод возвращает 0 без выбора шрифта, поэтому в документе PDF/A вместо этого необходимо использовать AddTrueTypeFont с внедрением, а любой запрос Embed=0 автоматически повышается до Embed=1 при активном режиме PDF/A. Это касается стороны записи. Более сложная проблема — сторона чтения: документ, который уже создал кто-то другой, полный словарей шрифтов, которые вы не создавали
Как EmbedMissingFonts восстанавливает загруженный документ?
losLab PDF Library восстанавливает шрифты на месте, а не перестраивает их заново. Когда генератор PDF записывает невнедренный шрифт TrueType, создаваемый им словарь FontDescriptor уже заполнен: FontName, FontBBox, Flags, Ascent, Descent, StemV — все они присутствуют. Единственное, что отделяет его от внедренного шрифта, — это отсутствие одной записи: ссылки на поток /FontFile2, содержащий саму программу шрифта. Таким образом, EmbedMissingFonts не изменяет ни словарь шрифта, ни кодировку, ни массив ширин, ни потоки контента, которые ссылаются на шрифт по имени ресурса. Метод считывает соответствующую программу шрифта из системы, сжимает её в новый объект потока и добавляет одну ссылку /FontFile2 (или /FontFile3 для шрифтов CIDFontType0) к уже существующему FontDescriptor. Все элементы, на которые указывают страницы документа, остаются ровно на своих местах, что делает эту операцию безопасной для выполнения на файлах, которые вы не контролируете
Поддержка охватывает обе архитектуры шрифтов, с которыми вы столкнетесь на практике: простые шрифты TrueType и составные шрифты Type0/CID, используемые для текстов CJK и современного вывода Unicode. Обход намеренно перечисляет каждый словарь Font в дереве объектов документа, а не полагается на пошаговый обход ресурсов по страницам, благодаря чему также извлекаются шрифты, на которые ссылаются аннотации или которые совместно используются на разных страницах. API представляет собой один вызов для загруженного документа
var
PDF: TPDFlib;
Repaired: Integer;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('supplier-invoice.pdf', '') <> 1 then
raise Exception.Create('Could not load PDF');
// Обходит каждый словарь Font; возвращает количество шрифтов,
// получивших программу шрифта. Шрифты, программы которых не удалось
// найти в системе, пропускаются без вызова ошибки.
Repaired := PDF.EmbedMissingFonts;
Writeln(Format('%d font program(s) embedded', [Repaired]));
PDF.SaveToFile('supplier-invoice-repaired.pdf');
finally
PDF.Free;
end;
end;
Одна деталь, о которой стоит знать, поскольку она объясняет, почему сопоставление имен работает лучше простого сравнения строк: библиотека нормализует имена BaseFont перед их поиском. Префиксы подмножеств (шаблон ABCDEF+ из шести прописных букв и знака плюс) удаляются, суффиксы в стиле PostScript, такие как ArialMT, разрешаются в Arial, а файлы TrueType Collection обнаруживаются и распаковываются, чтобы начертание внутри .ttc по-прежнему внедрялось корректно
Проверка исправления с помощью отчета о предварительной проверке
CreatePreflightReport представляет собой этап верификации, и цикл намеренно замыкается: та же проверка, которая выявила ошибку в файле, должна подтвердить его корректность. Код ошибки 00030 — это результат глубокого аудита PDF/A, который гласит: «По крайней мере один шрифт не внедрен (отсутствует FontFile/FontFile2/FontFile3)», и он относится к файлу в целом, поэтому один упущенный из виду шрифт не позволит пройти проверку. Запустите отчет для исходного файла, выполните исправление, сохраните и запустите его снова для полученного файла
function HasFontEmbeddingViolation(PDF: TPDFlib;
const FileName: string): Boolean;
var
Report: string;
begin
// ComplianceTests = 1 выбирает проверки PDF/A
Report := PDF.CreatePreflightReport(FileName, '', 1, 0);
Result := Pos('00030', Report) > 0;
end;
Для детального просмотра каждого шрифта, а не общего вердикта по файлу, загрузите исправленный документ снова и выполните перечисление: вызовы FindFonts, а затем SelectFont и GetFontIsEmbedded сообщают статус внедрения для каждого шрифта по отдельности. Это наиболее подходящий инструмент, если при пакетной обработке необходимо зафиксировать в журнале, какое именно начертание в каком файле не удалось исправить. Тот же шаблон перечисления описан в статье об извлечении текста, изображений и шрифтов из загруженных PDF-файлов, где он используется для извлечения, а не для исправления
Что происходит, если шрифт не установлен в системе?
EmbedMissingFonts пропускает любой шрифт, программу которого не удается найти, и сообщает об этом через возвращаемое значение: если итоговое число оказывается меньше количества подсчитанных вами невнедренных шрифтов, разница указывает на шрифты, отсутствующие в системе. Это честный режим обработки сбоев, который гораздо лучше альтернатив, поскольку создание вымышленной замещающей программы для шрифта, указанного в документе, изменило бы его отображение, что при архивном восстановлении абсолютно недопустимо. Для таких случаев losLab PDF Library предоставляет метод EmbedFontProgramFromFile, который внедряет предоставленный вызывающей состояной файл .ttf или .otf в указанный шрифт, позволяя конвейеру поставлять ожидаемые корпоративные шрифты и целенаправленно использовать их в качестве резервных
var
I, FontID: Integer;
begin
PDF.FindFonts;
for I := 1 to PDF.FontCount do
begin
FontID := PDF.GetFontID(I);
if (FontID > 0) and (PDF.SelectFont(FontID) = 1) then
if PDF.GetFontIsEmbedded = 0 then
// Сначала пробуем установить системный шрифт, затем переходим к
// файлу шрифта, поставляемому вместе с приложением
if PDF.EmbedFontProgram(PDF.FontName) = 0 then
PDF.EmbedFontProgramFromFile(PDF.FontName,
'fonts\CorporateSans.ttf');
end;
end;
Два ограничения заслуживают того, чтобы о них было сказано прямо. Во-первых, шрифты Type1 не восстанавливаются в текущей реализации: их запись /FontFile требует сегментированной структуры PFB из трех частей с явными ключами длины, и библиотека пропускает их вместо записи поврежденного потока. Они редко встречаются в современных документах, но всё же присутствуют в старых архивах. Во-вторых, внедрение шрифта связано с лицензированием. Права на внедрение шрифта TrueType принадлежат его создателю, поэтому в конвейере восстановления, который внедряет лицензионные программы шрифтов в документы, выходящие за пределы организации, кто-то должен подтвердить, что лицензии на шрифты действительно это допускают. Библиотека выполнит то, что вы запросите; но имеете ли вы право это запрашивать — вопрос к юридическому отделу, а не к компилятору
Внедрение необходимо, но недостаточно
Восстановление шрифтов устраняет ошибку 00030 и ничего более. Документ, который не соответствует PDF/A из-за шифрования, отсутствия метаданных XMP, зависимого от устройства цветового пространства без OutputIntent или отсутствия таблиц ToUnicode, всё равно не пройдет проверку даже после внедрения всех шрифтов. Именно поэтому восстановление должно осуществляться внутри цикла, управляемого предварительной проверкой, а не заменять его. Запустите полный отчет, исправьте то, что в нем указано, и пусть отчет покажет, когда задача выполнена. Существует также вопрос размера файлов: полная программа шрифтов CJK может занимать мегабайты, поэтому внедрение нескольких таких шрифтов может значительно увеличить размер небольшого документа. Противовесом является субсеттинг (выделение подмножеств шрифтов), описанный в статье об оптимизации размера PDF-файлов и субсеттинге шрифтов, который сокращает каждую внедряемую программу до глифов, фактически отображаемых в документе
Предотвращение появления ошибок в новых документах
Метод SetEmbedAllFonts — это превентивная часть той же функции: защита на стороне записи, которая не позволяет вашему собственному коду создавать документы с ошибками, исправляемыми в этой статье. При активированном SetEmbedAllFonts(1) любой последующий вызов AddTrueTypeFont, запрашивающий Embed=0, превращается во внедренную ссылку, что распространяет на каждый документ гарантию, которую уже обеспечивает режим PDF/A. Это влияет на шрифты, добавленные после вызова, а не на шрифты, которые уже находятся в загруженном файле, благодаря чему разделение обязанностей остается четким: SetEmbedAllFonts для создаваемых вами документов, а EmbedMissingFonts для наследуемых документов
PDF.NewDocument;
PDF.SetEmbedAllFonts(1);
// С этого момента вызов AddTrueTypeFont(Name, 0) ведет себя
// как AddTrueTypeFont(Name, 1): ни одна невнедренная
// ссылка не попадет в выходной файл
Обе части — защита на стороне записи и цепочка загрузка-восстановление-сохранение — входят в состав библиотеки losLab PDF Library для Delphi, C# и VB.NET вместе с механизмом предварительной проверки, подтверждающим результат. На странице продукта представлен полный справочник API по шрифтам, включая вызовы для внедрения отдельных шрифтов и субсеттинга