losLab PDF Library može ugraditi nedostajuće programe fontova već učitanog PDF-a jednim pozivom: EmbedMissingFonts prolazi kroz svaki rečnik fontova u dokumentu, pronalazi odgovarajući instalirani sistemski font prema njegovom BaseFont nazivu i zapisuje program fonta nazad u datoteku. Za timove koji popravljaju dokumente trećih strana koji ne prolaze PDF/A verifikaciju pri ugradnji fonta, ovo je popravka koja uklanja preflight grešku 00030
Scenario je deprimirajuće čest. Pipeline za unos arhive prima PDF-ove od dobavljača, kupaca ili biroa za skeniranje; dokumenti se ispravno prikazuju na svakom stolu u zgradi; a zatim PDF/A validator odbacuje celu seriju sa istom žalbom koja se ponavlja jednom po datoteci: barem jedan font nije ugrađen. Niko uzvodno neće ponovo generisati datoteke, pa pipeline mora da ih popravi. Ovaj članak pokriva taj put popravke. On je pratilac članka o preflight-u, koji pokriva detekciju PDF/A i PDF/UA kršenja: taj deo vam govori koji su dokumenti neispravni, a ovaj popravlja najčešći način na koji su oni neispravni
Zašto PDF/A zahteva da svaki font bude ugrađen?
ISO 19005-1 §6.3.4 zahteva da svaki font koji koristi usaglašeni dokument sadrži svoj program fonta unutar datoteke, jer je celokupno obećanje PDF/A-a ponovljivost: dokument se mora prikazati identično na računaru za pedeset godina koje ne deli nijedan font sa računarom koji ga je proizveo. Neugrađeni font je instrukcija da se Arial pronađe negdje na sistemu za prikazivanje, a stav standarda je da "negde na sistemu za prikazivanje" nije arhivska garancija. Kakve god glifove, metriku i pokrivenost zamenski font imao, to je ono što čitalac dobija, a to možda nije ono što je autor video
Istorijski krivac je konvencija Standard 14. PDF 1.0 je obećao da svaki pregledač dolazi sa fontovima Helvetica, Times, Courier, Symbol i ZapfDingbats, pa su generatori naučili da referenciraju te fontove po nazivu i ne ugrađuju ništa, a trideset godina alata i dalje radi upravo to. losLab PDF Library shvata ovaj zahtev dovoljno ozbiljno da je u načinu kreiranja PDF/A dokumenta AddStandardFont namerno prazna operacija (no-op): biblioteka ne isporučuje programe fontova Standard 14, ne može ugraditi ono što nema i odbija da zapiše neugrađenu referencu u dokument koji tvrdi da je usaglašen. Vraća 0 bez odabira fonta, pa PDF/A dokument umesto toga mora koristiti AddTrueTypeFont sa ugradnjom, a svaki zahtev za Embed=0 se tiho promovise u Embed=1 dok je aktivan PDF/A režim rada. To je strana pisca. Teži problem je strana čitaoca: dokument koji je neko drugi već napisao, pun rečnika fontova koje niste vi kreirali
Kako EmbedMissingFonts popravlja učitani dokument?
losLab PDF Library popravlja fontove na licu mesta umesto da ih ponovo izgrađuje. Kada PDF generator zapiše neugrađeni TrueType font, rečnik FontDescriptor koji proizvodi je već potpun: FontName, FontBBox, Flags, Ascent, Descent, StemV, svi su prisutni. Jedina stvar koja ga deli od ugrađenog fonta je odsustvo jednog unosa, reference toka /FontFile2 koja sadrži stvarni program fonta. Stoga EmbedMissingFonts ne dira rečnik fonta, kodiranje, niz širina ili bilo koji tok sadržaja koji referencira font po nazivu resursa. Čita odgovarajući program fonta sa sistema, komprimuje ga u novi objekat toka i dodaje jednu referencu /FontFile2 (ili /FontFile3 za CIDFontType0 fontove) u FontDescriptor koji je već tamo. Sve na šta stranice dokumenta ukazuju ostaje tačno tamo gde je i bilo, što operaciju čini sigurnom za pokretanje na datotekama koje ne kontrolišete
Pokrivenost uključuje obe arhitekture fontova sa kojima ćete se susresti u praksi: jednostavne TrueType fontove i kompozitne Type0/CID fontove, vrstu koja se proizvodi za CJK tekst i moderni Unicode izlaz. Prolazak namerno nabraja svaki rečnik Font u stablu objekata dokumenta, umjesto da se oslanja na prolazak kroz resurse stranicu po stranicu, pa se preuzimaju i fontovi referencirani iz anotacija ili deljeni na više stranica. API je jedan poziv na učitanom dokumentu
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;
Jedan detalj vredan pažnje, jer objašnjava zašto podudaranje naziva radi bolje od jednostavnog poređenja nizova karaktera: biblioteka normalizuje BaseFont nazive pre nego što ih potraži. Prefiksi podskupova (uzorak ABCDEF+ od šest velikih slova i znaka plus) se uklanjaju, sufiksi u PostScript stilu kao što je ArialMT razrešavaju se u Arial, a TrueType Collection datoteke se detektuju i raspakuju tako da se pismo koje se nalazi unutar .ttc datoteke i dalje ispravno ugrađuje
Verifikacija popravke pomoću preflight izveštaja
CreatePreflightReport je korak verifikacije, a petlja je namerno zatvorena: ista revizija koja je osudila datoteku trebalo bi da bude ona koja je oslobađa. Kod greške 00030 je nalaz dubinske revizije PDF/A koji glasi "At least one font is not embedded (FontFile/FontFile2/FontFile3 missing)", a prijavljuje se za datoteku kao celinu, pa je samo jedan zanemareni font održava aktivnom. Pokrenite izveštaj na izvornoj datoteci, popravite je, sačuvajte i ponovo ga pokrenite na izlaznoj datoteci
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;
Za prikaz po fontu umesto presude po datoteci, ponovo učitajte popravljeni dokument i nabrojite: FindFonts nakon čega slede SelectFont i GetFontIsEmbedded izveštavaju o statusu ugradnje font po font, što je pravi alat kada serijski posao treba da zabeleži tačno koje pismo u kojoj datoteci nije moglo biti popravljeno. Isti obrazac nabrajanja pojavljuje se u članku o izdvajanju teksta, slika i fontova iz učitanih PDF-ova, gde napaja izdvajanje umesto popravke
Šta se dešava kada font nije instaliran na sistemu?
EmbedMissingFonts preskače svaki font čiji program ne može da pronađe i prijavljuje preskakanje preko svoje povratne vrednosti: ako se broj vrati manji od broja neugrađenih fontova koje ste izbrojali, razlika je u fontovima koje sistem nema. Ovo je pošten način neuspeha i bolji je od alternativa, jer bi izmišljanje zamenskog programa za font naveden u dokumentu promenilo prikaz, što je upravo ono što arhivska popravka nikada ne sme da uradi. Za te slučajeve losLab PDF Library nudi EmbedFontProgramFromFile, koji ugrađuje .ttf ili .otf koji je isporučio pozivalac u navedeni font, tako da pipeline može da isporuči korporativne fontove koje očekuje da susretne i namerno se vrati na njih
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 granice zaslužuju da budu jasno izrečene. Prvo, Type1 fontovi se ne popravljaju u trenutnoj implementaciji: njihov unos /FontFile zahteva trodelnu PFB strukturu sa eksplicitnim ključevima dužine, a biblioteka ih preskače umjesto da zapisuje neispravan tok; oni su retki u modernim dokumentima, ali se pojavljuju u starim arhivama. Drugo, ugradnja fonta je čin licenciranja. Dozvole za ugradnju TrueType fonta pripadaju njegovoj livnici (foundry), a pipeline za popravku koji ugurava licencirane programe fontova u dokumente koji napuštaju organizaciju trebao bi da ima nekoga ko će potvrditi da licence za fontove to zaista dozvoljavaju. Biblioteka će uraditi ono što tražite; da li smete to da tražite pitanje je za vaše pravno odeljenje, a ne za vaš kompajler
Ugradnja je neophodna, ali ne i dovoljna
Popravljanje fontova uklanja samo grešku 00030 i ništa više. Dokument koji ne prolazi PDF/A zbog šifrovanja, nedostatka XMP metapodataka, prostora boja zavisnog od uređaja bez OutputIntent-a ili nedostatka ToUnicode mapa i dalje neće proći nakon što se ugrade svi fontovi, zbog čega popravka pripada petlji vođenoj preflight-om, a ne da je zamenjuje. Pokrenite ceo izveštaj, popravite ono što imenuje i dopustite izveštaju da vam kaže kada ste gotovi. Takođe postoji i dimenzija troška: puni CJK program fonta može težiti megabajtima, pa ugradnja nekoliko takvih može dramatično napumpati mali dokument. Protivteža je pravljenje podskupa (subsetting), pokriveno u članku o optimizaciji veličine PDF datoteke i pravljenju podskupova fontova, što skraćuje svaki ugrađeni program na glifove koje dokument stvarno prikazuje
Sprečavanje regresije novih dokumenata
SetEmbedAllFonts je preventivna polovina iste funkcije: zaštita na strani pisca koja sprečava vaš sopstveni kod da proizvodi dokumente koje ovaj članak popravlja. Kada je aktivan SetEmbedAllFonts(1), svaki sledeći poziv AddTrueTypeFont koji zahteva Embed=0 se promoviše u ugrađenu referencu, što na svaki dokument proširuje garanciju koju PDF/A režim već sprovodi. To utiče na fontove dodate nakon poziva, a ne na fontove koji su već u učitanoj datoteci, pa je podela rada čista: SetEmbedAllFonts za dokumente koje kreirate, a EmbedMissingFonts za dokumente koje nasleđujete
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 polovine, zaštita na strani pisca i staza učitaj-popravi-sačuvaj, deo su losLab PDF Library-ja za Delphi, C# i VB.NET, zajedno sa preflight mehanizmom koji proverava rezultat; stranica proizvoda sadrži kompletnu referencu API-ja za fontove uključujući pozive za ugradnju po fontu i pravljenje podskupa