Dizajnér vyberie písmo s jednoposchodovým a pre nadpisy, prečiarknutou nulou pre tabuľky alebo sadou ozdobných veľkých písmen (swash capitals) pre obálku. Tieto glyfy už v písme sú. Len nie sú predvolené. Predvolené a sa mapuje zo znaku cez tabuľku cmap na jeden glyf a alternatíva sa nachádza o niekoľko ID glyfov ďalej, dostupná len prostredníctvom pravidla nahradenia. Vytvorenie tejto alternatívy v PDF znamená prečítanie pravidla a vygenerovanie náhradného glyfu v toku obsahu. Tento článok je o čítaní týchto pravidiel, konkrétne typu jednoduchej substitúcie (single-substitution), v Object Pascale bez natívnej shaper knižnice pod ním.
Rozsah je zámerne úzky. Štylistické sady a alternatívy sú substitúcie typu jeden glyf na vstupe, jeden glyf na výstupe. Sú to tie časti rozvrhnutia OpenType, ktoré môžete vyriešiť malým, deterministickým prechodom tabuľky, vďaka čomu sa hodia pre engine v Pascale, ktorý chce zostaviť bez závislostí na C.
Prečo čisté Delphi namiesto HarfBuzz
HarfBuzz je zrejmou odpoveďou na "tvarovanie tohto textu" a pre plné obojsmerné, indické alebo arabské tvarovanie je to správna odpoveď. Je to však aj knižnica v C. Prepojenie s produktom v Delphi alebo C++Builder znamená dodávať natívny objekt pre každú cieľovú platformu a architektúru, rešpektovať jeho volaciu konvenciu, sledovať jeho frekvenciu vydávania verzií a čítať jeho licenčné podmienky voči vašim vlastným. Nič z toho nie je samo o sebe ťažké. Všetko je to však trenie, ktoré nikdy nezmizne, a nič to neprináša, keď skutočnou požiadavkou je "daj mi verziu ss01 tohto písmena".
Jednoduchá substitúcia nepotrebuje shaping engine. Potrebuje parser pre niekoľko formátov podtabuliek GSUB a jedno či dve binárne vyhľadávania. Napísanie tohto v Pascale udrží celý vývojový reťazec v rámci jedného kompilátora. Poctivým limitom je, že tento prístup rieši vyhľadávanie náhrad glyfov a nič iné. Nie je to riešenie bidi, nie je to zmena poradia pre indické písma a nie je to automatické kontextové tvarovanie. Tam, kde sú tieto potrebné, sú potrebné a dotaz na jednoduchú substitúciu ich nenahradí.
Hierarchia GSUB zhora nadol
Tabuľka nahradenia glyfov (Glyph Substitution table) je organizovaná ako reťazec nepriamych odkazov a dotaz na nahradenie prechádza týmto reťazcom zhora nadol. Na samom vrchu je ScriptList. Značka písma (script tag) ako latn vyberá položku a špeciálna značka DFLT je predvolené písmo, ktoré sa použije, ak sa nezhoduje žiadne konkrétnejšie písmo. Položka písma ukazuje na LangSys, jazykový systém, s predvoleným LangSys pre bežné prípady a voliteľnými pomenovanými pre jazyky, ktoré vyžadujú iné správanie. Zvyčajným príkladom je turečtina, kde bodkované a bezbodkové i vyžadujú vlastné spracovanie.
LangSys menuje sadu indexov funkcií. Každý index ukazuje do FeatureList, kde záznam funkcie nesie štvorbajtovú značku, okrem iného aj ss01, a zoznam vyhľadávacích indexov. Tieto indexy nakoniec ukazujú do LookupList, kde žijú samotné podtabuľky nahradenia. Takže vyriešenie ss01 znamená: nájsť písmo, nájsť jeho LangSys, nájsť funkciu so značkou ss01, zhromaždiť vyhľadávania, ktoré menuje, a použiť ich. HotPDF sa predvolene nastavuje na písmo DFLT a predvolený LangSys, s čím sa dodáva drvivá väčšina návrhov latinského textu, a ponúka spôsob, ako prepísať značku písma, keď písmo prepája svoje funkcie pod konkrétnym písmom.
Tabuľky pokrytia (Coverage tables) rozhodujú o tom, kto sa zúčastní
Každá podtabuľka nahradenia začína rovnakou otázkou: zúčastňuje sa tento vstupný glyf tohto pravidla, a ak áno, kde sa nachádza v indexovaní samotného pravidla. Na túto otázku odpovedá tabuľka pokrytia (Coverage table) a odpoveďou je index pokrytia, malé poradové číslo, ktoré zvyšok podtabuľky používa na vyhľadanie toho, čím sa glyf stane.
Pokrytie má dva formáty. Formát 1 je zoznam ID glyfov zoradený vzostupne. Glyf nájdete binárnym vyhľadávaním a jeho pozícia v zozname je jeho indexom pokrytia. Formát 2 je zoznam záznamov o rozsahoch, z ktorých každý obsahuje počiatočný glyf, koncový glyf a index pokrytia, na ktorý sa mapuje počiatočný glyf. Glyf v rozsahu získa svoj index pokrytia posunom od začiatku rozsahu. Formát 1 je kompaktný, keď sú zúčastnené glyfy roztrúsené, Formát 2, keď spadajú do súvislých radov. Oba sú zoradené, takže v oboch sa vyhľadáva v logaritmickom čase, a oba vracajú buď index pokrytia, alebo jasné "nepokryté", čo umožňuje enginu ponechať glyf bez zmeny.
Jednoduchá substitúcia, dva formáty
Jednoduchá substitúcia je LookupType 1 a mapuje jeden glyf na presne jednu náhradu. Má tiež dva formáty, pričom rozdelenie je optimalizáciou priestoru. Formát 1 ukladá jednu delta hodnotu so znamienkom. ID výstupného glyfu je ID vstupného glyfu plus táto delta, modulo 65536. Týmto spôsobom písmo kóduje substitúciu, pri ktorej každý zúčastnený glyf leží v rovnakom pevnom posune od svojej alternatívy, napríklad blok tabuľkových číslic umiestnených v konštantnej vzdialenosti od zodpovedajúcich starožitných číslic (oldstyle figures). Tabuľka pokrytia určuje, ktoré glyfy spĺňajú podmienky, a jedna delta slúži všetkým.
Formát 2 ukladá explicitné pole náhradných ID glyfov. Index pokrytia z tabuľky pokrytia je indexom do tohto poľa, takže glyf na indexe pokrytia 0 sa stane prvou položkou poľa, index pokrytia 1 druhou a tak ďalej. Formát 2 sa používa vtedy, keď alternatívy nie sú v jednotnom posune, čo je bežný prípad pre ručne vytvorené štylistické sady. Dotaz je z pohľadu volajúceho v oboch prípadoch rovnaký. Vezmite vstupný glyf, prežeňte ho cez Coverage, a ak je pokrytý, použite deltu alebo prečítajte pozíciu v poli.
var
Pdf: THotPDF;
BaseGID, AltGID: Word;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.BeginDoc;
Pdf.RegisterUnicodeTTF('C:\Fonts\MyStylisticFace.ttf');
Pdf.SetFont('My Stylistic Face', 12, []);
// Default glyph for 'a' through the font's cmap.
BaseGID := Pdf.GetUnicodeGlyphForCodepoint(Ord('a'));
// Stylistic Set 1: resolve the alternate via GSUB LookupType 1.
AltGID := Pdf.GetSingleSubstituteGlyph(BaseGID, 'ss01');
// AltGID = BaseGID means the feature did not touch this glyph.
if AltGID <> BaseGID then
{ emit AltGID in the content stream };
finally
Pdf.Free;
end;
end;
Zmluva, ktorú stojí za to si všimnúť, je pass-through (prechod). GetSingleSubstituteGlyph vracia ID vstupného glyfu nezmenené pri každom minutí: žiadne písmo, žiadna tabuľka GSUB, žiadna zodpovedajúca funkcia, žiadny zásah do pokrytia. To znamená, že volanie je bezpečné urobiť bezpodmienečne. Požiadate o alternatívu, a ak neexistuje, dostanete presne to, čo ste vložili, takže volajúci kód nikdy nemusí riešiť špeciálny prípad pre písmo, ktorému táto funkcia chýba.
Čo znamenajú značky štylistických funkcií
Značka funkcie (feature tag) je celou slovnou zásobou toho, o akú alternatívu žiadate, a značky relevantné pre štylistickú prácu tvoria krátky zoznam. Hlavnou dvojicou sú salt, štylistické alternatívy, univerzálny prístup k alternatívnym formám glyfu, a ss01 až ss20, dvadsať očíslovaných štylistických sád, ktoré písmo môže definovať, pričom každá je pomenovaným balíkom nahradení, ktoré dizajnér zoskupil dohromady. Písmo môže napríklad umiestniť jednoposchodové a a R s rovnou nohou pod ss03, takže povolenie tejto jednej sady zmení štýl oboch.
Okolo nich sa nachádza niekoľko ďalších značiek pre jednoduchú substitúciu. aalt je prístup ku všetkým alternatívam (access-all-alternates), zjednotenie každej alternatívy, ktorú glyf má, zvyčajne prezentované ako paleta glyfov. titl vyberá titulkové kapitálky rezané pre veľké veľkosti. subs a sups vymieňajú skutočné dolné a horné indexy namiesto zmenšených predvolených hodnôt. ordn vytvára ordinálne tvary, vyvýšené písmená v 1st a 2nd. frac vytvára zlomky, hoci plné diagonálne zlomky sa opierajú aj o ligatúry a kontextovú logiku, ktorá presahuje jednoduchú substitúciu. Pre prípady jedného glyfu je mechanizmus identický s ss01: odovzdajte značku dotazu na nahradenie a prečítajte alternatívny glyf.
// Try a stylistic-set feature, then fall back to plain alternates.
function ResolveAlternate(Pdf: THotPDF; BaseGID: Word;
const PreferredTag: AnsiString): Word;
begin
Result := Pdf.GetSingleSubstituteGlyph(BaseGID, PreferredTag);
if Result = BaseGID then
Result := Pdf.GetSingleSubstituteGlyph(BaseGID, 'salt');
// Still BaseGID if neither feature covers this glyph.
end;
cmap formát 12 a doplnkové roviny
Predtým, ako sa môže spustiť akékoľvek nahradenie, znak sa musí stať glyfom, čo je úlohou tabuľky cmap. Dotaz na nahradenie začína od ID glyfu, takže cesta je vždy znak na glyf cez cmap, potom glyf na alternatívu cez GSUB. Zaujímavou časťou cmap je jej dosah. Podtabuľka formátu 4 pokrýva základnú viacjazyčnú rovinu (Basic Multilingual Plane - BMP), prvých 65536 kódových bodov, čo stačí pre väčšinu latinského textu. Nestačí to však pre kódové body od U+10000 nahor, teda doplnkové roviny, kde dnes žijú matematické alfanumerické znaky, mnohé symboly a niekoľko živých písiem.
Formát 12 je podtabuľka, ktorá pokrýva celý rozsah U+0000 až U+10FFFF. Je to zoradený zoznam skupín, pričom každá skupina obsahuje počiatočný kódový bod, koncový kódový bod a počiatočné ID glyfu, takže súvislý rad kódových bodov sa mapuje na súvislý rad glyfov. HotPDF rieši kódové body pomocou hybridnej stratégie, ktorá zodpovedá tvaru dát. Kódové body v BMP sú obsluhované z priameho poľa indexovaného kódovým bodom, čo je jedno vyhľadanie bez hľadania. Kódové body v doplnkových rovinách sú obsluhované z riedkej tabuľky zoradenej podľa kódového bodu a prehľadávanej binárnym vyhľadávaním. Výsledkom je, že GetUnicodeGlyphForCodepoint berie celý Cardinal a odpovedá správne v celom rozsahu, pričom vracia ID glyfu 0, glyf .notdef, pre akýkoľvek kódový bod, ktorý písmo nemapuje.
var
Pdf: THotPDF;
Cp: Cardinal;
GID, StyledGID: Word;
begin
// A supplementary-plane code point: U+1D49C MATHEMATICAL SCRIPT CAPITAL A.
Cp := $1D49C;
GID := Pdf.GetUnicodeGlyphForCodepoint(Cp); // format 12 lookup
if GID <> 0 then
StyledGID := Pdf.GetSingleSubstituteGlyph(GID, 'ss01')
else
StyledGID := 0; // font has no glyph for this code point
end;
Kde tieto dotazy končia
Rozhrania API pre jednoduchú substitúciu odpovedajú na jeden typ otázky a je dôležité mať jasno v tom, na čo neodpovedajú. LookupType 1 is jedným z ôsmich typov nahradenia. Dotaz nerieši viacnásobné nahradenie LookupType 2, kde sa jeden glyf stáva niekoľkými, ani zliatkové (ligatúrové) nahradenie LookupType 4, kde sa niekoľko glyfov stáva jedným. Nerieši kontextové typy a typy s reťazeným kontextom (LookupType 5 a 6), ktoré sa spúšťajú len vtedy, keď sa glyf objaví v konkrétnom susedstve, ani typy rozšírenia a spätného reťazenia. Diagonálny zlomok, dévanágarská konjunktúra alebo arabská kaskáda počiatočného-stredného-koncového tvaru je sekvenčným problémom a vyhľadávanie jednoduchej substitúcie na úrovni jedného glyfu ho nedokáže vyjadriť.
Nevykonáva ani automatické tvarovanie. Nič tu neskúma beh textu, nerozhoduje o tom, ktoré funkcie zapnúť, a neaplikuje ich v poradí, ktoré písmo vyžaduje. Volajúci si vyberie značku funkcie a použije ju glyf po glyfe. To je presne ten správny nástroj pre štylistické sady a alternatívy, ktoré sú voliteľné a lokálne, a presne ten nesprávny nástroj pre písmo, ktoré potrebuje zmenu poradia znakov. Udržiavanie ostrých hraníc je to, čo umožňuje ceste nahradenia zostaviť malou a predvídateľnou.
Pre prípady, ktoré vyžadujú prácu na úrovni sekvencií, sa komplexnému písmu venuje náš článok o tvarovaní textu pre komplexné písma v Delphi. Ak sú vaše nahradenia súčasťou väčšej reportovacej úlohy, ktorá tiež umiestňuje obrázky a iné písma na stránku, príručka o výstupe reportov s písmami a obrázkami popisuje, ako tieto časti do seba zapadajú. Všetky bežia na rovnakom engine, HotPDF Component pre Delphi and C++Builder, ktorý nesie GSUB dotazy na nahradenie spolu s embeddingom písiem, subsettingom a textovými API, o ktorých sa píše inde na tomto blogu.