Tekninen artikkeli

OpenType GSUB -tyylivaihtoehdot puhtaassa Delphissä

Suunnittelija valitsee fontin yksikerroksisella kirjaimella a otsikoille tai leikatun nollan taulukoita varten tai sarjan näyttäviä isoja kirjaimia (swash capital) kanteen. Nämä merkit ovat jo fontissa. Ne eivät vain ole oletuksena. Oletus a yhdistää merkistä taulukon cmap kautta yhteen glyyfiin, ja varajäsen istuu muutaman kuviotunnuksen päässä tavoitettavissa vain korvaussäännön kautta. Vaihtoehdon tuottaminen PDF-tiedostossa tarkoittaa säännön lukemista ja korvaavan symbolin lähettämistä sisältövirrassa. Tässä artikkelissa on kyse noiden sääntöjen lukemisesta, yhden kerran korvaamisesta Object Pascalilla, jolla ei ole muotoilukirjastoa alla

Käyttöala on tarkoituksella kapea. Tyylisovellukset ja vaihtoehdot ovat yhden merkin ja yhden merkin korvaavia menetelmiä. Ne ovat osa OpenType-asettelua, jonka voit ratkaista pienellä deterministisellä pöytäkävelyllä, mikä tekee niistä hyvän sovituksen Pascal-moottorille, joka haluaa pysyä vapaana C-riippuvuuksista

Miksi puhdas Delphi HarfBuzzin sijaan

HarfBuzz on ilmeinen vastaus kysymykseen "muotoile tämä teksti", ja se on oikea vastaus täydelliseen kaksisuuntaiseen tai intialaiseen ja arabiankieliseen muotoiluun. Se on myös C-kirjasto. Sen liittäminen Delphi- tai C++Builder-tuotteeseen tarkoittaa natiiviobjektin toimittamista jokaiselle kohdealustalle ja -arkkitehtuurille, vastaamista sen kutsumiskäytäntöön, sen julkaisutahdin seurantaa ja sen lisenssiehtojen lukemista omiasi vastaan. Mikään niistä ei ole vaikeaa eristyksissä. Kaikki tämä on kitkaa, joka ei koskaan katoa, eikä se osta mitään, kun todellinen vaatimus on "anna minulle tämän kirjaimen ss01 lomake"

Yksi korvaaminen ei vaadi muotoilumoottoria. Se tarvitsee jäsennystoiminnon kouralliselle GSUB-alitaulukon muodoille ja yksi tai kaksi binäärihakua. Kun kirjoitat sen Pascalilla, koko työkalu pysyy yhden kääntäjän sisällä. Rehellinen raja on, että tämä lähestymistapa käsittelee symbolien korvaamishakut ja ei muuta. Se ei ole bidi-resoluutio, se ei ole intian uudelleenjärjestely, eikä se ole automaattista kontekstuaalista muotoilua. Sielä missä niitä tarvitaan, niitä tarvitaan ja yhden korvaamisen kysely ei korvaa niitä

GSUB-hierarkia ylhäältä alas

Glyph Substitution -taulukko on järjestetty epäsuorien elementtien ketjuksi, ja korvauskysely kävelee ketjun läpi ylhäältä. Ylimpänä on ScriptList. Skriptitunniste, kuten latn, valitsee merkinnän, ja erikoistunniste DFLT on oletusskripti, jota sovelletaan, kun tarkempi komentosarja ei täsmää. Skriptimerkintä osoittaa kohteeseen LangSys, kielijärjestelmä, jonka yhteisenä tapauksena on oletusarvoinen LangSys ja valinnaiset nimetyt kielet kielille, jotka vaativat erilaista käyttäytymistä. Turkki on tavanomainen esimerkki, jossa pisteellä tai pisteettömänä oleva i edellyttää omaa käsittelyään

