Motor de sustitución OpenType GSUB

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

 

Arabic Shaping  CFF / OpenType Subsetting

El motor OpenType GSUB (Glyph SUBstitution) dentro de HotPDF permite que los llamadores consulten, controlen e incrusten todo tipo de sustitución de glifos declarada por una fuente OpenType: ligaduras, alternativos estilísticos, variantes contextuales, formas de conformación Arabic / Indic, formas alternativas CJK, etc. Cada OpenType GSUB LookupType 1 a 8 está implementado y expuesto como superficie de consulta de capacidades; el llamador controla la emisión de texto y decide qué glifo sustituto escribir en el flujo de contenido de la página

 

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

 

Descripción

El motor se activa después de que RegisterUnicodeTTF analiza una fuente y almacena en caché sus tablas GSUB / GDEF / cmap. Cada consulta de sustitución recorre la cadena ScriptList / LangSysList / FeatureList / LookupList de la fuente y despacha al manejador LookupType adecuado. Los 12 métodos anteriores constituyen toda la superficie pública; todo lo demás (recorrido cmap, análisis de ScriptList, búsqueda en tabla Coverage, resolución ClassDef, respeto de LookupFlag, desempaquetado de Extension wrapper y despacho anidado de SequenceLookupRecord) queda detrás de esa superficie

 

Contrato defensivo en toda la API: las fuentes sin tabla GSUB, los feature tags que no tienen 4 bytes, las features no anunciadas por el script / idioma seleccionado, los GID que ninguna subtabla cubre y los glifos de entrada ignorados por LookupFlag devuelven todos un no-op seguro (False / OutGID = InputGID / OutGIDs vacío / ConsumedCount = 1), de modo que los llamadores nunca ven excepciones en casos rutinarios de "no se aplica sustitución"

 

LookupType matrix

LookupType 1 (Single Substitution): un glifo se asigna a un sustituto. Features canónicas: salt, ss01-ss20, smcp, onum, liga cuando LookupType 1 está cableado, más las formas posicionales árabes init / medi / fina / isol en fuentes que las controlan mediante GSUB. Utilice GetSingleSubstituteGlyph

LookupType 2 (Multiple Substitution): un glifo se divide en una secuencia de glifos sustitutos. Uso canónico: ccmp Glyph Composition / Decomposition (letras latinas acentuadas precompuestas divididas en base + marcas combinantes para posicionamiento posterior de marcas). Utilice GetMultipleSubstituteGlyphs

LookupType 3 (Alternate Substitution): un glifo se asigna a uno de N alternativos. Features canónicas: aalt (Access All Alternates), salt cuando está cableada como Type 3, titl (Titling Alternates), conjuntos estilísticos ss01-ss20 cuando el diseñador de la fuente ofrece más de un alternativo por posición. Utilice 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): coincide con una secuencia de glifos de entrada y despacha lookups anidados en posiciones específicas dentro de la coincidencia. Las tres variantes Format (1 secuencia literal, 2 secuencia ClassDef, 3 secuencia Coverage) están implementadas; el despachador SequenceLookupRecord vuelve a entrar en LookupList y maneja lookups anidados Single / Multiple / Alternate (primero) / Ligature con seguimiento activo de MatchPositions. Features canónicas: rclt (Required Contextual Alternates: init/medi/fina/isol árabe cuando GSUB lo controla), clig, calt, shaping índico pres / blws / psts / half / pstf / cjct. Utilice ApplyContextualSubst (un único punto de entrada cubre LookupType 5 y 6)

LookupType 7 (Extension Substitution): capa de indirección pura que la especificación OpenType define para fuentes cuya subtabla de sustitución queda fuera del alcance de 16 bits de LookupList. Todas las API públicas siguen de forma transparente la indirección Offset32 de 32 bits hasta la subtabla real LookupType 1 / 2 / 3 / 4 / 5 / 6 / 8. Esto desbloquea fuentes CJK / índicas pesadas (Noto Sans CJK, Noto Sans Devanagari) cuyo GSUB supera 64 KB. No hay API separada: el desempaquetado es automático

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

De forma predeterminada, el motor prefiere el script DFLT (o el primer script que declara la fuente) y el LangSys predeterminado. Llame a SetGSUBScript('latn' / 'arab' / 'cyrl' / 'hani' / 'kana' / 'deva' / 'beng' / 'taml' / etc.) y SetGSUBLanguage('ENG ' / 'TUR ' / 'AZE ' / 'JAN ' / 'KOR ' / 'ARA ' / etc., rellenado con espacios finales hasta 4 bytes) para fijar las consultas a un par script / idioma específico. La cadena vacía restaura la ruta predeterminada. Las selecciones persisten entre consultas y se borran con RegisterUnicodeTTF('', nil)

 

