OpenType GSUB Substitution Engine

Glyph substitution capability surface (v2.119.43 - v2.119.50)

 

Arabic Shaping  CFF / OpenType Subsetting

Двигателят OpenType GSUB (Glyph SUBstitution) в HotPDF позволява на ползвателите да питат, управляват и вграждат всеки вид замяна на глифове, който един OpenType шрифт декларира - лигатури, стилистични алтернативи, контекстни варианти, арабски / Indic форми за шейпинг, CJK алтернативни форми и т.н. Всякакъв OpenType GSUB LookupType от 1 до 8 е имплементиран и изложен като повърхност за запитване само за възможности; ползвателят управлява извеждането на текста и решава кой заместващ глиф да запише в потока за PDF съдържание

 

Public API

type

  TGSUBStringArray = array of AnsiString;

 

// LookupType 1 - Single Substitution (one glyph -> one glyph)

function GetSingleSubstituteGlyph(InputGID: Word; const FeatureTag: AnsiString): Word;

 

// LookupType 2 - Multiple Substitution (one glyph -> sequence of glyphs)

function GetMultipleSubstituteGlyphs(InputGID: Word; const FeatureTag: AnsiString;

  var OutGIDs: array of Word): Boolean;

 

// LookupType 3 - Alternate Substitution (one glyph -> one of N alternates)

function GetAlternateGlyphCount(InputGID: Word; const FeatureTag: AnsiString): Integer;

function GetAlternateGlyph(InputGID: Word; const FeatureTag: AnsiString;

  AlternateIndex: Integer): Word;

 

// LookupType 4 - Ligature Substitution (N glyphs -> one ligature)

function ApplyLigatureSubstitution(const InputGIDs: array of Word;

  StartIndex: Integer; const FeatureTag: AnsiString;

  out OutGID: Word; out ConsumedCount: Integer): Boolean;

 

// LookupType 5 + 6 - Contextual / Chained Contextual Substitution

function ApplyContextualSubst(const InputGIDs: array of Word;

  StartIndex: Integer; const FeatureTag: AnsiString;

  var OutGIDs: array of Word;

  out ConsumedLen: Integer): Boolean;

 

// LookupType 8 - Reverse Chained Contextual Single Substitution

function ApplyReverseChainedContextualSubst(const InputGIDs: array of Word;

  StartIndex: Integer; const FeatureTag: AnsiString;

  out OutGID: Word): Boolean;

 

// Script / LangSys selection (Phase 7)

procedure SetGSUBScript(const ScriptTag: AnsiString);

procedure SetGSUBLanguage(const LangTag: AnsiString);

function GetGSUBScripts: TGSUBStringArray;

function GetGSUBLanguages(const ScriptTag: AnsiString): TGSUBStringArray;

function GetGSUBFeatures(const ScriptTag, LangTag: AnsiString): TGSUBStringArray;

 

// TTF subsetter closure (Phase 9)

procedure MarkUnicodeGlyphUsed(GID: Word);

 

Описание

Двигателят се активира, след като RegisterUnicodeTTF е парсирал шрифт и е кеширал неговите GSUB / GDEF / cmap таблици. Всяко запитване за замяна обхожда веригата ScriptList / LangSysList / FeatureList / LookupList на шрифта и се насочва към съответния handler за LookupType. Дванадесетте метода по-горе са пълната публична повърхност; всичко останало (обхождане на cmap, парсване на ScriptList, lookup на Coverage table, резолюция на ClassDef, спазване на LookupFlag, разопаковане на Extension wrapper, вложено dispatch-ване на SequenceLookupRecord) стои зад тази повърхност

 

Защитният договор е еднакъв навсякъде: шрифтове без GSUB таблица, feature tags, които не са 4-байтови, функции, които избраната писменост / език не обявява, GID-ове, които никоя подтаблица не покрива, и входни glyph-ове, игнорирани от LookupFlag, всички връщат безопасен no-op (False / OutGID = InputGID / празни OutGIDs / ConsumedCount = 1), така че извикващите никога да не виждат изключения за обичайни случаи на "no substitution applies"

 

LookupType matrix

LookupType 1 (Single Substitution) - един glyph се съпоставя с един заместител. Канонични функции: salt, ss01-ss20, smcp, onum, liga, когато LookupType 1 е включен, плюс init / medi / fina / isol Arabic positional forms в шрифтове, които ги управляват през GSUB. Използвайте GetSingleSubstituteGlyph

