PDF dokument, který na vašem počítači vypadá perfektně a na cizím se vykreslí jako řada prázdných čtverečků, je nejčastější chybou fontů v softwaru pro dokumenty, a téměř nikdy to neznamená, že je text špatně. Znaky jsou nedotčené, kódování je v pořádku, piktogramy (glyphs) tam jednoduše chybí. To, co se mezi oběma počítači liší, jsou nainstalované fonty v operačním systému. Rozdíl mezi přenositelným souborem a křehkým souborem spočívá v jednom rozhodnutí učiněném při zápisu stránky: zda font cestoval uvnitř PDF, nebo se předpokládalo, že bude přítomen na cílové stanici
Pochopení toho, proč k tomu dochází a proč jiné selhání vytvoří text, který vypadá vyhledávatelně, ale po zkopírování je z něj nesmysl, znamená podívat se na to, jak PDF ukládá text. Neukládá věty. Ukládá kódy piktogramů spolu s programem fontu a tabulkami, které mapují jedno na druhé. Každá chyba ve vykreslování nebo extrakci se nachází v mezeře mezi těmito třemi prvky. Následuje prohlídka tohoto mechanismu, vycházející z normy ISO 32000, včetně volání v Delphi, která jej řídí tam, kde na nich záleží
Znaky, kódy a piktogramy jsou tři různé věci
Slovní zásoba lidi často mate, protože každodenní řeč spojuje tři různé myšlenky do slova „písmeno“. Znak (character) je abstraktní jednotka psaní, myšlenka velkého A, identifikovaná v Unicode jako U+0041. Piktogram (glyph) je nakreslený tvar, obrys křivky a dříku, který konkrétní font používá k zobrazení tohoto znaku. Mezi nimi se nachází kód: bajt nebo bajty v datovém toku (content stream), které prohlížeči říkají, jaký piktogram v aktuálním fontu má vykreslit
PDF pracuje v kódech. Když datový tok zobrazuje řetězec, tyto bajty jsou indexy do aktivního fontu, nikoli do Unicode. Kódování fontu určuje, že kód 65 znamená „vykresli piktogram uložený pod číslem 65“, a nic v této operaci neví, že výsledek vypadá pro člověka jako A. To je to, díky čemu se PDF vykresluje identicky všude, kde dokáže najít piktogramy, a také proč je extrakce samostatný problém oddělený od zobrazení: kreslení potřebuje pouze kód-na-piktogram, čtení potřebuje kód-na-Unicode. Jedná se o dvě různé tabulky, které se mohou navzájem lišit nebo nezávisle chybět
Typy fontů, se kterými se skutečně setkáte
Norma ISO 32000 definuje několik typů slovníků fontů a v praxi dokument, který obdržíte nebo vygenerujete, používá jeden ze tří. Vědět, na který z nich se díváte, vysvětluje většinu toho, co se může pokazit
Type 1 je původní formát obrysů PostScript od Adobe, postavený na kubických Bézierových křivkách. Čtrnáct standardních fontů, které musí každý vyhovující prohlížeč poskytovat (rodiny Helvetica, Times, Courier, Symbol a ZapfDingbats), jsou Type 1, a slovník fontů, který jeden z nich pojmenuje, může legálně program fontu vynechat. To je jediný případ, kdy je ponechání fontu nevloženého bezpečné spíše díky specifikaci než díky štěstí. U jakéhokoli jiného řezu písma Type 1 musí být program vložen, jinak prohlížeč dosadí nějakou náhradu, obvykle metricky podobný, ale vizuálně odlišný font
TrueType používá kvadratické křivky a pochází ze světa společností Apple a Microsoft. Většina systémových fontů je v tomto formátu, a ten také budete vkládat nejčastěji. Jednoduchý TrueType font v PDF je omezen na jednobajtové kódy, takže jeden takový font dokáže adresovat nanejvýš 256 piktogramů najednou. Tento limit je strukturálním důvodem, proč CJK a další rozsáhlá písma nemohou fungovat na jednoduchém fontu
Type 0, složený nebo CID font (CID-keyed), je odpovědí na tento limit. Používá vícebajtové kódy a CMap, aby je směroval přes potomka CIDFont, jehož obrysy jsou samy o sobě buď TrueType, nebo CFF/Type 1. Toto je jediný typ fontu, který dokáže nést tisíce piktogramů, takže jakékoli PDF obsahující čínštinu, japonštinu, korejštinu nebo širokou mnohojazyčnou směs používá Type 0, ať už o tom autor přemýšlel nebo ne. Daní za to je složitost: více pohyblivých částí, z nichž více musí být správně jak pro vykreslení, tak pro extrakci

