PDFlibPas rieši znaky, ktoré zvolené písmo nedokáže vykresliť, tak, že prehľadáva reťazec nainštalovaných náhradných rezov, zhluk po zhluku, pričom zachováva tvarovanie (shaping) aj poradie obojsmerných (bidi) behov. Funkciu zapnete pomocou SetAutomaticFontFallback, reťazec rozšírite pomocou AddFontFallback, a do súboru sa vloží iba to náhradné písmo, ktoré sa pri výstupe naozaj použilo
Problém, ktorý táto funkcia rieši, stretne skôr či neskôr každý generátor dokumentov — vo chvíli, keď príde meno zákazníka v písme, s ktorým predlohové písmo vôbec nepočítalo. Zlyhanie je tiché, a práve to ho robí nákladným
Prečo nepodporovaný text zmizne namiesto toho, aby vyvolal chybu?
Pretože PDF vôbec nepozná pojem písma, ktoré nedokáže vykresliť znak. Jednoduché písmo mapuje bajtové kódy cez kódovanie na názvy glyfov; zložené písmo mapuje kódy cez CMap na indexy glyfov. Ak požiadate o glyf, ktorý daný rez neobsahuje, dostanete index glyfu nula, .notdef, ktorý väčšina rezov vykreslí ako nič alebo ako prázdny rámček. Súbor je štrukturálne platný, textový operátor je správne formátovaný a stránka sa vykreslí. Je tam len prázdne miesto tam, kde malo byť meno
Norma ISO 32000-1 od producenta nikde nevyžaduje, aby si to všimol. Generátor, ktorý zapisuje text bez kontroly pokrytia, vytvorí technicky vyhovujúci PDF, ktorý ticho stratil obsah, a táto strata sa prejaví na obrazovke zákazníka o niekoľko týždňov neskôr. Práve preto ide funkcia náhradných písiem ruka v ruke so správou o chýbajúcich glyfoch: vyriešiť to, čo sa vyriešiť dá, je len polovica úlohy, tou druhou je nahlásiť to, čo sa vyriešiť nepodarilo
Náhrada prebieha na úrovni zhluku, nie na úrovni kódového bodu
Práve granularita je detail, ktorý oddeľuje funkčnú implementáciu od len vierohodne vyzerajúcej. Text nie je postupnosť nezávislých znakov. Slabika v dévanágarí, emoji s modifikátorom farby pleti, základné písmeno so spojovacími znamienkami: každé z toho je jeden zhluk, ktorý musí vykresliť jedno písmo, pretože rozhodnutia o tvarovaní vnútri neho závisia od tabuliek v danom reze
PDFlibPas rieši zhluky vcelku, takže zhluk, ktorý pokrýva náhradný rez, vykreslí tento rez celý. Rozdelenie uprostred zhluku, kde by sa polovica vykreslila z primárneho písma a polovica z náhradného, by dalo technicky prítomný, no viditeľne rozbitý výsledok — čo je diskutabilne horšie než prázdne miesto, od ktorého ste vyšli. Zachováva sa aj poradie behu, takže náhrada vnútri behu sprava doľava nemení poradie okolitého textu; ten istý mechanizmus je základom vertikálneho rozloženia opísaného v článku vertikálne písanie pre japončinu a čínštinu
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.SetAutomaticFontFallback(1);
// Poradie hľadania: vyhráva prvá zhoda, preto najširšie rezy dajte na koniec
Lib.AddFontFallback('Microsoft YaHei'); // Zjednodušená čínština
Lib.AddFontFallback('Meiryo'); // Japončina
Lib.AddFontFallback('Segoe UI Symbol');
Lib.AddFontFallback('Segoe UI Emoji');
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_REPORT);
Lib.AddTrueTypeFont('Arial', 1); // 1 = vložiť rez do súboru
Lib.SetTextSize(11);
Lib.DrawText(72, 720, 'Invoice for 北京示例科技有限公司');
Lib.DrawText(72, 700, 'Delivery status: on time');
Lib.SaveToFile('invoice.pdf');
finally
Lib.Free;
end;
end;
Poradie reťazca voľte zámerne. Riešenie použije prvý rez, ktorý zhluk pokrýva, takže široké pan-unikódové písmo umiestnené na začiatku vyhrá takmer vždy a vaše starostlivo vybrané rezy pre konkrétne písma sa nikdy nepoužijú. Konkrétne rezy dajte na začiatok a univerzálny rez až na koniec
Nahlásiť alebo prerušiť: aké zlyhanie chcete?
SetMissingGlyphPolicy prijíma buď PDF_MISSING_GLYPH_REPORT, kompatibilnú predvolenú hodnotu, alebo PDF_MISSING_GLYPH_ABORT. Pri politike hlásenia textová operácia pokračuje, nevyriešiteľné kódové body sa vynechajú ako predtým a každý z nich sa zaznamená. Pri politike prerušenia sa textová operácia odmietne skôr, než sa zapíše akýkoľvek obsah, a LastErrorCode sa nastaví na 521
Voľbu robte podľa účelu dokumentu. Dávka interných reportov by mala pokračovať vo vykresľovaní a zaznamenávať medzery, pretože mierne neúplný report dnes je lepší než žiadny report vôbec. Právne záväzná zmluva, faktúra alebo čokoľvek s menom na sebe by malo skončiť prerušením, pretože ticho vynechaný znak v mene zmluvnej strany je chyba, ktorú chcete objaviť vo vlastnom procese, a nie v spore. Politika prerušenia zlyhá ešte pred zápisom, takže po sebe nenechá žiadny polovičato vytvorený obsahový tok
var
Lib: TPDFlib;
Report: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_ABORT);
// ... zostavte dokument ...
if Lib.DrawText(72, 660, CustomerName) <> 1 then
if Lib.LastErrorCode = PDFLIB_ERROR_MISSING_GLYPH then
begin
Report := Lib.GetMissingGlyphReportJSON;
// {"valid":false,"policy":1,"eventCount":1,"events":[
// {"sequence":1,"documentIndex":0,"page":1,"utf16Index":12,
// "codePoint":21271,"unicode":"U+5317","fontName":"Arial",
// "fontType":"TrueType","operation":"DrawText"}]}
EscalateToOperator(Report);
end;
finally
Lib.Free;
end;
end;
Report je zámerne strojovo čitateľný a ohraničený. Každá udalosť nesie stránku, index UTF-16 vnútri reťazca, kódový bod v číselnej aj v tvare U+XXXX, vybrané písmo, jeho typ a operáciu, pri ktorej problém nastal, takže tiket podpory môže pomenovať presný znak namiesto opisovania príznaku. Sledovač uchováva posledných 256 udalostí, čo stačí na diagnostiku dokumentu a zároveň je to dosť málo na to, aby patologický beh nezmenil diagnostiku na pamäťový problém
Meranie a kreslenie sa musia zhodovať
Meranie šírky používa rovnaké rozhodnutia o náhrade na úrovni zhlukov ako kreslenie. Znie to samozrejme, a práve to je vec, ktorú väčšina domácky vytvorených náhradných vrstiev pokazí: opravia cestu kreslenia, no meranie ponechajú na primárnom písme, a každé textové pole, zarovnanie doprava či stĺpec tabuľky sa potom počíta zo šírok, ktoré nezodpovedajú tomu, čo sa naozaj vykreslilo
Keďže obe cesty zdieľajú rovnaké riešenie, reťazec zmeraný pred kreslením zaberie presne tú šírku, na akú bol zmeraný, vrátane náhradných behov. Práve to robí zapnutie náhrady globálne bezpečným, a nie len na miestach, ktoré ste ručne overili
Vloží sa len to, čo ste skutočne použili
Náhradné písma sa vkladajú lenivo (lazily): rez v reťazci, ktorý nikdy nevyriešil žiadny zhluk, do výstupu neprispeje ničím. Dokument obsahujúci jeden čínsky znak a 5 000 latinských neobsahuje celý CJK rez; obsahuje len to, čo pre daný jeden glyf vyprodukoval proces subsetovania — čo je správanie opísané v článku optimalizácia veľkosti PDF súborov a subsetovanie písiem
Vďaka tejto lenivosti je lacné nakonfigurovať aj široký reťazec. Zaregistrujte rezy, ktoré vaša množina dokumentov môže potrebovať naprieč všetkými jazykovými prostrediami, ktoré obsluhujete, a každý jednotlivý PDF súbor zaplatí len za to, čo skutočne použil. Pre dokumenty, ktoré ste sami nevygenerovali a kde chýbajúce rezy sú už vnútri existujúceho súboru, je oprava iná a je opísaná v článku vkladanie chýbajúcich písiem do existujúceho PDF
Jedno prevádzkové upozornenie stojí za to povedať jasne: náhrada sa rieši voči rezom nainštalovaným na stroji, ktorý kód spúšťa. Server bez nainštalovaných CJK písiem nemá na čo prejsť, a report vám to oznámi hneď pri prvom dokumente, a nie až po prvej sťažnosti. Dodávajte písma, na ktoré sa spoliehate, a overte si licenčné podmienky pre ich vkladanie
PDFlibPas je knižnica PDF pre Delphi, C++Builder a Lazarus so zodpovedajúcimi rozhraniami DLL a ActiveX, takže API pre náhradné písma aj pre chýbajúce glyfy je dostupné aj pre volajúcich mimo Pascalu. Úplná dokumentácia je na stránke PDFlibPas Delphi PDF library