Arabic / Persian / Urdu Shaping Support

Producer-side shaping pipeline (v2.85.0 - v2.119.68)

 

OpenType GSUB Engine  CFF / OpenType Subsetting

HotPDF 會在 producer-side 執行 shaping pipeline,在 PDF 文字輸出時將 Unicode 輸入的阿拉伯文、波斯文與烏爾都文 runs 摺疊為 Arabic Presentation Forms,因此閱讀端會收到可直接 render 的 positional / ligature glyphs,不需要自行具備 Harfbuzz-class shaper

 

What the pipeline does

Positional shaping(v2.85.0):每個 Arabic base letter 最多有四種 positional forms:isolated(isol)、initial(init)、medial(medi)、final(fina)。HotPDF 會檢查每個阿拉伯字元的 join class 及其相鄰字元的 join classes,然後在輸出前將輸入 codepoint 對應到適當的 Arabic Presentation Forms-B(U+FE70 - U+FEFC)glyph。Non-joining letters、joiners、transparent characters(combining marks)與 Tatweel kashida 都會依 Unicode Arabic Shaping algorithm 處理isol), initial (init), medial (medi), final (fina). HotPDF inspects each Arabic character's join class and the join classes of its immediate neighbors, then maps the input codepoint to the appropriate Arabic Presentation Forms-B (U+FE70 - U+FEFC) glyph before emission. Non-joining letters, joiners, transparent characters (combining marks), and the Tatweel kashida are all handled per the Unicode Arabic Shaping algorithm.

 

LAM-ALEF 必要連字(v2.119.32):阿拉伯 Unicode 成形規格要求任何 LAM(U+0644)後面緊接 ALEF(U+0627 一般形、U+0622 上方 madda、U+0623 上方 hamza、U+0625 下方 hamza)時,必須摺疊為單一連字字形(U+FEFB - U+FEFC 的獨立形與結尾形,並搭配適當變體)。這是阿拉伯文字排版中少數不可選的連字之一,也是正確性要求;若以分離字形呈現,母語讀者會立即看出文字格式錯誤。HotPDF 會在輸出期間執行此摺疊,因此呼叫端不需要在自己的成形流程中加入額外 shaping engine

 

Persian / Urdu core 9 letters(v2.119.35):波斯文與烏爾都文以不在 Arabic Presentation Forms-B block(U+FE70 - U+FEFC)中的字母擴充阿拉伯文。最常用的 9 個這類字母,包括 PEH(U+067E)、TCHEH(U+0686)、JEH(U+0698)、KEH(U+06A9 / U+06AF)、GAF(U+06AF)、NOON GHUNNA(U+06BA)、HEH DOACHASHMEE(U+06BE)、YEH BARREE(U+06D2)與 HEH GOAL(U+06C1),在 Arabic Presentation Forms-A(U+FB50 - U+FDFF)中有 presentation forms。HotPDF 現在會在 positional shaping 期間將這些字母對應到適當 Forms-A glyph,因此波斯文與烏爾都文在任何閱讀端都能以正確的 init / medi / fina / isol forms render

 

Arabic Extended-A + Supplement(v2.119.52、v2.119.56):joining-class table 現在涵蓋其餘 Arabic Extended-A characters(ALEF WASLA / NOON GHUNNA / HEH variants)、Arabic Supplement U+0750-U+077F,以及較高的 Arabic Extended-A U+08A0-U+08FF 範圍。具有 static Presentation Forms-A encoding 的字元會透過既有 4-position shaper 對應;沒有對應的字元(多數 Extended-A)只取得 joining-class classification,因此即使該字元本身原樣傳遞,相鄰字元仍可正確 shaping。v2.119.56 修正了 v2.119.52 引入的兩個錯誤 Forms-A mappings(U+06C2 / U+06C3)

 