Semántica strict-vs-fallback: un ScriptTag desconocido hace que las consultas posteriores devuelvan resultados no-op vacíos (para que los llamadores puedan detectar que el script elegido no está disponible); un LangTag desconocido vuelve al LangSys predeterminado del script según la convención OpenType. GetGSUBScripts / GetGSUBLanguages / GetGSUBFeatures enumeran lo que la fuente cargada anuncia realmente

 

LookupFlag honor and GDEF

Cada consulta lee el LookupFlag de cada tabla Lookup (y el uint16 final opcional markFilteringSet cuando useMarkFilteringSet está establecido) y omite los glifos de entrada marcados para ignorarse. Se respetan los bits definidos por la especificación: ignoreBaseGlyphs (0x0002, omitir clase GDEF 1), ignoreLigatures (0x0004, omitir clase 2), ignoreMarks (0x0008, omitir clase 3), useMarkFilteringSet (0x0010) y el byte alto markAttachmentType. ClassDef Format 1 + 2 se analizan ambos; los encabezados GDEF v1.0 / v1.1 / v1.2 se aceptan todos. Las fuentes sin tabla GDEF vuelven a "ningún glifo se ignora", de modo que la salida permanece idéntica byte a byte para llamadores que usan fuentes sin GDEF

 

TTF subsetter closure (MarkUnicodeGlyphUsed)

El subsetter TTF de HotPDF v2.84.0 deriva su conjunto de glifos usados desde FUnicodeUsedCps mediante el cmap. Los glifos sustitutos GSUB (alternativos estilísticos, ligaduras, variantes contextuales: todo lo que devuelven las 7 API de consulta anteriores) normalmente no tienen un punto de código que llegue a ellos mediante el cmap, por lo que antes eran invisibles para el subsetter y el lector consumidor renderizaba .notdef en su lugar

 

Después de emitir cualquier GID devuelto por GetSingleSubstituteGlyph / GetMultipleSubstituteGlyphs / GetAlternateGlyph / ApplyLigatureSubstitution / ApplyContextualSubst / ApplyReverseChainedContextualSubst en un stream de texto PDF, llame a MarkUnicodeGlyphUsed(GID) una vez por cada GID emitido para incorporarlo al subconjunto incrustado. El helper es idempotente, defensivo (los GID fuera de rango se descartan silenciosamente) y se integra con la pasada de cierre de glifos compuestos de v2.84.0: los llamadores solo necesitan marcar el GID sustituto de nivel superior, los componentes compuestos se incorporan automáticamente

 

Flujo de trabajo típico (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;

 

Flujo de trabajo típico (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;

 

Ámbito y limitaciones

El motor es una superficie de consulta de capacidades: responde "qué haría GSUB aquí", pero no ejecuta una canalización automática de conformación (maquetación de clase Harfbuzz, reordenamiento Indic con conocimiento de clústeres, resolución BiDi, posicionamiento GPOS, adjunto de marcas). Los llamadores son responsables de controlar el bucle de exploración, elegir qué sustituto / alternativo emitir, llamar a MarkUnicodeGlyphUsed por cada GID sustituto emitido y aplicar cualquier posicionamiento GPOS / de marcas que requiera la fuente

 

El shaping árabe / persa / urdu del lado productor (ligadura obligatoria LAM-ALEF + Arabic Presentation Forms-A) está implementado como una canalización integrada separada que se ejecuta automáticamente durante la emisión de texto; consulte 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 + respeto de LookupFlag (Phase 4). v2.119.47 Contextual + Chained Contextual + despachador SequenceLookupRecord (Phase 5). v2.119.48 Reverse Chained Contextual: matriz LookupType 1-8 cerrada (Phase 6). v2.119.49 API de selección Script / LangSys (Phase 7). v2.119.50 cierre del subsetter TTF mediante MarkUnicodeGlyphUsed (Phase 9; Phase 8 fue un spike de integración de shaping del lado productor y se dividió en 8a-8f para revisiones futuras)

 

Véase también: Arabic / Persian / Urdu Shaping, Subconjunto de fuentes CFF / OpenType Functions, THotPDF.EnableFontSubsetting