Dizajner odabire font s jednostavnim (single-story) slovom a za naslove, ili prekriženu nulu za tablice, ili pak set ukrašenih velikih slova (swash capitals) za naslovnicu. Ti glifovi već se nalaze u fontu. Oni jednostavno nisu zadani (default). Zadani a preslikava se od znaka kroz tablicu cmap do jednog glifa, a alternativa sjedi nekoliko ID-ova glifa dalje, dostupna samo kroz pravilo zamjene (substitution rule). Proizvodnja te alternative u PDF-u znači čitanje pravila i emitiranje zamjenskog glifa u struju sadržaja. Ovaj članak bavi se čitanjem tih pravila, onih za jednostruku zamjenu, u Object Pascalu bez ikakve izvorne biblioteke za oblikovanje (shaping) ispod haube
Doseg (scope) je namjerno uzak. Stilski setovi i alternative su zamjene jedan-glif-ulaz, jedan-glif-izlaz. Oni su onaj dio OpenType rasporeda koji možete riješiti malom, determinističkom šetnjom kroz tablicu, što ih čini prikladnim izborom za Pascal engine koji želi ostati bez ovisnosti o C-u
Zašto čisti Delphi, a ne HarfBuzz
HarfBuzz je očigledan odgovor na "oblikuj ovaj tekst", i za potpuno dvosmjerno (bidirectional), indijsko ili arapsko oblikovanje, to je pravi odgovor. To je također i C biblioteka. Njezino povezivanje (binding) u Delphi ili C++Builder proizvod znači isporuku izvornog objekta za svaku ciljnu platformu i arhitekturu, usklađivanje s njezinom konvencijom pozivanja, praćenje ritma njezinih izdanja i čitanje njezinih licencnih uvjeta u odnosu na vaše. Ništa od toga nije teško samo po sebi. Sve to zajedno predstavlja trenje koje nikada ne nestaje, a ne donosi ništa ako je stvarni zahtjev tek "daj mi formu ss01 za ovo slovo"
Jednostruka zamjena ne treba mehanizam za oblikovanje. Potreban joj je parser za šačicu GSUB formata podtablica i jedno ili dva binarna pretraživanja. Pisanje toga u Pascalu zadržava cijeli lanac alata unutar jednog prevoditelja (kompajlera). Iskreno ograničenje jest to da ovaj pristup obrađuje pretraživanja zamjena glifova (glyph substitution lookups) i ništa više. To nije bidi rezolucija (bidi resolution), to nije indijsko preslagivanje (Indic reordering), i to nije automatsko kontekstualno oblikovanje. Gdje su te stvari potrebne, one su potrebne, a upit za jednostruku zamjenu ih neće zamijeniti
GSUB hijerarhija, od vrha do dna
Tablica zamjene glifova (Glyph Substitution table) organizirana je kao lanac indirekcija, a upit o zamjeni hoda tim lancem od samog vrha. Na vrhu se nalazi ScriptList. Oznaka pisma (script tag) poput latn odabire unos, a posebna oznaka DFLT je zadano pismo koje se primjenjuje kada se ne podudara nijedno specifičnije pismo. Unos pisma pokazuje na LangSys, jezični sustav, sa zadanim LangSys-om za uobičajeni slučaj te opcionalnim imenovanim sustavima za jezike koji zahtijevaju drugačije ponašanje. Turski je uobičajen primjer, gdje točkasto i bez-točkasto slovo i zahtijevaju vlastiti način rukovanja
LangSys imenuje skup indeksa značajki (feature indices). Svaki indeks pokazuje na FeatureList, gdje zapis značajke (feature record) nosi četverobajtnu oznaku, među kojima je i ss01, i popis indeksa pretraživanja (lookup indices). Ti indeksi konačno pokazuju na LookupList, gdje žive stvarne podtablice zamjena. Dakle, razrješavanje ss01 znači: pronađi pismo, pronađi njegov LangSys, pronađi značajku čija je oznaka ss01, prikupi pretraživanja (lookups) koja imenuje i primijeni ih. HotPDF postavlja zadano na DFLT pismo i zadani LangSys, što je ono što velika većina dizajna latiničnog teksta isporučuje, i izlaže način za nadjačavanje (override) oznake pisma kada font svoje značajke usmjeri pod specifično pismo umjesto toga
Coverage tablice odlučuju tko sudjeluje
Svaka podtablica zamjene počinje s istim pitanjem: sudjeluje li ovaj ulazni glif u ovom pravilu i, ako da, gdje on sjedi unutar vlastitog indeksiranja tog pravila. Na to pitanje odgovara tablica pokrivenosti (Coverage table), a odgovor je indeks pokrivenosti (coverage index), mali redni broj (ordinal) koji ostatak podtablice koristi da potraži u što glif postaje
Pokrivenost dolazi u dva formata. Format 1 je popis ID-ova glifova sortiranih uzlaznim redoslijedom. Glif pronalazite binarnim pretraživanjem, a njegov položaj na popisu je njegov indeks pokrivenosti. Format 2 je popis zapisa raspona (range records), svaki sa početnim glifom, završnim glifom i indeksom pokrivenosti u koji se preslikava početni glif. Glif unutar raspona dobiva svoj indeks pokrivenosti pomakom (offsetting) u odnosu na početak raspona. Format 1 je kompaktan kada su sudjelujući glifovi razbacani, a Format 2 kada padaju u neprekinute (contiguous) nizove. Oba su sortirana, tako da se oba pretražuju u logaritamskom vremenu, i oba vraćaju ili indeks pokrivenosti ili čisti "nije pokriveno" što omogućuje mehanizmu da glif ostavi na miru
Jednostruka zamjena, dva formata
Jednostruka zamjena (Single Substitution) je LookupType 1, i preslikava jedan glif u točno jednu zamjenu. Također ima dva formata, a ta podjela služi za optimizaciju prostora. Format 1 pohranjuje jednu deltu s predznakom (signed delta). Izlazni ID glifa je ulazni ID glifa plus ta delta, modulo 65536. Ovako font kodira zamjenu u kojoj svaki sudjelujući glif sjedi na istom fiksnom pomaku od svoje alternative, na primjer blok znamenki iste visine (lining figures) postavljenih na stalnoj udaljenosti od odgovarajućih brojki starog stila (oldstyle figures). Tablica pokrivenosti govori koji glifovi se kvalificiraju, a jedna delta služi svima njima
Format 2 pohranjuje eksplicitni niz ID-ova zamjenskih glifova. Indeks pokrivenosti iz tablice pokrivenosti je indeks u tom nizu, tako da glif na indeksu pokrivenosti 0 postaje prvi unos niza, indeks pokrivenosti 1 drugi, i tako dalje. Format 2 se koristi kada alternative nisu na ravnomjernom pomaku, što je uobičajen slučaj za ručno izgrađene stilske setove. Upit je isti s pozivateljeve strane u oba slučaja. Uzmite ulazni glif, provucite ga kroz pokrivenost (Coverage), a ako je pokriven, primijenite deltu ili pročitajte mjesto (slot) u nizu
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;
Ugovor na koji vrijedi obratiti pažnju je "propusnost" (pass-through). GetSingleSubstituteGlyph vraća ulazni ID glifa nepromijenjen na svaki promašaj (miss): nema fonta, nema GSUB tablice, nema odgovarajuće značajke, nema pogotka u pokrivenosti. To znači da je poziv siguran za bezuvjetno izvođenje. Zatražite alternativu, a ako je nema, dobivate natrag točno ono što ste ubacili, tako da pozivni kod nikada ne treba pisati posebne slučajeve za font kojemu nedostaje značajka
Što znače oznake stilskih značajki
Oznaka značajke (feature tag) je cjelokupan vokabular o tome koju alternativu tražite, a oznake bitne za stilski rad su kratak popis. Glavni par su salt, stilske alternative (stylistic alternates) kao sveobuhvatan (catch-all) pristup alternativnim formama glifa, i ss01 do ss20, dvadeset numeriranih stilskih setova koje font može definirati, svaki od njih imenovani paket zamjena koje dizajner grupira zajedno. Font bi mogao, na primjer, staviti jednostavno (single-story) a i R s ravnom nogom pod ss03, pa tako omogućavanje tog jednog seta restilizira oboje
Oko njih se nalazi još nekoliko oznaka za jednostruke zamjene. aalt predstavlja pristup svim alternativama (access-all-alternates), uniju svake alternative koju glif ima, obično predstavljeno kao značajka palete glifova. titl odabire velika slova za naslove (titling capitals) skrojena za velike veličine. subs i sups zamjenjuju u prave potpisane (subscript) i natpisane (superscript) brojke umjesto zadanih smanjenih. ordn stvara redne oblike (ordinal forms), podignuta slova u 1st i 2nd. frac gradi razlomke, iako se pravi dijagonalni razlomci također oslanjaju na logiku ligatura i konteksta koja ide dalje od obične jednostruke zamjene. Za slučajeve jednostrukog glifa, mehanizam je identičan kao za ss01: proslijedite oznaku u upit za zamjenu i pročitajte nazad alternativni 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 i dodatne ravnine
Prije nego što se bilo kakva zamjena može pokrenuti, znak mora postati glif, a to je posao tablice cmap. Upit o zamjeni kreće od ID-a glifa, tako da putanja uvijek ide od znaka do glifa kroz cmap, a zatim od glifa do alternative kroz GSUB. Zanimljiv dio kod cmap tablice je njezin doseg. Podtablica formata 4 pokriva osnovnu višejezičnu ravninu (Basic Multilingual Plane), prvih 65536 kodnih točaka (code points), a to je dovoljno za većinu latiničnog teksta. Nije dovoljno za kodne točke od U+10000 naviše, dodatne ravnine (supplementary planes), što je mjesto gdje sada žive matematički alfanumerički znakovi, mnogi simboli i nekoliko živućih pisama
Format 12 je podtablica koja pokriva puni raspon od U+0000 do U+10FFFF. To je sortirana lista grupa, pri čemu je svaka grupa početna kodna točka, krajnja kodna točka i početni ID glifa, pa se tako neprekidan niz kodnih točaka preslikava na neprekidan niz glifova. HotPDF rješava kodne točke s hibridnom strategijom koja odgovara tome kako su podaci oblikovani. Kodne točke u BMP-u (Basic Multilingual Plane) poslužuju se iz izravnog niza indeksiranog prema kodnoj točki, jedan pronalazak (lookup) bez pretraživanja. Kodne točke u dodatnim ravninama poslužuju se iz rijetke (sparse) tablice sortirane po kodnoj točki i pretražuju se binarnim pretraživanjem. Rezultat je taj da GetUnicodeGlyphForCodepoint prihvaća puni Cardinal i točno odgovara unutar cijelog raspona, vraćajući ID glifa 0, odnosno .notdef glif, za svaku kodnu točku koju font ne mapira
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;
Gdje ovi upiti prestaju
API-ji za jednostruku zamjenu daju odgovor na jedan oblik pitanja, pa vrijedi biti jasan oko toga na što oni ne odgovaraju. LookupType 1 je jedan od osam tipova zamjene. Upit ne upravlja višestrukom zamjenom (LookupType 2), gdje jedan glif postaje nekoliko, niti zamjenom za ligature (LookupType 4), gdje nekoliko glifova postaje jedan. Ne upravlja kontekstualnim (contextual) i lančano-kontekstualnim (chaining-contextual) tipovima, LookupTypes 5 i 6, koji se okidaju samo kada se glif pojavi u određenom susjedstvu, niti vrstama za proširenje i obrnuto lančanje (reverse-chaining). Dijagonalni razlomak, Devanagari spojnica ili arapska kaskada početak-sredina-kraj su problem sekvence, a pretraživanje jednostruke zamjene po glifu (per-glyph single-substitution lookup) ga ne može izraziti
Također, ne izvodi ni automatsko oblikovanje (automatic shaping). Ovdje ništa ne pregledava niz teksta, ne odlučuje koje značajke treba uključiti niti ih primjenjuje redoslijedom koji pismo (script) zahtijeva. Pozivatelj odabire oznaku značajke i primjenjuje je glif po glif. Upravo je to pravi alat za stilske setove i alternative, koje su po izboru (opt-in) i lokalne, a u potpunosti pogrešan alat za pismo kojemu je potrebno preslagivanje. Održavanje te granice oštrom jest ono što omogućuje putanji zamjene da ostane mala i predvidljiva
Za slučajeve koji uistinu trebaju rad na razini sekvence (sequence-level work), priča o složenim pismima (complex-script) obrađena je u našem članku o oblikovanju teksta složenog pisma u Delphiju. Ako su vaše zamjene dio većeg posla s izvješćima koji također postavlja slike i druge fontove na stranicu, vodič za ispis izvješća s fontovima i slikama pokriva kako se ti dijelovi uklapaju. Sve to se izvodi na istom mehanizmu, HotPDF komponenti za Delphi i C++Builder, koja nosi GSUB upite za zamjenu uz ugrađivanje fonta (font embedding), podskupiranje (subsetting) i API-jeve za tekst koji su pokriveni drugdje na ovom blogu