موتور جایگزینی OpenType GSUB

سطح قابلیت‌های جایگزینی glyph (v2.119.43 - v2.119.50)

 

شکل‌دهی عربی  CFF / زیرمجموعه‌سازی OpenType

موتور OpenType GSUB (Glyph SUBstitution) داخل HotPDF به فراخواننده اجازه می‌دهد همه نوع جایگزینی گلیف را که یک فونت OpenType اعلام می‌کند پرس‌وجو، هدایت و صادر کند - لیگاتور‌ها، جایگزین‌های سبکی، واریانت‌های وابسته به متن، فرم‌های شکل‌دهی عربی / Indic، فرم‌های جایگزین CJK و غیره. همه LookupTypeهای 1 تا 8 در OpenType GSUB پیاده‌سازی شده‌اند و به‌صورت یک سطح پرس‌وجوی صرفاً قابلیتی در دسترس‌اند؛ فراخواننده خروجی متن را هدایت می‌کند و تصمیم می‌گیرد کدام گلیف جایگزین در جریان محتوای صفحه نوشته شود

 

رابط عمومی

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 یک فونت را parse کرده و جدول‌های GSUB / GDEF / cmap آن را در cache نگه دارد. هر پرس‌وجوی substitution زنجیرهٔ ScriptList / LangSysList / FeatureList / LookupList فونت را پیمایش می‌کند و آن را به handler مناسب LookupType می‌فرستد. 12 متد بالا تمام سطح عمومی را تشکیل می‌دهند؛ همهٔ چیزهای دیگر (پیمایش cmap، parse شدن ScriptList، lookup جدول Coverage، resolve شدن ClassDef، رعایت LookupFlag، بازکردن wrapper مربوط به Extension، و dispatch تو در توی SequenceLookupRecord) پشت همان سطح قرار دارند

 

قرارداد دفاعی در سراسر مسیر برقرار است: فونت‌های بدون جدول GSUB، feature tagهای غیرچهاربایتی، featureهایی که script / language انتخاب‌شده اعلام نمی‌کند، GIDهایی که هیچ subtableی پوشش نمی‌دهد، و input glyphهایی که LookupFlag نادیده می‌گیرد، همگی یک no-op امن برمی‌گردانند (False / OutGID = InputGID / OutGIDs خالی / ConsumedCount = 1) تا فراخواننده‌ها برای حالت‌های معمول «هیچ substitutionی اعمال نمی‌شود» exception نبینند

 

ماتریس LookupType

LookupType 1 (Single Substitution) - یک glyph به یک substitute نگاشت می‌شود. featureهای رایج: salt، ss01-ss20، smcp، onum، و liga وقتی LookupType 1 برای آن wired شده باشد، به‌علاوهٔ formهای موقعیتی عربی init / medi / fina / isol در فونت‌هایی که آن‌ها را از طریق GSUB هدایت می‌کنند. از GetSingleSubstituteGlyph استفاده کنید

LookupType 2 (Multiple Substitution) - یک glyph به دنباله‌ای از glyphهای جایگزین شکسته می‌شود. کاربرد رایج: ccmp برای Glyph Composition / Decomposition (حروف لاتین accentدارِ precomposed به base + combining markها می‌شکنند تا موقعیت‌دهی mark در مراحل بعدی انجام شود). از GetMultipleSubstituteGlyphs استفاده کنید

LookupType 3 (Alternate Substitution) - یک glyph به یکی از N alternate نگاشت می‌شود. featureهای رایج: aalt (Access All Alternates)، salt وقتی به‌صورت Type 3 wired شده باشد، titl (Titling Alternates)، و مجموعه‌های stylistic ss01-ss20 وقتی طراح فونت برای هر slot بیش از یک alternate ارائه کرده باشد. از GetAlternateGlyphCount + GetAlternateGlyph استفاده کنید

LookupType 4 (Ligature Substitution) - N glyph ورودی به یک ligature fold می‌شوند. featureهای رایج: liga (Standard Ligatures: fi / fl / ffi / ffl)، clig (Contextual Ligatures)، dlig (Discretionary Ligatures)، hlig (Historical Ligatures)، rlig (Required Ligatures - Arabic LAM-ALEF و موارد مشابه)، و ligatureهای scriptهای Indic (akhn، pres، blws، psts). از ApplyLigatureSubstitution استفاده کنید

