# UDA 造字底色不一致問題技術報告

**日期**：2026-04-19  
**專案**：STK 庫存管理系統 Clipper→Harbour GTWVT 轉換  
**模組**：`E:\ConvertFW\HBSTK\compat\usrfont.prg`  
**狀態**：已修復 ✅

---

## 1. 問題描述

Hint bar（操作提示列）顯示多個按鍵圖示（KY_INS、KY_ALT、KY_F9 等），這些字元全部都是 Unicode PUA 造字，使用同一套 HuaKang 字型渲染，前景色與背景色設定完全相同。

然而實際顯示時，部分按鍵圖示（如 KY_ALT，Unicode 0xE010）的底色呈現**黑色**，其餘（如 KY_F9，Unicode 0xECEA）則正確顯示 hint bar 的橘紅色背景。

使用者觀察：「相同的造字，一樣的程式，一樣的 COLOR，怎麼展示出來不一樣？」

---

## 2. 系統架構背景

### 2.1 渲染管道總覽

```
PRG 端                    C 端（usrfont.prg）
──────────────────────────────────────────────────────
UsrFontGetBig5(wc)        → 查詢/分配 BIG5 UDA slot
@ row, col SAY chr(hi)+chr(lo)
                          → GTWVT 儲存 BIG5 至虛擬畫面
                          → wcharGet 反查 → usChar (E000+slot)
                          → ScanAndDrawUda 偵測並繪製
                             ├─ EnsureUfntAlpha(lookupIdx)
                             │   ├─ ScaleGlyph1bit (USRFONT.24M 路徑)
                             │   └─ CaptureGlyphAlpha (TrueType 路徑)
                             ├─ DrawUdaAlphaBlend (alpha 混合，正確 BG)
                             └─ DrawDynUnicodeGlyph (SRCCOPY，黑底)
```

### 2.2 BIG5 UDA 對應關係

Windows CP950 的 BIG5 UDA 區段（FA40\~FEFE）與 Unicode PUA 有固定的直接對應：

| BIG5 | Unicode |
|------|---------|
| FA40 | E000    |
| FA41 | E001    |
| …    | …       |
| FA50 | E010    |
| …    | …       |

此對應由 `WideCharToMultiByte / MultiByteToWideChar (CP950)` 決定。

### 2.3 兩種 slot 分配方式

**方式 A：直接對應（BIG5_LOOKUP 範圍）**

適用於 Unicode 在 `[UDA_UNICODE_BASE, UDA_UNICODE_BASE + BIG5_LOOKUP_SIZE)` 即 `[0xE000, 0xE311)` 的字元。

`UsrFontGetBig5(wc)` 計算：`lookupIdx = wc - 0xE000`，直接映射至 BIG5 FA40+ slot。

**方式 B：動態分配（DynAllocSlot）**

適用於 Unicode 超出上述範圍的字元（如 0xECEA）。`DynAllocSlot` 在 FA40\~FEFE 中找一個未使用的 slot，設定 `s_aDynUnicode[slot] = wc`，並回傳 slot index。

---

## 3. 根本原因分析

### 3.1 KY_ALT（0xE010）的追蹤路徑

```
UsrFontGetBig5(0xE010)
  → lookupIdx = 0xE010 - 0xE000 = 16
  → BIG5 = FA50（hi=FA, lo=0x40+16=0x50）
  → 傳回 chr(0xFA)+chr(0x50)

@ row, col SAY KY_ALT ...
  → GTWVT 儲存 [FA][50]

wcharGet([FA][50])
  → lookupIdx = 16
  → s_aBig5Lookup[16] = -1（無 USRFONT.24M 字根）
  → s_aDynUnicode[16] = 0（尚未設定）
  → 呼叫 WideCharToMultiByte(CP950, [FA][50]) → 0xE010
  → 設定 s_aDynUnicode[16] = 0xE010   ← 自我參照！
  → 回傳 usChar = UDA_UNICODE_BASE + 16 = 0xE010

ScanAndDrawUda
  → lookupIdx = 16
  → s_aBig5Lookup[16] = -1 → 非 USRFONT.24M 路徑
  → s_aDynUnicode[16] = 0xE010 ≠ 0 → 進入 EnsureUfntAlpha

EnsureUfntAlpha(16)
  → wc = s_aDynUnicode[16] = 0xE010
  → Guard 判斷：0xE010 在 [0xE000, 0xE310] 內？→ YES
  → 判定為「自我參照，無意義」→ 跳過 CaptureGlyphAlpha
  → pAlpha = NULL → s_aUfntAlpha[16] = NULL

回到 ScanAndDrawUda
  → s_aUfntAlpha[16] = NULL
  → 落入 DrawDynUnicodeGlyph(wc=0xE010, crFG, crBG)
     → 填黑底（0x000000）
     → TextOut 白色字
     → StretchBlt SRCCOPY → 黑底直接貼上螢幕
     → crBG（橘紅色）完全被忽略
```

### 3.2 KY_F9（0xECEA）的追蹤路徑

