|
מנוע OpenType GSUB (Glyph SUBstitution) בתוך HotPDF מאפשר לקוראים לשאול, להפעיל ולהטמיע כל סוג של החלפת glyph שמוצהרת על ידי פונט OpenType: ligatures, חלופות סגנוניות, וריאנטים תלויי הקשר, צורות shaping ערביות / הודיות, צורות חלופיות של CJK ועוד. כל OpenType GSUB LookupType 1 עד 8 ממומש ונחשף כמשטח query של capability בלבד; הקורא מנהל את פליטת הטקסט ומחליט איזה substitute glyph לכתוב לזרם תוכן העמוד
Public API
type
TGSUBStringArray = array of AnsiString;
LookupType 1 (Single Substitution) - glyph אחד ממופה ל-substitute אחד. features קנוניים: salt, ss01-ss20, smcp, onum, liga כאשר LookupType 1 מחובר, וכן צורות מיקום ערביות init / medi / fina / isol בפונטים שמפעילים אותן דרך GSUB. השתמש ב-GetSingleSubstituteGlyph
function GetSingleSubstituteGlyph(InputGID: Word; const FeatureTag: AnsiString): Word;
LookupType 2 (Multiple Substitution) - glyph אחד מתפצל לרצף substitute glyphs. שימוש קנוני: ccmp Glyph Composition / Decomposition, שבו אותיות לטיניות מורכבות עם סימני הטעמה מתפצלות לבסיס וסימני combining לצורך mark positioning בהמשך. השתמש ב-GetMultipleSubstituteGlyphs
function GetMultipleSubstituteGlyphs(InputGID: Word; const FeatureTag: AnsiString;
var OutGIDs: array of Word): Boolean;
LookupType 3 (Alternate Substitution) - glyph אחד ממופה לאחת מתוך N חלופות. features קנוניים: aalt (Access All Alternates), salt כאשר הוא מחובר כ-Type 3, titl (Titling Alternates), ו-sets סגנוניים ss01-ss20 כאשר מעצב הפונט מציע יותר מחלופה אחת לכל slot. השתמש ב-GetAlternateGlyphCount + GetAlternateGlyph
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 (Contextual Substitution) + LookupType 6 (Chained Contextual Substitution) - מתאימים רצף input glyphs ומפעילים lookups מקוננים במיקומים מסוימים בתוך ההתאמה. כל שלוש גרסאות Format, רצף literal 1, רצף ClassDef 2 ורצף Coverage 3, ממומשות; dispatcher של SequenceLookupRecord נכנס מחדש ל-LookupList ומטפל ב-lookups מקוננים של Single / Multiple / Alternate ראשון / Ligature עם מעקב חי של MatchPositions. features קנוניים: rclt (Required Contextual Alternates - צורות init/medi/fina/isol בערבית כאשר הן מונעות GSUB), clig, calt, ועיצוב Indic pres / blws / psts / half / pstf / cjct. השתמש ב-ApplyContextualSubst, נקודת כניסה אחת לשני LookupType 5 ו-6
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 שלו. כל substitution query עובר בשרשרת ScriptList / LangSysList / FeatureList / LookupList של הפונט ומפנה אל handler ה-LookupType המתאים. 12 המתודות שלמעלה הן כל המשטח הציבורי; כל השאר, כולל מעבר cmap, ניתוח ScriptList, חיפוש Coverage table, פתרון ClassDef, כיבוד LookupFlag, פריסת Extension wrapper ו-dispatch מקונן של SequenceLookupRecord, נמצא מאחורי המשטח הזה
החוזה ההגנתי לכל האורך: פונטים ללא טבלת GSUB, feature tags שאינם בני 4 bytes, features שה-script / language שנבחר אינו מפרסם, GIDs שאף subtable אינו מכסה, ו-input glyphs ש-LookupFlag מסמן להתעלמות, כולם מחזירים no-op בטוח (False / OutGID = InputGID / OutGIDs ריק / ConsumedCount = 1) כך שקוראים אינם רואים חריגות במקרים שגרתיים של “אין substitution חל”
LookupType matrix
LookupType 1 (Single Substitution) - glyph אחד ממופה ל-substitute אחד. features קנוניים: salt, ss01-ss20, smcp, onum, liga כאשר LookupType 1 מחובר, וכן צורות מיקום ערביות init / medi / fina / isol בפונטים שמפעילים אותן דרך GSUB. השתמש ב-GetSingleSubstituteGlyph
LookupType 2 (Multiple Substitution) - glyph אחד מתפצל לרצף substitute glyphs. שימוש קנוני: ccmp Glyph Composition / Decomposition, שבו אותיות לטיניות מורכבות עם סימני הטעמה מתפצלות לבסיס וסימני combining לצורך mark positioning בהמשך. השתמש ב-GetMultipleSubstituteGlyphs
LookupType 3 (Alternate Substitution) - glyph אחד ממופה לאחת מתוך N חלופות. features קנוניים: aalt (Access All Alternates), salt כאשר הוא מחובר כ-Type 3, titl (Titling Alternates), ו-sets סגנוניים ss01-ss20 כאשר מעצב הפונט מציע יותר מחלופה אחת לכל slot. השתמש ב-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) - מתאימים רצף input glyphs ומפעילים lookups מקוננים במיקומים מסוימים בתוך ההתאמה. כל שלוש גרסאות Format, רצף literal 1, רצף ClassDef 2 ורצף Coverage 3, ממומשות; dispatcher של SequenceLookupRecord נכנס מחדש ל-LookupList ומטפל ב-lookups מקוננים של Single / Multiple / Alternate ראשון / Ligature עם מעקב חי של MatchPositions. features קנוניים: rclt (Required Contextual Alternates - צורות init/medi/fina/isol בערבית כאשר הן מונעות GSUB), clig, calt, ועיצוב Indic pres / blws / psts / half / pstf / cjct. השתמש ב-ApplyContextualSubst, נקודת כניסה אחת לשני LookupType 5 ו-6
LookupType 7 (Extension Substitution) - שכבת עקיפה טהורה שמפרט OpenType מגדיר לפונטים שבהם substitution subtable נמצא מעבר לטווח 16-bit של LookupList. כל API ציבורי עוקב בשקיפות אחרי עקיפת Offset32 בגודל 32-bit אל subtable האמיתי מסוג LookupType 1 / 2 / 3 / 4 / 5 / 6 / 8. הדבר פותח פונטים כבדים של CJK / Indic, כמו Noto Sans CJK ו-Noto Sans Devanagari, שה-GSUB שלהם עולה על 64 KB. אין API נפרד; הפריסה אוטומטית
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
כברירת מחדל המנוע מעדיף את script DFLT, או את ה-script הראשון שהפונט מצהיר עליו, ואת LangSys ברירת המחדל. קרא ל-SetGSUBScript('latn' / 'arab' / 'cyrl' / 'hani' / 'kana' / 'deva' / 'beng' / 'taml' / etc.) ול-SetGSUBLanguage('ENG ' / 'TUR ' / 'AZE ' / 'JAN ' / 'KOR ' / 'ARA ' / etc.), מרופד ברווחים ל-4 bytes, כדי לנעול queries לזוג script / language מסוים. מחרוזת ריקה מחזירה את baseline ברירת המחדל. הבחירות נשמרות בין queries ונמחקות ב-RegisterUnicodeTTF('', nil)
סמנטיקת strict-vs-fallback: ScriptTag לא מוכר גורם ל-queries הבאים להחזיר תוצאות no-op ריקות כדי שקוראים יוכלו לזהות שה-script שבחרו אינו זמין; LangTag לא מוכר נופל חזרה אל LangSys ברירת המחדל של ה-script לפי מוסכמת OpenType. GetGSUBScripts / GetGSUBLanguages / GetGSUBFeatures מונות את מה שהפונט הטעון מפרסם בפועל
LookupFlag honor and GDEF
כל query קורא את LookupFlag של כל Lookup table, ואת markFilteringSet האופציונלי מסוג uint16 שמופיע בסוף כאשר useMarkFilteringSet מוגדר, ומדלג על input glyphs שהדגלים מסמנים להתעלמות. הביטים שמוגדרים במפרט מכובדים: ignoreBaseGlyphs (0x0002, דילוג על GDEF class 1), ignoreLigatures (0x0004, דילוג על class 2), ignoreMarks (0x0008, דילוג על class 3), useMarkFilteringSet (0x0010), וה-byte הגבוה markAttachmentType. ClassDef Format 1 ו-2 מנותחים, וכותרות GDEF v1.0 / v1.1 / v1.2 מתקבלות כולן. פונטים ללא טבלת GDEF נופלים חזרה ל-“אף glyph אינו מתעלם” כדי שהפלט יישאר זהה ברמת byte עבור קוראים המשתמשים בפונטים ללא GDEF
TTF subsetter closure (MarkUnicodeGlyphUsed)
TTF subsetter של HotPDF v2.84.0 גוזר את קבוצת glyphs שבשימוש מתוך FUnicodeUsedCps דרך cmap. GSUB substitute glyphs, כמו חלופות סגנוניות, ligatures ווריאנטים תלויי הקשר, כלומר כל מה ש-7 query APIs שלמעלה מחזירים, בדרך כלל אינם נגישים דרך codepoint ב-cmap, ולכן בעבר היו בלתי נראים ל-subsetter והקורא הצרכני רינדר במקומם .notdef
לאחר פליטת כל GID שהוחזר על ידי GetSingleSubstituteGlyph / GetMultipleSubstituteGlyphs / GetAlternateGlyph / ApplyLigatureSubstitution / ApplyContextualSubst / ApplyReverseChainedContextualSubst אל PDF text stream, קרא ל-MarkUnicodeGlyphUsed(GID) פעם אחת לכל GID שנפלט כדי לכלול אותו ב-embedded subset. העזר idempotent והגנתי, משליך בשקט GIDs מחוץ לטווח, ומשתלב עם מעבר composite-glyph closure של v2.84.0: הקוראים צריכים לסמן רק את ה-substitute GID העליון, ורכיבי composite נמשכים אוטומטית
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
המנוע הוא משטח query של capability בלבד: הוא עונה “מה GSUB היה עושה כאן”, אך אינו מריץ pipeline shaping אוטומטי, כגון layout ברמת Harfbuzz, סידור מחדש מודע cluster עבור Indic, פתרון BiDi, מיקום GPOS או mark attachment. הקוראים אחראים להפעיל את לולאת הסריקה, לבחור איזה substitute / alternate לפלוט, לקרוא ל-MarkUnicodeGlyphUsed לכל substitute GID שנפלט, ולהחיל כל GPOS / mark positioning שהפונט דורש
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 ופוצל ל-8a-8f עבור תיקונים עתידיים)
See also: Arabic / Persian / Urdu Shaping, CFF / OpenType Font Subsetting Functions, THotPDF.EnableFontSubsetting
|