Techninis straipsnis

OpenType GSUB stilistiniai alternatyvūs simboliai (Stylistic Alternates) gryname (Pure) Delphi

Dizaineris pasirenka šriftą su vienaukšte (single-story) a antraštėms, perbrauktu (slashed) nuliu lentelėms arba pasvirusių didžiųjų raidžių (swash capitals) rinkiniu viršeliui. Tie glifai jau yra šrifte. Jie tiesiog nėra numatytieji (default). Numatytoji a yra atvaizduojama (maps) iš simbolio per cmap lentelę į vieną glifą, o alternatyvus simbolis sėdi (sits) už kelių glifo ID, pasiekiamas tik per keitimo taisyklę (substitution rule). To alternatyvaus simbolio sukūrimas PDF faile reiškia taisyklės perskaitymą ir pakaitalo glifo (substitute glyph) išleidimą (emitting) turinio sraute (content stream). Šis straipsnis yra apie tų taisyklių, būtent vieno keitimo (single-substitution) tipo, skaitymą „Object Pascal“ kalba (Object Pascal) be jokios (apačioje esančios) gimtosios formavimo bibliotekos (native shaping library)

Apimtis sąmoningai yra siaura. Stilistiniai rinkiniai (Stylistic sets) ir alternatyvūs simboliai (alternates) yra vieno glifo įvedimo (single-glyph-in), vieno glifo išvedimo (single-glyph-out) keitimai. Jie yra ta „OpenType“ išdėstymo dalis, kurią galite išspręsti mažu, deterministiniu lentelių perėjimu (table walk), o tai padaro juos tinkamais (a good fit) „Pascal“ varikliui (engine), kuris nori išlikti laisvas nuo C priklausomybių

Kodėl grynas Delphi, o ne HarfBuzz

HarfBuzz yra akivaizdus atsakymas į (klausimą) „suformuokite šį tekstą“, ir pilnam dvikrypčiui (bidirectional), indų ar arabų kalbų (teksto) formavimui (shaping) tai yra teisingas atsakymas. Tai taip pat yra C biblioteka. Jos susiejimas (Binding) į Delphi arba C++Builder produktą reiškia gimtojo objekto (native object) pristatymą (shipping) kiekvienai tikslinei platformai ir architektūrai, atitinkant jos iškvietimo konvenciją (calling convention), sekant jos išleidimų ritmą (release cadence) ir suderinant jos licencijos sąlygas su jūsiškėmis. Nė vienas iš šių dalykų nėra sunkus pats savaime (in isolation). Visa tai yra trintis (friction), kuri niekada nedingsta, ir ji nieko neduoda (buys nothing), kai tikrasis reikalavimas yra „duok man ss01 šios raidės formą“

Vieno (simbolio) keitimas (Single substitution) nereikalauja formavimo variklio (shaping engine). Jam reikia saujelės GSUB polentelių (subtable) formatų analizatoriaus (parser) ir vienos ar dviejų dvejetainių (binary) paieškų. Tai parašius „Pascal“ kalba, visa įrankių grandinė (toolchain) išlieka vieno kompiliatoriaus viduje. Sąžiningas apribojimas yra tas, kad šis metodas (approach) apdoroja glifų keitimo (glyph substitution) paieškas (lookups) ir nieko daugiau. Tai nėra bidi (dvikrypčio teksto) išsprendimas, tai nėra Indų kalbų perrikiavimas (Indic reordering), ir tai nėra automatinis kontekstinis formavimas (automatic contextual shaping). Kur jų reikia, ten jų reikia, ir vieno keitimo (single-substitution) užklausa (query) jų nepakeis

GSUB hierarchija (iš viršaus į apačią)

Glifų keitimo (Glyph Substitution) lentelė yra suorganizuota kaip netiesioginių nuorodų grandinė (chain of indirections), ir keitimo užklausa (substitution query) pereina tą grandinę nuo viršaus. Viršuje yra „ScriptList“ (rašmenų sąrašas). Skripto žyma (script tag), tokia kaip latn, pasirenka įrašą, o speciali žyma (special tag) DFLT yra numatytasis skriptas (rašmuo), kuris taikomas tada, kai neatitinka joks konkretesnis skriptas. Skripto įrašas nurodo (points at) į „LangSys“, kalbos sistemą (language system), su numatytuoju „LangSys“ įprastam atvejui ir pasirinktinais (optional) pavadintaisiais toms kalboms, kurioms reikia kitokios elgsenos. Turkų kalba (Turkish) yra įprastas pavyzdys, kai taškuotoji (dotted) ir betaškė (dotless) i reikalauja savo (atskiro) apdorojimo