Persian / Urdu Form-B full coverage(v2.119.57):joining-class table 擴充到完整 U+0672-U+06D5 範圍(約 80 個字元,涵蓋 REH / DAL / SEEN / SAD / TAH / AIN / FEH / QAF / KAF / GAF / LAM / NOON / HEH / WAW / YEH variants),並新增 26 個 Presentation Forms-A mappings(15 個 D-class 4-form + 11 個 R-class 2-form)。特別是烏爾都文標準 'h' HEH DOACHASHMEE(U+06BE → FBAA-FBAD)與詞尾 yeh YEH BARREE(U+06D2 → FBAE-FBAF),現在已正確對應到 Forms-A slots;這些 slots 曾被 v2.119.52 的 ALEF WASLA / NOON GHUNNA 暫時修正誤用。Static Forms-A coverage 現在涵蓋 40+ 個 source characters

 

YEH-HAMZA + vowel ligature post-pass(v2.119.58):positional shaping 後加入 _ApplyArabicShaping 的 post-pass,涵蓋 Forms-A block U+FBEA-U+FBFB 中的 8 組 ligature pairs:YEH-HAMZA + ALEF / AE / WAW / U / OE / YU / E / ALEF MAKSURA。每組會輸出 isolated form(FBEA / FBEC / FBEE / FBF0 / FBF2 / FBF4 / FBF6 / FBF9)加上 base+1 final form。starting / medial form slots FBF8 / FBFB 透過 sfArabicGSUB opt-in 留給 GSUB engine。建立於與 v2.119.32 LAM-ALEF 相同的 skeleton_ApplyArabicShaping after positional shaping covers the 8 ligature pairs in Forms-A block U+FBEA-U+FBFB - YEH-HAMZA + ALEF / AE / WAW / U / OE / YU / E / ALEF MAKSURA. Each pair emits the isolated form (FBEA / FBEC / FBEE / FBF0 / FBF2 / FBF4 / FBF6 / FBF9) plus the base+1 final form. The starting / medial form slots FBF8 / FBFB are left to the GSUB engine via sfArabicGSUB opt-in. Built on the same skeleton as v2.119.32 LAM-ALEF.

 

Allah ligature (v2.119.60): the four-character sequence ALEF + LAM + LAM + HEH folds into U+FDF2 ARABIC LIGATURE ALLAH ISOLATED FORM. Implemented as a codepoint-level static post-pass after v2.119.32 LAM-ALEF and v2.119.58 YEH-HAMZA in the _ApplyArabicShaping chain.

 

Bismillah phrase ligature (v2.119.62): the standard 22-codepoint Bismillah phrase "بسم الله الرحمن الرحيم" folds into a single glyph U+FDFD ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM. Runs as the very first pre-pass in _ApplyArabicShaping (before LAM-ALEF), so the folded Bismillah glyph reaches the rest of the pipeline as one codepoint and is not retouched by downstream substitutions.

 

Public Query Helpers

GetArabicJoiningClass returns the Unicode joining class used by the static Arabic shaper. GetArabicPosition resolves one character in a run to isolated, initial, medial, or final position so callers can inspect or mirror the same shaping decisions before emitting text

 

How callers invoke it

先用 PDF.AutoShapeArabic:= True 開啟 pipeline;之後它會在 Unicode 文字輸出方法內自動執行,不需要明確的「幫我 shape」呼叫。把 Unicode 輸入的阿拉伯文 / 波斯文 / 烏爾都文字串傳給 THPDFPage.TextOut、THPDFPage.UnicodeTextOut 或 THPDFPage.RtLTextOut,HotPDF 就會在寫入 PDF 文字顯示 operator 前,把每個輸入 codepoint 對應到它的 presentation form。以頁面字型繪製的 free text annotations 也會以同樣方式被 shaping。原始 Unicode 位元組同時會擷取進 FUnicodeUsedCps 供 ToUnicode CMap 產生使用,所以閱讀端的複製 / 貼上與螢幕閱讀器看到的仍是原始 Unicode 內容

 

Font requirements

The font registered via RegisterUnicodeTTF must contain glyphs for both the Arabic Presentation Forms-B block (U+FE70 - U+FEFC) and (for Persian / Urdu) the relevant Arabic Presentation Forms-A range (U+FB50 - U+FDFF). Recommended fonts that ship the full set:

 

