OpenType GSUB Substitution Engine

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

 

Arabic Shaping  CFF / OpenType Subsetting

Το OpenType GSUB (Glyph SUBstitution) engine μέσα στο HotPDF επιτρέπει στους callers να ερωτούν, να οδηγούν και να ενσωματώνουν κάθε είδους glyph substitution που δηλώνει μια OpenType font - ligatures, stylistic alternates, contextual variants, Arabic / Indic shaping forms, CJK alternate forms κ.ο.κ. Κάθε OpenType GSUB LookupType 1 έως 8 υλοποιείται και εκτίθεται ως capability-only query surface· ο caller οδηγεί την εκπομπή κειμένου και αποφασίζει ποιο substitute glyph θα γραφτεί στο page content stream

 

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

 

Περιγραφή

Το engine ενεργοποιείται αφού το RegisterUnicodeTTF αναλύσει μια γραμματοσειρά και αποθηκεύσει σε cache τα GSUB / GDEF / cmap tables. Κάθε substitution query διατρέχει την αλυσίδα ScriptList / LangSysList / FeatureList / LookupList της γραμματοσειράς και δρομολογεί στον κατάλληλο LookupType handler. Οι 12 μέθοδοι παραπάνω είναι η πλήρης δημόσια επιφάνεια· οτιδήποτε άλλο (cmap walk, ScriptList parsing, Coverage table lookup, ClassDef resolution, LookupFlag honor, Extension wrapper unwrapping, SequenceLookupRecord nested dispatch) ζει πίσω από αυτή την επιφάνεια

 

Αμυντικό contract παντού: γραμματοσειρές χωρίς GSUB table, feature tags που δεν είναι 4 bytes, features που δεν διαφημίζει το επιλεγμένο script / language, GIDs που δεν καλύπτει subtable και input glyphs που αγνοούνται από LookupFlag επιστρέφουν όλα ασφαλές no-op (False / OutGID = InputGID / empty OutGIDs / ConsumedCount = 1), ώστε οι callers να μη βλέπουν ποτέ εξαιρέσεις σε συνηθισμένες περιπτώσεις "δεν εφαρμόζεται substitution"

 

LookupType matrix

LookupType 1 (Single Substitution) - ένα glyph αντιστοιχίζεται σε ένα substitute. Κανονικά features: salt, ss01-ss20, smcp, onum, liga όταν είναι συνδεδεμένο ως LookupType 1, καθώς και init / medi / fina / isol Arabic positional forms σε γραμματοσειρές που τα οδηγούν μέσω GSUB. Χρησιμοποιήστε GetSingleSubstituteGlyph

LookupType 2 (Multiple Substitution) - ένα glyph διασπάται σε ακολουθία substitute glyphs. Κανονικός χρήστης: ccmp Glyph Composition / Decomposition (προ-συντεθειμένα τονισμένα λατινικά γράμματα διασπώνται σε βάση + combining marks για downstream mark positioning). Χρησιμοποιήστε GetMultipleSubstituteGlyphs

LookupType 3 (Alternate Substitution) - ένα glyph αντιστοιχίζεται σε ένα από N alternates. Κανονικά features: aalt (Access All Alternates), salt όταν είναι συνδεδεμένο ως Type 3, titl (Titling Alternates), ss01-ss20 stylistic sets όταν ο σχεδιαστής της γραμματοσειράς προσφέρει περισσότερα από ένα alternate ανά slot. Χρησιμοποιήστε 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) - ταιριάζει μια ακολουθία input glyphs και δρομολογεί nested lookups σε συγκεκριμένες θέσεις μέσα στο match. Υλοποιούνται και οι τρεις Format variants (1 literal sequence, 2 ClassDef sequence, 3 Coverage sequence)· ο SequenceLookupRecord dispatcher επανεισέρχεται στο LookupList και χειρίζεται nested lookups Single / Multiple / Alternate (first) / Ligature με live MatchPositions tracking. Κανονικά features: rclt (Required Contextual Alternates - Arabic init/medi/fina/isol όταν οδηγούνται από GSUB), clig, calt, Indic shaping pres / blws / psts / half / pstf / cjct. Χρησιμοποιήστε ApplyContextualSubst (ένα entry point καλύπτει LookupType 5 και 6)

LookupType 7 (Extension Substitution) - καθαρό επίπεδο έμμεσης αναφοράς που ορίζει η OpenType spec για γραμματοσειρές των οποίων το substitution subtable βρίσκεται πέρα από την 16-bit εμβέλεια του LookupList. Κάθε δημόσια API ακολουθεί διαφανώς την 32-bit Offset32 έμμεση αναφορά προς το πραγματικό LookupType 1 / 2 / 3 / 4 / 5 / 6 / 8 subtable. Ξεκλειδώνει βαριές CJK / Indic γραμματοσειρές (Noto Sans CJK, Noto Sans Devanagari) των οποίων το GSUB ξεπερνά τα 64 KB. Δεν υπάρχει ξεχωριστή API - το unwrap είναι αυτόματο

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

