Motorul de substituție OpenType GSUB

Suprafață de capabilități pentru substituția glifelor (v2.119.43 - v2.119.50)

 

Arabic Shaping  CFF / OpenType Subsetting

Motorul OpenType GSUB (Glyph SUBstitution) din HotPDF le permite apelanților să interogheze, să conducă și să încorporeze orice tip de substituție de glif declarată de un font OpenType - ligaturi, alternative stilistice, variante contextuale, forme de formare arabă / Indic, forme alternative CJK și așa mai departe. Toate LookupType-urile OpenType GSUB de la 1 la 8 sunt implementate și expuse ca o suprafață de interogare orientată doar pe capabilități; apelantul conduce emiterea textului și decide ce glif substitut să scrie în fluxul de conținut al paginii

 

API public

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);

 

Descriere

Motorul se activează după ce RegisterUnicodeTTF a parsat un font și i-a pus în cache tabelele GSUB / GDEF / cmap. Fiecare interogare de substituție parcurge lanțul ScriptList / LangSysList / FeatureList / LookupList al fontului și trimite cererea către handlerul LookupType adecvat. Cele 12 metode de mai sus reprezintă întreaga suprafață publică; tot restul (parcurgerea cmap, parsarea ScriptList, căutarea tabelelor Coverage, rezolvarea ClassDef, respectarea LookupFlag, dezambalarea wrapper-ului Extension, dispatch-ul imbricat SequenceLookupRecord) stă în spatele acestei suprafețe

 

Contract defensiv peste tot: fonturile fără tabel GSUB, feature tag-urile care nu au 4 octeți, funcțiile pe care scriptul / limba selectată nu le declară, GID-urile acoperite de niciun subtable și glifele de intrare ignorate de LookupFlag se întorc toate ca no-op sigur (False / OutGID = InputGID / OutGIDs goale / ConsumedCount = 1), astfel încât apelanții să nu vadă niciodată excepții pentru cazurile de rutină de tipul "nu se aplică nicio substituție"

 

Matricea LookupType

LookupType 1 (Single Substitution) - un glif se mapează la un singur substitut. Funcții canonice: salt, ss01-ss20, smcp, onum, liga când LookupType 1 este conectat, plus formele poziționale arabe init / medi / fina / isol în fonturile care le conduc prin GSUB. Folosește GetSingleSubstituteGlyph

LookupType 2 (Multiple Substitution) - un glif se descompune într-o secvență de glife substitut. Utilizare canonica: ccmp Glyph Composition / Decomposition (literele latine precompuse cu diacritice se separă în bază + semne combinante pentru poziționarea ulterioară a marcajelor). Folosește GetMultipleSubstituteGlyphs

LookupType 3 (Alternate Substitution) - un glif se mapează la una dintre N alternative. Funcții canonice: aalt (Access All Alternates), salt când este conectat ca Type 3, titl (Titling Alternates), seturile stilistice ss01-ss20 când designerul fontului oferă mai mult de o alternativă pe slot. Folosește GetAlternateGlyphCount + GetAlternateGlyph

LookupType 4 (Ligature Substitution) - N glife de intrare se comasează într-o ligatură. Funcții canonice: liga (Standard Ligatures: fi / fl / ffi / ffl), clig (Contextual Ligatures), dlig (Discretionary Ligatures), hlig (Historical Ligatures), rlig (Required Ligatures - Arabic LAM-ALEF și similare), ligaturi pentru scripturi Indic (akhn, pres, blws, psts). Folosește ApplyLigatureSubstitution

LookupType 5 (Contextual Substitution) + LookupType 6 (Chained Contextual Substitution) - potrivește o secvență de glife de intrare și redirecționează interogări imbricate către poziții specifice din interiorul potrivirii. Sunt implementate toate cele trei variante de Format (1 secvență literală, 2 secvență ClassDef, 3 secvență Coverage); dispatcher-ul SequenceLookupRecord reia LookupList și tratează interogări imbricate Single / Multiple / Alternate (first) / Ligature cu urmărirea în timp real a MatchPositions. Funcții canonice: rclt (Required Contextual Alternates - forme arabice init/medi/fina/isol când sunt conduse prin GSUB), clig, calt, formare Indic pres / blws / psts / half / pstf / cjct. Folosiți ApplyContextualSubst (un singur punct de intrare acoperă atât LookupType 5, cât și 6)

LookupType 7 (Extension Substitution) - un strat pur de indirection pe care specificația OpenType îl definește pentru fonturile a căror sub-tabelă de substituție se află dincolo de raza de 16 biți a LookupList. Fiecare API public urmărește transparent indirectionarea Offset32 pe 32 de biți către sub-tabela reală LookupType 1 / 2 / 3 / 4 / 5 / 6 / 8. Deblochează fonturi grele CJK / Indic (Noto Sans CJK, Noto Sans Devanagari) ale căror GSUB depășesc 64 KB. Fără API separat - despachetarea este automată

LookupType 8 (Reverse Chained Contextual Single Substitution) - substituție context-aware 1:1 al cărei aspect distinctiv este că apelanții trebuie să o aplice în ordine de scanare INVERSĂ peste o secvență cu mai multe glife (sfârșit -> început), deoarece fiecare substitut poate depinde de contextul de anticipare din VIITOR care nu trebuie să fi fost substituit încă. Utilizare canonică: alternative contextuale arabe / siriace / N'Ko / Indic ale căror forme finale depind de gliful următor. Folosiți ApplyReverseChainedContextualSubst; apelantul conduce bucla de scanare inversă

 