Allah ligature(v2.119.60):四字元序列 ALEF + LAM + LAM + HEH 會摺疊為 U+FDF2 ARABIC LIGATURE ALLAH ISOLATED FORM。實作為 _ApplyArabicShaping chain 中 v2.119.32 LAM-ALEF 與 v2.119.58 YEH-HAMZA 之後的 codepoint-level static post-pass

 

Bismillah phrase ligature(v2.119.62):標準 22-codepoint Bismillah phrase「بسم الله الرحمن الرحيم」會摺疊為單一 glyph U+FDFD ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM。它作為 _ApplyArabicShaping 中最先執行的 pre-pass(早於 LAM-ALEF),因此已摺疊的 Bismillah glyph 會以一個 codepoint 進入後續 pipeline,不會再被 downstream substitutions 改動

 

Public Query Helpers

GetArabicJoiningClass 會回傳 static Arabic shaper 使用的 Unicode joining class。GetArabicPosition 會將 run 中的一個字元解析為 isolated、initial、medial 或 final 位置,讓呼叫端可在輸出文字前檢查或鏡像相同的 shaping decisions

 .notdef.

 

Typical workflow

 

PDF.RegisterUnicodeTTF('NotoArab', 'NotoSansArabic-Regular.ttf');

PDF.AutoShapeArabic:= True;

PDF.BeginDoc;

PDF.CurrentPage.SetFont('NotoArab', [], 14);

