Motor de sustitución OpenType GSUB

Superficie de capacidad de sustitución de glifos (v2.119.43 - v2.119.50)

 

Arabic Shaping  CFF / OpenType Subsetting

El motor GSUB (Glyph SUBstitution) de OpenType integrado en HotPDF permite consultar, dirigir e incrustar todos los tipos de sustitución de glifos que declara una fuente OpenType: ligaduras, alternativas estilísticas, variantes contextuales, formas de shaping árabe / índico, formas alternativas CJK, etc. Todos los LookupType 1 a 8 de GSUB de OpenType están implementados y expuestos como una superficie de consulta solo de capacidad; quien llama dirige la emisión del texto y decide qué glifo sustituto escribir en el flujo de contenido de la página.

 

API pública

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 - Sustitución contextual / contextual encadenada

function ApplyContextualSubst(const InputGIDs: array of Word;

  StartIndex: Integer; const FeatureTag: AnsiString;

  var OutGIDs: array of Word;

  out ConsumedLen: Integer): Boolean;

 

// LookupType 8 - Sustitución simple contextual inversa encadenada

function ApplyReverseChainedContextualSubst(const InputGIDs: array of Word;

  StartIndex: Integer; const FeatureTag: AnsiString;

  out OutGID: Word): Boolean;

 

// Selección de Script / LangSys (fase 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;

 

// Cierre del subsetter TTF (fase 9)

procedure MarkUnicodeGlyphUsed(GID: Word);

 

Descripción

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

 

Contrato defensivo de principio a fin: las fuentes sin tabla GSUB, las etiquetas de funcionalidad de 4 bytes no estándar, las funcionalidades que el script / idioma seleccionado no anuncia, 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) para que los llamadores nunca vean excepciones en los casos rutinarios de "no hay sustitución aplicable".

 

Matriz de LookupTypes

LookupType 1 (Single Substitution) - un glifo se mapea a un sustituto. Funcionalidades canónicas: salt, ss01-ss20, smcp, onum, liga cuando LookupType 1 está cableado, más las formas posicionales árabes init / medi / fina / isol en las fuentes que las dirigen a través de GSUB. Use GetSingleSubstituteGlyph.

LookupType 2 (Multiple Substitution) - un glifo se divide en una secuencia de glifos sustitutos. Usuario canónico: ccmp Glyph Composition / Decomposition (letras latinas acentuadas precompuestas que se dividen en base + signos combinantes para el posicionamiento posterior de signos). Use GetMultipleSubstituteGlyphs.

LookupType 3 (Alternate Substitution) - un glifo se mapea a una de N alternates. Características canónicas: aalt (Access All Alternates), salt cuando se cablea como Type 3, titl (Titling Alternates), conjuntos estilísticos ss01-ss20 cuando el diseñador de la fuente ofrece más de un alternate por hueco. Use GetAlternateGlyphCount + GetAlternateGlyph.

LookupType 4 (Ligature Substitution) - N glifos de entrada se pliegan en una ligadura. Características canónicas: liga (Standard Ligatures: fi / fl / ffi / ffl), clig (Contextual Ligatures), dlig (Discretionary Ligatures), hlig (Historical Ligatures), rlig (Required Ligatures: LAM-ALEF árabe y similares), ligaduras de scripts índicos (akhn, pres, blws, psts). Use ApplyLigatureSubstitution.

LookupType 5 (Contextual Substitution) + LookupType 6 (Chained Contextual Substitution) - hacen coincidir una secuencia de glifos de entrada y despachan lookups anidados en posiciones concretas dentro de la coincidencia. Se implementan las tres variantes de Format (1 secuencia literal, 2 secuencia ClassDef, 3 secuencia Coverage); el despachador de SequenceLookupRecord vuelve a entrar en LookupList y gestiona lookups anidados Single / Multiple / Alternate (first) / Ligature con seguimiento en vivo de MatchPositions. Features canónicas: rclt (Required Contextual Alternates - init/medi/fina/isol del árabe cuando lo guía GSUB), clig, calt, y las de shaping índico pres / blws / psts / half / pstf / cjct. Use ApplyContextualSubst (un único punto de entrada cubre LookupType 5 y 6).

LookupType 7 (Extension Substitution) - capa de pura indirección que la especificación OpenType define para fuentes cuya subtabla de sustitución vive más allá del alcance de 16 bits del LookupList. Toda API pública sigue de forma transparente la indirección Offset32 de 32 bits hacia la subtabla real LookupType 1 / 2 / 3 / 4 / 5 / 6 / 8. Desbloquea fuentes CJK / índicas pesadas (Noto Sans CJK, Noto Sans Devanagari) cuyo GSUB supera los 64 KB. Sin API separada: el desenvuelto es automático.

LookupType 8 (Reverse Chained Contextual Single Substitution) - sustitución 1:1 sensible al contexto cuya particularidad es que quien llama debe aplicarla en orden de recorrido INVERSO sobre una tirada de varios glifos (final -> inicio), porque cada sustituto puede depender de un contexto de lookahead FUTURO que aún no debe haber sido sustituido. Uso canónico: alternativas contextuales de árabe / siríaco / N'Ko / índico cuya forma final depende del glifo siguiente. Use ApplyReverseChainedContextualSubst; el bucle de recorrido inverso lo controla quien llama.

 