LookupType 2 (Multiple Substitution) - един glyph се разделя на поредица от substitute glyphs. Canonical потребител: ccmp Glyph Composition / Decomposition (предварително съставени акцентувани латински букви се разделят на базов + combining mark за downstream позициониране на марки). Използвайте GetMultipleSubstituteGlyphs

LookupType 3 (Alternate Substitution) - един glyph се съпоставя с един от N алтернативи. Канонични функции: aalt (Access All Alternates), salt, когато е включен като Type 3, titl (Titling Alternates), ss01-ss20 stylistic sets, когато дизайнерът на шрифта предлага повече от една алтернатива за слот. Използвайте GetAlternateGlyphCount + GetAlternateGlyph

LookupType 4 (Ligature Substitution) - N input glyphs fold into one ligature. Canonical features: liga (Standard Ligatures: fi / fl / ffi / ffl), clig (Contextual Ligatures), dlig (Discretionary Ligatures), hlig (Historical Ligatures), rlig (Required Ligatures - Arabic LAM-ALEF and similar), Indic script ligatures (akhn, pres, blws, psts). Use ApplyLigatureSubstitution.

LookupType 5 (Contextual Substitution) + LookupType 6 (Chained Contextual Substitution) - съвпада с входна glyph последователност и изпраща вложени lookups на конкретни позиции вътре в съвпадението. И трите Format варианта (1 literal sequence, 2 ClassDef sequence, 3 Coverage sequence) са реализирани; dispatcher-ът SequenceLookupRecord влиза повторно в LookupList и обработва вложени lookups от тип Single / Multiple / Alternate (first) / Ligature с активно проследяване на MatchPositions. Канонични функции: rclt (Required Contextual Alternates - Arabic init/medi/fina/isol, когато са GSUB-driven), clig, calt, Indic shaping pres / blws / psts / half / pstf / cjct. Използвайте ApplyContextualSubst (една входна точка покрива LookupType 5 и 6)

LookupType 7 (Extension Substitution) - чист слой на индирекция, който OpenType спецификацията дефинира за шрифтове, чиято substitution subtable е извън 16-битовия обсег на LookupList. Всеки публичен API прозрачно следва 32-битовата Offset32 индирекция към реалната LookupType 1 / 2 / 3 / 4 / 5 / 6 / 8 подтаблица. Отключва тежки CJK / Indic шрифтове (Noto Sans CJK, Noto Sans Devanagari), чийто GSUB надхвърля 64 KB. Няма отделен API - разопаковането е автоматично

LookupType 8 (Reverse Chained Contextual Single Substitution) - context-aware 1:1 substitution whose distinguishing feature is that callers must apply it in REVERSE scan order over a multi-glyph run (end -> start) because each substitute may depend on FUTURE lookahead context that must not have been substituted yet. Canonical use: Arabic / Syriac / N'Ko / Indic contextual alternates whose final form depends on the following glyph. Use ApplyReverseChainedContextualSubst; the caller drives the reverse scan loop.

 

Script / LangSys selection

По подразбиране двигателят предпочита скрипта DFLT (или първия скрипт, който шрифтът декларира) и стандартния LangSys. Извикайте SetGSUBScript('latn' / 'arab' / 'cyrl' / 'hani' / 'kana' / 'deva' / 'beng' / 'taml' / etc.) и SetGSUBLanguage('ENG ' / 'TUR ' / 'AZE ' / 'JAN ' / 'KOR ' / 'ARA ' / etc., trailing-space padded to 4 bytes), за да фиксирате запитванията към конкретна двойка скрипт / език. Празен низ връща базовото поведение по подразбиране. Изборите се запазват между запитванията и се изчистват при RegisterUnicodeTTF('', nil)

 

Strict-vs-fallback семантика: непознат ScriptTag кара последващите заявки да връщат празни no-op резултати (така извикващите могат да засекат, че избраната писменост не е налична); непознат LangTag се връща към default LangSys на писмеността според OpenType конвенцията. GetGSUBScripts / GetGSUBLanguages / GetGSUBFeatures изброяват това, което зареденият шрифт наистина обявява

 

LookupFlag honor and GDEF