PDF.CurrentPage.RtLTextOut(100, 700, 0,

  UnicodeString(#$0645#$0631#$062D#$0628#$0627));  // "marhaba" (hello)

PDF.EndDoc;

 

Automatic Phase 8 pipeline integration (v2.119.59 - v2.119.68)

v2.119.59 introduced the opt-in ShapingFeatures: THPDFShapingFeatures property and the THPDFShapingFeature enum, which elevates the shaping pipeline beyond static post-pass folding into automatic GSUB-driven font-specific substitution. Set PDF.ShapingFeatures := [sfArabicGSUB] to apply the registered font's native Arabic init, medi, fina, isol, rlig, calt, and rclt lookups and emit the resulting contextual glyph IDs; add sfCursiveAttachment to apply GPOS cursive entry/exit anchors to that shaped run; add sfStandardLigatures for Latin Standard Ligatures (FB00-FB06 ff / fi / fl / ffi / ffl / ſt / st via v2.119.65 ApplyLatinLigatureRefinement); add sfIndicShaping to enable the Devanagari pre-pass reorder (v2.119.67). Default [] preserves byte-identical output for callers who depend on the static pipeline.

 

設定 sfArabicGSUB 時,static Unicode Presentation Forms shaper 會被略過,改用字型自己的 GSUB rules。當這些 glyphs 無法從 font cmap 取得時,HotPDF 會透過 synthetic codepoints 輸出 native contextual glyph IDs、保持 embedded subset 封閉,並為 copy and paste 寫入 ToUnicode reverse mappings。若呼叫端希望 static shaper 繼續處理字型 GSUB declarations 未涵蓋的 codepoints,應保持 sfArabicGSUB 關閉並依賴 static post-pass chainsfArabicGSUB is set, the static Unicode Presentation Forms shaper is bypassed in favour of the font's own GSUB rules. HotPDF emits native contextual glyph IDs through synthetic codepoints when those glyphs are not reachable from the font cmap, keeps the embedded subset closed, and writes ToUnicode reverse mappings for copy and paste. Callers who want the static shaper to keep running for codepoint coverage outside what the font's GSUB declares should leave sfArabicGSUB off and rely on the static post-pass chain

 

ToUnicode CMap ligature reverse mapping (v2.119.61, v2.119.62, v2.119.65)

The Adobe-Identity-UCS ToUnicode CMap emitted by RegisterUnicodeTTF ships bfchar reverse-mapping entries for every ligature codepoint the post-pass chain can produce. v2.119.61 added 27 entries (8 LAM-ALEF + 18 YEH-HAMZA family + 1 Allah); v2.119.62 added Bismillah (U+FDFD); v2.119.65 added 7 Latin Standard Ligature entries (FB00-FB06). Consumer-reader copy / paste resolves any ligature glyph back to the source codepoint sequence, so the rendered PDF stays accessibility-friendly.

 

PUA synthetic codepoint emit (v2.119.68)

AssignSyntheticCodepointForGID(GID; out CP): Boolean + GetSyntheticCodepointForGID(GID): Word 讓 producer code 可輸出沒有自然 Unicode codepoint 可透過 font cmap 抵達的 GSUB substitute GIDs。allocator 會在 Private Use Area(U+E000 - U+F8FF,6400 個 slots)分配 codepoints,並將設定鏡射到 FUnicodeCpToGid(讓閱讀端的 /CIDToGIDMap 可把 synthetic CP 解析回 target GID)、FAcroFormUnicodeAdvances(讓 v2.65 word-wrap 找到正確 em-fraction),以及 per-GID reverse-lookup table(讓重複設定請求具備冪等性)。這適用於 GSUB 引入但 cmap 無法抵達的 Devanagari cluster shapes、stylistic alternates 與 CJK ideographic variation sequencesAssignSyntheticCodepointForGID(GID; out CP): Boolean + GetSyntheticCodepointForGID(GID): Word let producer code emit GSUB substitute GIDs that have no natural Unicode codepoint reachable through the font's cmap. The allocator hands out codepoints in the Private Use Area (U+E000 - U+F8FF, 6400 slots) and mirrors the assignment into FUnicodeCpToGid (so /CIDToGIDMap resolves the synthetic CP back to the target GID at the consumer reader), FAcroFormUnicodeAdvances (so v2.65 word-wrap finds the correct em-fraction), and a per-GID reverse-lookup table (so repeat assignment requests are idempotent). Use this for Devanagari cluster shapes, stylistic alternates, and CJK ideographic variation sequences that GSUB introduces but cmap does not reach.

 

Relationship to the OpenType GSUB engine

上述 static post-pass shaper(v2.85.0 + v2.119.32 / 58 / 60 / 62)預設獨立於 OpenType GSUB engine。透過 ShapingFeatures 啟用 sfArabicGSUB 時,GSUB engine 會成為 producer-side emission path 的一部分:HotPDF 會查詢 cmap 以建立 base GID array,對該 glyph run 套用 native Arabic positional 與 contextual GSUB lookups,為沒有 Unicode Presentation Form 的 substitute glyph IDs 分配 synthetic codepoints,並呼叫 MarkUnicodeGlyphUsed 讓 substitute glyphs 保留在 embedded font subset 內sfArabicGSUB is enabled through ShapingFeatures, the GSUB engine becomes part of the producer-side emission path: HotPDF consults the cmap to build a base GID array, applies native Arabic positional and contextual GSUB lookups to that glyph run, assigns synthetic codepoints for substitute glyph IDs that have no Unicode Presentation Form, and calls MarkUnicodeGlyphUsed to keep the substitute glyphs inside the embedded font subset.

 

對於沒有 Unicode codepoint 的 substitute glyphs,呼叫端可將 ShapingFeatures 與 v2.119.68 PUA synthetic-codepoint allocator 搭配使用,讓 substitute GID 仍可透過標準 hex pipeline 抵達。對字型 GSUB declarations 未涵蓋的 codepoints,static post-pass chain 仍是預設 fallbackShapingFeatures with the v2.119.68 PUA synthetic-codepoint allocator so the substitute GID is still reachable through the standard hex pipeline. The static post-pass chain remains the default fallback for codepoints that the font's GSUB declarations do not cover.

 

Scope and limitations

BiDi algorithm 仍不屬於本頁範圍:呼叫端仍需要自行排列混合 LTR 與 RTL runs,或使用獨立的 BiDi library。GPOS、Indic/Tibetan/Mongolian、Hebrew、N'Ko、Adlam、Thai/Lao 與 Javanese 支援現在位於各自的 opt-in shaping flag 與 API 頁面,因此本頁只聚焦 static 與 GSUB-based Arabic shaping

 

請參閱: OpenType GSUB Substitution Engine, Automatic Shaping Pipeline (Phase 8), Syriac / Mongolian / Devanagari Shaping, THotPDF.AssignSyntheticCodepointForGID, THPDFPage.RtLTextOut, THPDFPage.UnicodeTextOut, CFF / OpenType Font Subsetting