Selección de Script / LangSys

Por defecto el motor prefiere el script DFLT (o el primero que declare la fuente) y el LangSys por defecto. 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 concreto. La cadena vacía restaura la línea base de la ruta por defecto. Las selecciones persisten entre consultas y se limpian en RegisterUnicodeTTF('', nil).

 

Semántica estricta frente a fallback: un ScriptTag desconocido hace que las consultas posteriores devuelvan resultados vacíos sin efecto (así quien llama puede detectar que el script elegido no está disponible); un LangTag desconocido recurre al LangSys por defecto del script, según la convención de OpenType. GetGSUBScripts / GetGSUBLanguages / GetGSUBFeatures enumeran lo que la fuente cargada anuncia realmente.

 

Respeto de LookupFlag y GDEF

Cada consulta lee el LookupFlag de cada tabla Lookup (y el uint16 opcional markFilteringSet final cuando useMarkFilteringSet está activado) y omite los glifos de entrada marcados para ignorar. Se respetan los bits definidos por la especificación: ignoreBaseGlyphs (0x0002, omite la clase 1 de GDEF), ignoreLigatures (0x0004, omite la clase 2), ignoreMarks (0x0008, omite la clase 3), useMarkFilteringSet (0x0010) y el byte alto markAttachmentType. Se analizan tanto ClassDef Format 1 como 2; se aceptan las cabeceras GDEF v1.0 / v1.1 / v1.2. Las fuentes sin tabla GDEF recurren a «ningún glifo se ignora», de modo que la salida sigue siendo idéntica byte a byte para quienes usan fuentes sin GDEF.

 

Cierre del subsetter TTF (MarkUnicodeGlyphUsed)

El subsetter TTF v2.84.0 de HotPDF deriva su conjunto de glifos usados de FUnicodeUsedCps a través del cmap. Los glifos sustitutos GSUB (alternates estilísticos, ligaduras, variantes contextuales: todo lo que devuelven las 7 APIs de consulta anteriores) normalmente no tiene ningún codepoint que llegue a ellos vía cmap, así que antes eran invisibles al subsetter y el lector consumidor renderizaba .notdef en su lugar.

 

Tras emitir cualquier GID devuelto por GetSingleSubstituteGlyph / GetMultipleSubstituteGlyphs / GetAlternateGlyph / ApplyLigatureSubstitution / ApplyContextualSubst / ApplyReverseChainedContextualSubst a un stream de texto PDF, llame a MarkUnicodeGlyphUsed(GID) una vez por GID emitido para arrastrarlo al subset incrustado. El helper es idempotente, defensivo (los GIDs fuera de rango se descartan en silencio) 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 arrastran solos.

 

Flujo típico (versalitas latinas)

 

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

  emite SmallCapGID en el content stream de la página...

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

end;

 

Flujo típico (ligadura árabe LAM-ALEF)

 

PDF.SetGSUBScript('arab');

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

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

begin

  emite LigGID + avanza ConsumedCount

  PDF.MarkUnicodeGlyphUsed(LigGID);

end;

 

Alcance y limitaciones

El motor es una superficie de consulta solo de capacidades: responde a «qué haría GSUB aquí», pero no ejecuta un pipeline de shaping automático (layout tipo Harfbuzz, reordenado consciente de clústeres para índico, resolución BiDi, posicionamiento GPOS, anclaje de marcas). Los llamadores son responsables de dirigir el bucle de escaneo, elegir qué sustituto / alternate emitir, llamar a MarkUnicodeGlyphUsed por cada GID sustituto emitido y aplicar el posicionamiento GPOS / de marcas que la fuente exija.

 

El shaping árabe / persa / urdu en el lado del productor (ligadura obligatoria LAM-ALEF + Arabic Presentation Forms-A) está implementado como un pipeline integrado separado que se ejecuta automáticamente durante la emisión de texto; consulte Shaping árabe / persa / urdu.

 

Trazado de versiones

v2.119.43 Single Substitution + Fase 1. v2.119.44 Multiple + Alternate (Fase 2). v2.119.45 Ligature (Fase 3). v2.119.46 Extension + GDEF + respeto de LookupFlag (Fase 4). v2.119.47 Contextual + Chained Contextual + despachador de SequenceLookupRecord (Fase 5). v2.119.48 Reverse Chained Contextual - matriz LookupType 1-8 cerrada (Fase 6). v2.119.49 API de selección de Script / LangSys (Fase 7). v2.119.50 clausura del subsetter TTF mediante MarkUnicodeGlyphUsed (Fase 9; la Fase 8 fue un spike de integración de shaping en el lado productor y se dividió en 8a-8f para futuras revisiones).

 

Véase también: Arabic / Persian / Urdu Shaping, CFF / OpenType Font Subsetting Functions, THotPDF.EnableFontSubsetting