losLab PDF Library може вбудовувати відсутні програми шрифтів вже завантаженого PDF-файлу за допомогою одного виклику: EmbedMissingFonts обходить кожен словник шрифтів у документі, знаходить відповідний встановлений системний шрифт за його назвою BaseFont і записує програму шрифту назад у файл. Для команд, які виправляють сторонні документи, що не проходять перевірку PDF/A через вбудовування шрифтів, це саме те виправлення, яке усуває помилку перевірки 00030
Цей сценарій є надзвичайно поширеним. Конвеєр імпорту архіву отримує PDF-файли від постачальників, клієнтів або скануючого бюро; документи чудово відображаються на кожному робочому столі в будівлі; а потім валідатор PDF/A відхиляє всю партію з тією самою скаргою, що повторюється по одному разу для кожного файлу: принаймні один шрифт не вбудовано. Ніхто вище за течією не буде повторно генерувати файли, тому конвеєр має їх виправити. Ця стаття описує цей шлях виправлення. Вона є супутньою до статті про попередню перевірку, яка охоплює виявлення порушень PDF/A та PDF/UA: той матеріал підкаже вам, які документи пошкоджені, а цей виправляє найпоширенішу причину їх пошкодження
Чому PDF/A вимагає вбудовування кожного шрифту?
ISO 19005-1 §6.3.4 вимагає, щоб кожен шрифт, який використовується сумісним документом, містив свою програму шрифту всередині файлу, оскільки головною обіцянкою PDF/A є відтворюваність: документ повинен відображатися ідентично на комп'ютері через п'ятдесят років, який не має спільних шрифтів з комп'ютером, що його створив. Невбудований шрифт — це інструкція знайти Arial десь у системі перегляду, а позиція стандарту полягає в тому, що «десь у системі перегляду» не є гарантією архівування. Які б гліфи, метрики та покриття не мав замінений шрифт, саме це отримає читач, і це може не відповідати тому, що бачив автор
Історичним винуватцем є конвенція Standard 14. PDF 1.0 обіцяв, що кожен переглядач постачається із Helvetica, Times, Courier, Symbol та ZapfDingbats, тому generator-и навчилися посилатися на ці шрифти за назвою та нічого не вбудовувати, і тридцять років інструментарію досі роблять саме це. losLab PDF Library ставиться до цієї вимоги досить серйозно, тому в режимі створення PDF/A виклик AddStandardFont навмисно є холостою операцією: бібліотека не постачає програми шрифтів Standard 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');
// Walks every Font dictionary; returns how many fonts
// gained a font program. Fonts whose program cannot be
// found on the system are skipped, not failed.
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 selects the PDF/A checks
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
// Try the installed system font first, then fall back
// to a font file shipped alongside the application
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);
// From here on, AddTrueTypeFont(Name, 0) behaves
// like AddTrueTypeFont(Name, 1): no non-embedded
// reference can reach the output file
Обидві половини, захист на стороні запису та шлях «завантаження-відновлення-збереження», є частиною losLab PDF Library для Delphi, C# та VB.NET, разом із рушієм попередньої перевірки, який перевіряє результат; сторінка продукту містить повний довідник API шрифтів, включаючи виклики вбудовування та субсетування для окремих шрифтів