LangSys nimeää joukon ominaisuusindeksejä. Jokainen indeksi osoittaa kohtaan FeatureList, jossa on ominaisuustietue, joka kantaa neljän tavun tagin, mukaan lukien ss01, ja luettelon hakemistoista. Nuo indeksit osoittavat lopulta paikkaan LookupList, jossa varsinaiset korvaavat alitaulukot sijaitsevat. Joten asian ss01 ratkaiseminen tarkoittaa: etsi komentosarja, löydä sen LangSys, löydä sen ominaisuus, jonka tunniste on ss01, kerää sen nimittämät haut ja ota ne käyttöön. HotPDF käyttää oletusarvoisesti DFLT -skriptiä ja oletus-LangSys:tä, mikä on se, mitä suurin osa latinalaisista tekstimallituksista lähetetään, ja se paljastaa tavan ohittaa skriptin tunniste, kun fontti johdottaa sen ominaisuudet tietyn skriptin alle sen sijaan

Kattavuustaulukot päättävät kuka osallistuu

Jokainen korvaava alitaulukko alkaa samalla kysymyksellä: osallistuuko tämä syötemerkki tähän sääntöön, ja jos on, missä se on säännön omassa indeksoinnissa. Tähän kysymykseen vastaa kattavuustaulukko (Coverage table), ja vastaus on peittoindeksi, pieni ordinaali, jota muu alitaulukko käyttää etsiessään sitä miksi kyseisestä merkistä tulee

Kattavuus (coverage) on kahta muotoa. Formaatti 1 on luettelo kuvioiden tunnuksista lajiteltuna nousevassa järjestyksessä. Löydät merkin binäärihaulla, ja sen paikka luettelossa on sen kattavuusindeksi. Formaatti 2 on luettelo aluetietueista, jokaisessa aloitusmerkki, lopetusmerkki ja peittoindeksi johon aloitusmerkki on kartoitettu. Alueen sisällä oleva symboli saa kattavuusindeksinsä korvaamalla alueen alkupisteen. Muoto 1 on kompakti, kun osallistuvat kuviot on hajallaan, muoto 2 kun ne jakautuvat vierekkäisiin ajoihin. Molemmat on lajiteltu, joten molempia haetaan logaritmisessa ajassa, ja molemmat palauttavat joko peittoindeksin tai puhtaan "ei katettu", jonka avulla moottori voi jättää glyph:n ennalleen

Yksittäiskorvaus, kaksi muotoa

Yksittäinen korvaaminen (Single Substitution) on LookupType 1, ja se yhdistää yhden kirjasimen täsmälleen yhteen korvaavaan kirjaisimeen. Sillä on myös kaksi muotoa ja jako on avaruusoptimointi. Muoto 1 tallentaa yhden allekirjoitetun deltan. Lähtömerkin tunnus (GID) on syöttömerkin tunnus plus kyseinen delta modulo 65536. Näin fontti koodaa korvauksen, jossa jokainen osallistuva symboli sijaitsee samalla kiinteällä etäisyydellä varajäsenestään, esimerkiksi lohko vuorauskuvioita, jotka on sijoitettu vakioetäisyydelle vastaavista vanhan mallin kuvioista. Kattavuustaulukko kertoo ketkä merkit kelpaavat ja se yksi delta palvelee kaikkia heitä

Muoto 2 tallentaa eksplisiittisen joukon korvaavia glyyfitunnisteita. Kattavuustaulukon kattavuusindeksi on indeksi tuohon taulukkoon, joten kattavuusindeksin 0 merkistä tulee ensimmäinen taulukkosyöttö, peittoindeksi 1 toinen, ja niin edelleen. Muotoa 2 käytetään silloin, kun vaihtoehdot eivät ole tasaisella offsetilla, mikä on yleinen tapaus käsintehdyissä tyylisarjoissa. Kysely on molemmilla kerroilla sama soittajan puolelta. Ota syötemerkki, aja se kattavuuden (Coverage) läpi ja jos se peitetään, käytä deltaa tai lue ryhmäpaikka

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, []);

    // Oletuskuvio kohteelle 'a' fontin cmap:n kautta.
    BaseGID := Pdf.GetUnicodeGlyphForCodepoint(Ord('a'));

    // Tyylijoukko 1: ratkaise vaihtoehto GSUB LookupType 1:n kautta.
    AltGID := Pdf.GetSingleSubstituteGlyph(BaseGID, 'ss01');

    // AltGID = BaseGID tarkoittaa, että ominaisuus ei koskettanut tätä kuviota.
    if AltGID <> BaseGID then
      { emit AltGID in the content stream };
  finally
    Pdf.Free;
  end;
