THotPDF.AssignSyntheticCodepointForGID / GetSyntheticCodepointForGID

THotPDF PUA synthetic codepoint allocator (v2.119.68)

 

GSUB Engine  Auto Shaping Pipeline  Arabic Shaping

为 OpenType GSUB substitute GIDs 分配并查询 Private Use Area(U+E000 - U+F8FF)synthetic codepoints,这些 GID 没有可通过字体 cmap 到达的自然 Unicode codepoint。它补上 v2.119.43-66 GSUB query 和 refinement APIs 留下的 producer-side GID-level emission 缺口

 

Delphi 语法:

function AssignSyntheticCodepointForGID(GID: Word; out SyntheticCP: Word): Boolean;

function GetSyntheticCodepointForGID(GID: Word): Word;

 

Why the API exists

v2.119.32-67 producer-side automatic shaping pipeline(Arabic / Latin / Devanagari)要求 GSUB engine 返回的 substitute GIDs 能通过 Unicode codepoint 到达;现有 hex-encoded text pipeline 输出的是 codepoints 而不是 GIDs,消费端阅读器再通过嵌入的 /CIDToGIDMap 将 codepoint 解析回 GID。对于字体 cmap 中有自然 Unicode codepoint 的 substitute GIDs(Arabic Presentation Forms、Latin Standard Ligatures FB00-FB06),现有管线工作正常

 

但落在字体内部 GIDs 上的 font-specific substitutes 没有任何 codepoint 存在于字体 cmap 中,例如多数 Devanagari cluster shapes、字体设计者只作为编号 GID 提供的 stylistic alternates、CJK ideographic variation sequences(IVS)、以及没有对应 Presentation Form 的 discretionary ligatures。v2.119.68 之前,这些 GIDs 无法通过 producer-side hex pipeline 到达;v2.119.68 允许调用方为任意 GID 在 Private Use Area 分配 synthetic codepoint,从而补上该缺口

 

AssignSyntheticCodepointForGID semantics

为提供的 GID 分配下一个可用 PUA codepoint(从 U+E000 开始),并将分配同步到现有 producer-side hex pipeline + consumer-reader resolution chain 依赖的所有缓存:

 

1. FUnicodeCpToGid[SyntheticCP] := GID - so the producer-side hex pipeline emits SyntheticCP into the text-showing operator and the consumer reader resolves SyntheticCP back to GID through /CIDToGIDMap at render time

2. FAcroFormUnicodeAdvances[SyntheticCP] := em-fraction - so the v2.65 word-wrap calculator finds the correct hmtx advance for the synthetic codepoint when it appears in AcroForm text-field content

3. FUnicodeSyntheticCpForGID[GID] := SyntheticCP - the per-GID reverse-lookup table used by GetSyntheticCodepointForGID to make repeat AssignSyntheticCodepointForGID calls idempotent (the second call with the same GID returns the already-allocated SyntheticCP)

 

成功时返回 True,并将 SyntheticCP 设为已分配 codepoint。以下防御性条件会返回 False(并将 SyntheticCP 保持为 0):未注册字体(从未调用 RegisterUnicodeTTF,或以空参数调用以重置状态)、无效 GID(0 或超出 cmap glyph count)、PUA 范围耗尽(U+E000 - U+F8FF 的 6400 个槽位全部分配)、入口处缓存未初始化

 

GetSyntheticCodepointForGID semantics

对任何既有分配执行纯函数查询。如果之前已经调用 AssignSyntheticCodepointForGID(GID, ...),则返回为GID(如果之前调用过 AssignSyntheticCodepointForGID(GID, ...));否则返回 0(0 不是有效 PUA codepoint,因此也作为“未分配”哨兵)。不执行分配。可在任何 AssignSyntheticCodepointForGID 运行前安全调用

 

Allocator state lifecycle

FUnicodeSyntheticCpForGID 和 next-available-PUA cursor(FUnicodeNextSyntheticCp)会在首次 AssignSyntheticCodepointForGID 调用时惰性分配。cursor 从 0(未初始化)开始,并在首次分配时跳到 $E000;后续分配依次移动到 $E001、$E002,直到 $F8FF。每次 RegisterUnicodeTTF('', nil) 都会随其余 per-font subset state 一起将两个字段重置为空 / 0,因此复用 THotPDF 实例生成多个文档的调用方,每个文档都会从新的 PUA cursor 开始

 

