OpenType GSUB Substitution Engine

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

 

Arabic Shaping  CFF / OpenType Subsetting

Le moteur OpenType GSUB (Glyph SUBstitution) intégré à HotPDF permet aux appelants d’interroger, de piloter et d’embarquer tout type de substitution de glyphe déclaré par une police OpenType - ligatures, variantes stylistiques, variantes contextuelles, formes de façonnage Arabic / Indic, formes alternatives CJK, et ainsi de suite. Tous les LookupType OpenType GSUB de 1 à 8 sont implémentés et exposés comme surface de requête de capacités uniquement ; l’appelant pilote l’émission du texte et décide quel glyphe substitut écrire dans le flux de contenu de la page

 

API publique

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

 

Description

Le moteur s’active après que RegisterUnicodeTTF a analysé une police et mis en cache ses tables GSUB / GDEF / cmap Chaque requête de substitution parcourt la chaîne ScriptList / LangSysList / FeatureList / LookupList de la police et se dirige vers le gestionnaire LookupType approprié Les 12 méthodes ci-dessus constituent toute la surface publique ; tout le reste (parcours cmap, analyse ScriptList, recherche dans la table Coverage, résolution ClassDef, prise en compte de LookupFlag, déballage de l’enveloppe Extension, dispatch imbriqué SequenceLookupRecord) se trouve derrière cette surface

 

Contrat défensif partout : les polices sans table GSUB, les balises de fonctionnalité non codées sur 4 octets, les fonctionnalités que le script / la langue sélectionnés ne déclarent pas, les GID qu’aucune sous-table ne couvre et les glyphes d’entrée ignorés par LookupFlag renvoient tous un comportement sans effet sûr (False / OutGID = InputGID / OutGIDs vides / ConsumedCount = 1) de sorte que les appelants ne voient jamais d’exception pour les cas ordinaires "aucune substitution ne s’applique"

 

Tableau des LookupType

LookupType 1 (Single Substitution) - un glyphe est remplacé par un seul glyphe de substitution. Fonctions canoniques : salt, ss01-ss20, smcp, onum, liga lorsque LookupType 1 est câblé, ainsi que les formes positionnelles arabes init / medi / fina / isol dans les polices qui les exposent via GSUB. Utilisez GetSingleSubstituteGlyph

LookupType 2 (Multiple Substitution) - un glyphe se découpe en une séquence de glyphes de substitution. Cas d’usage canonique : ccmp Glyph Composition / Decomposition (les lettres latines accentuées précomposées se séparent en base + signes combinants pour le positionnement ultérieur des marques). Utilisez GetMultipleSubstituteGlyphs

LookupType 3 (Alternate Substitution) - un glyphe est remplacé par l’une des N alternatives. Fonctions canoniques : aalt (Access All Alternates), salt lorsqu’il est câblé comme Type 3, titl (Titling Alternates), ss01-ss20 pour les ensembles stylistiques lorsque le créateur de la police propose plusieurs alternatives par emplacement. Utilisez GetAlternateGlyphCount + GetAlternateGlyph

LookupType 4 (Ligature Substitution) - N glyphes d’entrée se fondent en une seule ligature. Fonctions canoniques : liga (ligatures standard : fi / fl / ffi / ffl), clig (ligatures contextuelles), dlig (ligatures discrétionnaires), hlig (ligatures historiques), rlig (ligatures requises - LAM-ALEF arabe et équivalents), ligatures des écritures Indic (akhn, pres, blws, psts). Utilisez ApplyLigatureSubstitution

LookupType 5 (Contextual Substitution) + LookupType 6 (Chained Contextual Substitution) - fait correspondre une séquence de glyphes d’entrée et déclenche des lookups imbriqués à des positions spécifiques dans la correspondance. Les trois variantes de format (1 séquence littérale, 2 séquence ClassDef, 3 séquence Coverage) sont implémentées ; le répartiteur SequenceLookupRecord réentre dans LookupList et traite les lookups imbriqués Single / Multiple / Alternate (premier) / Ligature avec un suivi actif de MatchPositions. Fonctions canoniques : rclt (Required Contextual Alternates - init/medi/fina/isol arabes lorsque GSUB les pilote), clig, calt, façonnage Indic pres / blws / psts / half / pstf / cjct. Utilisez ApplyContextualSubst (un point d’entrée couvre les LookupType 5 et 6)

LookupType 7 (Extension Substitution) - couche d’indirection pure définie par la spec OpenType pour les polices dont la sous-table de substitution se trouve au-delà de la portée 16 bits de LookupList. Chaque API publique suit de façon transparente l’indirection Offset32 de 32 bits vers la vraie sous-table LookupType 1 / 2 / 3 / 4 / 5 / 6 / 8. Débloque les polices CJK / Indic lourdes (Noto Sans CJK, Noto Sans Devanagari) dont le GSUB dépasse 64 KB. Pas d’API séparée - le déballage est automatique

LookupType 8 (Reverse Chained Contextual Single Substitution) - substitution contextuelle 1:1 dont la particularité est que les appelants doivent l’appliquer en ordre d’analyse inversé sur une séquence multi-glyphe (fin -> début) car chaque substitution peut dépendre d’un contexte de regard en avant futur qui ne doit pas encore avoir été substitué. Cas d’usage canonique : alternatives contextuelles arabe / syriaque / N'Ko / Indic dont la forme finale dépend du glyphe suivant. Utilisez ApplyReverseChainedContextualSubst ; l’appelant pilote la boucle d’analyse inverse

 