end;

Huomionarvoinen sopimus on läpikulku. GetSingleSubstituteGlyph palauttaa muuttumattoman syöttömerkin tunnuksen jokaisen hutin jälkeen: ei fonttia, ei GSUB-taulukkoa, ei täsmäävää ominaisuutta, ei kattavuushakua. Tämä tarkoittaa, että puhelu on turvallista soittaa ehdoitta. Pyydät varajäsentä, ja jos sellaista ei ole, saat takaisin juuri sen, minkä laitoit, joten soittavan koodin ei koskaan tarvitse käyttää erityistapausta fonttia varten josta puuttuu toiminto

Mitä tyyliominaisuustunnisteet (Stylistic Feature Tags) tarkoittavat

Ominaisuustunniste on sen vaihtoehdon koko sanasto, jota pyydät, ja tyylityön kannalta olennaiset tunnisteet ovat lyhyt luettelo. Otsikkopari on salt, tyylilliset vaihtoehdot, kaiken kattava pääsy glyfin vaihtoehtoisiin muotoihin ja ss01 kautta ss20, kaksikymmentä numeroitua tyylisarjaa jotka fontti voi määritellä, ja kukin on nimetty paketti korvauksia jotka suunnittelija ryhmittelee yhteen. Fontti voi esimerkiksi laittaa yksikerroksisen arvon a ja suorajalkaisen arvon R kohtaan ss03, joten sen yhden joukon ottaminen käyttöön muotoilee ne molemmat uudelleen

Näiden ympärillä on useita muita yksittäisen korvaamisen tunnisteita. aalt on access-all-alternates (kaikki vaihtoehdot), joissa on yhdistelmä jokaista merkkiä kohden, joka esitetään yleensä glyyfi-palettiominaisuutena. titl valitsee suurta kokoa varten leikatut otsikkokirjaimet. subs ja sups vaihtuvat aidoissa ali- ja yläindeksiluvuissa pienennettyjen oletusarvojen sijaan. ordn tuottaa ordinaalimuotoja, korotettuja kirjaimia ensimmäiselle ja toiselle (1st ja 2nd). frac rakentaa murtolukuja, vaikkakin kokonaiset diagonaaliset murtoluvut nojaavat myös ligatuuri- ja kontekstuaaliseen logiikkaan, joka menee normaalin yhden korvauksen ulkopuolelle. Yhden merkin tapauksissa mekanismi on identtinen arvon ss01 kanssa: välitä tagi korvaavalle kyselylle ja lue vaihtoehtoinen kirjain

// Kokeile tyylisarjaominaisuutta ja palaa sitten takaisin tavallisiin vaihtoehtoihin.
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');
  // Edelleen BaseGID, jos kumpikaan ominaisuus ei kata tätä kuviota.
end;

Cmap formaatti 12 ja täydentävät tasot

Ennen kuin mikään korvaaminen voi käydä, merkistä täytyy tulla kirjainmerkki, ja se on cmap-taulukon tehtävä. Korvauskysely alkaa glyyfi tunnuksesta (GID), joten polku on aina muodosta symboliin komennon cmap kautta, sitten muodosta symbolista varajäseneen GSUB:n kautta. cmapin mielenkiintoinen osa on sen kattavuus. Muodon 4 alitaulukko kattaa monikielisen perustason, ensimmäiset 65536 koodipistettä, ja se riittää useimmille latinalaisille teksteille. Se ei riitä koodipisteille kohteesta U+10000 ylöspäin, täydentävillä tasoilla, joissa asuvat matemaattiset aakkosnumeeriset numerot, monet symbolit ja useat elävät skriptit nykyään elävät

