|
OpenType GSUB Substitution Engine Glyph substitution capability surface (v2.119.43 - v2.119.50)
|
Arabic Shaping CFF / OpenType Subsetting |
|
Рушій OpenType GSUB (Glyph SUBstitution) у 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 керує emit тексту та вирішує, який 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);
Опис Рушій активується після того, як RegisterUnicodeTTF розібрав font і закешував таблиці GSUB / GDEF / cmap Кожен substitution query проходить ланцюг ScriptList / LangSysList / FeatureList / LookupList і передає роботу відповідному handler LookupType 12 methods вище є повною public surface; усе інше (cmap walk, parsing ScriptList, пошук Coverage table, resolution ClassDef, дотримання LookupFlag, unwrap Extension wrapper, nested dispatch SequenceLookupRecord) приховано за цією surface
Захисний контракт всюди однаковий: fonts без 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 підключено, а також positional forms init / medi / fina / isol для Arabic у fonts, які проводять їх через GSUB Використовуйте GetSingleSubstituteGlyph LookupType 2 (Multiple Substitution) - один glyph розкладається в sequence substitute glyphs Типовий користувач: ccmp Glyph Composition / Decomposition (precomposed accented Latin letters розкладаються на base + combining marks для подальшого mark positioning) Використовуйте GetMultipleSubstituteGlyphs LookupType 3 (Alternate Substitution) - один glyph відображається в один з N alternates Типові features: aalt (Access All Alternates), salt, коли підключено як Type 3, titl (Titling Alternates), stylistic sets ss01-ss20, коли дизайнер font пропонує більше ніж один 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) - зіставляє input glyph sequence і запускає nested lookups у конкретних позиціях match Реалізовано всі три Format variants (1 literal sequence, 2 ClassDef sequence, 3 Coverage sequence); dispatcher SequenceLookupRecord знову входить у LookupList і обробляє nested lookups Single / Multiple / Alternate (перший) / Ligature з live MatchPositions tracking Типові features: rclt, clig, calt, Indic shaping pres / blws / psts / half / pstf / cjct Використовуйте ApplyContextualSubst (одна entry point покриває LookupType 5 і 6) LookupType 7 (Extension Substitution) - шар непрямого доступу, який OpenType spec визначає для fonts, чия substitution subtable лежить за межами 16-bit reach LookupList Кожен public API прозоро проходить 32-bit Offset32 indirection до реальної 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 За замовчуванням рушій віддає перевагу script DFLT (або першому script, який оголошує font) і default LangSys Викликайте SetGSUBScript('latn' / 'arab' / 'cyrl' / 'hani' / 'kana' / 'deva' / 'beng' / 'taml' / etc.) і SetGSUBLanguage('ENG ' / 'TUR ' / 'AZE ' / 'JAN ' / 'KOR ' / 'ARA ' / etc., padded trailing-space до 4 bytes), щоб закріпити queries за конкретною парою script / language Empty string відновлює baseline default-path Вибір зберігається між queries і очищується під час RegisterUnicodeTTF('', nil)
Семантика strict-vs-fallback: невідомий ScriptTag змушує наступні queries повертати empty no-op results (щоб callers могли визначити, що обраний script недоступний); невідомий LangTag fallback до default LangSys script за OpenType convention GetGSUBScripts / GetGSUBLanguages / GetGSUBFeatures перелічують те, що завантажений font реально рекламує
LookupFlag honor and GDEF Кожен query читає LookupFlag кожної Lookup table (і optional trailing markFilteringSet uint16, коли встановлено useMarkFilteringSet) та пропускає input glyphs, позначені для ignore Дотримуються bits, визначені spec: 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 обидва parse; headers GDEF v1.0 / v1.1 / v1.2 приймаються Fonts без GDEF table fallback до «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 renderив .notdef замість них
Після emit будь-якого GID, повернутого GetSingleSubstituteGlyph / GetMultipleSubstituteGlyphs / GetAlternateGlyph / ApplyLigatureSubstitution / ApplyContextualSubst / ApplyReverseChainedContextualSubst у PDF text stream, викличте MarkUnicodeGlyphUsed(GID) один раз для кожного emitted GID, щоб включити його в embedded subset Helper idempotent, defensive (out-of-range GIDs quietly dropped) і інтегрується з composite-glyph closure pass v2.84.0: callers мають mark лише 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 Рушій є capability-only query surface: він відповідає «що зробив би GSUB тут», але не запускає automatic shaping pipeline (layout рівня Harfbuzz, cluster-aware reordering для Indic, BiDi resolution, GPOS positioning, mark attachment) Callers самі відповідають за scan loop, вибір substitute / alternate для emit, виклик 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 (Phase 4) v2.119.47 Contextual + Chained Contextual + dispatcher SequenceLookupRecord (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 для майбутніх revisions)
See also: Arabic / Persian / Urdu Shaping, CFF / OpenType Font Subsetting Functions, THotPDF.EnableFontSubsetting |