PDF súbor, ktorý na vašom počítači vyzerá perfektne, no na inom stroji sa zobrazí ako rad prázdnych štvorčekov, je najčastejšou chybou písiem v softvéri na prácu s dokumentmi. Takmer nikdy to však neznamená, že samotný text je nesprávny. Znaky sú neporušené, kódovanie je v poriadku, len glyfy skrátka chýbajú. Rozdiel medzi oboma počítačmi spočíva v tom, ktoré písma mal nainštalované operačný systém, a rozdiel medzi prenosným a nestabilným súborom je výsledkom jedného rozhodnutia pri vytváraní stránky: či bolo písmo pribalené priamo v PDF, alebo sa predpokladalo, že je k dispozícii v cieľovom systéme.
Aby sme pochopili, prečo k tomu dochádza a prečo iný typ zlyhania spôsobuje, že vyhľadateľný text sa po skopírovaní zmení na nečitateľnú spleť znakov, musíme sa pozrieť na to, ako PDF ukladá text. Neukladá celé vety. Ukladá kódy glyfov, program písma a tabuľky, ktoré ich navzájom mapujú. Každá chyba vykresľovania alebo extrakcie pramení z nesúladu medzi týmito tromi časťami. Nasledujúci text ponúka pohľad na tento mechanizmus na základe normy ISO 32000 spolu s volaniami v Delphi, ktoré ho v prípade potreby ovládajú.
Znaky, kódy a glyfy sú tri rôzne veci
Terminológia často ľudí pletie, pretože bežná reč spája tri odlišné koncepty do jediného slova „písmeno“. Znak je abstraktná jednotka písma, napríklad predstava veľkého písmena A, ktoré je v kódovaní Unicode označené ako U+0041. Glyf je vykreslený tvar, teda krivka a obrys, ktoré konkrétne písmo používa na zobrazenie tohto znaku. Medzi nimi sa nachádza kód: bajt alebo bajty v dátovom toku obsahu (content stream) ktoré prehliadaču hovoria, ktorý glyf z aktuálneho písma má vykresliť.
PDF pracuje s kódmi. Keď dátový tok obsahu zobrazuje reťazec, tieto bajty predstavujú indexy do aktívneho písma, nie Unicode znaky. Kódovanie písma určuje, že kód 65 znamená „vykresli glyf uložený pod číslom 65“, pričom systém nevie, že výsledok vyzerá pre človeka ako písmeno A. Vďaka tomu sa PDF vykresľuje rovnako všade tam, kde dokáže nájsť potrebné glyfy. To je zároveň dôvod, prečo je extrakcia textu iným problémom ako samotné zobrazenie: na vykreslenie stačí prevod kódu na glyf, no na čítanie je potrebný prevod kódu na Unicode a ide o dve rôzne tabuľky, ktoré sa môžu líšiť alebo nezávisle chýbať.
Typy písiem, s ktorými sa reálne stretnete
Norma ISO 32000 definuje niekoľko typov slovníkov písiem. V praxi dokument, ktorý dostanete alebo vygenerujete, používa jeden z troch hlavných typov. Znalosť toho, s ktorým typom pracujete, objasňuje väčšinu možných problémov.
Type 1 je pôvodný formát obrysov PostScript od spoločnosti Adobe, postavený na kubických Bezierových krivkách. Štandardných štrnásť písiem, ktoré musí poskytovať každý kompatibilný prehliadač (rodiny písiem Helvetica, Times, Courier, Symbol a ZapfDingbats), patrí do typu Type 1. Ak slovník písiem odkazuje na jedno z nich, môže legálne vynechať samotný program písma. To je jediný prípad, kedy je ponechanie písma bez vloženia bezpečné priamo podľa špecifikácie a nie len vďaka šťastiu. Pri akomkoľvek inom písme typu Type 1 musí byť program vložený, inak prehliadač použije náhradné písmo, ktoré je zvyčajne metricky podobné, ale vizuálne odlišné.
TrueType využíva kvadratické krivky a pochádza zo sveta spoločností Apple a Microsoft. Ide o najčastejší typ systémových písiem a zároveň formát, ktorý budete vkladať najčastejšie. Jednoduché TrueType písmo v PDF je obmedzené na jednobajtové kódy, takže jedno takéto písmo môže naraz adresovať maximálne 256 glyfov. Tento limit je konštrukčným dôvodom, prečo jazyky CJK (čínština, japončina, kórejčina) a iné rozsiahle sady znakov nemôžu fungovať s jednoduchým písmom.
Type 0, zložené písmo alebo písmo indexované pomocou CID, je riešením tohto limitu. Používa viacbajtové kódy a tabuľku CMap na ich smerovanie cez odvodené písmo CIDFont, ktorého samotné obrysy sú typu TrueType alebo CFF/Type 1. Toto je jediný typ písma, ktorý dokáže pojať tisíce glyfov. Každé PDF obsahujúce čínštinu, japončinu, kórejčinu alebo pestrú viacjazyčnú zmes používa typ Type 0, bez ohľadu na to, či si to autor uvedomoval. Daňou za to je však komplexnosť: viac pohyblivých častí, z ktorých väčšina musí byť správna pre vykresľovanie aj extrakciu.