„LangSys“ įvardija ypatybių indeksų (feature indices) rinkinį. Kiekvienas indeksas (index) nurodo į „FeatureList“ (ypatybių sąrašą), kur ypatybės (feature) įrašas neša keturių baitų žymą, tarp jų ir ss01, bei paieškos indeksų (lookup indices) sąrašą. Tie indeksai galiausiai nurodo į „LookupList“ (paieškų sąrašą), kur gyvena pačios keitimų polentelės (substitution subtables). Taigi, ss01 išsprendimas reiškia: rasti skriptą (rašmenį), rasti jo „LangSys“, rasti ypatybę (feature), kurios žyma yra ss01, surinkti paieškas (lookups), kurias ji įvardija, ir jas pritaikyti. HotPDF pagal nutylėjimą naudoja (defaults to) DFLT skriptą ir numatytąjį „LangSys“, kas yra tai, ką pristato (ship) didžioji dauguma lotyniško teksto dizainų (Latin text designs), ir ji atveria (exposes) būdą perrašyti (override) skripto žymą, kai šriftas vietoj to sujungia (wires) savo ypatybes su konkrečiu skriptu

Coverage (aprėpties) lentelės nusprendžia, kas dalyvauja

Kiekviena keitimo polentelė (substitution subtable) prasideda tuo pačiu klausimu: ar šis įvesties glifas dalyvauja šioje taisyklėje, ir jei taip, kur jis sėdi (sits) pačios taisyklės indeksavime. Į tą klausimą atsako „Coverage“ (aprėpties) lentelė, o atsakymas yra aprėpties indeksas (coverage index) – mažas eilės skaičius (ordinal), kurį likusi polentelės dalis naudoja norint rasti, kuo virsta glifas

„Coverage“ būna dviejų formatų. 1 formatas yra glifų ID sąrašas, surūšiuotas didėjimo tvarka. Glifą randate atlikdami dvejetainę (binary) paiešką, o jo vieta sąraše yra jo aprėpties indeksas (coverage index). 2 formatas yra rėžių (range) įrašų sąrašas, kur kiekviename iš jų yra pradžios glifas, pabaigos glifas ir aprėpties indeksas (coverage index), į kurį (iš)atvaizduojamas pradžios glifas. Glifas, esantis rėžio (range) viduje, savo aprėpties indeksą gauna pasislinkdamas (offsetting) nuo rėžio pradžios. 1 formatas yra kompaktiškas, kai dalyvaujantys glifai yra išsibarstę (scattered), o 2 formatas – kai jie patenka į ištisinius srautus (contiguous runs). Abu (formatai) yra surūšiuoti (sorted), todėl abiejų paieška užtrunka logaritminį laiką, ir abu grąžina (return) arba aprėpties indeksą, arba švarų „neapimta“ (not covered), kas leidžia varikliui (engine) palikti glifą ramybėje

Single Substitution (Vienas keitimas), du formatai

„Single Substitution“ (Vienas keitimas) yra „LookupType 1“ (1 paieškos tipas), ir jis (iš)atvaizduoja (maps) vieną glifą į lygiai vieną pakaitalą (replacement). Jis (taip pat) turi du formatus, o šis padalijimas (split) yra vietos (space) optimizavimas. 1 formatas saugo vieną delta su ženklu (signed delta). Išvesties glifo ID yra įvesties glifo ID plius ta delta, pagal modulį (modulo) 65536. Būtent taip šriftas užkoduoja keitimą, kuriame kiekvienas dalyvaujantis glifas sėdi (sits) tuo pačiu fiksuotu poslinkiu (fixed offset) nuo savo alternatyvaus (simbolio), pavyzdžiui, lygiavimo skaitmenų (lining figures) blokas, patalpintas pastoviu atstumu nuo atitinkamų senojo stiliaus skaitmenų (oldstyle figures). „Coverage“ lentelė pasako, kurie glifai atitinka reikalavimus (qualify), o ta viena delta aptarnauja juos visus

2 formatas saugo aiškų pakaitalų (substitute) glifų ID masyvą (array). Aprėpties indeksas (coverage index) iš „Coverage“ lentelės yra indeksas (index) į tą masyvą, todėl glifas, kurio aprėpties indeksas yra 0, tampa pirmuoju masyvo įrašu, aprėpties indeksas 1 tampa antruoju, ir taip toliau. 2 formatas naudojamas tada, kai alternatyvūs simboliai nėra (išsidėstę) vienodu (uniform) poslinkiu, o tai yra dažnas atvejis rankomis kuriamiems stilistiniams rinkiniams (stylistic sets). Bet kuriuo atveju iškvietėjo (caller) pusėje užklausa (query) yra ta pati. Paimkite įvesties glifą, paleiskite jį per „Coverage“ ir, jei jis yra apimtas (covered), pritaikykite deltą (delta) arba nuskaitykite (read) masyvo lizdą (array slot)

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;