Sélection du script / LangSys

Par défaut, le moteur privilégie le script DFLT (ou le premier script déclaré par la police) ainsi que le LangSys par défaut. Appelez SetGSUBScript('latn' / 'arab' / 'cyrl' / 'hani' / 'kana' / 'deva' / 'beng' / 'taml' / etc.) et SetGSUBLanguage('ENG ' / 'TUR ' / 'AZE ' / 'JAN ' / 'KOR ' / 'ARA ' / etc., trailing-space padded to 4 bytes) pour verrouiller les requêtes sur une paire script / langue précise. Une chaîne vide restaure la ligne de base du chemin par défaut. Les sélections persistent entre les requêtes et sont effacées sur RegisterUnicodeTTF('', nil)

 

Sémantique strict versus repli: un ScriptTag inconnu fait que les requêtes suivantes renvoient des résultats vides sans effet (les appelants peuvent ainsi détecter que le script choisi n’est pas disponible) ; un LangTag inconnu revient au LangSys par défaut du script selon la convention OpenType. GetGSUBScripts / GetGSUBLanguages / GetGSUBFeatures énumèrent ce que la police chargée annonce réellement

 

Prise en compte de LookupFlag et GDEF

Chaque requête lit le LookupFlag de chaque table Lookup (ainsi que le markFilteringSet uint16 optionnel en fin de table lorsque useMarkFilteringSet est défini) et saute les glyphes d’entrée marqués à ignorer Les bits définis par la spécification sont respectés : ignoreBaseGlyphs (0x0002, saute la classe GDEF 1), ignoreLigatures (0x0004, saute la classe 2), ignoreMarks (0x0008, saute la classe 3), useMarkFilteringSet (0x0010) et l’octet haut markAttachmentType Les formats ClassDef 1 et 2 sont tous deux analysés ; les en-têtes GDEF v1.0 / v1.1 / v1.2 sont tous acceptés Les polices sans table GDEF retombent sur "aucun glyphe n’est ignoré", de sorte que la sortie reste identique au niveau des octets pour les appelants qui utilisent des polices sans GDEF

 

Clôture du sous-ensemble TTF (MarkUnicodeGlyphUsed)

Le sous-ensemble TTF v2.84.0 de HotPDF déduit son ensemble de glyphes utilisés à partir de FUnicodeUsedCps via la cmap Les glyphes de substitution GSUB (alternatives stylistiques, ligatures, variantes contextuelles - tout ce que renvoient les 7 API de requête ci-dessus) n’ont généralement aucun point de code qui les atteigne via la cmap, ils étaient donc auparavant invisibles pour le sous-ensemble et le lecteur consommateur affichait .notdef à leur place

 

Après avoir émis dans un flux texte PDF tout GID renvoyé par GetSingleSubstituteGlyph / GetMultipleSubstituteGlyphs / GetAlternateGlyph / ApplyLigatureSubstitution / ApplyContextualSubst / ApplyReverseChainedContextualSubst, appelez MarkUnicodeGlyphUsed(GID) une fois par GID émis pour le faire entrer dans le sous-ensemble embarqué L’aide est idempotente, défensive (les GID hors plage sont ignorés silencieusement) et s’intègre au passage de clôture des glyphes composites v2.84.0 : les appelants n’ont besoin de marquer que le GID de substitution de premier niveau - les composants composites sont tirés automatiquement

 

Type de flux (petites capitales latines)

 

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;

 

Workflow type (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;

 

Portée et limites

Le moteur reste une surface de requête de capacités uniquement : il répond à la question "que ferait GSUB ici", mais n’exécute pas de pipeline de façonnage automatique (mise en page de type Harfbuzz, réordonnancement sensible aux clusters pour Indic, résolution BiDi, positionnement GPOS, attache des marques). Les appelants doivent piloter la boucle de balayage, choisir quelle substitution / variante émettre, appeler MarkUnicodeGlyphUsed pour chaque GID substitut émis et appliquer tout positionnement GPOS / marque requis par la police

 

Le façonnage côté producteur pour l’arabe / le persan / l’ourdou (ligature obligatoire LAM-ALEF + Arabic Presentation Forms-A) est implémenté comme un pipeline intégré distinct qui s’exécute automatiquement pendant l’émission du texte - voir 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 + respect de LookupFlag (Phase 4). v2.119.47 répartiteur Contextual + Chained Contextual + SequenceLookupRecord (Phase 5). v2.119.48 Reverse Chained Contextual - matrice LookupType 1-8 fermée (Phase 6). v2.119.49 API de sélection Script / LangSys (Phase 7). v2.119.50 clôture du sous-ensemble TTF via MarkUnicodeGlyphUsed (Phase 9 ; la Phase 8 était un pic d’intégration du façonnage côté producteur et a été scindée en 8a-8f pour les révisions futures)

 

Voir aussi: Arabic / Persian / Urdu Shaping, CFF / OpenType Font Subsetting Functions, THotPDF.EnableFontSubsetting