```
UsrFontGetBig5(0xECEA)
  → 0xECEA 超出 [0xE000, 0xE311) 範圍
  → DynAllocSlot: 找到空 slot（例如 slot=770）
  → s_aDynUnicode[770] = 0xECEA   ← 非自我參照
  → BIG5 = FE42（hi=FE, lo=0x40+...）
  → 傳回 chr(0xFE)+chr(...)

wcharGet([FE][...])
  → lookupIdx = 770
  → s_aDynUnicode[770] = 0xECEA（已設定）
  → 回傳 usChar = UDA_UNICODE_BASE + 770 = 0xE302

ScanAndDrawUda
  → lookupIdx = 770
  → s_aDynUnicode[770] = 0xECEA ≠ 0 → EnsureUfntAlpha

EnsureUfntAlpha(770)
  → wc = 0xECEA
  → Guard 判斷：0xECEA 在 [0xE000, 0xE310] 內？→ NO（0xECEA > 0xE310）
  → 呼叫 CaptureGlyphAlpha(0xECEA, nW, nH)
     → 使用華康楷書W5A 渲染 F9 鍵圖示
     → 取得 alpha mask
  → s_aUfntAlpha[770] = alpha mask

回到 ScanAndDrawUda
  → DrawUdaAlphaBlend(alpha, crFG, crBG)
     → 每個像素：output = crFG * alpha + crBG * (1-alpha)
     → 底色正確顯示橘紅色
```

### 3.3 問題根源

`EnsureUfntAlpha` 中的 guard 條件：

```c
/* Only capture real (non-PUA-self-ref) Unicode */
if( wc < ( HB_WCHAR ) UDA_UNICODE_BASE ||
    wc > ( HB_WCHAR )( UDA_UNICODE_BASE + BIG5_LOOKUP_SIZE - 1 ) )
   pAlpha = CaptureGlyphAlpha( wc, nW, nH );
```

**Guard 的原意**：避免為「沒有實際字形」的自我參照 PUA 字元浪費時間呼叫 CaptureGlyphAlpha。

**Guard 的錯誤假設**：認為 E000\~E310 範圍的 PUA 字元沒有 HuaKang 字形。實際上，HuaKang 字型在整段 PUA 範圍（E000\~ECXX）都設計了按鍵圖示字形，包括 E010（Alt）、E000（ESC）等。

---

## 4. 修復方案

### 4.1 修改位置

`usrfont.prg`，函式 `EnsureUfntAlpha`

### 4.2 修改內容

**修改前：**
```c
else if( s_aDynUnicode[ lookupIdx ] != 0 )
{
   HB_WCHAR wc = s_aDynUnicode[ lookupIdx ];
   /* Only capture real (non-PUA-self-ref) Unicode */
   if( wc < ( HB_WCHAR ) UDA_UNICODE_BASE ||
       wc > ( HB_WCHAR )( UDA_UNICODE_BASE + BIG5_LOOKUP_SIZE - 1 ) )
      pAlpha = CaptureGlyphAlpha( wc, nW, nH );
}
```

**修改後：**
```c
else if( s_aDynUnicode[ lookupIdx ] != 0 )
{
   /* HuaKang has glyphs throughout PUA range (incl. self-refs E000..E310);
    * always capture - if no glyph, alpha=0 -> solid crBG (no black BG leak) */
   pAlpha = CaptureGlyphAlpha( s_aDynUnicode[ lookupIdx ], nW, nH );
}
```

### 4.3 修改理由

- 移除 self-ref guard 後，所有 `s_aDynUnicode != 0` 的 slot 都走 `CaptureGlyphAlpha → DrawUdaAlphaBlend`
- 若 HuaKang 字型對某個 Unicode 確實沒有字形，`CaptureGlyphAlpha` 返回全零 alpha mask，`DrawUdaAlphaBlend` 會填入純 crBG 底色，視覺上顯示為空白格，比黑底更合理
- 不影響有 USRFONT.24M 字根的 slot（`s_aBig5Lookup[lookupIdx] >= 0` 分支優先執行）

### 4.4 Patch 腳本

`E:\ConvertFW\HBSTK\build\patch_ensure_alpha_selfref.py`

---

## 5. 渲染路徑比較

| 項目 | DrawDynUnicodeGlyph（修復前 KY_ALT） | DrawUdaAlphaBlend（修復後 / KY_F9） |
|------|--------------------------------------|--------------------------------------|
| 背景處理 | 黑底（0x000000）硬填 | crBG 完整參與 alpha 混合 |
| 合成方式 | StretchBlt SRCCOPY（覆蓋） | 逐像素 alpha blend |
| 字形來源 | TextOutW 即時渲染（系統字型 fallback） | CaptureGlyphAlpha 快取 alpha mask |
| 底色正確 | ❌ | ✅ |
| 快取效率 | 每次重繪 | 首次後快取至 s_aUfntAlpha |

---

## 6. 驗證結果

修復後，hint bar 所有按鍵圖示（KY_INS、KY_ALT、KY_ENTER、KY_DEL、KY_F9、KY_F10 等）底色一致，均正確顯示橘紅色背景，與 hint bar 顏色相符。

---

## 7. 相關檔案

| 檔案 | 說明 |
|------|------|
| `compat\usrfont.prg` | 主程式，含 EnsureUfntAlpha 修改 |
| `compat\usrfont.prg.bak_dynalpha_20260419` | 修改前備份 |
| `build\patch_ensure_alpha_selfref.py` | 自動化 patch 腳本 |
| `source\WINFORM.PRG` | Hint bar acMessage 定義（KY_* 組合） |
| `source\TLIB.PRG` | BottomMessage 函式，實際 SAY 至畫面 |
| `include\CustomChar.ch` | KY_* Unicode PUA 定義 |