LookupType 5 (Contextual Substitution) + LookupType 6 (Chained Contextual Substitution) - با یک دنبالهٔ glyph ورودی match می‌شوند و nested lookupها را در موقعیت‌های مشخص داخل همان match dispatch می‌کنند. هر سه variant قالب (1 دنبالهٔ literal، 2 دنبالهٔ ClassDef، 3 دنبالهٔ Coverage) پیاده‌سازی شده‌اند؛ dispatcherِ SequenceLookupRecord دوباره وارد LookupList می‌شود و nested lookupهای Single / Multiple / Alternate (اولی) / Ligature را با tracking زندهٔ MatchPositions مدیریت می‌کند. featureهای رایج: rclt (Required Contextual Alternates - Arabic init/medi/fina/isol وقتی GSUB-driven باشد)، clig، calt، و shaping Indic شامل pres / blws / psts / half / pstf / cjct. از ApplyContextualSubst استفاده کنید (یک نقطهٔ ورود هر دو LookupType 5 و 6 را پوشش می‌دهد)

LookupType 7 (Extension Substitution) - یک لایهٔ pure indirection که spec OpenType برای فونت‌هایی تعریف می‌کند که subtable جایگزینی آن‌ها فراتر از دسترس 16 بیتی LookupList قرار دارد. هر API عمومی به‌طور شفاف indirection 32 بیتی Offset32 را تا subtable واقعی LookupType 1 / 2 / 3 / 4 / 5 / 6 / 8 دنبال می‌کند. این سازوکار فونت‌های سنگین CJK / Indic مثل Noto Sans CJK و Noto Sans Devanagari را که GSUB آن‌ها از 64 KB فراتر می‌رود، باز می‌کند. API جداگانه‌ای ندارد - unwrap به‌صورت خودکار انجام می‌شود

LookupType 8 (Reverse Chained Contextual Single Substitution) - substitution 1:1 آگاه از زمینه که ویژگی متمایز آن این است که فراخواننده باید آن را در ترتیب reverse scan روی یک run چندglyphی (end -> start) اعمال کند، چون هر substitute ممکن است به زمینهٔ lookahead آینده وابسته باشد؛ زمینه‌ای که هنوز نباید substitution شده باشد. کاربرد رایج: alternateهای contextual عربی / سریانی / N'Ko / Indic که form نهایی آن‌ها به glyph بعدی وابسته است. از ApplyReverseChainedContextualSubst استفاده کنید؛ حلقهٔ reverse scan را خود فراخواننده هدایت می‌کند

 

انتخاب Script / LangSys

به‌طور پیش‌فرض موتور GSUB اسکریپت DFLT (یا نخستین اسکریپتی را که فونت اعلام می‌کند) و LangSys پیش‌فرض را ترجیح می‌دهد. برای قفل‌کردن پرس‌وجوها روی یک جفت script / language مشخص، SetGSUBScript('latn' / 'arab' / 'cyrl' / 'hani' / 'kana' / 'deva' / 'beng' / 'taml' / etc.) و SetGSUBLanguage('ENG ' / 'TUR ' / 'AZE ' / 'JAN ' / 'KOR ' / 'ARA ' / etc., trailing-space padded to 4 bytes) را فراخوانی کنید. رشتهٔ خالی مسیر پیش‌فرض را برمی‌گرداند. این انتخاب‌ها بین پرس‌وجوها باقی می‌مانند و با RegisterUnicodeTTF('', nil) پاک می‌شوند

 

معنای strict-vs-fallback: یک ScriptTag ناشناخته باعث می‌شود پرس‌وجوهای بعدی نتیجهٔ no-op خالی برگردانند تا فراخواننده بتواند بفهمد script انتخاب‌شده در دسترس نیست؛ یک LangTag ناشناخته طبق convention OpenType به LangSys پیش‌فرض همان script fallback می‌شود. GetGSUBScripts / GetGSUBLanguages / GetGSUBFeatures آنچه فونت بارگذاری‌شده واقعاً اعلام می‌کند را فهرست می‌کنند

 

رعایت LookupFlag و GDEF