Veľkosť súboru ovplyvňuje jeden dôležitý detail na pozadí tohto obrázka. Písmo je knižnica obrysov, nie bitmapy s pevnou veľkosťou, takže rovnaký vložený program obsluhuje každú veľkosť bodu na stránke. Zmena veľkosti (škálovanie) je transformácia aplikovaná v čase kreslenia, čo je dôvod, prečo nadpis a hlavný text zdieľajú rovnaký vložený rez a prečo sú náklady na vloženie vyjadrené na jedno písmo, nie na každú veľkosť zvlášť.
Vkladanie písiem je rozdiel medzi prenosnosťou a nestabilitou
Vloženie (embedding) znamená, že program písma, teda samotné údaje o obrysoch, je zapísaný do PDF ako dátový tok. Prehliadač na počítači, ktorý o vašom písme nikdy nepočul, prečíta tieto obrysy priamo zo súboru a vykreslí presné glyfy. Ak vloženie vynecháte, spoliehate sa na to, že cieľový počítač má nainštalované písmo s rovnakým názvom. Ak ho nemá, prehliadač použije náhradné písmo. Pri štrnástich štandardných písmach je táto náhrada presne definovaná a bezproblémová. Pri ostatných písmach sa výsledok môže pohybovať od miernej odchýlky v inom reze až po prázdne štvorčeky, ak žiadne náhradné písmo nepokrýva danú sadu znakov.
V komponente HotPDF sa toto správanie ovláda jedinou vlastnosťou, ktorú je potrebné nastaviť pred otvorením dokumentu. Vlastnosť FontEmbedding hovorí knižnici, aby pribalila použité rezy písiem priamo do súboru:
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;
Poradie príkazov nie je len formálne. V metóde BeginDoc zapisuje HotPDF štruktúru dokumentu, preto musí byť vlastnosť FontEmbedding nastavený na hodnotu True pred týmto volaním. Ak ju priradíte neskôr, nevygeneruje sa žiadna chyba ani varovanie, iba súbor potichu odíde bez vložených písiem. To je ten najhorší druh chyby: prejde všetkými testami na počítači vývojára, kde je písmo náhodou nainštalované, a prejaví sa až u zákazníka, u ktorého chýba.
Vkladanie písiem je tiež oblasť, kde sa licencovanie stretáva s vývojom. Program písma obsahuje príznaky (flags) definujúce, či sa môže voľne vkladať, či je určený len na náhľad, alebo sa nesmie vkladať vôbec. Dodržiavanie týchto pravidiel je vašou zodpovednosťou, nie zodpovednosťou vykresľovacieho modulu, a skutočnosť, že to technicky funguje, neznamená, že je to legálne povolené.
Podmnožiny písiem: Vkladanie iba použitých glyfov
Úplné vloženie zapíše do súboru celý program písma. Rozsiahle TrueType písmo pre CJK môže mať aj niekoľko megabajtov a jeho úplné vloženie kvôli zobrazeniu niekoľkých znakov je neefektívne, čo sa prejaví najmä pri viacstránkových dokumentoch. Tvorba podmnožín (subsetting) to rieši tak, že zapíše len tie glyfy, na ktoré dokument odkazuje. Potom písmo premenuje pomocou šesťpísmenového štítku a znamienka plus (napríklad ABCDEF+Calibri v zozname písiem exportovaného PDF), aby si čítačka nikdy nepomýlila čiastočné písmo s plnohodnotným systémovým písmom rovnakého názvu.
Pri väčšine generovaných dokumentov je tvorba podmnožín správnou predvolenou voľbou. Udržiava veľkosť súboru úmernú obsahu a nie veľkosti zdrojového písma, čo je dôležité najmä pri veľkých viacjazyčných písmach, ktoré by inak súbor preťažili. Jediným úskalím je, že podmnožina obsahuje iba to, čo sa použilo pri vytváraní dokumentu. Ak sa neskorší proces pokúsi pridať text pomocou tohto písma, potrebné glyfy v súbore už nemusia byť k dispozícii, čo predstavuje obmedzenie pri dodatočných úpravách cudzieho PDF súboru.
Písma Unicode a problém so štvorčekmi pri CJK
Keď text nie je v obyčajnej latinke, možnosti jednoduchého písma končia. Riešením je explicitne zaregistrovať písmo podporujúce Unicode a nechať komponent HotPDF vytvoriť z neho písmo typu Type 0. Metóda RegisterUnicodeTTF načíta TrueType súbor na základe zadanej cesty, potom je možné zaregistrovaný názov použiť v SetFont rovnako ako akýkoľvek iný:
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;
Tento krok závisí od dvoch vecí. Písmo musí pokrývať znaky použité v reťazci: TrueType písmo určené iba pre latinku nezačne zobrazovať čínske glyfy len preto, že ste to požadovali. Výsledkom by boli opäť prázdne štvorčeky, tentoraz preto, že glyf v danom písme naozaj neexistuje. Druhou podmienkou je, že vkladanie písiem musí zostať zapnuté, pretože písmo typu Type 0 zostavené zo zaregistrovaného TrueType písma nemá pre čítačku zmysel, ak nemôže nájsť jeho obrysy. Pre zmiešaný obsah sú spoľahlivou voľbou písma so širokým pokrytím, ako napríklad rodiny písiem Noto a Arial Unicode MS, ktoré sú vložené a majú vytvorené podmnožiny.
Písma s orientáciou sprava doľava (RTL) a zložité znakové sady pridávajú k samotnému pokrytiu ešte vrstvu tvarovania. HotPDF poskytuje metódu RtLTextOut pre arabčinu a hebrejčinu, ktorá rieši smerové usporiadanie znakov, takže stačí zadať logické poradie a knižnica sa postará o ich správne rozloženie. Správne zobrazenie arabčiny si vyžaduje správne pokrytie písma, tvarovanie a smer, čo sú tri rôzne veci. Výskyt štvorčeka v tomto prípade môže znamenať zlyhanie ktorejkoľvek z nich.
Tabuľka ToUnicode: Kde prebieha kopírovanie textu
Všetko vyššie uvedené sa týka kreslenia. Extrakcia textu je druhou stranou mince a zlyháva z vlastných dôvodov. Prehliadač vykresľuje stránku pomocou mapovania kódov na glyfy v písme, no keď používateľ označí a skopíruje text, prehliadač potrebuje tieto kódy prevésť späť na Unicode znaky. Toto spätné mapovanie zabezpečuje tabuľka ToUnicode CMap, čo je voliteľný dátový tok pripojený k písmu.
Ak je táto tabuľka prítomná a správna, skopírovaný text sa zobrazí so správnymi znakmi. Ak chýba, je nesprávna alebo bolo písmo rozdelené na podmnožiny s vlastnými kódmi glyfov bez zápisu tabuľky ToUnicode, stránka bude vyzerať bezchybne, no po skopírovaní dostanete len nezmyselné znaky. Kódy glyfov sa totiž načítajú, akoby to boli priamo Unicode kódy, čo v prípade vlastného kódovania podmnožiny nie je pravda. To je dôvod, prečo naskenovaný dokument s vrstvou OCR môže byť vyhľadateľný, zatiaľ čo digitálne vytvorené PDF z nepozorného generátora nie je. Vykresľovanie a extrakcia využívajú iné tabuľky, takže súbor môže v jednej oblasti fungovať a v druhej zlyhať. Ak je pre vás extrakcia dôležitá, považujte správnu mapu ToUnicode za nevyhnutnú požiadavku a overte si ju skopírovaním textu zo vzorky.
Como rýchlo diagnostikovať chybu písma
Spôsob zlyhania vám napovie, kde hľadať problém. Prázdne štvorčeky na inom počítači takmer vždy znamenajú, že písmo nebolo vložené. Skontrolujte preto najprv vloženie a potom pokrytie glyfov. Štvorčeky, ktoré sa zobrazujú aj na vašom počítači, poukazujú na pokrytie: písmo danú znakovú sadu neobsahuje, bez ohľadu na nastavenie vloženia. Text, ktorý sa vykresľuje správne, ale kopíruje sa ako nezmysly, predstavuje problém s ToUnicode, nie s vykresľovaním. Zmena písiem alebo ich vkladanie tu nepomôže, pretože samotné vykresľovanie nebolo prerušené. Ak chcete skontrolovať hotový súbor, otvorte ho v programe Acrobat a pozrite si Vlastnosti dokumentu, časť Písma: správny záznam zobrazuje typ, uvádza „Vložené“ alebo „Vložená podmnožina“ a uvádza kódovanie. Písmo, ktoré malo byť vložené a nie je, tam uvidíte skôr, ako vás na to upozorní zákazník.
Keď pochopíte rozdiel medzi znakom, kódom a glyfom, nič z toho už nebude zložité. Vkladajte písma, s ktorými kreslíte, vytvárajte podmnožiny pri veľkých písmach, siahnite po Unicode písme a použite RegisterUnicodeTTF, len čo text prestane byť v latinke, a zachovajte správnu mapu ToUnicode, ak sa z textu bude neskôr čítať. S týmito pravidlami sa štvorčeky prestanú zobrazovať. Čo sa týka súvisiacich mechanizmov, anatómia minimálneho PDF ukazuje, kde sa v strome objektov nachádza slovník písiem, a sprievodca štruktúrou dokumentu popisuje, ako sa zdroje zdieľajú medzi stránkami.
Volania SetFont, FontEmbedding a RegisterUnicodeTTF uvedené v tomto článku sú súčasťou komponentu HotPDF Component pre Delphi a C++Builder.