Suunnittelija valitsee fontin, jossa on yksikertainen a otsikoita varten, tai vinoviivallinen nolla taulukoita varten, tai sarja koristekirjaimia kantta varten. Nämä glyfit ovat jo fontissa. Ne eivät vain ole oletusarvoisia. Oletusarvoinen a viittaa merkistä cmap-taulukon kautta yhteen glyfiin, ja vaihtoehtoinen glyfi sijaitsee muutaman glyfi-id:n päässä, saavutettavissa vain korvaussäännön kautta. Tämän vaihtoehdon tuottaminen PDF-tiedostossa tarkoittaa säännön lukemista ja korvaavan glyfin viemistä sisältövirtaan. Tämä artikkeli käsittelee näiden sääntöjen lukemista, erityisesti yksittäiskorvaustyyppiä, Object Pascalilla ilman natiivia muotoilukirjastoa.
Laajuus on kapea tarkoituksella. Tyylijoukot ja -vaihtoehdot ovat yhden glyfin sisäänmenon ja yhden glyfin ulostulon korvauksia. Ne ovat se osa OpenType-asettelua, jonka voit ratkaista pienellä, deterministisellä taulukkokävelyllä, mikä tekee niistä hyvän valinnan Pascal-moottorille, joka haluaa pysyä vapaana C-riippuvuuksista.
Miksi puhdas Delphi eikä HarfBuzz
HarfBuzz on ilmeinen vastaus kysymykseen "muotoile tämä teksti", ja täydelliseen kaksisuuntaiseen, indialaisen tai arabialaisen tekstin muotoiluun se on oikea vastaus. Se on kuitenkin C-kirjasto. Sen sitominen Delphi- tai C++Builder-tuotteeseen tarkoittaa natiivin objektin toimittamista jokaiselle kohdealustalle ja arkkitehtuurille, sen kutsukäytännön noudattamista, sen julkaisutahdin seuraamista ja sen lisenssiehtojen vertaamista omiisi. Mikään tästä ei ole vaikeaa erillään. Kaikki se on kitkaa, joka ei koskaan katoa, eikä se tuo mitään lisäarvoa, kun todellinen vaatimus on vain "anna minulle tämän kirjaimen ss01-muoto".
Yksittäinen korvaus ei tarvitse muotoilumoottoria. Se tarvitsee jäsentäjän muutamalle GSUB-alitaulukkomuodolle sekä binäärihaun tai kaksi. Sen kirjoittaminen Pascalilla pitää koko työkaluketjun yhden kääntäjän sisällä. Oikea rajoitus on, että tämä lähestymistapa käsittelee vain glyfien korvaushakuja eikä mitään muuta. Se ei ole bidi-ratkaisu, se ei ole indialainen uudelleenjärjestely, eikä se ole automaattinen kontekstuaalinen muotoilu. Missä niitä tarvitaan, niitä tarvitaan, eikä yksittäiskorvauskysely korvaa niitä.
GSUB-hierarkia ylhäältä alas
Glyfien korvaustaulukko on järjestetty epäsuorien viittausten ketjuksi, ja korvauskysely kävelee ketjua ylhäältä alkaen. Huipulla on ScriptList. Kirjoitusjärjestelmätunniste, kuten latn, valitsee merkinnän, ja erityinen tunniste DFLT on oletuskirjoitusjärjestelmä, jota sovelletaan, kun mikään tarkempi kirjoitusjärjestelmä ei täsmää. Kirjoitusjärjestelmämerkintä osoittaa LangSys-kielijärjestelmään, jossa on oletusarvoinen LangSys yleistä tapausta varten ja valinnaiset nimetyt kielijärjestelmät kieliä varten, jotka tarvitsevat erilaista käyttäytymistä. Turkki on tavallinen esimerkki, jossa pisteellinen ja pisteetön i vaativat oman käsittelynsä.
LangSys nimeää joukon ominaisuusindeksejä. Jokainen indeksi osoittaa FeatureList-luetteloon, jossa ominaisuustietue kantaa nelitavuista tunnistetta, ss01 muiden joukossa, ja luetteloa hakuhakemistoista. Nämä indeksit osoittavat lopulta LookupList-luetteloon, jossa varsinaiset korvausalitaulukot sijaitsevat. Joten tunnisteen ss01 ratkaiseminen tarkoittaa: etsi kirjoitusjärjestelmä, etsi sen LangSys, etsi ominaisuus, jonka tunniste on ss01, kerää sen nimeämät haut ja sovelta niitä. HotPDF käyttää oletuksena DFLT-kirjoitusjärjestelmää ja oletus-LangSys-kielijärjestelmää, mikä on suurin osa latinalaisen tekstin suunnittelusta käyttää, ja se tarjoaa tavan ohittaa kirjoitusjärjestelmätunniste, kun fontti kytkee ominaisuutensa tietyn kirjoitusjärjestelmän alle.
Kattavuustaulukot päättävät osallistujat
Jokainen korvausalitaulukko alkaa samalla kysymyksellä: osallistuuko tämä syöteglyfi tähän sääntöön, ja jos osallistuu, missä se sijaitsee säännön omassa indeksoinnissa. Tähän kysymykseen vastaa kattavuustaulukko, ja vastaus on kattavuusindeksi, pieni järjestysluku, jota loppuosa alitaulukosta käyttää etsiessään, miksi glyfi muuttuu.
Kattavuus esiintyy kahdessa muodossa. Muoto 1 on nousevaan järjestykseen lajiteltu luettelo glyfi-id-tunnuksista. Löydät glyfin binäärihaulla, ja sen sijainti luettelossa on sen kattavuusindeksi. Muoto 2 on luettelo aluetietueista, joista jokainen sisältää aloitusglyfin, pääteglyfin ja kattavuusindeksin, johon aloitusglyfi kuvaa. Alueen sisällä oleva glyfi saa kattavuusindeksinsä siirtymänä alueen alusta. Muoto 1 on kompakti, kun osallistuvat glyfit ovat hajallaan, Muoto 2 taas silloin, kun ne muodostavat yhtenäisiä sarjoja. Molemmat on lajiteltu, joten molempia haetaan logaritmisessa ajassa, ja molemmat palauttavat joko kattavuusindeksin tai puhtaan "ei kattavuutta" -tuloksen, jonka ansiosta moottori voi jättää glyfin rauhaan.
Yksittäinen korvaus, kaksi muotoa
Yksittäinen korvaus on hakutyyppi 1, ja se kuvaa yhden glyfin täsmälleen yhdeksi korvaavaksi glyfiksi. Silläkin on kaksi muotoa, ja jako on tilan optimointia. Muoto 1 tallentaa yhden etumerkillisen deltan. Ulostuloglyfin id on syöteglyfin id plus kyseinen delta, modulo 65536. Näin fontti koodaa korvauksen, jossa jokainen osallistuva glyfi sijaitsee samalla kiinteällä siirtymällä vaihtoehdostaan; esimerkiksi numeromerkkien lohko, joka on asetettu vakioetäisyydelle vastaavista vanhanaikaisista numeroista. Kattavuustaulukko kertoo, mitkä glyfit täyttävät ehdot, ja yksi delta palvelee niitä kaikkia.
Muoto 2 tallentaa eksplisiittisen taulukon korvaavista glyfi-id-tunnuksista. Kattavuustaulukon kattavuusindeksi on indeksi tähän taulukkoon, joten glyfi kattavuusindeksissä 0 muuttuu ensimmäiseksi taulukon alkioksi, kattavuusindeksi 1 toiseksi ja niin edelleen. Muotoa 2 käytetään, kun vaihtoehdot eivät ole tasaisella siirtymällä, mikä on tavallinen tapaus käsin rakennetuissa tyylijoukoissa. Kysely on samanlainen kutsujan puolelta molemmissa tapauksissa. Ota syöteglyfi, aja se kattavuuden läpi, ja jos se kuuluu kattavuuden piiriin, sovelta deltaa tai lue taulukon 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, []);
// 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;
Huomion arvoinen seikka on ohituskäyttäytyminen. GetSingleSubstituteGlyph palauttaa syötteen glyfi-id:n muuttumattomana jokaisessa epäonnistumisessa: ei fonttia, ei GSUB-taulukkoa, ei vastaavaa ominaisuutta, ei kattavuushittiä. Tämä tarkoittaa, että kutsu on turvallista tehdä ehdottomasti. Pyydät vaihtoehtoa, ja jos sellaista ei ole, saat takaisin juuri sen, mitä annoit syötteeksi, joten kutsuvan koodin ei koskaan tarvitse ottaa erikseen huomioon fonttia, josta ominaisuus puuttuu.
Mitä tyylilliset ominaisuustunnisteet tarkoittavat
Ominaisuustunniste on koko sanasto siitä, mitä vaihtoehtoa pyydät, ja tyylilliseen työhön liittyvät tunnisteet ovat lyhyt luettelo. Päätunnistepari on salt eli tyylilliset vaihtoehdot, joka on yleinen pääsy glyfin vaihtoehtoisiin muotoihin, ja ss01–ss20, eli kaksikymmentä numeroitua tyylijoukkoa, joita fontti voi määritellä, kukin suunnittelijan ryhmittelemä nippu korvauksia. Fontti voi esimerkiksi laittaa yksikertaisen a-kirjaimen ja suorajalkaisen R-kirjaimen tunnisteen ss03 alle, jolloin kyseisen yhden joukon käyttöönotto muuttaa molemmat.
Näiden ympärillä on useita muita yksittäiskorvaustunnisteita. aalt on access-all-alternates, kaikkien glyfin vaihtoehtojen liitto, joka esitetään yleensä glyfipalettiominaisuutena. titl valitsee otsikkokirjaimet, jotka on suunniteltu suuria kokoja varten. subs ja sups vaihtavat tilalle todelliset ala- ja yläindeksinumerot pienennettyjen oletusarvojen sijaan. ordn tuottaa järjestyslukumuotoja, kuten kohotetut kirjaimet ilmauksissa 1st ja 2nd. frac rakentaa murtolukuja, vaikka täydelliset vinot murtoluvut tukeutuvat myös ligatuuri- ja kontekstuaaliseen logiikkaan, joka menee pelkän yksittäiskorvauksen ohi. Yksittäisten glyfien tapauksessa mekanismi on identtinen ss01-tunnisteen kanssa: välitä tunniste korvauskyselylle ja lue vaihtoehtoinen glyfi.
// 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-muoto 12 ja lisätasot
Ennen kuin mitään korvausta voidaan ajaa, merkin on muututtava glyfiksi, ja se on cmap-taulukon tehtävä. Korvauskysely alkaa glyfi-id:stä, joten polku on aina merkistä glyfiksi cmap-taulukon kautta ja sitten glyfistä vaihtoehdoksi GSUB:n kautta. Mielenkiintoinen osa cmap-taulukkoa on sen kattavuus. Muodon 4 alitaulukko kattaa peruskielitason, eli ensimmäiset 65536 koodipistettä, ja se riittää useimmille latinalaisille teksteille. Se ei riitä koodipisteille U+10000:sta ylöspäin, lisätasoille, joissa matemaattiset aakkosnumeeriset merkit, monet symbolit ja useat elävät kirjoitusjärjestelmät nykyään sijaitsevat.
Muoto 12 on alitaulukko, joka kattaa koko alueen U+0000 - U+10FFFF. Se on lajiteltu luettelo ryhmistä, joista jokainen ryhmä on aloitus koodipiste, pääte koodipiste ja aloitus glyfi-id, joten koodipisteiden yhtenäinen sarja kuvautuu glyfien yhtenäiseksi sarjaksi. HotPDF ratkaisee koodipisteet hybridistrategialla, joka vastaa tietojen muotoa. BMP-alueen koodipisteet palvellaan suorasta taulukosta koodipisteellä indeksoituna, mikä on yksi haku ilman etsintää. Lisätasojen koodipisteet palvellaan harvasta taulukosta, joka on lajiteltu koodipisteen mukaan ja jota haetaan binäärihaulla. Tuloksena on, että GetUnicodeGlyphForCodepoint ottaa täyden Cardinal-arvon ja vastaa oikein koko alueella, palauttaen glyfi-id:n 0 kaikille koodipisteille, joita fontti ei kuvaa.
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;
Mihin nämä kyselyt päättyvät
Yksittäiskorvaus-API:t vastaavat yhteen kysymysmuotoon, ja on syytä olla selvillä siitä, mihin ne eivät vastaa. LookupType 1 on yksi kahdeksasta korvaustyypistä. Kysely ei käsittele LookupType 2 -monikorvausta, jossa yhdestä glyfistä tulee useita, eikä LookupType 4 -ligatuurikorvausta, jossa useasta glyfistä tulee yksi. Se ei käsittele kontekstuaalisia eikä ketjutettuja kontekstuaalisia tyyppejä, jotka käynnistyvät vain, kun glyfi esiintyy tietyssä ympäristössä, eikä laajennus- tai käänteisketjutustyyppejä. Vino murtoluku, devanagari-konjunkti tai arabialainen alku-keski-loppu-sarja on jakso-ongelma, eikä glyfikohtainen yksittäiskorvaushaku voi ilmaista sitä.
Se ei myöskään suorita automaattista muotoilua. Mikään tässä ei tarkasta tekstisarjaa, päätä mitkä ominaisuudet otetaan käyttöön, ja sovelta niitä siinä järjestyksessä kuin kirjoitusjärjestelmä vaatii. Kutsuja valitsee ominaisuustunnisteen ja soveltaa sitä glyfi glyfiltä. Se on juuri oikea työkalu tyylijoukoille ja -vaihtoehdoille, jotka ovat valinnaisia ja paikallisia, ja juuri väärä työkalu kirjoitusjärjestelmälle, joka vaatii uudelleenjärjestelyä. Rajan pitäminen terävänä on se, mikä pitää korvauspolun pienenä ja ennakoitavana.
Niissä tapauksissa, joissa tarvitaan jaksotason työtä, monimutkaisten kirjoitusjärjestelmien tarina jatkuu artikkelissamme monimutkaisten kirjoitusjärjestelmien tekstin muotoilusta Delphissä. Jos korvauksesi ovat osa suurempaa raportointityötä, joka sijoittaa myös kuvia ja muita fontteja sivulle, ohje raportin tulostamisesta fonteilla ja kuvilla kattaa, miten nämä osat sopivat yhteen. Kaikki nämä toimivat samalla moottorilla, eli Delphin ja C++Builderin HotPDF Componentilla, joka kuljettaa GSUB-korvauskyselyt fonttien upottamisen, osittamisen ja tekstin API-liittymien rinnalla, joita käsitellään muualla tässä blogissa.