Sutartis (contract), kurią verta pastebėti, yra praleidimas (pass-through). GetSingleSubstituteGlyph grąžina įvesties glifo ID nepakeistą kiekvieno praleidimo (miss) atveju: nėra šrifto, nėra GSUB lentelės, nėra atitinkančios ypatybės (feature), nėra aprėpties pataikymo (coverage hit). Tai reiškia, kad šį iškvietimą (call) saugu atlikti besąlygiškai (unconditionally). Jūs prašote alternatyvaus (simbolio), o jei jo nėra, atgaunate lygiai tai, ką įdėjote (put in), todėl iškviečiančiam (calling) kodui niekada nereikia išskirti (special-case) šrifto, neturinčio šios ypatybės

Ką reiškia stilistinių ypatybių (stylistic feature) žymos (tags)

Ypatybės žyma (feature tag) yra visas žodynas apie tai, kokio alternatyvaus (simbolio) prašote, o žymos, susijusios su stilistiniu darbu, yra trumpas sąrašas. Pagrindinė pora yra salt, stilistiniai alternatyvūs simboliai (stylistic alternates), „catch-all“ prieiga prie glifo alternatyviųjų formų, ir nuo ss01 iki ss20, dvidešimt sunumeruotų stilistinių rinkinių (stylistic sets), kuriuos gali apibrėžti šriftas, kur kiekvienas yra pavadintas (named) keitimų (substitutions) paketas (bundle), kuriuos dizaineris sugrupuoja kartu. Pavyzdžiui, šriftas gali padėti vienaukštę (single-story) a ir tiesiakoję (straight-leg) R po ss03, todėl įgalinus tą vieną rinkinį (set) bus pakeistas abiejų stilius (restyles)

Aplink juos (sėdi) dar kelios vieno keitimo (single-substitution) žymos. aalt yra prieiga prie visų alternatyviųjų (access-all-alternates) – visų alternatyviųjų (simbolių), kuriuos turi glifas, sąjunga (union), paprastai pateikiama kaip glifų paletės (glyph-palette) funkcija (feature). titl pasirenka didžiąsias (titulines) raides (titling capitals), iškirptas (cut) dideliems dydžiams. subs ir sups pakeičia (swap in) tikrus apatinius ir viršutinius (subscript and superscript) skaitmenis (figures) vietoj sumažinto (scaled-down) dydžio numatytųjų reikšmių (defaults). ordn sukuria (produces) kelintinių skaitvardžių (ordinal) formas, pakeltas raides žodžiuose 1st ir 2nd. frac sukuria (builds) trupmenas (fractions), nors pilnos įstrižinės (diagonal) trupmenos taip pat remiasi ligatūrų (ligature) ir kontekstine logika (contextual logic), kuri peržengia (goes past) paprasto vieno keitimo (single substitution) ribas. Vieno glifo (single-glyph) atvejams (cases) mechanizmas yra identiškas kaip ir ss01: perduokite žymą (tag) į keitimo (substitution) užklausą ir perskaitykite grąžintą (read back) alternatyvų 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 12 formatas ir papildomos plokštumos (supplementary planes)

Prieš tai, kai gali būti paleistas koks nors keitimas (substitution), simbolis turi tapti glifu (glyph), o tai yra cmap lentelės darbas. Keitimo užklausa (substitution query) prasideda nuo glifo ID, todėl kelias visada yra (nuo) simbolio į glifą per cmap, o tada (nuo) glifo į alternatyvų (simbolį) per GSUB. Įdomiausia cmap dalis yra jos aprėptis (reach). 4 formato polentelė (subtable) apima (covers) „Basic Multilingual Plane“ (Pagrindinę daugiakalbę plokštumą), pirmuosius 65536 kodo taškus (code points), ir to pakanka daugumai lotyniško (Latin) teksto. To nepakanka kodo taškams (code points) nuo U+10000 į viršų – papildomoms plokštumoms (supplementary planes), kur dabar gyvena matematiniai raidžių ir skaičių simboliai (mathematical alphanumerics), daugybė simbolių (symbols) ir keletas gyvų skriptų (rašmenų) (living scripts)