Selectarea Script / LangSys

În mod implicit, motorul preferă scriptul DFLT (sau primul script declarat de font) și LangSys-ul implicit. Apelați SetGSUBScript('latn' / 'arab' / 'cyrl' / 'hani' / 'kana' / 'deva' / 'beng' / 'taml' / etc.) și SetGSUBLanguage('ENG ' / 'TUR ' / 'AZE ' / 'JAN ' / 'KOR ' / 'ARA ' / etc., completat cu spații la sfârșit până la 4 octeți) pentru a fixa interogările la o pereche script / limbă specifică. Un șir gol restaurează baza căii implicite. Selecțiile persistă între interogări și sunt șterse la RegisterUnicodeTTF('', nil)

 

Semantica strict-vs-fallback: un ScriptTag necunoscut face ca interogările ulterioare să returneze rezultate goale de tip no-op, astfel încât apelanții să poată detecta că scriptul ales nu este disponibil; un LangTag necunoscut revine la LangSys-ul implicit al scriptului, conform convenției OpenType. GetGSUBScripts / GetGSUBLanguages / GetGSUBFeatures enumeră ceea ce fontul încărcat anunță efectiv

 

Respectarea LookupFlag și GDEF

Fiecare interogare citește LookupFlag-ul fiecărei tabele Lookup (și markFilteringSet-ul opțional de 16 biți de la final când useMarkFilteringSet este setat) și sare peste glifele de intrare marcate pentru ignorare. Sunt respectate biții definiți de specificație: ignoreBaseGlyphs (0x0002, sari peste clasa GDEF 1), ignoreLigatures (0x0004, sari peste clasa 2), ignoreMarks (0x0008, sari peste clasa 3), useMarkFilteringSet (0x0010) și byte-ul înalt markAttachmentType. Sunt parsate atât ClassDef Format 1 cât și 2; sunt acceptate toate antetele GDEF v1.0 / v1.1 / v1.2. Fonturile fără tabel GDEF revin la „nicio glifă nu este ignorată”, astfel încât ieșirea rămâne identică la nivel de octet pentru apelanții care folosesc fonturi fără GDEF

 

Închiderea subsetter-ului TTF (MarkUnicodeGlyphUsed)

Subsetter-ul TTF din HotPDF v2.84.0 își derivă setul de glife folosite din FUnicodeUsedCps prin cmap. Glifele substituite GSUB (alternative stilistice, ligaturi, variante contextuale - tot ce returnează cele 7 API-uri de interogare de mai sus) nu au de obicei niciun codepoint care să ajungă la ele prin cmap, astfel că înainte erau invizibile pentru subsetter, iar cititorul consumator afișa .notdef în locul lor

 

După emiterea oricărui GID returnat de GetSingleSubstituteGlyph / GetMultipleSubstituteGlyphs / GetAlternateGlyph / ApplyLigatureSubstitution / ApplyContextualSubst / ApplyReverseChainedContextualSubst într-un stream de text PDF, apelați MarkUnicodeGlyphUsed(GID) o dată pentru fiecare GID emis, pentru a-l include în subsetul încorporat. Helperul este idempotent, defensiv (GID-urile din afara intervalului sunt eliminate silențios) și se integrează cu pasul de închidere a glifelor compozite din v2.84.0: apelanții trebuie să marcheze doar GID-ul substituit de nivel superior - componentele compozite sunt aduse automat

 

Flux tipic de lucru (majuscule mici latine)

 

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;

 

Flux tipic de lucru (ligatura arabă LAM-ALEF)

 

PDF.SetGSUBScript('arab');

Run := [LamGID, FathaGID, AlefGID];  // post-cmap GIDs

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

begin

  // emit LigGID + advance by ConsumedCount

  PDF.MarkUnicodeGlyphUsed(LigGID);

end;

 

Domeniu și limitări

Motorul este o suprafață de interogare doar pentru capabilități: răspunde la întrebarea „ce ar face GSUB aici”, dar nu rulează un pipeline automat de shaping (layout de clasă Harfbuzz, reordonare conștientă de clustere pentru Indic, rezolvare BiDi, poziționare GPOS, atașare de mark-uri). Apelanții sunt responsabili să conducă bucla de scanare, să aleagă ce substitut / alternativă să emită, să apeleze MarkUnicodeGlyphUsed pentru fiecare GID substituit emis și să aplice orice poziționare GPOS / mark cerută de font

 

Shaping-ul arab / persan / urdu pe partea producătorului (ligatura obligatorie LAM-ALEF + Arabic Presentation Forms-A) este implementat ca un pipeline intern separat care rulează automat în timpul emiterii textului - vezi Arabic / Persian / Urdu Shaping

 

Istoric versiuni

v2.119.43 Single Substitution + Faza 1. v2.119.44 Multiple + Alternate (Faza 2). v2.119.45 Ligature (Faza 3). v2.119.46 Extension + GDEF + respectarea LookupFlag (Faza 4). v2.119.47 Contextual + Chained Contextual + dispecer SequenceLookupRecord (Faza 5). v2.119.48 Reverse Chained Contextual - matricea LookupType 1-8 închisă (Faza 6). v2.119.49 API de selecție Script / LangSys (Faza 7). v2.119.50 închiderea subsetterului TTF prin MarkUnicodeGlyphUsed (Faza 9; Faza 8 a fost un spike de integrare a shapingului pe partea producătorului și a fost împărțită în 8a-8f pentru revizii viitoare)

 

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