Oblikovalec izbere pisavo z enonivojsko črko a za naslove, ali prečrtano ničlo za tabele, ali nabor okrašenih velikih začetnic (swash capitals) za naslovnico. Ti glifi so že v pisavi. Preprosto niso privzeti. Privzeti a se preslika iz znaka prek tabele cmap v en glif, alternativa pa se nahaja nekaj ID-jev glifov stran, dosegljiva le prek pravila zamenjave. Ustvarjanje te alternative v PDF pomeni branje tega pravila in oddajanje nadomestnega glifa v tok vsebine. Ta članek govori o branju teh pravil, natančneje tistih za enojno zamenjavo (single-substitution), v jeziku Object Pascal brez uporabe zunanjih knjižnic za oblikovanje.
Obseg je namenoma ozek. Stilistični nizi in alternative so zamenjave po načelu en-glif-not, en-glif-ven. So tisti del postavitve OpenType, ki ga lahko razrešite z majhnim, determinističnim sprehodom skozi tabele, zaradi česar so primerni za pogon v jeziku Pascal, ki želi ostati brez odvisnosti od jezika C.
Zakaj čisti Delphi namesto HarfBuzz
HarfBuzz je očiten odgovor na vprašanje "oblikuj to besedilo" in za popolno dvosmerno oblikovanje ali oblikovanje indijskih in arabskih pisav je to pravi odgovor. Vendar pa gre za knjižnico v jeziku C. Povezovanje takšne knjižnice v izdelek za Delphi ali C++Builder pomeni pošiljanje izvornega objekta za vsako ciljno platformo in arhitekturo, prilagajanje njene klicne konvencije, sledenje ciklom izdaj in branje licenčnih pogojev glede na vaše lastne. Nič od tega ni težko samo po sebi. Vse to pa predstavlja trenje, ki nikoli ne izgine, in ne prinaša ničesar, ko je dejanska zahteva le: "daj mi obliko ss01 te črke".
Enojna zamenjava ne potrebuje pogona za oblikovanje. Potrebuje le razčlenjevalnik za nekaj formatov podtabel GSUB in eno ali dve binarni iskanji. Pisanje tega v jeziku Pascal ohranja celotno orodno verigo znotraj enega prevajalnika. Iskrena omejitev je, da ta pristop obravnava le poizvedbe po zamenjavi glifov in nič drugaga. To ni dvosmerno razreševanje, to ni indijsko preurejanje in to ni samodejno kontekstualno oblikovanje. Kjer so te funkcije potrebne, so nepogrešljive in poizvedba po enojni zamenjavi jih ne more nadomestiti.
Hierarhija GSUB od vrha do dna
Tabela za zamenjavo glifov (Glyph Substitution table) je organizirana kot veriga posrednih povezav, poizvedba po zamenjavi pa prehodi to verigo od vrha. Na vrhu je seznam ScriptList. Značka pisave, kot je latn, izbere vnos, posebna značka DFLT je privzeta pisava, ki se uporabi, ko se ne ujema nobena specifična pisava. Vnos pisave kaže na jezikovni sistem LangSys z zadanim jezikovnim sistemom za pogostejše primere in neobveznimi poimenovanimi sistemi za jezike, ki zahtevajo drugačno obnašanje. Turščina je običajen primer, kjer pika na črki i in njena odsotnost zahtevata posebno obravnavo.
Sistem LangSys določa nabor indeksov funkcij. Vsak indeks kaže na seznam FeatureList, kjer zapis funkcije nosi štiribajtno značko (med njimi tudi ss01) in seznam indeksov iskanja. Ti indeksi na koncu kažejo na seznam LookupList, kjer se nahajajo dejanske podtabelo za zamenjavo. Razreševanje ss01 torej pomeni: najti pisavo, najti njen LangSys, najti funkcijo z značko ss01, zbrati iskanja, ki jih poimenuje, in jih uporabiti. HotPDF privzeto uporablja pisavo DFLT in privzeti LangSys, kar je tisto, kar uporablja velika večina latinic, ponuja pa tudi možnost preglasitve značke pisave, kadar pisava svoje funkcije povezuje pod specifično pisavo.
Tabele pokritosti določajo, kdo sodeluje
Vsaka podtabela za zamenjavo se začne z enakim vprašanjem: ali ta vhodni glif sodeluje v tem pravilu in, če sodeluje, kje se nahaja v indeksiranju tega pravila. Na to vprašanje odgovarja tabela pokritosti (Coverage table), odgovor pa je indeks pokritosti (coverage index), majhno vrstilno število, ki ga preostanek podtabelo uporabi za iskanje tega, v kar se glif spreminja.
Pokritost je na voljo v dveh oblikah. Format 1 je seznam ID-jev glifov, razvrščenih v naraščajočem vrstnem redu. Glif najdete z binarnim iskanjem, njegovo mesto na seznamu pa je njegov indeks pokritosti. Format 2 je seznam zapisov obsegov, kjer je vsak sestavljen iz začetnega glifa, končnega glifa in indeksa pokritosti, v katerega se preslika začetni glif. Glif znotraj obsega dobi svoj indeks pokritosti z odmikom od začetka obsega. Format 1 je kompakten, ko so sodelujoči glifi razpršeni, Format 2 pa, ko so v neprekinjenih zaporedjih. Oba formata sta razvrščena, zato se po obeh išče v logaritemskem času, oba pa vrneta bodisi indeks pokritosti bodisi jasen status "ni pokrit", ki pogonu omogoča, da pusti glif pri miru.
Enojna zamenjava, dva formata
Enojna zamenjava (Single Substitution) je LookupType 1 in preslika en glif v natanko eno zamenjavo. Prav tako ima dva formata, delitev pa je optimizacija prostora. Format 1 hrani eno predznačeno delto. Izhodni ID glifa je vhodni ID glifa plus ta delta, po modulu 65536. Na ta način pisava kodira zamenjavo, kjer se vsak sodelujoči glif nahaja na istem fiksnem odmiku od svoje alternative, na primer blok poravnanih številk, postavljenih na konstantni razdalji od ustreznih klasičnih številk (oldstyle figures). Tabela pokritosti določa, kateri glifi ustrezajo pogojem, ena delta pa služi vsem.
Format 2 hrani eksplicitno matriko nadomestnih ID-jev glifov. Indeks pokritosti iz tabele pokritosti je indeks v tej matriki, tako da glif pri indeksu pokritosti 0 postane prvi vnos v matriki, indeks pokritosti 1 postane drugi in tako naprej. Format 2 se uporablja, ko alternative niso na enakomernem odmiku, kar je pogost primer pri ročno izdelanih stilističnih naborih. Poizvedba je z uporabniške strani v obeh primerih enaka. Vzemite vhodni glif, ga spustite skozi pokritost in če je pokrit, uporabite delto ali preberite mesto v matriki.
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;
Pogodba, ki jo je vredno opaziti, je prehod (pass-through). GetSingleSubstituteGlyph vrne vhodni ID glifa nespremenjen ob vsakem zgrešenem iskanju: če ni pisave, ni tabele GSUB, ni ujemajoče se funkcije ali ni zadetka pokritosti. To pomeni, da je klic varno izvesti brezpogojno. Zahtevate alternativo in če je ni, dobite nazaj natanko tisto, kar ste vnesli, tako da klicna koda nikoli ne potrebuje posebne obravnave za pisavo, ki nima te funkcije.
Kaj pomenijo značke stilističnih funkcij
Značka funkcije predstavlja celotno besedišče za to, katero alternativo zahtevate, seznam značk, pomembnih za stilistično delo, pa je kratek. Glavni par sestavljata salt, stilistične alternative (stylistic alternates), ki predstavlja splošen dostop do alternativnih oblik glifa, ter ss01 do ss20, dvajset poimenovanih stilističnih naborov, ki jih pisava lahko določi, pri čemer je vsak poimenovan paket zamenjav, ki jih je oblikovalec združil. Pisava lahko na primer postavi enonivojski a in ravni R pod ss03, tako da omogočanje tega nabora spremeni videz obeh.
Okoli teh se nahaja še nekaj drugih značk za enojno zamenjavo. aalt je dostop do vseh alternativ (access-all-alternates), unija vseh alternativ, ki jih glif ima, običajno predstavljena kot paleta glifov. titl izbere naslovne črke (titling capitals), prilagojene za velike velikosti. subs in sups zamenjata prave podpisane in nadpisane številke namesto pomanjšanih privzetih. ordn ustvari vrstne oblike, kot so dvignjene črke v 1. in 2. frac gradi ulomke, čeprav se polni diagonalni ulomki opirajo tudi na ligaturni in kontekstualni sistem, ki presega običajno enojno zamenjavo. Za primere posameznih glifov je mehanizem enak kot pri ss01: pošljite značko poizvedbi po zamenjavi in preberite nadomestni glif.
// 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 format 12 in dopolnilne ravnine
Preden se lahko izvede kakršna koli zamenjava, mora znak postati glif, kar je naloga tabele cmap. Poizvedba po zamenjavi se začne z ID-jem glifa, zato je pot vedno znak do glifa prek cmap, nato pa glif do alternative prek GSUB. Zanimiv del cmap je njen doseg. Podtabela formata 4 pokriva osnovno večjezično ravnino (Basic Multilingual Plane), prvih 65536 kodnih točk, kar zadošča za večino latiničnih besedil. To pa ne zadošča za kodne točke od U+10000 navzgor, dopolnilne ravnine (supplementary planes), kjer se zdaj nahajajo matematični alfanumerični znaki, številni simboli in več živih pisav.
Format 12 is the subtable that covers the full U+0000 to U+10FFFF range. It is a sorted list of groups, each group a start code point, an end code point, and a start glyph id, so a contiguous run of code points maps to a contiguous run of glyphs. HotPDF resolves code points with a hybrid strategy that matches how the data is shaped. Code points in the BMP are served from a direct array indexed by the code point, a single lookup with no search. Code points in the supplementary planes are served from a sparse table sorted by code point and searched with a binary search. The result is that GetUnicodeGlyphForCodepoint takes a full Cardinal and answers correctly across the whole range, returning glyph id 0, the .notdef glyph, for any code point the font does not map.
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;
Kje se te poizvedbe ustavijo
Vmesniki API za enojno zamenjavo odgovarjajo na eno vrsto vprašanj in koristno je razjasniti, na kaj ne odgovarjajo. LookupType 1 je ena od osmih vrst zamenjav. Poizvedba ne obravnava večkratne zamenjave LookupType 2, kjer en glif postane več glifov, niti ligaturne zamenjave LookupType 4, kjer več glifov postane en sam glif. Prav tako ne obravnava kontekstualnih in verižnih kontekstualnih vrst LookupType 5 in 6, ki se sprožita le, ko se glif pojavi v določeni soseščini, ter razširitvenih in obratnih verižnih vrst. Diagonalni ulomek, veznik Devanagari ali arabska začetna-srednja-končna kaskada predstavljajo problem zaporedja in iskanje enojne zamenjave na glif tega ne more izraziti.
Pravko ne izvaja samodejnega oblikovanja. Nič tukaj ne pregleduje niza besedila, se odloča, katere funkcije vklopiti, in jih uporablja v vrstnem redu, ki ga zahteva pisava. Klicatelj izbere značko funkcije in jo uporabi glif za glifom. To je natanko pravo orodje za stilistične nabore in alternative, ki so izbirni in lokalni, ter povsem napačno orodje za pisave, ki zahtevajo preurejanje. Ohranjanje ostre meje je tisto, kar omogoča, da pot zamenjave ostane majhna in predvidljiva.
Za primere, ki zahtevajo delo na ravni zaporedij, je zgodba o zapletenih pisavah obravnavana v našem članku o oblikovanju besedila z zapletenimi pisavami v Delphi. Če so vaše zamenjave del večjega poročila, ki na stran postavlja tudi slike in druge pisave, priročnik za izhod poročil s pisavami in slikami opisuje, kako se ti kosi povezujejo. Vse to deluje na istem pogonu, komponenti HotPDF Component za Delphi in C++Builder, ki vsebuje poizvedbe po zamenjavi GSUB skupaj z vmesniki API za vdelavo pisav, podnaborom pisav in besedilom, ki so obravnavani drugje na tem blogu.