losLab PDF Library dokáže vložiť chýbajúce programy písiem z už načítaného PDF pomocou jediného volania: EmbedMissingFonts prejde každý slovník písiem v dokumente, vyhľadá zodpovedajúce nainštalované systémové písmo podľa jeho názvu BaseFont a zapíše program písma späť do súboru. Pre tímy, ktoré opravujú dokumenty tretích strán, ktoré neprejdú validáciou PDF/A kvôli vkladaniu písiem, je to riešenie, vďaka ktorému preflight chyba 00030 zmizne
Tento scenár je až frustrujúco bežný. Archivačná linka prijíma PDF súbory od dodávateľov, zákazníkov alebo skenovacieho pracoviska; dokumenty sa bez problémov vykresľujú na každom počítači v budove; a potom validátor PDF/A odmietne celú dávku s rovnakou sťažnosťou zopakovanou pre každý súbor: aspoň jedno písmo nie je vložené. Nikto vyššie v reťazci súbory znova nevygeneruje, takže linka ich musí opraviť. Tento článok sa venuje tejto ceste opravy. Je to sprievodný text k článku o preflight kontrole, ktorý sa zaoberá zisťovaním porušení PDF/A a PDF/UA: ten vám povie, ktoré dokumenty sú chybné, a tento opraví najčastejší spôsob ich poškodenia
Prečo PDF/A vyžaduje vloženie každého písma?
ISO 19005-1 §6.3.4 vyžaduje, aby každé písmo použité v vyhovujúcom dokumente malo svoj program písma uložený priamo v súbore, pretože celým prísľubom formátu PDF/A je reprodukovateľnosť: dokument sa musí vykresliť identicky na stroji o päťdesiat rokov, ktorý nezdieľa žiadne písma so strojom, na ktorom bol vytvorený. Nevložené písmo je pokynom nájsť Arial niekde v zobrazovacom systéme, pričom postoj štandardu je taký, že „niekde v zobrazovacom systéme“ nepredstavuje archivačnú záruku. Nech už má náhradné písmo akékoľvek glyfy, metriky a pokrytie, čitateľ dostane presne to, čo môže byť odlišné od toho, čo videl autor
Historickým vinníkom je konvencia Standard 14. Formát PDF 1.0 sľuboval, že každý prehliadač obsahuje písma Helvetica, Times, Courier, Symbol a ZapfDingbats, takže generátory sa naučili odkazovať na tieto písma podľa názvu a nič nevkladať, a tridsať rokov nástrojov robí presne to isté. Knižnica losLab PDF Library berie túto požiadavku tak vážne, že v režime vytvárania PDF/A je AddStandardFont zámerne prázdnou operáciou (no-op): knižnica nedodáva programy písiem Standard 14, nemôže vložiť to, čo nemá, a odmieta zapísať nevložený odkaz do dokumentu, ktorý deklaruje zhodu. Vráti hodnotu 0 bez výberu písma, takže dokument PDF/A musí namiesto toho použiť AddTrueTypeFont s vkladaním a akákoľvek požiadavka na Embed=0 sa pri aktívnom režime PDF/A ticho zmení na Embed=1. To je strana zápisu. Ťažším problémom je strana čítania: dokument, ktorý už napísal niekto iný, plný slovníkov písiem, ktoré ste nevytvorili
Ako EmbedMissingFonts opravuje načítaný dokument?
losLab PDF Library opravuje písma priamo na mieste, namiesto toho, aby ich znova vytvárala. Keď generátor PDF zapíše nevložené písmo TrueType, slovník FontDescriptor, ktorý vygeneruje, je už kompletný: FontName, FontBBox, Flags, Ascent, Descent, StemV sú všetky prítomné. Jediná vec, ktorá ho odlišuje od vloženého písma, je absencia jedného záznamu, odkazu na prúd /FontFile2, ktorý obsahuje samotný program písma. Funkcia EmbedMissingFonts sa preto nedotýka slovníka písma, kódovania, poľa šírok ani žiadneho prúdu obsahu, ktorý na písmo odkazuje prostredníctvom názvu prostriedku. Prečíta zodpovedajúci program písma zo systému, skomprimuje ho do nového objektu prúdu a pripojí jeden odkaz /FontFile2 (alebo /FontFile3 pre písma CIDFontType0) k slovníku FontDescriptor, ktorý už existuje. Všetko, na čo odkazujú stránky dokumentu, zostáva presne tam, kde to bolo, vďaka čomu je táto operácia bezpečná pre súbory, ktoré nemáte pod kontrolou
Pokrytie zahŕňa obe architektúry písiem, s ktorými sa v praxi stretnete: jednoduché písma TrueType a zložené písma Type0/CID, aké sa vytvárajú pre texty CJK a moderný výstup Unicode. Prechod zámerne prechádza každý slovník Font v strome objektov dokumentu, namiesto toho, aby sa spoliehal na prechod prostriedkov po jednotlivých stránkach, takže sa zachytia aj písma odkazované z anotácií alebo zdieľané naprieč stránkami. API predstavuje jedno jediné volanie nad načítaným dokumentom
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;
Jeden detail, ktorý stojí za to vedieť, pretože vysvetľuje, prečo porovnávanie názvov funguje lepšie ako naivné porovnanie reťazcov: knižnica pred vyhľadávaním normalizuje názvy BaseFont. Prefixy podmnožín (vzor ABCDEF+ pozostávajúci zo šiestich veľkých písmen a znamienka plus) sú odstránené, prípony v štýle PostScript, ako napríklad ArialMT, sa preložia na Arial a súbory TrueType Collection sú detegované a rozbalené, takže rez písma nachádzajúci sa v .ttc sa stále správne vloží
Overenie opravy pomocou preflight správy
CreatePreflightReport je krok overenia a cyklus sa zámerne uzatvára: rovnaký audit, ktorý súbor označil za chybný, by mal byť ten, ktorý ho schváli. Kód chyby 00030 je nález hĺbkového auditu PDF/A, ktorý hovorí: „At least one font is not embedded (FontFile/FontFile2/FontFile3 missing)“ (Aspoň jedno písmo nie je vložené), a vykazuje sa pre súbor ako celok, takže jediné prehliadnuté písmo ho udržiava aktívny. Spustite správu nad zdrojovým súborom, opravte ho, uložte a znova ju spustite nad výstupom
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;
Ak chcete zobrazenie pre jednotlivé písma a nie verdikt pre celý súbor, znova načítajte opravený dokument a prejdite ho: FindFonts nasledovaný SelectFont a GetFontIsEmbedded hlási stav vloženia písmo po písme, čo je správny nástroj, keď dávková úloha potrebuje zaznamenať, ktorý konkrétny rez v ktorom súbore nebolo možné opraviť. Rovnaký vzor prechodu sa objavuje v článku o extrakcii textu, obrázkov a písiem z načítaných PDF, kde namiesto opravy slúži na extrakciu
Čo sa stane, ak písmo nie je nainštalované v systéme?
Funkcia EmbedMissingFonts preskočí každé písmo, ktorého program nedokáže nájsť, a toto preskočenie oznámi prostredníctvom svojej návratovej hodnoty: ak sa vráti nižší počet než počet nevložených písiem, ktoré ste napočítali, rozdiel predstavuje písma, ktoré systém nemá. Toto je korektný chybový stav a je lepší ako alternatívy, pretože vymyslenie náhradného programu pre písmo pomenované v dokumente by zmenilo vykresľovanie, čo je presne to, čo archívna oprava nikdy nesmie urobiť. Pre tieto prípady poskytuje losLab PDF Library funkciu EmbedFontProgramFromFile, ktorá vloží volajúcim dodaný súbor .ttf alebo .otf do pomenovaného písma, takže linka môže distribuovať firemné písma, s ktorými očakáva, že sa stretne, a zámerne sa k nim vrátiť ako k zálohe
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;
Dve hranice si zaslúžia jasné vyjadrenie. Po prvé, písma Type1 sa v súčasnej implementácii neopravujú: ich záznam /FontFile vyžaduje trojsegmentovú štruktúru PFB s explicitnými kľúčmi dĺžky a knižnica ich preskakuje, namiesto toho, aby zapísala poškodený prúd; v moderných dokumentoch sú zriedkavé, ale v starých archívoch sa vyskytujú. Po druhé, vkladanie písma je licenčný úkon. Povolenia na vkladanie písma TrueType patria jeho tvorcovi a prevádzkovateľ opravnej linky, ktorá vkladá licencované programy písiem do dokumentov opúšťajúcich organizáciu, by si mal nechať potvrdiť, či to licencie k písmam skutočne povoľujú. Knižnica urobí to, o čo požiadate; to, či môžete požiadať, je otázka pre vaše právne oddelenie, nie pre váš kompilátor
Vloženie je nevyhnutné, ale nie postačujúce
Oprava písiem odstráni chybu 00030 a nič iné. Dokument, ktorý zlyhá pri validácii PDF/A kvôli šifrovaniu, chýbajúcim metadátam XMP, farebnému priestoru závislému od zariadenia bez OutputIntent alebo chýbajúcim mapám ToUnicode, bude stále zlyhávať aj po vložení každého písma, čo je dôvod, prečo oprava patrí do cyklu riadeného preflightom a nenahrádza ho. Spustite celú správu, opravte to, čo označí, a nechajte správu, aby vám povedala, kedy ste skončili. Existuje aj rozmer nákladov: celý program písma CJK môže mať megabajty, takže vloženie niekoľkých z nich môže dramaticky nafúknuť malý dokument. Protiváhou je vytváranie podmnožín (subsetting), o ktorom sa píše v článku o optimalizácii veľkosti súborov PDF a vytváraní podmnožín písiem, čo zmenší každý vložený program len na glyfy, ktoré dokument skutočne vykresľuje
Zabránenie regresii nových dokumentov
SetEmbedAllFonts je preventívna polovica tej istej funkcie: ochrana na strane zápisu, ktorá zabraňuje vášmu vlastnému kódu vytvárať dokumenty, ktoré tento článok opravuje. S aktívnym SetEmbedAllFonts(1) sa akékoľvek následné volanie AddTrueTypeFont požadujúce Embed=0 zmení na vložený odkaz, čím sa na každý dokument rozšíri záruka, ktorú už režim PDF/A vynucuje. Ovplyvňuje to písma pridané po volaní, nie písma, ktoré už sú v načítanom súbore, takže delenie práce je jasné: SetEmbedAllFonts pre dokumenty, ktoré vytvárate, a EmbedMissingFonts pre dokumenty, ktoré preberáte
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
Obe polovice, ochrana na strane zápisu aj cesta načítania-opravy-uloženia, sú súčasťou losLab PDF Library pre Delphi, C# a VB.NET, spolu s preflight modulom, ktorý overuje výsledok; stránka produktu obsahuje úplný referenčný manuál pre API písiem vrátane volaní pre vkladanie a vytváranie podmnožín pre jednotlivé písma