|
OpenType GSUB Substitution Engine Glyph substitution capability surface (v2.119.43 - v2.119.50)
|
Arabic Shaping CFF / OpenType Subsetting |
|
HotPDF 內的 OpenType GSUB(Glyph SUBstitution)engine 讓呼叫端查詢、驅動並嵌入 OpenType font 宣告的各類 glyph substitution,包括 ligatures、stylistic alternates、contextual variants、Arabic / Indic shaping forms、CJK alternate forms 等。OpenType GSUB LookupType 1 到 8 均已實作,並以 capability-only query surface 形式公開;呼叫端負責驅動文字輸出,並決定要將哪個 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 解析字型並快取其 GSUB / GDEF / cmap tables 後,engine 便會啟用。每次 substitution query 都會走訪字型的 ScriptList / LangSysList / FeatureList / LookupList chain,並分派到適當的 LookupType handler。上方 12 個 methods 是完整 public surface;其他細節(cmap walk、ScriptList parsing、Coverage table lookup、ClassDef resolution、LookupFlag honor、Extension wrapper unwrapping、SequenceLookupRecord nested dispatch)都位於此 surface 後方 LookupType matrix LookupType 1(Single Substitution)- 一個 glyph 對應到一個 substitute。典型 features:salt、ss01-ss20、smcp、onum、以 LookupType 1 連接的 liga,以及透過 GSUB 驅動這些 forms 的字型中的 init / medi / fina / isol Arabic positional forms。使用 GetSingleSubstituteGlyph LookupType 2(Multiple Substitution)- 一個 glyph 會拆成 substitute glyphs 序列。典型用途:ccmp Glyph Composition / Decomposition(預組 accented Latin letters 拆成 base + combining marks,供後續 mark positioning 使用)。使用 GetMultipleSubstituteGlyphs LookupType 3(Alternate Substitution)- 一個 glyph 對應到 N 個 alternates 中的一個。典型 features:aalt(Access All Alternates)、以 Type 3 連接的 salt、titl(Titling Alternates),以及字型設計者為每個 slot 提供多個 alternate 時的 ss01-ss20 stylistic sets。使用 GetAlternateGlyphCount + GetAlternateGlyph LookupType 4 (Ligature Substitution) - N input glyphs fold into one ligature. Canonical features: LookupType 2(Multiple Substitution) - 一個 glyph 拆成一連串替代 glyph。典型案例: LookupType 3(Alternate Substitution) - 一個 glyph 對應 N 個替代之一。典型 feature: LookupType 4(Ligature Substitution) - N 個輸入 glyph 摺成一個連結字。典型 feature: LookupType 5(Contextual Substitution) + LookupType 6(Chained Contextual Substitution) - 比對輸入 glyph 序列,並在比對範圍內的特定位置派發巢狀 lookup。三種 Format 變體(1 字面序列、2 ClassDef 序列、3 Coverage 序列)都已實作;SequenceLookupRecord 分派器會重入 LookupList,並在即時追蹤 MatchPositions 的情況下處理 Single / Multiple / Alternate(第一個)/ Ligature 巢狀 lookup。典型 feature: LookupType 7(Extension Substitution) - OpenType 規格為替代子表超出 LookupList 16-bit 定址範圍的字型定義的純間接層。每個公開 API 都會透明地跟隨 32-bit Offset32 間接,抵達真正的 LookupType 1 / 2 / 3 / 4 / 5 / 6 / 8 子表。解鎖 GSUB 超過 64 KB 的大型 CJK / Indic 字型(Noto Sans CJK、Noto Sans Devanagari)。沒有獨立 API - 解包裝自動完成。 LookupType 8(Reverse Chained Contextual Single Substitution) - 具上下文感知的 1:1 替代,特點是呼叫端必須以反向掃描順序(尾 -> 頭)套用到多位元組 glyph run,因為每個替代可能依賴尚未被替代的「未來」前視上下文。典型案例:最終形式取決於下一個 glyph 的阿拉伯文 / 敘利亞文 / N'Ko / Indic contextual alternates。使用 LookupType 5(Contextual Substitution)+ LookupType 6(Chained Contextual Substitution)- 匹配 input glyph sequence,並在 match 內特定位置分派 nested lookups。三種 Format variants(1 literal sequence、2 ClassDef sequence、3 Coverage sequence)均已實作;SequenceLookupRecord dispatcher 會重新進入 LookupList,並透過即時 MatchPositions tracking 處理 Single / Multiple / Alternate(first)/ Ligature nested lookups。典型 features:rclt(Required Contextual Alternates;GSUB 驅動時的 Arabic init/medi/fina/isol)、clig、calt、Indic shaping pres / blws / psts / half / pstf / cjct。使用 ApplyContextualSubst(一個 entry point 同時涵蓋 LookupType 5 與 6) LookupType 7(Extension Substitution)- OpenType spec 為 substitution subtable 超出 LookupList 16-bit 可達範圍的字型所定義的純 indirection layer。每個 public API 都會透明地跟隨 32-bit Offset32 indirection 到真正的 LookupType 1 / 2 / 3 / 4 / 5 / 6 / 8 subtable。這解除 GSUB 超過 64 KB 的大型 CJK / Indic fonts(Noto Sans CJK、Noto Sans Devanagari)限制。沒有獨立 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
嚴格與後備語意:未知的
LookupFlag 與 GDEF 每次查詢都會讀取每個 Lookup 表的 LookupFlag(以及設定
TTF 子集化閉包(MarkUnicodeGlyphUsed) HotPDF v2.84.0 的 TTF 子集化器透過 cmap 從
把
Script / LangSys selection 預設情況下,engine 會偏好 DFLT script(或字型宣告的第一個 script)與 default LangSys。呼叫 SetGSUBScript('latn' / 'arab' / 'cyrl' / 'hani' / 'kana' / 'deva' / 'beng' / 'taml' / etc.) 和 SetGSUBLanguage('ENG ' / 'TUR ' / 'AZE ' / 'JAN ' / 'KOR ' / 'ARA ' / etc.,以 trailing-space 補足 4 bytes),即可將 queries 固定到特定 script / language pair。空字串會還原 default-path baseline。選擇會跨 queries 保留,並在 RegisterUnicodeTTF('', nil) 時清除
Strict-vs-fallback semantics:未知 ScriptTag 會讓後續 queries 回傳 empty no-op results(讓呼叫端可偵測所選 script 不可用);未知 LangTag 則依 OpenType convention fallback 到該 script 的 default LangSys。GetGSUBScripts / GetGSUBLanguages / GetGSUBFeatures 會列舉已載入字型實際宣告的內容
LookupFlag honor and GDEF 每個 query 都會讀取各 Lookup table 的 LookupFlag(以及設定 useMarkFilteringSet 時選用的尾端 markFilteringSet uint16),並略過標記為 ignore 的 input glyphs。規格定義的 bits 都會遵守:ignoreBaseGlyphs(0x0002,略過 GDEF class 1)、ignoreLigatures(0x0004,略過 class 2)、ignoreMarks(0x0008,略過 class 3)、useMarkFilteringSet(0x0010),以及 high byte markAttachmentType。ClassDef Format 1 + 2 都會解析;GDEF v1.0 / v1.1 / v1.2 headers 都可接受。沒有 GDEF table 的字型會 fallback 到「沒有 glyph 被忽略」,因此使用 GDEF-less fonts 的呼叫端仍可保持 byte-identical output
TTF subsetter closure (MarkUnicodeGlyphUsed) HotPDF 的 v2.84.0 TTF subsetter 會透過 cmap 從 FUnicodeUsedCps 推導 used-glyph set。GSUB substitute glyphs(stylistic alternates、ligatures、contextual variants,也就是上方 7 個 query APIs 回傳的一切)通常沒有可經由 cmap 抵達它們的 codepoint,因此過去對 subsetter 是不可見的,閱讀端會在其位置 render .notdef
將 GetSingleSubstituteGlyph / GetMultipleSubstituteGlyphs / GetAlternateGlyph / ApplyLigatureSubstitution / ApplyContextualSubst / ApplyReverseChainedContextualSubst 回傳的任何 GID 輸出到 PDF text stream 後,請對每個已輸出的 GID 呼叫一次 MarkUnicodeGlyphUsed(GID),將它拉入 embedded subset。此 helper 具冪等性且防禦式(out-of-range GIDs 會被靜默丟棄),並與 v2.84.0 composite-glyph closure pass 整合:呼叫端只需要標記 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
請參閱: Arabic / Persian / Urdu Shaping, CFF / OpenType Font Subsetting Functions, THotPDF.EnableFontSubsetting |