Jeden detail za tímto obrázkem ovlivňuje velikost souboru. Font je knihovna obrysů, nikoli bitmap s pevnou velikostí, takže stejný vložený program slouží pro každou velikost bodů na stránce. Změna měřítka (scaling) je transformace aplikovaná v době vykreslování, a to je důvod, proč nadpis a text dokumentu sdílejí jeden vložený řez písma a proč se nároky na velikost u vkládání počítají za font, nikoli za jeho velikost
Vkládání (embedding) tvoří rozdíl mezi přenositelným a křehkým dokumentem
Vkládání (embedding) znamená, že program fontu, samotná data obrysů, se do PDF zapíše jako datový tok. Prohlížeč na počítači, který o vašem fontu nikdy neslyšel, si tyto obrysy přečte přímo ze souboru a nakreslí přesné piktogramy. Pokud vkládání přeskočíte, sázíte na to, že cíl má font stejného jména. Pokud nemá, prohlížeč přejde k náhradě. U standardních čtrnácti fontů je tato náhrada definovaná a neškodná. U všech ostatních se pohybuje od těsného vedle s jiným řezem písma až po výsledek s prázdnými čtverečky, když žádná náhrada dané písmo nepokrývá
S HotPDF je ovládání jednou vlastností, která se nastavuje před otevřením dokumentu. Vlastnost FontEmbedding říká knihovně, aby přibalila řezy písma, kterými kreslí, přímo do souboru:
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.Compression := cmFlateDecode;
Pdf.FontEmbedding := True; // outlines travel inside the file
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Calibri', [], 11);
Pdf.CurrentPage.TextOut(72, 760, 0, 'This renders the same on a machine without Calibri.');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Pořadí není kosmetické. BeginDoc je místo, kde HotPDF potvrzuje strukturu dokumentu, takže FontEmbedding musí být true (pravda) ještě před tímto voláním. Přiřadíte-li jej až poté, nenastane žádná chyba ani varování, jen soubor, který v tichosti odešel bez svých fontů. To je nejhorší druh chyby: projde každým testem na vývojářově počítači, kde je font náhodou nainstalován, a objeví se až u zákazníka, kde nainstalován není
Při vkládání (embedding) se také licencování setkává s inženýrstvím. Program fontu nese příznaky popisující, zda smí být vkládán volně, pouze pro náhled, nebo vůbec. Respektování těchto příznaků je vaší odpovědností, nikoli odpovědností vykreslovacího modulu, a rčení „fungovalo to“ neznamená totéž jako „bylo to povoleno“
Subsetting (vytváření podmnožin): vložte pouze piktogramy, které jste použili
Úplné vložení (full embedding) zapíše do souboru celý program fontu. Rozsáhlý řez CJK TrueType může mít velikost i několika megabajtů, a vkládat jej celý kvůli zobrazení tuctu znaků je plýtváním, které se u vícestránkového dokumentu ještě násobí. Subsetting to řeší zápisem pouze těch piktogramů, na které dokument odkazuje, a následným přejmenováním fontu pomocí šestimístné značky a znaménka plus (ve formátu ABCDEF+Calibri v každém seznamu fontů v PDF s podmnožinami), aby si prohlížeč nikdy nespletl částečný řez písma s plným systémovým fontem téhož jména
Pro většinu generovaných dokumentů je subsetting správnou výchozí volbou. Udržuje velikost souboru úměrnou obsahu, spíše než zdrojovému fontu, na čemž nejvíce záleží u velkých mnohojazyčných fontů, které by jinak souboru dominovaly. Jediným háčkem je to, že podmnožina obsahuje pouze to, co bylo použito v době vytvoření. Pokud se některý navazující proces pokusí přidat text do fontu s vytvořenou podmnožinou později, piktogramy, které bude potřebovat, už v souboru nemusí být. Jde o reálné omezení při inkrementální úpravě cizího PDF
Fonty Unicode a problém se čtverečky CJK
Pokud se nejedná o obyčejnou latinku, cesta s jednoduchým fontem vyprší a řešením je explicitně zaregistrovat font podporující Unicode a nechat z něj HotPDF vytvořit font Type 0. Metoda RegisterUnicodeTTF načte soubor TrueType pomocí cesty; poté lze registrovaný název používat ve volání SetFont stejně jako u jakéhokoli jiného fontu:
Pdf.FontEmbedding := True;
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansCJKsc-Regular.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('NotoSansCJKsc-Regular', [], 14);
Pdf.CurrentPage.TextOut(72, 720, 0, '你好,世界 こんにちは 안녕하세요');
Pdf.EndDoc;
Dvě věci mohou tento proces podpořit, nebo jej zhatit. Font musí pokrývat dané písmo v řetězci: TrueType obsahující pouze latinku nevygeneruje čínské piktogramy, když ho o to požádáte, a výsledkem jsou opět prázdné čtverečky, tentokrát z toho důvodu, že se daný piktogram v daném řezu písma skutečně nenachází. Vkládání (embedding) navíc musí zůstat zapnuté, protože font Type 0 sestavený z registrovaného formátu TTF ztrácí pro prohlížeč smysl, pokud ten nedokáže najít obrysy. Pro smíšený obsah je trvanlivou volbou řez písma s širokým pokrytím, kde obvyklými odpověďmi bývají rodiny fontů Noto a Arial Unicode MS, jak s použitím embedding, tak i subsetting
Zápis zprava doleva a složitá písma přidávají nad pokrytí navíc vrstvu tvarování (shaping). HotPDF obsahuje pro arabštinu a hebrejštinu metodu RtLTextOut, která zpracovává směrové přeřazení, takže předáte logické pořadí a necháte knihovnu uspořádat rozložení. Aby byla arabština správně, je zapotřebí zkombinovat pokrytí, tvarování a směr. Jde o tři samostatné věci a čtvereček může znamenat to, že jakákoli z nich selhala
Tabulka ToUnicode: kde žije kopírování a vkládání (copy-paste)
Vše výše uvedené se týká vykreslování. Extrakce je naopak zrcadlovým obrazem a selhává ze svých vlastních důvodů. Prohlížeč vykresluje stránku pomocí mapování kódů na piktogramy (code-to-glyph) uloženém ve fontu, ale když uživatel text vybere a zkopíruje, prohlížeč potřebuje proměnit tyto stejné kódy zpět na Unicode. Toto reverzní mapování provádí tabulka ToUnicode CMap, volitelný stream připojený k fontu
Pokud je přítomna a v pořádku, zkopírovaný text se převede do správných znaků. Jestliže ovšem chybí či obsahuje chyby, případně byla pro daný font vytvořena podmnožina s vlastními kódy piktogramů a žádná tabulka ToUnicode nebyla zapsána, stránka vypadá dokonale a schránka se naplní nesmysly: kódy piktogramů se čtou tak, jako by to byly znaky v kódování Unicode, kterými však u podmnožiny s vlastním kódováním nejsou. Z tohoto důvodu lze skenovaný dokument s textovou vrstvou OCR prohledávat, zatímco čistě digitální PDF od nedbalého generátoru nikoliv. Vykreslování a extrakce čerpají z jiných tabulek, takže soubor může vyhovět jedné z nich, ale u té druhé selhat. Záleží-li vám u výstupu na extrakci, nakládejte se správnou tabulkou ToUnicode jako s povinností, a namísto spoléhání na to, že tam je, proveďte raději pro jistotu zkoušku překopírováním textu ze vzorku
Jak rychle diagnostikovat chybu fontu
Režim poruchy (failure mode) vám napoví, kde hledat. Prázdné čtverečky na jiném počítači téměř vždy znamenají, že font nebyl vložen, takže zkontrolujte na prvním místě embedding, a teprve poté pokrytí piktogramy. Čtverečky, které se objeví dokonce i na vašem vlastním počítači, poukazují na pokrytí: font dané písmo neobsahuje, nehledě na vložení. Text, který se správně vykreslí, ale při zkopírování produkuje nesmysly, představuje chybu tabulky ToUnicode, a nejde tedy o problém s vykreslením. Pokud byste v tomto případě zasahovali do fontů nebo jejich vkládání, k ničemu by to nevedlo, jelikož kreslení v tomto případě nebylo vadné. Abyste nahlédli do hotového souboru, otevřete si jej v programu Acrobat a podívejte se do záložky Vlastnosti dokumentu, Písma (Document Properties, Fonts). Záznam o zdravém souboru zobrazí daný typ, hlásí stav Vložený nebo Vložený podřízený soubor (Embedded Subset) a uvádí název kódování. Font, jenž měl být vložen a nebyl, se na onom místě prozradí dříve, než s ním narazí zákazník
Nic z toho není příliš exotické, pokud máte jasno v rozdělení na znaky, kódy a piktogramy. Vložte fonty, se kterými budete chtít vykreslovat, vytvořte podmnožiny u těch rozsáhlejších fontů, ve chvíli, kdy text opustí latinku, sáhněte po fontu podporujícím Unicode a zavolejte metodu RegisterUnicodeTTF a zachovejte správnou tabulku ToUnicode, pokud si bude kdokoliv daný text zkoušet extrahovat. Když tohle dodržíte, čtverečky se přestanou objevovat. Co se týká okolních mechanismů, anatomie minimálního PDF ukazuje, kde se ve stromu objektů nachází slovník fontů, a návod na strukturu dokumentu se pak věnuje tomu, jak jsou tyto zdroje sdíleny napříč stránkami
Zde ukázaná volání SetFont, FontEmbedding a RegisterUnicodeTTF jsou součástí knihovny HotPDF Component pro Delphi a C++Builder