Από προεπιλογή, το engine προτιμά το DFLT script (ή το πρώτο script που δηλώνει η γραμματοσειρά) και το default LangSys. Καλέστε SetGSUBScript('latn' / 'arab' / 'cyrl' / 'hani' / 'kana' / 'deva' / 'beng' / 'taml' / etc.) και SetGSUBLanguage('ENG ' / 'TUR ' / 'AZE ' / 'JAN ' / 'KOR ' / 'ARA ' / etc., με padding trailing-space σε 4 bytes) για να καρφιτσώσετε queries σε συγκεκριμένο script / language pair. Κενή string επαναφέρει το default-path baseline. Οι επιλογές επιμένουν μεταξύ queries και καθαρίζονται σε RegisterUnicodeTTF('', nil)

 

Strict-vs-fallback semantics: άγνωστο ScriptTag κάνει τα επόμενα queries να επιστρέφουν empty no-op results (ώστε οι callers να μπορούν να εντοπίσουν ότι το επιλεγμένο script δεν είναι διαθέσιμο)· άγνωστο LangTag επιστρέφει στο default LangSys του script σύμφωνα με την OpenType convention. GetGSUBScripts / GetGSUBLanguages / GetGSUBFeatures απαριθμούν τι διαφημίζει πραγματικά η φορτωμένη γραμματοσειρά

 

LookupFlag honor and GDEF

Κάθε query διαβάζει το LookupFlag κάθε Lookup table (και το προαιρετικό trailing markFilteringSet uint16 όταν είναι ορισμένο useMarkFilteringSet) και παραλείπει input glyphs που επισημαίνονται για ignore. Τα spec-defined bits τηρούνται: ignoreBaseGlyphs (0x0002, skip GDEF class 1), ignoreLigatures (0x0004, skip class 2), ignoreMarks (0x0008, skip class 3), useMarkFilteringSet (0x0010) και το high byte markAttachmentType. ClassDef Format 1 + 2 αναλύονται και τα δύο· GDEF v1.0 / v1.1 / v1.2 headers γίνονται όλα αποδεκτά. Γραμματοσειρές χωρίς GDEF table επιστρέφουν σε "κανένα glyph δεν αγνοείται", ώστε η έξοδος να μένει byte-identical για callers που χρησιμοποιούν GDEF-less fonts

 

TTF subsetter closure (MarkUnicodeGlyphUsed)

Ο TTF subsetter της v2.84.0 του HotPDF αντλεί το used-glyph set του από FUnicodeUsedCps μέσω του cmap. Τα GSUB substitute glyphs (stylistic alternates, ligatures, contextual variants - όλα όσα επιστρέφουν οι 7 query APIs παραπάνω) συνήθως δεν έχουν codepoint που τα φτάνει μέσω του cmap, οπότε παλαιότερα ήταν αόρατα στον subsetter και ο consumer reader απέδιδε .notdef στη θέση τους

 

Αφού εκπέμψετε οποιοδήποτε GID επιστρέφεται από GetSingleSubstituteGlyph / GetMultipleSubstituteGlyphs / GetAlternateGlyph / ApplyLigatureSubstitution / ApplyContextualSubst / ApplyReverseChainedContextualSubst σε PDF text stream, καλέστε MarkUnicodeGlyphUsed(GID) μία φορά ανά εκπεμπόμενο GID για να το τραβήξετε στο embedded subset. Ο helper είναι idempotent, αμυντικός (out-of-range GIDs απορρίπτονται σιωπηρά) και ενσωματώνεται με το composite-glyph closure pass της v2.84.0: οι callers χρειάζεται να σημειώσουν μόνο το top-level substitute 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];  // post-cmap GIDs

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

begin

  // emit LigGID + advance by ConsumedCount

  PDF.MarkUnicodeGlyphUsed(LigGID);

end;

 

Scope and limitations

Το engine είναι capability-only query surface: απαντά "τι θα έκανε εδώ το GSUB", αλλά δεν εκτελεί αυτόματη shaping pipeline (Harfbuzz-class layout, cluster-aware reordering για Indic, BiDi resolution, GPOS positioning, mark attachment). Οι callers είναι υπεύθυνοι να οδηγούν το scan loop, να επιλέγουν ποιο substitute / alternate θα εκπέμψουν, να καλούν MarkUnicodeGlyphUsed για κάθε emitted substitute 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 closed (Phase 6). v2.119.49 Script / LangSys selection API (Phase 7). v2.119.50 TTF subsetter closure via 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