Technical Article

OpenType GSUB stiliaus alternatyvos grynoje Delphi aplinkoje

Dizaineris pasirenka šriftą su vieno aukšto a antraštėms, perbrauktu nuliu lentelėms arba dekoratyvinių didžiųjų raidžių rinkiniu viršeliui. Šie glifai jau yra šrifte. Jie tiesiog nėra numatytieji. Numatytasis a atvaizduojamas iš simbolio per cmap lentelę į vieną glifą, o alternatyva yra už kelių glifų identifikatorių, pasiekiama tik per pakeitimo taisyklę. Šios alternatyvos sukūrimas PDF dokumente reiškia taisyklės perskaitymą ir pakaitinio glifo išvedimą turinio sraute. Šis straipsnis yra apie šių taisyklių (vieno pakeitimo tipo) skaitymą Object Pascal kalba be jokios vietinės formavimo (angl. shaping) bibliotekos po juo

Apimtis yra siaura tyčia. Stiliaus rinkiniai ir alternatyvos yra vieno glifo įvesties ir vieno glifo išvesties pakeitimai. Tai yra OpenType išdėstymo dalis, kurią galite išspręsti atlikdami nedidelį, deterministinį lentelės perėjimą, todėl jie puikiai tinka Pascal varikliui, kuris nori išlikti laisvas nuo C priklausomybių

Kodėl gryna Delphi, o ne HarfBuzz

HarfBuzz yra akivaizdus atsakymas į užklausą „suformuok šį tekstą“, o pilnam dvikrypčiui (bidi), indų ar arabų kalbų formavimui tai yra teisingas atsakymas. Tai taip pat yra C biblioteka. Jos susiejimas Delphi arba C++Builder produkte reiškia vietinio objekto pristatymą kiekvienai tikslinei platformai ir architektūrai, jos iškvietimo konvencijos atitikimą, jos leidimų stebėjimą ir licencijos sąlygų skaitymą pagal jūsų pačių sąlygas. Nieko iš to nėra sudėtinga atskirai. Visa tai yra trintis, kuri niekada neišnyksta, ir ji nieko neduoda, kai tikrasis reikalavimas yra „duok man šios raidės ss01 formą“

Vieno pakeitimo atveju nereikia formavimo variklio. Reikia kelių GSUB sublentelių formatų analizatoriaus (angl. parser) ir vienos ar dviejų dvejetainių paieškų. Šio kodo rašymas Pascal kalba išlaiko visą įrankių grandinę viename kompiliatoriuje. Sąžiningas apribojimas yra tas, kad šis požiūris tvarko tik glifų pakeitimo paieškas ir nieko daugiau. Tai nėra dvikrypčio teksto sprendimas, tai nėra indų kalbų perrikiavimas ir tai nėra automatinis kontekstinis formavimas. Kai to reikia, to reikia, ir vieno pakeitimo užklausa jų neatstos

GSUB hierarchija nuo viršaus iki apačios

Glifų pakeitimo (angl. Glyph Substitution) lentelė yra suorganizuota kaip netiesioginių nuorodų grandinė, o pakeitimo užklausa eina šia grandine nuo viršaus. Viršuje yra ScriptList. Scenarijaus žyma, pavyzdžiui, latn, pasirenka įrašą, o speciali žyma DFLT yra numatytasis scenarijus, kuris taikomas, kai neatitinka joks konkretesnis scenarijus. Scenarijaus įrašas rodo į LangSys (kalbos sistemą) su numatytąja LangSys bendram atvejui ir pasirenkamomis įvardytomis sistemomis kalboms, kurioms reikia kitokio elgesio. Turkų kalba yra įprastas pavyzdys, kur raidėms i su tašku ir be taško reikia atskiro apdorojimo

LangSys nurodo funkcijos indeksų rinkinį. Kiekvienas indeksas rodo į FeatureList, kur funkcijos įrašas neša keturių baitų žymą (tarp jų ir ss01) bei paieškos indeksų sąrašą. Šie indeksai galiausiai rodo į LookupList, kur gyvena tikrosios pakeitimo sublentelės. Taigi, ss01 išsprendimas reiškia: rasti scenarijų, rasti jo LangSys, rasti funkciją, kurios žyma yra ss01, surinkti jos nurodytas paieškas ir jas pritaikyti. HotPDF naudoja numatytąjį DFLT scenarijų ir numatytąją LangSys sistemą, su kuria tiekiama didžioji dalis lotyniškų tekstų dizaino, ir suteikia galimybę perrašyti scenarijaus žymą, kai šriftas susieja savo funkcijas su konkrečiu scenarijumi

