Chýbajúci glyf v PDF nie je chyba. Producent žiada o znak, ktorý vybraný font nedokáže namapovať, font vráti index glyfu nula a súbor, ktorý vyjde, je štrukturálne platný, otvára sa všade a ukazuje prázdny štvorec tam, kde má byť meno alebo suma. Nikto v generujúcej pipeline to nezistí. Príjemca áno. HotPDF zatvára tú slučku s TrackUnresolvedGlyphs: zapnite ho a cesta kreslenia textu zaznamená každý kódový bod, ktorého dohľadanie glyfu sa rozrieši na index nula, pričom spustí OnUnresolvedGlyph raz za jedinečný nález s kódovým bodom, fontom, na ktorom zlyhal, scriptom, do ktorého patrí, a návrhom fontov, ktoré by ho pokryli
Detekcia je polovica odpovede. Druhou polovicou je SetFontFallbackChain, ktorá registruje usporiadaný zoznam fontov per script, takže bežné prípady sa rozriešia samy a k vášmu handleru sa dostanú len skutočné medzery. Spolu menia triedu defektov, ktorú kedysi hlásili zákazníci, na kontrolu v čase zostavovania
Prečo chýbajúci glyf nič nespustí?
Pretože ISO 32000 nekladie na producenta žiadnu povinnosť overiť pokrytie a index glyfu nula je legitímny glyf. Je to .notdef, ktorej obrys volí návrhár fontu: zvyčajne prázdny alebo dutý obdĺžnik, niekedy nič. Prehliadač, ktorý ho nakreslí, sa správa správne. Extrakcia textu môže dokonca vrátiť správne znaky, pretože mapovanie /ToUnicode sa zapisuje zo zdrojového textu, nie z obrysov, takže automatizovaná kontrola obojsmerného prechodu s radosťou prejde dokumentom, ktorého viditeľný text má diery
Praktickým dôsledkom je, že pokrytie sa musí kontrolovať v momente kreslenia, keď knižnica ešte vie, ktorý kódový bod bol vyžiadaný a ktorý glyf font skutočne ponúkol. Potom je informácia preč
Detektor musí sledovať stav subsetu, nie device context
Tu sa pokazila prvá implementácia a dôvod stojí za pochopenie, pretože platí pre akúkoľvek kontrolu pokrytia priskrutkovanú na textovú pipeline. HotPDF má dve textové cesty. Jedna emituje cez registrovaný Unicode TrueType font s mapou znakov v pamäti postavenou v čase registrácie. Druhá je legacy GDI cesta, ktorá vytvára čerstvý device context a handle fontu na každý beh znakov
Posudzovať pokrytie z GDI cesty je beznádejné. Jej mapovanie nie je mapovanie, ktoré skončí v emitovanom content streame, tie dve nie sú synchronizované, takže detektor čítajúci GDI výsledky hlási celý tlačiteľný rozsah ASCII ako nevyriešený. Autoritatívna odpoveď býva v registrovanom fonte: mapy znakov, ktorú parsuje RegisterUnicodeTTF, dotazovanej cez GetUnicodeGlyphForCodepoint. Detektor je preto strážený stavom subset-ready, nie žiadnou GDI podmienkou a jednoducho nebeží na dokumentoch, ktoré nikdy neregistrovali Unicode font, čo je správne, pretože tie dokumenty sú aj tak obmedzené na štandardné kódovania
Hneď vedľa sedí druhá pasca. GDI rodinné meno fontu a PostScript meno vytiahnuté z binárky fontu pri registrácii sú odlišné reťazce a nie v spôsobe, ktorý by ste mohli normalizovať: rodina volaná Arial Unicode MS nesie PostScript meno ArialMT. Každá brána napísaná ako „je aktuálne vybraný font ten, ktorý sme registrovali“, porovnávaná podľa mena, je mŕtvy kód, ktorý nikdy nezastrelní. Strážte stav, nikdy mená fontov
Netestujte detektor glyfov emodži
Zjavný testovací prípad je usmievajúca sa tvár a presvedčí vás, že detektor je rozbitý. Bežné kódové body emodži v astrálnych rovinách sa rozriešia cez cestu syntézy private-use, ktorá ich mapuje priamo na index glyfu, takže nikdy nedosiahnu všeobecnú vetvu pokrytia. Detektor sa správa správne a test meria zlú cestu
Použite namiesto toho nepridelený kódový bod. U+0378 je v Unicode natrvalo nepridelený, takže žiadny font ho legitímne nemôže namapovať a precvičuje presne vetvu, ktorú chcete overiť. Toto rozlíšenie medzi „funkcia je rozbitá“ a „test si vybral vstup, ktorý funkciu obchádza“ stojí reálne hodiny a nepridelené kódové body sú najlacnejší spôsob, ako sa tomu vyhnúť
type
TCoverageAudit = class
private
FFindings: TStringList;
public
procedure Handle(Sender: TObject;
const Info: THPDFUnresolvedGlyphInfo);
property Findings: TStringList read FFindings;
end;
procedure TCoverageAudit.Handle(Sender: TObject;
const Info: THPDFUnresolvedGlyphInfo);
begin
// Spustí sa raz za jedinečný kódový bod, nie raz za výskyt
FFindings.Add(Format('U+%.4X missing in %s (script %d), try: %s',
[Info.CodePoint, String(Info.FontName), Ord(Info.Script),
String(Info.SuggestedFonts)]));
end;
// Zapojenie do generujúcej úlohy
Pdf := THotPDF.Create(nil);
try
Pdf.TrackUnresolvedGlyphs := True;
Pdf.OnUnresolvedGlyph := Audit.Handle;
Pdf.RegisterUnicodeTTF('C:\Windows\Fonts\arial.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 720, 0, CustomerName);
Pdf.EndDoc;
if Audit.Findings.Count > 0 then
// Nechať úlohu zlyhať namiesto dodania strany so štvorcami
raise Exception.Create(Audit.Findings.Text);
finally
Pdf.Free;
end;
Reťaze fallbacku sú per script, nie per font
Dôvod, prečo je fallback rozsahovaný podľa scriptu namiesto podľa zdrojového fontu, je, že medzery pokrytia sa kopia podľa písacieho systému. Latinský textový font nemá Dévanágarí, thajčinu, Han a emodži, všetko naraz, a náhrada pre každý je iný font. Deklarácia jednej reťaze per script preto popisuje skutočné nasadenie: jeden latinský font pre text, jeden CJK font, jeden font emodži, jeden catch-all
// THPDFFontScript covers hfsCommon, hfsLatin, hfsGreek, hfsCyrillic,
// hfsHebrew, hfsArabic, hfsIndic, hfsSoutheastAsian, hfsCJK, hfsKana,
// hfsHangul, hfsEmoji and hfsOther
Pdf.SetFontFallbackChain(hfsCJK,
['Microsoft YaHei', 'SimSun', 'Yu Gothic']);
Pdf.SetFontFallbackChain(hfsArabic, ['Segoe UI', 'Arial']);
Pdf.SetFontFallbackChain(hfsEmoji, ['Segoe UI Emoji']);
Pdf.SetFontFallbackChain(hfsOther, ['Arial Unicode MS']);
Fallback a detekcia sú doplnkové, nie alternatívne. Reťaze zvládnu pokrytie, ktoré ste predvídali; detektor hlási pokrytie, ktoré ste nepredvídali, čo je na systéme spracujúcom ľubovoľné zákaznícke dáta tá zaujímavá polovica. Všimnite si, že náhrada fontu mení metriky, takže odsek, ktorý prepadne, sa môže prelomiť; ak na layoute záleží, uzáver a subsetting správanie náhradného fontu stojí za naštudovanie v článku o uzávere subsetu fontov a scripty potrebujúce preusporiadanie alebo spájanie rieši shaping etapa popísaná v tvarovaní textu komplexných scriptov
Ako doplniť správanie bez rizika existujúcej cesty
Toto isté vydanie pridalo legacy fallback tabuľky kern pre medzeru párov a spôsob, akým bol rozsahovaný, je vzor hodný skopírovania. Namiesto pridania nového rozhodovacieho bodu do logiky kerningu visí fallback na vetve skorého ukončenia, ktorá už existovala pre fonty bez tabuľky GPOS. Moderný font s GPOS sa k nej nikdy nedostane, takže jeho správanie je nezmenené konštrukciou, nie testovaním. Cesty, ktoré neregistrujú Unicode font, produkujú dva nulové offsety, takže aj one sú nezmenené
To je všeobecný tvar nízkorizikového doplnku do zrelej renderovacej knižnice: nájsť vetvu, ktorá momentálne neprodukuje nič, a umiestniť tam nové správanie. Mení to „veríme, že to nič neregresovalo“ na „toto nemôže nič regresovať“, čo je oveľa lepšia vec povedať o textovom engine, ktorým prechádzajú faktúry iných ľudí
Spravte z toho bránu, nie log
Nálezy pokrytia sú užitočné len vtedy, keď na nich niečo zlyhá. V službe generujúcej dokumenty je produktívne usporiadanie ponechať tracking zapnutý v nočnej regresnej úlohe nad korpusom skutočných mien zákazníkov, adries a popisov produktov a dať úlohe zlyhať pri každom náleze. Pretože udalosť sa spustí raz za jedinečný kódový bod, nie raz za výskyt, výstup zostáva dostatočne malý na čítanie aj vtedy, keď chýba celý script
V produkcii sa ten istý handler lepšie používa ako telemetria: zaznamenať kódový bod a font, pokračovať v obsluhe dokumentu a nechať agregát povedať, ktorý script pridať k deployment sade fontov nabližšie. Renderovacie správanie pre vložené a nahradené fonty ďalej pokrýva renderovanie glyfov vložených fontov a kompletný zoznam vlastností vrátane TrackUnresolvedGlyphs je zdokumentovaný na produktovej stránke HotPDF Delphi PDF component