典型工作流 (Devanagari cluster shape)

 

PDF.RegisterUnicodeTTF('NotoDeva', 'NotoSansDevanagari-Regular.ttf');

PDF.ShapingFeatures := [sfIndicShaping];

PDF.SetGSUBScript('deva');

 

// Get a font-internal cluster GID through the GSUB engine

ClusterGID := PDF.GetSingleSubstituteGlyph(BaseGID, 'nukt');

if ClusterGID <> BaseGID then

begin

  // Check if cmap reaches the substitute - usually no for Indic

  // clusters, since cluster GIDs are font-internal

  // Allocate a synthetic codepoint that the producer-side

  // hex pipeline can emit

  if PDF.AssignSyntheticCodepointForGID(ClusterGID, SyntheticCP) then

  begin

    // SyntheticCP is now in the U+E000-F8FF range; emit it

    // through UnicodeTextOut just like a normal codepoint

    PDF.CurrentPage.UnicodeTextOut(X, Y, 0, UnicodeChar(SyntheticCP));

    PDF.MarkUnicodeGlyphUsed(ClusterGID);

  end;

end;

 

Idempotency example

 

PDF.AssignSyntheticCodepointForGID(150, CP1);  // CP1 = $E000

PDF.AssignSyntheticCodepointForGID(151, CP2);  // CP2 = $E001

PDF.AssignSyntheticCodepointForGID(150, CP3);  // CP3 = $E000 (idempotent)

CP4 := PDF.GetSyntheticCodepointForGID(150);  // CP4 = $E000

CP5 := PDF.GetSyntheticCodepointForGID(999);  // CP5 = 0 (no assignment)

 

Consumer-reader behaviour

消费端阅读器会在 text-showing operator 中看到 PUA codepoint,并通过文档嵌入的 /CIDToGIDMap 将其解析到目标 GID,然后用嵌入字体程序渲染该 GID。从阅读器视角看,经 cmap 路由到 GID 的“自然”Unicode codepoint,与经 /CIDToGIDMap 路由到 GID 的 PUA synthetic codepoint 没有区别,都会产生相同渲染 glyph

 

复制 / 粘贴行为:当 ToUnicode CMap 将 PUA codepoints 声明为 identity mappings 时,PUA codepoints 会按自身往返。希望往返为源 Unicode 字符(产生 substitute 的输入 run)的调用方,可用 RegisterToUnicodeReverseMapping注册 reverse mapping,或通过 BeginTaggedContent 编写ActualText 标记内容序列属性,并在括起的括号内容内输出合成 codepoint。HotPDF 对包含补充平面 Unicode 字符的 RegisterUnicodeTTF-backed AcroForm appearance streams 会自动使用相同的 internal-CID pattern

 

Phase 8 roadmap closure

v2.119.68 / Phase 8c.6 闭合 Phase 8 GSUB engine roadmap:LookupType 1-8 查询 API(Phase 1-6)、Script / LangSys selection API(Phase 7)、TTF subsetter closure 入口(Phase 9)、静态 post-pass 连字折叠(v2.119.32 / 58 / 60 / 62)、opt-in automatic pipeline(v2.119.59)、Arabic rlig + Latin liga / clig + rclt 自动输出(Phase 8b / 8c.2 / 8b / GSUB 'rclt')、ToUnicode reverse-mapping(v2.119.61 / 62 / 65)、advance query(v2.119.64)、Devanagari Indic reorder pre-pass(v2.119.67),以及现在的 PUA synthetic codepoint GID-level emit(v2.119.68),全部集成为单一 producer-side shaping surface,可处理 OpenType 字体能产生的各种 substitute glyph

 

另请参阅: OpenType GSUB Substitution Engine, Automatic Shaping Pipeline (Phase 8), Arabic / Persian / Urdu 塑形支持, Syriac / Mongolian / Devanagari Shaping, THotPDF.BeginTaggedContent