Padengimo lentelės nusprendžia, kas dalyvauja

Kiekviena pakeitimo sublentelė prasideda tuo pačiu klausimu: ar šis įvesties glifas dalyvauja šioje taisyklėje ir, jei taip, kur jis yra pačios taisyklės indeksavime. Į šį klausimą atsako padengimo (angl. Coverage) lentelė, o atsakymas yra padengimo indeksas – nedidelis eilės skaičius, kurį likusi sublentelės dalis naudoja ieškodama, kuo glifas tampa

Padengimas pateikiamas dviem formatais. Format 1 yra glifų identifikatorių sąrašas, surūšiuotas didėjimo tvarka. Glifą randate atlikdami dvejetainę paiešką, o jo pozicija sąraše yra jo padengimo indeksas. Format 2 yra diapazonų įrašų sąrašas, kurio kiekvienas susideda iš pradinio glifo, galinio glifo ir padengimo indekso, į kurį nukreipiamas pradinis glifas. Glifas, esantis diapazone, gauna savo padengimo indeksą pagal poslinkį nuo diapazono pradžios. Format 1 yra kompaktiškas, kai dalyvaujantys glifai yra išsibarstę, o Format 2 – kai jie patenka į gretimus segmentus. Abu yra surūšiuoti, todėl abu ieškomi per logaritminį laiką, ir abu grąžina padengimo indeksą arba aiškų „nepadengta“, kas leidžia varikliui palikti glifą ramybėje

Vieno pakeitimo du formatai

Vieno pakeitimo (angl. Single Substitution) tipas yra LookupType 1, ir jis susieja vieną glifą su tiksliai vienu pakaitalu. Jis taip pat turi du formatus, o šis padalijimas yra vietos optimizavimas. Format 1 saugo vieną pasirašytą skirtumą (angl. delta). Išvesties glifo identifikatorius yra įvesties glifo identifikatorius plius tas skirtumas, modulo 65536. Taip šriftas užkoduoja pakeitimą, kai kiekvienas dalyvaujantis glifas yra nutolęs vienodu fiksuotu atstumu nuo savo alternatyvos, pavyzdžiui, lygiaverčių skaičių blokas, patalpintas pastoviu atstumu nuo atitinkamų senojo stiliaus skaičių. Padengimo lentelė nurodo, kurie glifai atitinka reikalavimus, ir tas vienas skirtumas tarnauja jiems visiems

Format 2 saugo aiškų pakaitinių glifų identifikatorių masyvą. Padengimo indeksas iš padengimo lentelės yra indeksas į tą masyvą, todėl glifas, esantis padengimo indekse 0, tampa pirmuoju masyvo įrašu, padengimo indekse 1 – antruoju ir t. t. Format 2 naudojamas tada, kai alternatyvos nėra vienodu atstumu, o tai yra įprastas rankiniu būdu sukurtų stiliaus rinkinių atvejis. Užklausa iš kviečiančiojo pusės yra tokia pati abiem atvejais. Paimkite įvesties glifą, paleiskite jį per padengimą ir, jei jis yra padengtas, pritaikykite skirtumą arba perskaitykite masyvo lizdą

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, kurią verta pastebėti, yra tiesioginis perdavimas. GetSingleSubstituteGlyph grąžina nepasikeitusį įvesties glifo identifikatorių kiekvieno praleidimo atveju: nėra šrifto, nėra GSUB lentelės, nėra atitinkamos funkcijos, nėra padengimo pataikymo. Tai reiškia, kad šį iškvietimą saugu atlikti besąlygiškai. Jūs prašote alternatyvos, o jei jos nėra, gaunate būtent tai, ką įdėjote, todėl kviečiančiam kodui niekada nereikia taikyti specialių atvejų šriftui, kuriame trūksta šios funkcijos