Muoto 12 on alitaulukko, joka kattaa koko alueen U+0000 - U+10FFFF. Se on lajiteltu ryhmäluettelo, jokaisessa ryhmässä on aloituskoodipiste, lopetuskoodipiste ja alku-GID, jotta jatkuva koodipisteiden suoritus yhdistyy jatkuvaan kirjaimien sarjaan. HotPDF ratkaisee koodipisteet hybridistrategialla, joka vastaa datan muokkausta. BMP:n koodipisteet tarjoillaan suorasta koodipisteen indeksoidusta taulukosta, yhdestä hausta ilman hakua. Lisätasoissa olevat koodipisteet tarjoillaan harvasta taulukosta, joka on lajiteltu koodipisteen perusteella ja jota etsitään binäärihaulla. Tuloksena on, että GetUnicodeGlyphForCodepoint ottaa täyden Cardinaln ja vastaa oikein koko alueella palauttaen glyfi tunnukseksi 0, koodin .notdef, sille mille tahansa koodipisteelle, jota kirjasin ei kartoita

var
  Pdf: THotPDF;
  Cp: Cardinal;
  GID, StyledGID: Word;
begin
  // Täydentävän tason koodipiste: U+1D49C MATHEMATICAL SCRIPT CAPITAL A.
  Cp := $1D49C;
  GID := Pdf.GetUnicodeGlyphForCodepoint(Cp);  // formaatti 12 haku
  if GID <> 0 then
    StyledGID := Pdf.GetSingleSubstituteGlyph(GID, 'ss01')
  else
    StyledGID := 0;  // fontissa ei ole glyyfiä tälle koodipisteelle
end;

Mihin nämä kyselyt pysähtyvät

Yhden kerran korvaavat sovellusliittymät vastaavat yhteen kysymyksen muotoon, ja on syytä tehdä selväksi mihin niihin ne eivät vastaa. LookupType 1 on yksi kahdeksasta korvaustyypistä. Kysely ei käsittele LookupType 2 usean korvaamisen koodia, jossa yhdestä kuvasta tulee useita, eikä LookupType 4 -ligatuurin korvausta jossa useasta muodostuu yksi. Se ei käsittele kontekstuaalisia ja ketjutuskontekstisia tyyppejä, hakutyyppejä 5 ja 6, jotka laukeavat vain kun symboli esiintyy tietyssä naapurustossa, eikä lisäosien ja peruutusketjujen tyyppejä. Lävistäjäosa, devanagarin yhdysmerkki tai arabian alkumedian-loppukaskadi on sekvenssiongelma ja kirjaimen yksittäinen vaihtohaku ei voi ilmaista sitä

Se ei myöskään suorita automaattista muotoilua. Mikään tässä ei tarkasta tekstiä, päätä, mitkä ominaisuudet otetaan käyttöön ja ei käytä niitä komentotiedoston edellyttämässä järjestyksessä. Soittaja valitsee ominaisuustunnisteen ja soveltaa sitä glyfi kerrallaan. Se on juuri oikea työkalu tyylijoukkoille ja -vaihtoehdoille, jotka ovat osallistu-itse ja paikallisia, ja täsmälleen väärä työkalu komentosarjalle, joka kaipaa uudelleenjärjestelyä. Rajan pitäminen terävänä antaa vaihtopolun pysyä pienenä ja ennustettavana

Tapauksissa, jotka vaativat sekvenssitason työtä, monimutkaisten komentosarjojen (complex-script) tarina on käsitelty artikkelissamme monimutkaisten komentosarjojen (complex-script) muotoilemisesta Delphissä. Jos vaihtamisesi on osa laajempaa raportointityötä joka asettaa myös kuvia ja muita fontteja sivulle, opas fontteja ja kuvia sisältävän raportin tulostukseen kattaa näiden osien yhdistämisen toisiinsa. Kaikki nämä toimivat samalla moottorilla, HotPDF Component Delphille ja C++Builderille, joka suorittaa GSUB-korvauskyselyt muualla tässä blogissa käsiteltyjen fonttien upottamisen, alajoukon ja tekstisovellusliittymien ohella