هر پرس‌وجو LookupFlag هر جدول Lookup را می‌خواند (و وقتی useMarkFilteringSet تنظیم شده باشد، markFilteringSet اختیاریِ 16بیتیِ انتهای آن را هم در نظر می‌گیرد) و glyphهای ورودی علامت‌خورده برای ignore را رد می‌کند. بیت‌های تعریف‌شده در spec رعایت می‌شوند: ignoreBaseGlyphs (0x0002، رد class 1 در GDEF)، ignoreLigatures (0x0004، رد class 2)، ignoreMarks (0x0008، رد class 3)، useMarkFilteringSet (0x0010)، و بایت بالاییِ markAttachmentType. هر دو Format 1 و 2 از ClassDef parse می‌شوند؛ headerهای GDEF v1.0 / v1.1 / v1.2 همگی پذیرفته می‌شوند. فونت‌های بدون جدول GDEF به حالت «هیچ glyphی نادیده گرفته نمی‌شود» برمی‌گردند تا خروجی برای فراخواننده‌هایی که از فونت‌های بدون GDEF استفاده می‌کنند کاملاً همسانِ بایتی بماند

 

بستن subsetterِ TTF (MarkUnicodeGlyphUsed)

subsetterِ TTF نسخهٔ v2.84.0 در HotPDF مجموعهٔ glyphهای استفاده‌شده را از طریق cmap و بر پایهٔ FUnicodeUsedCps استخراج می‌کند. glyphهای جایگزین GSUB (alternateهای stylistic، ligatureها، variantهای contextual - هر چیزی که 7 API پرس‌وجوی بالا برمی‌گردانند) معمولاً هیچ codepointی ندارند که از طریق cmap به آن‌ها برسد، بنابراین قبلاً برای subsetter نامرئی بودند و reader مصرف‌کننده به‌جای آن‌ها .notdef رندر می‌کرد

 

پس از صدور هر GID برگشتی از GetSingleSubstituteGlyph / GetMultipleSubstituteGlyphs / GetAlternateGlyph / ApplyLigatureSubstitution / ApplyContextualSubst / ApplyReverseChainedContextualSubst در یک PDF text stream، برای هر GID صادرشده یک بار MarkUnicodeGlyphUsed(GID) را فراخوانی کنید تا آن glyph وارد subset تعبیه‌شده شود. این helper idempotent و دفاعی است (GIDهای خارج از محدوده بی‌صدا حذف می‌شوند) و با مرحلهٔ بستن composite glyph نسخهٔ v2.84.0 یکپارچه می‌شود: فراخواننده فقط لازم است GID جایگزینِ سطح بالا را علامت بزند - مؤلفه‌های composite به‌صورت خودکار کشیده می‌شوند

 

گردش‌کار معمول (حروف کوچک لاتین)

 

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;

 

گردش‌کار معمول (لیگاتور Arabic LAM-ALEF)

 

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;

 

دامنه و محدودیت‌ها

این موتور فقط یک سطح پرس‌وجوی قابلیتی است: به سؤال «GSUB اینجا چه می‌کرد» پاسخ می‌دهد، اما یک خط لوله شکل‌دهی خودکار را اجرا نمی‌کند (چیدمان در سطح Harfbuzz، بازآرایی آگاه از خوشه برای Indic، حل BiDi، موقعیت‌دهی GPOS، اتصال mark). مسئولیت اجرای حلقه اسکن، انتخاب این‌که کدام substitute / alternate صادر شود، فراخوانی MarkUnicodeGlyphUsed برای هر GID جایگزین صادرشده، و اعمال هر موقعیت‌دهی GPOS / mark موردنیاز فونت بر عهده فراخواننده است

 

شکل‌دهی عربی / فارسی / اردو در سمت تولیدکننده (ligature اجباری LAM-ALEF + Arabic Presentation Forms-A) به‌صورت یک خط لولهٔ داخلی جداگانه پیاده‌سازی شده است که هنگام text emission به‌طور خودکار اجرا می‌شود - شکل‌دهی عربی / فارسی / اردو را ببینید

 

رد نسخه

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 API انتخاب Script / LangSys (Phase 7). v2.119.50 بستن subsetterِ TTF از طریق MarkUnicodeGlyphUsed (Phase 9؛ Phase 8 یک spike در ادغام shaping سمت تولیدکننده بود و برای نسخه‌های آینده به 8a-8f تقسیم شد)

 

همچنین ببینید: شکل‌دهی عربی / فارسی / اردو, CFF / OpenType Font Subsetting Functions, THotPDF.EnableFontSubsetting