Всяка заявка чете LookupFlag на всяка Lookup таблица (и незадължителния trailing markFilteringSet uint16, когато е зададен useMarkFilteringSet) и пропуска входните glyph-ове, отбелязани за игнориране. Специфицираните от стандарта битове се спазват: ignoreBaseGlyphs (0x0002, пропуска GDEF class 1), ignoreLigatures (0x0004, пропуска class 2), ignoreMarks (0x0008, пропуска class 3), useMarkFilteringSet (0x0010) и горния байт markAttachmentType. И двата ClassDef формата 1 + 2 се парсват; GDEF v1.0 / v1.1 / v1.2 headers се приемат. Шрифтове без GDEF таблица падат обратно на "no glyph is ignored", така че изходът остава byte-identical за извикващи, които използват GDEF-less шрифтове

 

TTF subsetter closure (MarkUnicodeGlyphUsed)

TTF subsetter-ът на HotPDF v2.84.0 извежда своя used-glyph набор от FUnicodeUsedCps през cmap. GSUB заместителните glyph-ове (stylistic alternates, ligatures, contextual variants - всичко, което горните 7 query API-а връщат) обикновено нямат codepoint, който да достига до тях през cmap, така че преди бяха невидими за subsetter-а и потребителският reader ги рендираше като .notdef

 

След като изведете в PDF текстов поток всеки GID, върнат от GetSingleSubstituteGlyph / GetMultipleSubstituteGlyphs / GetAlternateGlyph / ApplyLigatureSubstitution / ApplyContextualSubst / ApplyReverseChainedContextualSubst, извикайте MarkUnicodeGlyphUsed(GID) по веднъж за всеки изведен GID, за да го включите във вградения subset. Помощният метод е idempotent, защитен (GID-ове извън обхват се пропускат безшумно) и се интегрира с v2.84.0 composite-glyph closure pass: извикващите трябва да маркират само top-level заместителния GID - composite components се извличат автоматично

 

Typical workflow (Latin small caps)

 

PDF.RegisterUnicodeTTF('myFont', 'C:\\Windows\\Fonts\\arial.ttf');

PDF.SetGSUBScript('latn');

PDF.SetGSUBLanguage('');  // default LangSys

SmallCapGID := PDF.GetSingleSubstituteGlyph(InputGID, 'smcp');

if SmallCapGID <> InputGID then

begin

  // emit SmallCapGID into the page content stream...

  PDF.MarkUnicodeGlyphUsed(SmallCapGID);  // pull into subset

end;

 

Typical workflow (Arabic LAM-ALEF ligature)

 

PDF.SetGSUBScript('arab');

Пример: Run := [LamGID, FathaGID, AlefGID];  // GID след cmap

if PDF.ApplyLigatureSubstitution(Run, 0, 'rlig', LigGID, ConsumedCount) then

begin

  // emit LigGID + advance by ConsumedCount

  PDF.MarkUnicodeGlyphUsed(LigGID);

end;

 

Обхват и ограничения

Двигателят е повърхност за запитване само за възможности: той отговаря "какво би направил GSUB тук", но не изпълнява автоматичен конвейер за шейпинг (layout от клас Harfbuzz, пренареждане с оглед на клъстери за Indic, разрешаване на BiDi, GPOS позициониране, прикрепване на марки). Ползвателите носят отговорност да управляват цикъла на сканиране, да избират кой заместител / алтернатива да изведат, да извикват MarkUnicodeGlyphUsed за всеки изведен заместителен GID и да прилагат всеки GPOS / mark positioning, който шрифтът изисква

 

Producer-side Arabic / Persian / Urdu shaping (LAM-ALEF mandatory ligature + Arabic Presentation Forms-A) is implemented as a separate built-in pipeline that runs automatically during text emission - see Arabic / Persian / Urdu Shaping.

 

Version trace

v2.119.43 Single Substitution + Phase 1. v2.119.44 Multiple + Alternate (Phase 2). v2.119.45 Ligature (Phase 3). v2.119.46 Extension + GDEF + LookupFlag honor (Phase 4). v2.119.47 Contextual + Chained Contextual + SequenceLookupRecord dispatcher (Phase 5). v2.119.48 Reverse Chained Contextual - LookupType 1-8 matrix е затворен (Phase 6). v2.119.49 Script / LangSys selection API (Phase 7). v2.119.50 TTF subsetter closure чрез MarkUnicodeGlyphUsed (Phase 9; Phase 8 беше producer-side shaping integration spike и е разделен в 8a-8f за бъдещи ревизии)

 

See also: Arabic / Persian / Urdu Shaping, CFF / OpenType Font Subsetting Functions, THotPDF.EnableFontSubsetting