12 formatas yra polentelė (subtable), apimanti visą (the full) U+0000 iki U+10FFFF diapazoną. Tai yra surūšiuotas grupių sąrašas, kuriame kiekviena grupė yra (turi) pradžios kodo taškas (start code point), pabaigos kodo taškas ir pradžios glifo ID, todėl nepertraukiama (contiguous) kodo taškų eilė (run) (iš)atvaizduojama (maps) į nepertraukiamą glifų eilę. HotPDF išsprendžia (resolves) kodo taškus naudodama hibridinę strategiją, kuri atitinka (matches) tai, kaip suformuoti duomenys. Kodo taškai (Code points) BMP (baziniame daugiakalbiame lygmenyje) (BMP) yra patiekiami (served) iš tiesioginio masyvo, indeksuoto (indexed) pagal kodo tašką (code point) – (tai) viena paieška (single lookup) be jokio (realaus) ieškojimo (no search). Papildomų plokštumų (supplementary planes) kodo taškai yra patiekiami (served) iš retos (sparse) lentelės, surūšiuotos pagal kodo tašką ir ieškomos atliekant dvejetainę (binary) paiešką. Rezultatas yra tas, kad GetUnicodeGlyphForCodepoint priima pilną Cardinal reikšmę ir teisingai atsako visame diapazone (range), grąžindama glifo (id) 0, t. y. .notdef glifą, bet kuriam kodo taškui, kurio šriftas neatvaizduoja (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;

Kur šios užklausos (queries) sustoja

Vieno keitimo (single-substitution) API atsako į vienos formos (vienokio pobūdžio) (one shape of) klausimą, ir verta aiškiai suprasti, į ką jie neatsako. „LookupType 1“ yra vienas iš aštuonių keitimo tipų. Ši užklausa neapdoroja (does not handle) „LookupType 2“ kelių (multiple) pakeitimų (multiple substitution), kai vienas glifas virsta keliais, nei „LookupType 4“ ligatūrų keitimų (ligature substitution), kai keli glifai virsta vienu. Ji (ši užklausa) neapdoroja kontekstinių ir grandininių-kontekstinių (chaining-contextual) tipų, t.y. „LookupTypes 5“ ir 6, kurie suveikia (fire) tik tada, kai glifas pasirodo tam tikroje kaimynystėje, o taip pat plėtinių (extension) ir atvirkštinio grandinimo (reverse-chaining) tipų. Įstrižinė (diagonal) trupmena (fraction), Devanagari (rašto) junginys (conjunct) ar arabų kalbos pradinė-vidurinė-galinė kaskada (initial-medial-final cascade) yra sekos (sequence) problema, ir vieno glifo (per-glyph) vieno keitimo (single-substitution) paieška (lookup) to išreikšti negali

Ji taip pat neatlieka automatinio formavimo (automatic shaping). Niekas čia netikrina (inspects) teksto eigos (run of text), nenusprendžia, kurias ypatybes (features) įjungti (turn on), ir nepritaiko jų ta tvarka (order), kurios reikalauja skriptas (rašmuo) (script). Iškvietėjas (caller) pasirenka ypatybės žymą (feature tag) ir pritaiko ją kiekvienam glifui atskirai (glyph by glyph). Būtent tai yra tinkamas (the right) įrankis stilistiniams rinkiniams (stylistic sets) ir alternatyviesiems (simboliams) (alternates), kurie yra pasirenkami (opt-in) ir vietiniai (local), bei visiškai (exactly the) netinkamas įrankis skriptui, kuriam reikia (reikalauja) pertvarkymo (reordering). Suteikiant riboms aštrumo (Keeping the boundary sharp) (Griežtas ribų laikymasis), tai leidžia keitimų (substitution) keliui (path) išlikti mažam ir nuspėjamam

Atvejams (For the cases), kuriems iš tikrųjų reikia sekos lygio (sequence-level) darbo, sudėtingų skriptų istorija (complex-script story) (tema) nagrinėjama mūsų straipsnyje apie sudėtingų skriptų (rašmenų) teksto formavimą „Delphi“ aplinkoje. Jei jūsų keitimai (substitutions) yra didesnio ataskaitų teikimo (reporting) darbo dalis, kai puslapyje taip pat dedami (places) paveikslėliai ir kiti šriftai, ataskaitų išvesties (report output) su šriftais ir paveikslėliais vadove aptariama, kaip visos šios dalys (pieces) dera kartu. Visi jie veikia ant to paties variklio (engine), HotPDF komponento (Component), skirto „Delphi“ ir „C++Builder“, kuris kartu su šriftų įterpimo (embedding), poaibių kūrimo (subsetting) ir teksto API, aprašytais kitose šio tinklaraščio (blog) vietose, turi (carries) GSUB keitimo užklausas (substitution queries)