losLab PDF Library poate încorpora programele de font lipsă dintr-un PDF deja încărcat printr-un singur apel: EmbedMissingFonts parcurge fiecare dicționar de fonturi din document, localizează fontul de sistem instalat corespunzător după numele său BaseFont și scrie programul de font înapoi în fișier. Pentru echipele care repară documente terțe care nu trec de validarea PDF/A din cauza încorporării fonturilor, aceasta este remedierea care face ca eroarea preflight 00030 să dispară
Scenariul este deosebit de comun. O linie de procesare pentru arhivare primește PDF-uri de la furnizori, clienți sau de la un birou de scanare; documentele se afișează corect pe fiecare birou din clădire; iar apoi validatorul PDF/A respinge întregul lot cu aceeași plângere repetată o dată pentru fiecare fișier: cel puțin un font nu este încorporat. Nimeni din amonte nu va regenera fișierele, așa că linia de procesare trebuie să le repare. Acest articol acoperă acea cale de remediere. Este partenerul articolului preflight, care acoperă detectarea încălcărilor PDF/A și PDF/UA: acel material vă spune ce documente sunt deteriorate, iar acesta remediază cea mai frecventă problemă a lor
De ce necesită PDF/A ca fiecare font să fie încorporat?
ISO 19005-1 §6.3.4 impune ca fiecare font utilizat de un document conform să conțină programul său de font în interiorul fișierului, deoarece întreaga promisiune a PDF/A este reproductibilitatea: documentul trebuie să se afișeze identic pe o mașină peste cincizeci de ani care nu partajează fonturi cu mașina care l-a produs. Un font neîncorporat este o instrucțiune de a căuta Arial undeva pe sistemul de vizualizare, iar poziția standardului este că „undeva pe sistemul de vizualizare” nu reprezintă o garanție de arhivare. Indiferent de glifele, metricile și acoperirea pe care le are fontul de înlocuire, cititorul le va obține pe acestea, iar acestea s-ar putea să nu fie cele pe care le-a văzut autorul
Principalul vinovat istoric este convenția Standard 14. PDF 1.0 a promis că fiecare vizualizator include Helvetica, Times, Courier, Symbol și ZapfDingbats, astfel încât generatoarele au învățat să facă referire la acele fonturi după nume și să nu încorporeze nimic, iar treizeci de ani de instrumente fac exact același lucru. losLab PDF Library tratează această cerință cu suficientă seriozitate încât, în modul de creare PDF/A, AddStandardFont este în mod deliberat o operațiune nulă (no-op): biblioteca nu livrează programele de font Standard 14, nu poate încorpora ceea ce nu are și refuză să scrie o referință neîncorporată într-un document care pretinde conformitate. Returnează 0 fără a selecta un font, așa că un document PDF/A trebuie să utilizeze în schimb AddTrueTypeFont cu încorporare, iar orice solicitare Embed=0 este promovată în mod silențios la Embed=1 în timp ce modul PDF/A este activ. Aceasta este partea de scriere. Problema mai dificilă este partea de citire: un document pe care altcineva l-a scris deja, plin de dicționare de fonturi pe care nu le-ați creat dvs
Cum repară EmbedMissingFonts un document încărcat?
losLab PDF Library repară fonturile pe loc, mai degrabă decât să le reconstruiască. Atunci când un generator PDF scrie un font TrueType neîncorporat, dicționarul FontDescriptor pe care îl produce este deja complet: FontName, FontBBox, Flags, Ascent, Descent, StemV, toate prezente. Singurul lucru care îl separă de un font încorporat este absența unei singure intrări, referința de flux /FontFile2 care deține programul de font propriu-zis. Astfel, EmbedMissingFonts nu atinge dicționarul de fonturi, codificarea, tabloul de lățimi sau orice flux de conținut care face referire la font prin numele resursei. Acesta citește programul de font corespunzător de pe sistem, îl comprimă într-un nou obiect de flux și adaugă o singură referință /FontFile2 (sau /FontFile3 pentru fonturile CIDFontType0) la FontDescriptor-ul existent. Tot ceea ce indică paginile documentului rămâne exact acolo unde a fost, ceea ce face ca operațiunea să fie sigură de rulat pe fișiere pe care nu le controlați
Acoperirea include ambele arhitecturi de fonturi pe care le veți întâlni în practică: fonturi TrueType simple și fonturi compuse Type0/CID, tipul produs pentru textul CJK și rezultatul Unicode modern. Parcurgerea enumeră în mod deliberat fiecare dicționar Font din arborele de obiecte al documentului, în loc să se bazeze pe o parcurgere pagină cu pagină a resurselor, astfel încât fonturile la care se face referire din adnotări sau care sunt partajate între pagini sunt de asemenea preluate. API-ul este un singur apel pe documentul încărcat
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;
Un detaliu care merită știut, deoarece explică de ce potrivirea numelor funcționează mai bine decât o simplă comparare de șiruri: biblioteca normalizează numele BaseFont înainte de a le căuta. Prefixele de subset (modelul ABCDEF+ format din șase litere majuscule și un semn plus) sunt eliminate, sufixele în stil PostScript precum ArialMT sunt rezolvate ca Arial, iar fișierele TrueType Collection sunt detectate și despachetate astfel încât un font dintr-un .ttc să se încorporeze corect în continuare
Verificarea reparării cu un raport preflight
CreatePreflightReport este pasul de verificare, iar bucla este închisă în mod deliberat: același audit care a respins fișierul ar trebui să fie cel care îl aprobă. Codul de eroare 00030 este rezultatul auditului profund PDF/A care precizează „Cel puțin un font nu este încorporat (lipsește FontFile/FontFile2/FontFile3)” și este raportat pentru fișier ca întreg, astfel încât un singur font omis îl menține activ. Rulați raportul pe fișierul sursă, reparați, salvați și rulați-l din nou pe cel rezultat
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;
Pentru o vizualizare individuală a fonturilor, în loc de un verdict pe fișier, încărcați din nou documentul reparat și enumerați: FindFonts urmat de SelectFont și GetFontIsEmbedded raportează starea de încorporare font cu font, acesta fiind instrumentul potrivit atunci când o sarcină pe loturi trebuie să înregistreze exact care font din care fișier nu a putut fi reparat. Același model de enumerare apare în articolul despre extragerea textului, imaginilor și fonturilor din PDF-uri încărcate, unde servește extragerii în loc de reparare
Ce se întâmplă când fontul nu este instalat în sistem?
EmbedMissingFonts omite orice font al cărui program nu îl poate găsi și raportează omiterea prin valoarea sa de returnare: dacă numărul returnat este mai mic decât numărul de fonturi neîncorporate pe care le-ați numărat, diferența reprezintă fonturile pe care sistemul nu le are. Acesta este modul cinstit de eșec și este mai bun decât alternativele, deoarece inventarea unui program de înlocuire pentru un font numit în document ar schimba afișarea, ceea ce este exact lucrul pe care o reparare de arhivare nu trebuie să îl facă niciodată. Pentru aceste cazuri, losLab PDF Library oferă EmbedFontProgramFromFile, care încorporează un .ttf sau .otf furnizat de apelant în fontul specificat, astfel încât o linie de procesare să poată distribui fonturile companiei pe care se așteaptă să le întâlnească și să apeleze la ele în mod deliberat
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;
Două limite merită menționate în mod clar. În primul rând, fonturile Type1 nu sunt reparate în implementarea curentă: intrarea lor /FontFile necesită structura PFB din trei segmente cu chei de lungime explicite, iar biblioteca le omite în loc să scrie un flux incorect; acestea sunt rare în documentele moderne, dar apar în arhivele vechi. În al doilea rând, încorporarea unui font este un act de licențiere. Permisiunile de încorporare ale unui font TrueType aparțin creatorului său, iar o linie de reparare care introduce programe de font licențiate în documente care părăsesc organizația ar trebui să aibă confirmarea cuiva că licențele de font permit de fapt acest lucru. Biblioteca va face ceea ce cereți; dacă aveți permisiunea de a cere este o întrebare pentru departamentul juridic, nu pentru compilator
Încorporarea este necesară, dar nu suficientă
Repararea fonturilor elimină eroarea 00030 și nimic altceva. Un document care nu respectă standardul PDF/A din cauza criptării, a metadatelor XMP lipsă, a unui spațiu de culoare dependent de dispozitiv fără OutputIntent sau a hărților ToUnicode absente va eșua în continuare după ce fiecare font este încorporat, motiv pentru care repararea aparține unei bucle bazate pe preflight în loc să o înlocuiască pe aceasta. Rulați raportul complet, remediați ceea ce indică și lăsați raportul să vă spună când ați terminat. Există și o dimensiune a costului: un program complet de font CJK ajunge la megaocteți, astfel încât încorporarea mai multora poate mări dramatic un document mic. Contragreutatea este subsetarea, acoperită în articolul despre optimizarea dimensiunii fișierului PDF și subsetarea fonturilor, care reduce fiecare program încorporat la glifele pe care documentul le afișează de fapt
Prevenirea regresiei noilor documente
SetEmbedAllFonts este jumătatea de prevenire a aceleiași caracteristici: o protecție la nivel de scriere care împiedică propriul cod să producă documentele pe care acest articol le repară. Cu SetEmbedAllFonts(1) activ, orice apel ulterior AddTrueTypeFont care solicită Embed=0 este promovat la o referință încorporată, ceea ce extinde la fiecare document garanția pe care modul PDF/A o impune deja. Afectează fonturile adăugate după apel, nu fonturile deja aflate într-un fișier încărcat, astfel încât împărțirea sarcinilor este clară: SetEmbedAllFonts pentru documentele pe care le creați și EmbedMissingFonts pentru documentele pe care le moșteniți
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
Ambele jumătăți, protecția la nivel de scriere și calea de încărcare-reparare-salvare, fac parte din losLab PDF Library pentru Delphi, C# și VB.NET, alături de motorul preflight care verifică rezultatul; pagina produsului conține referința completă a API-ului de font, inclusiv apelurile de încorporare și subsetare per font