Ką reiškia stiliaus funkcijų žymos

Funkcijos žyma yra visas žodynas to, kurios alternatyvos prašote, o žymos, susijusios su stiliaus darbu, yra trumpas sąrašas. Pagrindinė pora yra salt (stiliaus alternatyvos) – bendra prieiga prie glifo alternatyvių formų, ir ss01 iki ss20 – dvidešimt numeruotų stiliaus rinkinių, kuriuos gali apibrėžti šriftas, kiekvienas iš jų yra įvardytas pakeitimų paketas, kurį dizaineris sugrupuoja kartu. Pavyzdžiui, šriftas gali patalpinti vieno aukšto a ir tiesios kojelės R po ss03, todėl įjungus tą vieną rinkinį pakeičiamas abu

Aplink juos yra dar kelios vieno pakeitimo žymos. aalt yra prieiga prie visų alternatyvų – visų glifo alternatyvų sąjunga, paprastai pateikiama kaip glifų paletės funkcija. titl pasirenka antraštines didžiąsias raides, skirtas dideliems dydžiams. subs ir sups pakeičia tikrus apatinius ir viršutinius indeksus, o ne sumažintus numatytuosius nustatymus. ordn sukuria eilės skaičių formas – pakeltas raides žodžiuose 1st ir 2nd. frac kuria trupmenas, nors pilnos įstrižos trupmenos taip pat remiasi ligatūromis ir kontekstine logika, kuri peržengia paprasto vieno pakeitimo ribas, o HotPDF jas sprendžia per atskiras lookup klases. Naudokite tą žymą, kuri tiksliai įvardija norimą pakeitimą, o GSUB analizatorius atliks lentelės perėjimą ir grąžins pakaitinį 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

Before any substitution can run, a character has to become a glyph, and that is the cmap table's job. The substitution query starts from a glyph id, so the path is always character to glyph through cmap, then glyph to alternate through GSUB. The interesting part of cmap is its reach. A format 4 subtable covers the Basic Multilingual Plane, the first 65536 code points, and that is enough for most Latin text. It is not enough for code points from U+10000 upward, the supplementary planes, which is where mathematical alphanumerics, many symbols, and several living scripts now live.

Format 12 is the subtable that covers the full U+0000 to U+10FFFF range. It is a sorted list of groups, each group a start code point, an end code point, and a start glyph id, so a contiguous run of code points maps to a contiguous run of glyphs. HotPDF resolves code points with a hybrid strategy that matches how the data is shaped. Code points in the BMP are served from a direct array indexed by the code point, a single lookup with no search. Code points in the supplementary planes are served from a sparse table sorted by code point and searched with a binary search. The result is that GetUnicodeGlyphForCodepoint takes a full Cardinal and answers correctly across the whole range, returning glyph id 0, the .notdef glyph, for any code point the font 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 sustoja

The single-substitution APIs answer one shape of question, and it is worth being clear about what they do not answer. LookupType 1 is one of eight substitution types. The query does not handle LookupType 2 multiple substitution, where one glyph becomes several, nor LookupType 4 ligature substitution, where several glyphs become one. It does not handle the contextual and chaining-contextual types, LookupTypes 5 and 6, that fire only when a glyph appears in a particular neighbourhood, nor the extension and reverse-chaining types. A diagonal fraction, a Devanagari conjunct, or an Arabic initial-medial-final cascade is a sequence problem, and a per-glyph single-substitution lookup cannot express it.

It also does not perform automatic shaping. Nothing here inspects a run of text, decides which features to turn on, and applies them in the order the script requires. The caller chooses the feature tag and applies it glyph by glyph. That is exactly the right tool for stylistic sets and alternates, which are opt-in and local, and exactly the wrong tool for a script that needs reordering. Keeping the boundary sharp is what lets the substitution path stay small and predictable.

For the cases that do need sequence-level work, the complex-script story is taken up in our article on complex-script text shaping in Delphi. If your substitutions are part of a larger reporting job that also places images and other fonts on the page, the guide to report output with fonts and images covers how those pieces fit together. All of these run on the same engine, the HotPDF Component for Delphi and C++Builder, which carries the GSUB substitution queries alongside the font embedding, subsetting, and text APIs covered elsewhere on this blog.