|
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 сам управляет text emission и решает, какой 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 разобрал font и закешировал его GSUB / GDEF / cmap tables. Каждый substitution query проходит цепочку ScriptList / LangSysList / FeatureList / LookupList и dispatch-ится в соответствующий LookupType handler. 12 методов выше — полная публичная поверхность; все остальное скрыто за ней: cmap walk, ScriptList parsing, Coverage table lookup, ClassDef resolution, LookupFlag honor, Extension wrapper unwrapping и SequenceLookupRecord nested dispatch
Defensive contract во всем engine: fonts без GSUB table, non-4-byte feature tags, features, не объявленные выбранным script / language, GIDs, не покрытые subtable, и input glyphs, проигнорированные LookupFlag, возвращают safe no-op (False / OutGID = InputGID / empty OutGIDs / ConsumedCount = 1), поэтому callers не получают exceptions для обычного случая «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 в fonts, которые проводят их через GSUB. Используйте GetSingleSubstituteGlyph LookupType 2 (Multiple Substitution): один glyph разбивается на последовательность substitute glyphs. Типичный пользователь — ccmp Glyph Composition / Decomposition, когда precomposed accented Latin letters разбиваются на base + combining marks для downstream mark positioning. Используйте GetMultipleSubstituteGlyphs LookupType 3 (Alternate Substitution): один glyph сопоставляется с одним из N alternates. Типовые features: aalt (Access All Alternates), salt при wiring как Type 3, titl (Titling Alternates), ss01-ss20 stylistic sets, когда designer шрифта предлагает более одного alternate на slot. Используйте GetAlternateGlyphCount + GetAlternateGlyph LookupType 4 (Ligature Substitution) - N input glyphs fold into one ligature. Canonical features: LookupType 5 (Contextual Substitution) + LookupType 6 (Chained Contextual Substitution): сопоставляет входную glyph sequence и dispatch-ит nested lookups в конкретных позициях внутри match. Реализованы все три Format variants; SequenceLookupRecord dispatcher повторно входит в LookupList и обрабатывает nested lookups Single / Multiple / Alternate (first) / Ligature с живым MatchPositions tracking. Типовые features: rclt, clig, calt, Indic shaping pres / blws / psts / half / pstf / cjct. Используйте ApplyContextualSubst LookupType 7 (Extension Substitution): чистый слой косвенности, определенный OpenType spec для fonts, где substitution subtable находится за пределами 16-bit reach LookupList. Каждый public API прозрачно следует 32-bit Offset32 к реальной subtable LookupType 1 / 2 / 3 / 4 / 5 / 6 / 8. Это разблокирует тяжелые CJK / Indic fonts (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
Script / LangSys selection По умолчанию engine предпочитает DFLT script (или первый script, объявленный font) и default LangSys. Вызовите SetGSUBScript('latn' / 'arab' / 'cyrl' / 'hani' / 'kana' / 'deva' / 'beng' / 'taml' / etc.) и SetGSUBLanguage('ENG ' / 'TUR ' / 'AZE ' / 'JAN ' / 'KOR ' / 'ARA ' / etc., trailing-space padded до 4 bytes), чтобы закрепить queries за конкретной парой script / language. Пустая строка восстанавливает default-path baseline. Выбор сохраняется между queries и очищается на RegisterUnicodeTTF('', nil)
Семантика strict-vs-fallback: неизвестный ScriptTag заставляет последующие queries возвращать пустые no-op results, чтобы callers могли обнаружить недоступность выбранного script; неизвестный LangTag откатывается к default LangSys этого script по соглашению OpenType. GetGSUBScripts / GetGSUBLanguages / GetGSUBFeatures перечисляют то, что фактически объявляет загруженный font
LookupFlag honor and GDEF Каждый query читает LookupFlag каждой Lookup table и необязательный trailing markFilteringSet uint16, когда установлен useMarkFilteringSet, и пропускает input glyphs, помеченные для ignore. Учитываются spec-defined bits: ignoreBaseGlyphs, ignoreLigatures, ignoreMarks, useMarkFilteringSet и high byte markAttachmentType. ClassDef Format 1 + 2 оба разбираются; headers GDEF v1.0 / v1.1 / v1.2 принимаются. Fonts без GDEF table откатываются к «no glyph is ignored», поэтому output остается byte-identical для callers с GDEF-less fonts
TTF subsetter closure (MarkUnicodeGlyphUsed) TTF subsetter HotPDF v2.84.0 выводит 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) один раз для каждого emitted GID, чтобы включить его в embedded subset. Helper идемпотентен, defensive (out-of-range GIDs silently dropped) и интегрирован с 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 здесь», но не запускает automatic 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, требуемого font
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 закрыта (Phase 6). v2.119.49 Script / LangSys selection API (Phase 7). v2.119.50 TTF subsetter closure через MarkUnicodeGlyphUsed (Phase 9; Phase 8 была producer-side shaping integration spike и разделена на 8a-8f для будущих revision)
См. также: Arabic / Persian / Urdu Shaping, CFF / OpenType Font Subsetting Functions, THotPDF.EnableFontSubsetting |