Dizajner bira font s jednokatnim a za naslove, ili prekriženom nulom za tablice, ili skupom ukrasnih velikih slova (swash capitals) za naslovnicu. Ti se glifovi već nalaze u fontu. Oni jednostavno nisu zadani. Zadani a mapira se iz znaka kroz tablicu cmap na jedan glif, a alternativi se nalaze nekoliko ID-ova glifova dalje, dostupni samo kroz pravilo zamjene. Proizvodnja tog alternativa u PDF-u znači čitanje tog pravila i emitiranje zamjenskog glifa u tok sadržaja. Ovaj se članak bavi čitanjem tih pravila, onih s jednostrukom zamjenom (single-substitution), u Object Pascalu bez ikakve izvorne knjižnice za oblikovanje u pozadini.
Opseg je namjerno uzak. Stilski setovi i alternativi su zamjene tipa jedan glif unutra, jedan glif van. Oni su dio OpenType izgleda koji možete riješiti malim, determinističkim prolaskom kroz tablicu, što ih čini prikladnima za Pascal motor koji želi ostati slobodan od C ovisnosti.
Zašto čisti Delphi, a ne HarfBuzz
HarfBuzz is the obvious answer to "shape this text", and for full bidirectional, Indic, or Arabic shaping it is the right answer. It is also a C library. Binding it into a Delphi or C++Builder product means shipping a native object for every target platform and architecture, matching its calling convention, tracking its release cadence, and reading its licence terms against your own. None of that is hard in isolation. All of it is friction that never goes away, and it buys nothing when the actual requirement is "give me the ss01 form of this letter".
Jednostruka zamjena ne treba motor za oblikovanje. Treba joj parser za nekoliko formata GSUB podtablica i jedno ili dva binarna pretraživanja. Pisanje toga u Pascalu drži cijeli alatni lanac unutar jednog prevoditelja (compilera). Iskreno ograničenje je da ovaj pristup rješava pretraživanja zamjene glifova i ništa drugo. To nije dvosmjerno rješavanje, to nije indijsko preslagivanje i to nije automatsko kontekstualno oblikovanje. Tamo gdje su oni potrebni, zaista su potrebni, i upit o jednostrukoj zamjeni 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 prolazi kroz lanac od vrha. Na vrhu je ScriptList. Oznaka pisma (script tag) kao što je 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 i izbornim imenovanim jezičnim sustavima za jezike koji zahtijevaju drugačije ponašanje. Turski je uobičajeni primjer, gdje točkasto i beztočkasto i zahtijevaju vlastitu obradu.
LangSys imenuje skup indeksa značajki (features). Svaki indeks pokazuje na FeatureList, gdje zapis značajke nosi četverobajtnu oznaku, među kojima je i ss01, te popis indeksa pretraživanja (lookups). Ti indeksi konačno pokazuju na LookupList, gdje žive stvarne podtablice zamjene. Dakle, rješavanje ss01 znači: pronaći pismo, pronaći njegov LangSys, pronaći značajku čija je oznaka ss01, prikupiti pretraživanja koja ona imenuje i primijeniti ih. HotPDF se zadano postavlja na pismo DFLT i zadani LangSys, što je ono što velika većina dizajna latiničnog teksta isporučuje, i izlaže način za nadjačavanje oznake pisma kada font umjesto toga povezuje svoje značajke pod određenim pismom.
Tablice pokrivenosti odlučuju tko sudjeluje
Svaka podtablica zamjene počinje istim pitanjem: sudjeluje li ovaj ulazni glif u ovom pravilu i, ako sudjeluje, gdje se nalazi u vlastitom indeksiranju pravila. Na to pitanje odgovara tablica pokrivenosti (Coverage table), a odgovor je indeks pokrivenosti, mali redni broj koji ostatak podtablice koristi za traženje onoga što glif postaje.
Pokrivenost dolazi u dva formata. Format 1 is a list of glyph ids sorted in ascending order. You find a glyph with a binary search, and its position in the list is its coverage index. Format 2 is a list of range records, each a start glyph, an end glyph, and the coverage index that the start glyph maps to. A glyph inside a range gets its coverage index by offsetting from the range's start. Format 1 is compact when the participating glyphs are scattered, Format 2 when they fall into contiguous runs. Both are sorted, so both are searched in logarithmic time, and both return either a coverage index or a clean "not covered" that lets the engine leave the glyph alone.
Jednostruka zamjena, dva formata
Jednostruka zamjena (Single Substitution) je LookupType 1 i ona mapira jedan glif na točno jednu zamjenu. Ona također ima dva formata, a podjela je zapravo optimizacija prostora. Format 1 pohranjuje jednu predznačenu deltu. Izlazni ID glifa je ulazni ID glifa plus ta delta, po modulu 65536. Na ovaj način font kodira zamjenu u kojoj se svaki sudjelujući glif nalazi na istom fiksnom odmaku od svog alternativa, na primjer blok poravnatih brojeva (lining figures) postavljenih na konstantnu udaljenost od odgovarajućih starih brojeva (oldstyle figures). Tablica pokrivenosti (Coverage table) govori koji glifovi ispunjavaju uvjete, a jedna delta služi svima njima.
Format 2 pohranjuje eksplicitni niz (array) zamjenskih ID-ova 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 alternativi nisu na ujednačenom odmaku, što je uobičajen slučaj za ručno izrađene stilske setove. Upit je isti s pozivateljeve strane u oba slučaja. Uzmite ulazni glif, provucite ga kroz Coverage i, ako je pokriven, primijenite deltu ili pročitajte utor niza.
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 koji vrijedi primijetiti je prolaz (pass-through). GetSingleSubstituteGlyph vraća nepromijenjen ulazni ID glifa pri svakom promašaju: nema fonta, nema GSUB tablice, nema odgovarajuće značajke, nema pogotka u pokrivenosti. To znači da je poziv sigurno uputiti bezuvjetno. Tražite alternativu, a ako je nema, dobivate natrag točno ono što ste unijeli, tako da pozivajući kod nikada ne mora posebno tretirati font kojem nedostaje ta značajka.
Što znače oznake stilskih značajki
Oznaka značajke (feature tag) je cijeli vokabular onoga što tražite, a oznake relevantne za stilski rad su kratak popis. Glavni par je salt, stilski alternativi (stylistic alternates), sveobuhvatni pristup zamjenskim oblicima glifa, te ss01 do ss20, dvadeset numeriranih stilskih setova koje font može definirati, od kojih je svaki imenovani paket zamjena koje dizajner grupira zajedno. Font bi mogao staviti jednokatno a i ravnonogo R pod ss03, na primjer, pa omogućavanje tog jednog seta mijenja stil obaju slova.
Oko njih se nalazi još nekoliko oznaka za jednostruku zamjenu. aalt je pristup svim alternativima (access-all-alternates), unija svakog alternativa koji glif ima, obično predstavljen kao značajka palete glifova. titl odabire naslovna velika slova izrezana za velike veličine. subs i sups ubacuju prave indeksne (subscript) i eksponentne (superscript) brojke umjesto smanjenih zadanih. ordn proizvodi redne oblike, podignuta slova u 1st i 2nd. frac gradi razlomke, iako se puni dijagonalni razlomci također oslanjaju na ligaturnu i kontekstualnu logiku koja nadilazi jednostavnu jednostruku zamjenu. Za slučajeve s jednim glifom, mehanizam je identičan ss01: proslijedite oznaku upitu za zamjenu i pročitajte natrag zamjenski 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 dopunske ravnine
Prije nego što bilo koji znak može postati glif, a to je posao tablice cmap. Upit za zamjenu počinje od ID-a glifa, pa je putanja uvijek znak u glif kroz cmap, a zatim glif u alternativu kroz GSUB. Zanimljiv dio cmap-a je njegov doseg. Podtablica formata 4 pokriva osnovnu višejezičnu ravninu (Basic Multilingual Plane - BMP), prvih 65536 kodnih točaka, i to je dovoljno za većinu latiničnih tekstova. To nije dovoljno za kodne točke od U+10000 naviše, dopunske ravnine (supplementary planes), gdje danas žive matematički alfanumerici, many symbols, and several living scripts now live.
Format 12 je podtablica koja pokriva cijeli raspon od U+0000 do U+10FFFF. To je sortirani popis grupa, pri čemu je svaka grupa početna kodna točka, krajnja kodna točka i početni ID glifa, tako da se neprekinuti niz kodnih točaka mapira u neprekinuti niz glifova. HotPDF rješava kodne točke hibridnom strategijom koja odgovara načinu na koji su podaci oblikovani. Kodne točke u BMP-u poslužuju se iz izravnog niza indeksiranog kodnom točkom, što je jedno traženje bez pretraživanja. Kodne točke u dopunskim ravninama poslužuju se iz rijetke tablice sortirane po kodnoj točki i pretražuju se binarnim pretraživanjem. Rezultat je da GetUnicodeGlyphForCodepoint uzima cijeli Cardinal i odgovara točno u cijelom rasponu, vraćajući ID glifa 0, glif .notdef, za bilo koju 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 se ovi upiti zaustavljaju
API-ji za jednostruku zamjenu odgovaraju na jedan oblik pitanja i važno je biti jasan oko toga na što ne odgovaraju. LookupType 1 je jedan od osam tipova zamjene. Upit ne rješava LookupType 2 višestruku zamjenu, gdje jedan glif postaje nekoliko njih, niti LookupType 4 zamjenu ligatura, gdje nekoliko glifova postaje jedan. Ne rješava kontekstualne tipove i tipove ulančanog konteksta, LookupTypes 5 i 6, koji se aktiviraju samo kada se glif pojavi u određenom susjedstvu, kao ni tipove proširenja i obrnutog ulančavanja. Dijagonalni razlomak, devanagari konjunkt ili arapska početno-medijalno-završna kaskada je problem sekvence, a pretraživanje jednostruke zamjene po glifu to ne može izraziti.
Također ne obavlja automatsko oblikovanje. Ništa ovdje ne pregledava niz teksta, ne odlučuje koje značajke uključiti i ne primjenjuje ih redoslijedom koji pismo zahtijeva. Pozivatelj bira oznaku značajke i primjenjuje je glif po glif. To je točno pravi alat za stilske setove i alternative, koji su izborni (opt-in) i lokalni, a potpuno pogrešan alat za pismo koje zahtijeva preslagivanje. Držanje granice oštrom je ono što omogućuje stazi zamjene da ostane mala i predvidljiva.
Za slučajeve koji zahtijevaju rad na razini sekvence, priča o složenim pismima nastavlja se u našem članku o oblikovanju teksta sa složenim pismima u Delphiu. Ako su vaše zamjene dio većeg posla izvješćivanja koji također postavlja slike i druge fontove na stranicu, vodič za izlaz izvješća s fontovima i slikama pokriva kako se ti dijelovi uklapaju. Sve to radi na istom motoru, HotPDF komponenti za Delphi i C++Builder, koja nosi upite za GSUB zamjenu uz ugradnju fontova, podskupove i API-je za tekst pokrivene drugdje na ovom blogu.