# USRFONT 系統技術參考手冊

> 文件版本：2026-04-18  
> 適用檔案：`E:\ConvertFW\HBSTK\compat\usrfont.prg`  
> 系統：STK 庫存管理系統 / Harbour GTWVT Console Mode

---

## 目錄

1. [系統概述](#1-系統概述)
2. [架構圖](#2-架構圖)
3. [常數定義](#3-常數定義)
4. [資料結構](#4-資料結構)
5. [靜態變數](#5-靜態變數)
6. [三種檔案格式（詳細）](#6-三種檔案格式詳細)
7. [核心流程](#7-核心流程)
8. [函式說明](#8-函式說明)
9. [渲染路徑（A/B 雙路）](#9-渲染路徑ab-雙路)
10. [BIG5 Hook 機制](#10-big5-hook-機制)
11. [UDA 索引計算](#11-uda-索引計算)
12. [與 ASCFONT 的比較](#12-與-ascfont-的比較)
    - [12.1 統一命名慣例（.UMAP / .BCACHE）](#121-統一命名慣例umap--bcache)
13. [重要陷阱與注意事項](#13-重要陷阱與注意事項)

---

## 1. 系統概述

### 1.1 目的

USRFONT 系統負責在 GTWVT Console 視窗中正確顯示 **BIG5 使用者自定義字（UDA, User-Defined Area）**。

BIG5 UDA 範圍為 `FA40`–`FEFE`（5 個高位元組 × 157 個低位元組 = 785 個可能字位），對應客戶於原 CLIPPER/DOS 系統中使用的造字區自定義中文字（例如：廠商名稱、品項代碼中的非標準字）。

GTWVT 是純 Unicode 終端機，無法直接處理 BIG5 UDA 字位，因此 USRFONT 採用兩層策略：

1. **字型檔（USRFONT.24M）**：84 個預先點陣化的 1-bit 字形
2. **動態 Unicode 映射（USRFONT.UMAP）**：785 個字位的 Unicode 對照表

### 1.2 ASCFONT 對比

| 項目 | USRFONT | ASCFONT |
|------|---------|---------|
| 來源字元 | BIG5 FA40–FEFE（UDA 雙位元組） | ASCII 控制字元（<0x20, 0x7F） |
| 字形數量 | 84（字型檔）+ 動態映射 | 13 個固定符號 |
| 觸發條件 | `usChar` 在 E000–E30F 範圍 | `usChar < 0x0020 \|\| == 0x007F` |
| 快取格式 | UFNT（USRFONT.BCACHE） | ASCF（ASCFONT.BCACHE） |
| 映射表 | USRFONT.UMAP（1570 bytes raw） | ASCFONT.UMAP（512 bytes raw） |
| 字形寬度 | 雙寬（nCellW × 2） | 單寬（nCellW） |

---

## 2. 架構圖

```
BIG5 來源串流（程式輸出）
        │
        ▼
┌─────────────────────────────┐
│  usrfont_wcharGet (Hook)    │  ← 攔截 GTWVT 的 BIG5→Unicode 轉換
│  Big5ToLookupIdx            │
│  FA40–FEFE → PUA E000+idx  │
└─────────────┬───────────────┘
              │ Unicode PUA 存入 GTWVT 內部 Buffer
              ▼
       GTWVT 視窗重繪（WM_PAINT）
              │
              ▼
┌─────────────────────────────────────────────────────────────┐
│  ScanAndDrawUda（WndProc 攔截）                              │
│                                                              │
│  逐格掃描 hb_gtGetChar()                                     │
│       │                                                      │
│       ├─ usChar = E000–E30F（USRFONT UDA）                  │
│       │       │                                              │
│       │       ├─ s_aBig5Lookup[idx] >= 0（有 24M 字形）     │
│       │       │       └─► DrawOneGlyph                       │
│       │       │               ├─ EnsureUfntAlpha             │
│       │       │               │   ├─ ScaleGlyph1bit（1-bit）│
│       │       │               │   └─ 存入 s_aUfntAlpha[]    │
│       │       │               └─► DrawUdaAlphaBlend（A路）  │
│       │       │                   或 StretchBlt fallback（B路）│
│       │       │                                              │
│       │       └─ s_aDynUnicode[idx] != 0（有 Unicode 映射） │
│       │               └─► EnsureUfntAlpha                   │
│       │                   ├─ wc 非 PUA → CaptureGlyphAlpha │
│       │                   │   (TrueType → alpha mask)       │
│       │                   └─ wc 是 PUA → DrawDynUnicodeGlyph│
│       │                       (SSAA HuaKang W5A TrueType)   │
│       │                                                      │
│       ├─ usChar < 0x20 || == 0x7F（ASCFONT 符號）           │
│       │       └─► DrawAscFontGlyph（ASCFONT 系統）          │
│       │                                                      │
│       └─ usChar = E311–F8FF（IME 超範圍 PUA）               │
│               └─► DrawDynUnicodeGlyph                        │
│                                                              │
│  掃描結束 → s_nUfntDirty → SaveUsrFontUmap（BCACHE）        │
└─────────────────────────────────────────────────────────────┘
              │
              ▼
       正確顯示於 GTWVT 視窗
```

---

## 3. 常數定義

```c
/* USRFONT.24M 字型檔結構 */
#define FILE_HEADER_SIZE   256   /* CMEX_PTN 檔頭大小（不含字形資料）*/
#define GLYPH_TAG_SIZE       4   /* 每個字形槽的 tag（01 80 xx xx）*/
#define GLYPH_BITMAP_SIZE   72   /* 24×24 點陣圖 = 3 bytes/列 × 24 列 */
#define GLYPH_SLOT_SIZE     76   /* tag(4) + bitmap(72) */
#define MAX_GLYPH_COUNT     84   /* 字型檔最大字形數 */
#define GLYPH_WIDTH         24   /* 字形原始寬度（像素）*/
#define GLYPH_HEIGHT        24   /* 字形原始高度（像素）*/

/* BIG5 UDA 範圍計算 */
#define UDA_UNICODE_BASE  0xE000 /* Unicode PUA 起始（BIG5 FA40 對應）*/
#define UDA_FB_COUNT       157   /* 每個高位元組的有效低位元組數
                                    = 63(40-7E) + 94(A1-FE) = 157 */
#define BIG5_LOOKUP_SIZE   785   /* 5 高位元組(FA-FE) × 157 = 785 字位 */
#define CMEX_TAG_BASE    0xE000  /* CMEX tag = U+E000 + UDA 索引 */

/* 動態渲染緩衝（SSAA 超取樣）*/
#define DYN_SCALE            4   /* 4× 縮放倍率（96 = 24×4 像素）*/
#define DYN_FONT_H  (GLYPH_HEIGHT * DYN_SCALE)  /* 96px 字型高度 */
#define DYN_BUF_W   (GLYPH_WIDTH  * DYN_SCALE)  /* 96px 緩衝寬度 */
#define DYN_BUF_H   (GLYPH_HEIGHT * DYN_SCALE)  /* 96px 緩衝高度 */

/* 繪製記錄（用於 RestoreScreen 重繪）*/
#define MAX_DRAWN_CELLS    256

/* ASCFONT（共用同一 usrfont.prg，但獨立邏輯）*/
#define ASCFONT_PUA_BASE 0xE400
#define ASCFONT_COUNT       13
```

---

## 4. 資料結構

### 4.1 GLYPH_INFO — 字形映射項目

```c
typedef struct {
    unsigned char big5hi;   /* BIG5 高位元組（FA–FE）*/
    unsigned char big5lo;   /* BIG5 低位元組（40–7E, A1–FE）*/
    int           filePos;  /* 字形在 USRFONT.24M 的槽位序號（0–83）*/
} GLYPH_INFO;
```

- 由 `USRFONT_LOAD` 掃描 CMEX tag 後建立
- 84 個槽位（`s_aGlyphs[MAX_GLYPH_COUNT]`）

### 4.2 DRAWN_CELL — 繪製記錄

```c
typedef struct {
    int       row, col;   /* 視窗格位置 */
    HB_USHORT usChar;     /* 該格對應的 Unicode PUA 字元 */
} DRAWN_CELL;
```

- 最多記錄 256 格（`s_aDrawnCells[]`）
- 用於 `WM_TIMER` 重繪（解決 GTWVT RestoreScreen 清除字形問題）

### 4.3 ASCF_ENTRY — ASCFONT 快取項目（共用）

```c
typedef struct {
    unsigned char asc_byte;  /* ASCII 控制字元值 */
    BYTE *        pAlpha;    /* nW×nH alpha mask 指標（malloc）*/
} ASCF_ENTRY;
```

---

## 5. 靜態變數

### 5.1 字型資料

| 變數 | 型別 | 說明 |
|------|------|------|
| `s_pFontData` | `BYTE *` | USRFONT.24M 完整檔案內容 |
| `s_nFontDataLen` | `int` | 檔案大小（bytes）|
| `s_aGlyphs[]` | `GLYPH_INFO[84]` | 字形映射表（BIG5 ↔ 槽位）|
| `s_nGlyphs` | `int` | 已載入字形數量 |
| `s_aBig5Lookup[]` | `int[785]` | lookupIdx → s_aGlyphs 索引（-1=無）|

### 5.2 Unicode 動態映射

| 變數 | 型別 | 說明 |
|------|------|------|
| `s_aDynUnicode[]` | `HB_WCHAR[785]` | lookupIdx → Unicode codepoint（0=未定義）|
| `s_szMapFile[]` | `char[MAX_PATH]` | USRFONT.UMAP 路徑 |

### 5.3 USRFONT Alpha 快取

| 變數 | 型別 | 說明 |
|------|------|------|
| `s_aUfntAlpha[]` | `BYTE *[785]` | lookupIdx → alpha mask（lazy 生成）|
| `s_nUfntCellW` | `int` | 快取建立時的 cell 全寬（= nCellW×2）|
| `s_nUfntCellH` | `int` | 快取建立時的 cell 高度 |
| `s_nUfntDirty` | `int` | 1=有新字形需存入 BCACHE |
| `s_szUfntUmapFile[]` | `char[MAX_PATH]` | USRFONT.BCACHE 路徑 |

### 5.4 GDI 資源（共用快取，避免重複建立）

| 變數 | 說明 |
|------|------|
| `s_hdcMemScan` / `s_hBmpScan` | 24×24 共用 DIBSection（DrawOneGlyph B路）|
| `s_pBitsScan` | s_hBmpScan 的直接像素指標 |
| `s_hDynFont` | HuaKang W5A 字型句柄（96px, FW_BOLD）|
| `s_hdcDynBuf` / `s_hbmDynBuf` | 96×96 SSAA 緩衝 DIBSection |
| `s_pDynBits` | s_hbmDynBuf 直接像素指標 |

### 5.5 視窗 Hook

| 變數 | 說明 |
|------|------|
| `s_pOrigWndProc` | GTWVT 原始 WndProc |
| `s_hWndGtwvt` | GTWVT 視窗句柄 |
| `s_pOrigWcharGet/Put/Len` | 原始 BIG5 codepage 函式指標 |
| `s_aColors[16]` | CGA 16 色 COLORREF 表 |

---

## 6. 三種檔案格式（詳細）

---

### 6.1 USRFONT.24M — 點陣字型檔（CMEX_PTN 格式）

**用途**：儲存 84 個 UDA 自定義字的原始 1-bit 點陣圖  
**來源**：原 CLIPPER/DOS 系統的 CMEX 造字程式產生  
**大小**：256 + 84×76 = **6640 bytes**（固定）

#### 整體結構

```
┌──────────────────────────────────────────────┐
│  FILE HEADER（256 bytes，保留，不解析）       │
├──────────────────────────────────────────────┤
│  GLYPH SLOT #0（76 bytes）                   │
├──────────────────────────────────────────────┤
│  GLYPH SLOT #1（76 bytes）                   │
├──────────────────────────────────────────────┤
│  ...                                          │
├──────────────────────────────────────────────┤
│  GLYPH SLOT #83（76 bytes）                  │
└──────────────────────────────────────────────┘
```

#### 每個 GLYPH SLOT（76 bytes）

```
Offset  Size  內容
──────  ────  ───────────────────────────────────────
  0      1    固定值 0x01
  1      1    固定值 0x80
  2      2    CMEX Tag（Little-Endian WORD）
               = UDA_UNICODE_BASE + UDA 索引
               = 0xE000 + ((hi-0xFA)*157 + loOfs)
  4     72    1-bit 點陣圖（24列 × 3 bytes/列 = 72 bytes）
               每列 3 bytes = 24 bits，bit7=左側像素
               pixel(col) = (row_byte[col/8] >> (7-col%8)) & 1
               1 = 前景色，0 = 背景色
```

#### CMEX Tag 解碼

```
CMEX Tag（WORD, LE）= 0xE000 + udaIndex

udaIndex 反推 BIG5：
  hiOfs = udaIndex / 157        → hi = 0xFA + hiOfs
  loOfs = udaIndex % 157
  if loOfs < 63: lo = 0x40 + loOfs
  else:          lo = 0xA1 + (loOfs - 63)
```

#### 讀取範例（C 語言）

```c
BYTE * pSlot    = s_pFontData + 256 + slotIndex * 76;
WORD   cmexTag  = pSlot[2] | (pSlot[3] << 8);
BYTE * pBitmap  = pSlot + 4;   /* 72 bytes 1-bit bitmap */

/* 取得 (row, col) 像素值 */
int bit = (pBitmap[row*3 + col/8] >> (7 - col%8)) & 1;
```

---

### 6.2 USRFONT.UMAP — Unicode 動態對照表（Raw Array 格式）

**用途**：儲存 785 個 BIG5 UDA 字位對應的 Unicode codepoint  
**大小**：785 × 2 = **1570 bytes**（固定）  
**格式**：純 Raw Binary，無 Magic Header  
**重要**：此檔案結構與舊版系統完全相容，**不可更改格式**

#### 整體結構

```
┌──────────────────────────────────────────────┐
│  s_aDynUnicode[0]   (2 bytes, LE WORD)       │  ← lookupIdx=0 (BIG5 FA40)
├──────────────────────────────────────────────┤
│  s_aDynUnicode[1]   (2 bytes, LE WORD)       │  ← lookupIdx=1 (BIG5 FA41)
├──────────────────────────────────────────────┤
│  ...                                          │
├──────────────────────────────────────────────┤
│  s_aDynUnicode[784] (2 bytes, LE WORD)       │  ← lookupIdx=784 (BIG5 FEFE)
└──────────────────────────────────────────────┘
```

#### 每個項目的值意義

| 值 | 意義 | 渲染行為 |
|----|------|----------|
| `0x0000` | 未定義 | 不渲染（顯示 GTWVT 預設）|
| `0xE000`–`0xE30F` | PUA 自參照（UDA 範圍）| DrawDynUnicodeGlyph（HuaKang TrueType PUA 字形）|
| `0x4E00`–`0x9FFF` | CJK 標準 Unicode | GTWVT 直接渲染（IsNativeFullwidth=TRUE）|
| 其他 Unicode | 非 CJK 符號 | CaptureGlyphAlpha → DrawUdaAlphaBlend |
| （USRFONT.24M 有字形的 slot） | 由 s_aBig5Lookup 優先處理 | ScaleGlyph1bit → DrawUdaAlphaBlend |

#### lookupIdx 計算

```c
/* BIG5 FA40–FEFE → lookupIdx（0–784）*/
int Big5ToLookupIdx( unsigned char hi, unsigned char lo )
{
    int hiOfs = hi - 0xFA;         /* 0–4 */
    int loOfs;
    if( lo >= 0x40 && lo <= 0x7E )
        loOfs = lo - 0x40;         /* 0–62 (共 63 個) */
    else if( lo >= 0xA1 && lo <= 0xFE )
        loOfs = (lo - 0xA1) + 63;  /* 63–156 (共 94 個) */
    else
        return -1;                  /* 非法低位元組 */
    return hiOfs * 157 + loOfs;    /* 0–784 */
}
```

#### 讀取/寫入（C 語言）

```c
/* 讀取（LoadDynMap）*/
HB_WCHAR tmpMap[ 785 ];
ReadFile( hFile, tmpMap, 785 * 2, &dwRead, NULL );
/* 只載入 s_aBig5Lookup[i] < 0 的 slot（避免覆蓋 24M 字形）*/
for( i = 0; i < 785; i++ )
    if( tmpMap[i] != 0 && s_aBig5Lookup[i] < 0 )
        s_aDynUnicode[i] = tmpMap[i];

/* 寫入（SaveDynMap）*/
WriteFile( hFile, s_aDynUnicode, 785 * 2, &dwWritten, NULL );
```

#### 初始資料範例（十六進位）

```
Offset 0x00: 00E0 01E0 02E0 03E0 00 00 ...
             ↑         ↑         ↑
             slot 0    slot 1    slot 2
             =0xE000   =0xE001   =0xE002
             (FA40 → U+E000 PUA 自參照，
              由 HuaKang W5A 的 PUA 字形渲染)
```

---

### 6.3 USRFONT.BCACHE — Alpha Mask 離線快取（UFNT 格式）

**用途**：儲存已渲染的 alpha mask bitmap（離線備援，加速重啟後首次顯示）  
**格式**：自訂 UFNT 二進位格式，含 Magic Header  
**大小**：動態（依字形數量與 cell 尺寸而定）  
**注意**：與 cell 尺寸（字型大小）綁定，視窗縮放後自動重建

#### 整體結構

```
┌──────────────────────────────────────────────┐
│  Magic: "UFNT" (4 bytes, ASCII)              │
├──────────────────────────────────────────────┤
│  Cell Width  wW (2 bytes, LE WORD)           │  ← = nCellW × 2（雙寬）
├──────────────────────────────────────────────┤
│  Cell Height wH (2 bytes, LE WORD)           │  ← = nCellH
├──────────────────────────────────────────────┤
│  Count wCount (2 bytes, LE WORD)             │  ← 快取字形數量（最多 785）
├──────────────────────────────────────────────┤
│  ENTRY #0                                     │
│    lookupIdx (2 bytes, LE WORD)              │  ← 0–784
│    alpha[wW × wH] (wW*wH bytes)              │  ← 0=BG, 255=FG, 中間=抗鋸齒
├──────────────────────────────────────────────┤
│  ENTRY #1                                     │
│    lookupIdx (2 bytes)                        │
│    alpha[wW × wH] (wW*wH bytes)              │
├──────────────────────────────────────────────┤
│  ... × wCount                                 │
└──────────────────────────────────────────────┘
```

#### Header 計算（10 bytes）

```
Offset  Size  內容
──────  ────  ─────────────────────────────
  0      4    "UFNT"（Magic）
  4      2    wW = nCellW × 2（全寬 cell 像素寬度）
  6      2    wH = nCellH（cell 像素高度）
  8      2    wCount（快取字形總數）
```

#### 每個 Entry 大小

```
Entry Size = 2 (lookupIdx) + wW × wH (alpha bytes)
```

例如：wW=32, wH=22 時，每個 Entry = 2 + 32×22 = 706 bytes

#### Alpha Mask 格式

- 每 byte 代表一個像素的不透明度：`0`=完全透明（背景色）、`255`=完全不透明（前景色）
- 來源分為兩類：
  - **ScaleGlyph1bit**（USRFONT.24M 字形）：每個像素只有 0 或 255（1-bit 放大）
  - **CaptureGlyphAlpha**（TrueType 擷取）：含中間值（ClearType 抗鋸齒）

#### 讀取驗證（LoadUsrFontUmap）

```c
/* 必須通過以下所有驗證才能使用快取 */
1. Magic == "UFNT"          → 否則重建
2. wW == 目前 nCellW × 2    → 否則重建（視窗縮放後）
3. wH == 目前 nCellH         → 否則重建

/* 驗證失敗 → 清除 s_aUfntAlpha[]，返回 HB_FALSE */
/* 驗證成功 → 逐一載入 Entry 到 s_aUfntAlpha[lookupIdx] */
```

#### 寫入條件（SaveUsrFontUmap）

```c
/* 觸發條件：s_nUfntDirty == 1（EnsureUfntAlpha 生成新字形）*/
/* 在 ScanAndDrawUda 掃描結束後、ReleaseDC 之前執行 */
if( s_nUfntDirty ) {
    SaveUsrFontUmap();
    s_nUfntDirty = 0;
}
```

---

### 6.4 三種格式對照表

| 屬性 | USRFONT.24M | USRFONT.UMAP | USRFONT.BCACHE |
|------|-------------|--------------|----------------|
| 用途 | 原始字形點陣圖 | Unicode 對照表 | Alpha 渲染快取 |
| Magic | 無（CMEX_PTN）| 無（Raw Array）| "UFNT" |
| 大小 | 固定 6640 bytes | 固定 1570 bytes | 動態（依尺寸）|
| 格式 | 1-bit 點陣圖 | Raw HB_WCHAR[] | 8-bit alpha mask |
| 來源 | CMEX 造字程式 | 系統自動生成 | 程式首次渲染後快取 |
| 可刪除？ | 不可（核心資料）| 不可（映射資料）| 可（會重建）|
| 需重建條件 | 不需 | 不需 | 視窗尺寸改變時 |
| 路徑設定 | `s_szMapFile`（讀）| `s_szMapFile`（讀寫）| `s_szUfntUmapFile`（讀寫）|
| 路徑由來 | `USRFONT_LOAD` 參數 | 同左（副檔名 .UMAP）| 同左（副檔名 .BCACHE）|

---

## 7. 核心流程

### 7.1 初始化流程（啟動時）

```
main.prg
  └─ UsrFontInit( cFontFile )           [Harbour]
       ├─ USRFONT_LOAD( cFontFile )     [C HB_FUNC]
       │    ├─ 讀取 USRFONT.24M 到 s_pFontData
       │    ├─ 掃描 CMEX tag → 建立 s_aBig5Lookup[] + s_aGlyphs[]
       │    ├─ 設定 s_szMapFile = USRFONT.UMAP 路徑
       │    ├─ 設定 s_szUfntUmapFile = USRFONT.BCACHE 路徑
       │    └─ LoadDynMap() → 載入 s_aDynUnicode[]
       │
       ├─ 替換 BIG5 codepage hook
       │    s_pOrigWcharGet = cdp->wcharGet
       │    cdp->wcharGet   = usrfont_wcharGet
       │
       └─ hb_idleAdd( UsrFontTryHook )
            └─（等待 GTWVT 視窗出現後）
                 SetWindowLongPtr( GWLP_WNDPROC, UsrFontWndProc )
```

### 7.2 字元輸出流程（每次 Say/QOut）

```
Harbour 程式輸出 BIG5 UDA 字串
  │
  ├─ hb_gtPutStr() → 內部呼叫 cdp->wcharGet
  │
  ▼
usrfont_wcharGet( pSrc, hi=FAxx, lo=xxxx )
  ├─ lookupIdx = Big5ToLookupIdx( hi, lo )
  ├─ 若 s_aBig5Lookup[idx] >= 0（24M 有字形）
  │    → *wc = UDA_UNICODE_BASE + idx（E000+idx）
  ├─ 若 s_aDynUnicode[idx] != 0（有 Unicode 映射）
  │    → 若是 CJK → *wc = 真實 Unicode（GTWVT 直渲）
  │    → 否則 → *wc = UDA_UNICODE_BASE + idx
  └─ 首次遇到 → MultiByteToWideChar(CP950) 自動建立映射
                → SaveDynMap()
```

### 7.3 渲染流程（WM_PAINT 時）

```
WM_PAINT → UsrFontWndProc
  ├─ 呼叫原始 WndProc（GTWVT 繪製系統字型）
  │
  └─ ScanAndDrawUda( hWnd )
       ├─ 取得 nCellW, nCellH（from GetClientRect / nRows / nCols）
       ├─ 若 cell 尺寸改變 → LoadUsrFontUmap（BCACHE）
       │
       └─ 逐格掃描：
            ├─ UDA 字元（E000–E30F）
            │    ├─ 有 24M 字形 → DrawOneGlyph
            │    │    ├─ EnsureUfntAlpha（首次：ScaleGlyph1bit）
            │    │    └─ DrawUdaAlphaBlend（A路）
            │    │         或 StretchBlt（B路 fallback）
            │    │
            │    └─ 有 Unicode 映射 → EnsureUfntAlpha
            │         ├─ wc 非 PUA → CaptureGlyphAlpha
            │         │    └─ DrawUdaAlphaBlend
            │         └─ wc 是 PUA → DrawDynUnicodeGlyph
            │              └─ SSAA 96px → HALFTONE StretchBlt
            │
            ├─ 控制字元（< 0x20, 0x7F）→ ASCFONT 系統（略）
            └─ 超範圍 PUA（E311–F8FF）→ DrawDynUnicodeGlyph
       │
       └─ 若 s_nUfntDirty → SaveUsrFontUmap（BCACHE）
```

---

## 8. 函式說明

### 8.1 Harbour 層函式

| 函式 | 用途 |
|------|------|
| `UsrFontInit( cFile )` | 初始化整個 USRFONT 系統 |
| `UsrFontTryHook()` | Idle callback，等待 GTWVT 視窗出現後 subclass |
| `USRFONT_LOAD( cFile )` | 載入 .24M 字型檔，建立映射表 |
| `USRFONT_GETBIG5( nUnicode )` | Unicode → BIG5 雙位元組字串 |
| `USRFONT_SETPALETTE( aColors )` | 更新 16 色調色盤 |
| `USRFONT_DEBUGMAP()` | 輸出映射表 debug 資訊 |

### 8.2 C 層內部函式

| 函式 | 說明 |
|------|------|
| `Big5ToLookupIdx(hi, lo)` | BIG5 雙位元組 → lookupIdx（0–784）|
| `IsNativeFullwidth(wc)` | 判斷 Unicode 是否可由 GTWVT 直接渲染 |
| `DynAllocSlot(wc)` | 為 Unicode 分配空閒 UDA slot |
| `SaveDynMap()` | 儲存 s_aDynUnicode[] → USRFONT.UMAP |
| `LoadDynMap()` | 載入 USRFONT.UMAP → s_aDynUnicode[] |
| `ScaleGlyph1bit(pSrc, nW, nH)` | 24×24 1-bit → nW×nH alpha mask（最近鄰）|
| `CaptureGlyphAlpha(wc, nW, nH)` | TrueType 字形 → nW×nH alpha mask（DirectDC）|
| `EnsureUfntAlpha(hdc, idx, nW, nH)` | 確保 alpha cache 已填入（Lazy）|
| `DrawUdaAlphaBlend(...)` | 以 alpha mask 渲染字形（CPU 混色 + BitBlt）|
| `DrawOneGlyph(...)` | USRFONT.24M 字形渲染入口（A路 + B路）|
| `DrawDynUnicodeGlyph(...)` | SSAA 動態 TrueType 渲染（96px → StretchBlt）|
| `ScanAndDrawUda(hWnd)` | 主掃描函式（WM_PAINT 後呼叫）|
| `LoadUsrFontUmap(nW, nH)` | 載入 USRFONT.BCACHE alpha 快取 |
| `SaveUsrFontUmap()` | 儲存 USRFONT.BCACHE alpha 快取 |
| `UsrFontWndProc(...)` | GTWVT 視窗 subclass handler |

---

## 9. 渲染路徑（A/B 雙路）

### 9.1 USRFONT.24M 字形（主路）

```
DrawOneGlyph()
  │
  ├─【A 路】EnsureUfntAlpha → s_aUfntAlpha[idx] 有值
  │    └─ DrawUdaAlphaBlend
  │         ├─ CreateCompatibleDC + CreateDIBSection（nW×nH 32-bit）
  │         ├─ CPU Alpha Blend 迴圈：
  │         │    pixel.R = (BG.R*(255-a) + FG.R*a) / 255
  │         ├─ CreateRectRgn clip → BitBlt → 清除 ClipRgn
  │         └─ DeleteDC → DeleteObject（正確順序，防 GDI 洩漏）
  │
  └─【B 路】（fallback，EnsureUfntAlpha 失敗時）
       ├─ 從 s_pFontData 讀取 1-bit bitmap
       ├─ 逐像素寫入 24×24 DIBSection
       └─ StretchBlt( COLORONCOLOR, 24×24 → nW×nH )
```

### 9.2 動態 Unicode 字形（PUA 自參照）

```
DrawDynUnicodeGlyph()
  │
  ├─ 建立/重用 96×96 DIBSection（s_hdcDynBuf）
  ├─ 建立/重用 96px HuaKang W5A FW_BOLD ANTIALIASED 字型
  ├─ 黑底白字 TextOutW（白色 = R=255）
  ├─ GetTextExtentPoint32W 取得實際字寬
  └─ StretchBlt( HALFTONE, srcW×96 → nW×nH )
       ↑ SSAA：96px 高解析度縮小到 cell 大小（抗鋸齒效果）
```

### 9.3 動態 Unicode 字形（非 PUA，alpha 快取路）

```
EnsureUfntAlpha() → CaptureGlyphAlpha()
  │
  ├─ CreateCompatibleDC（from screen DC）
  ├─ CreateDIBSection（nW×nH 32-bit，黑底）
  ├─ 建立 HuaKang W5A 字型（-nH 高度，ClearType）
  ├─ GetTextMetrics → yOff = -tmInternalLeading
  ├─ 特殊處理：C1 控制字元（0x80–0x9F）
  │    → GetGlyphIndicesW + ETO_GLYPH_INDEX（TextOutW 無效）
  ├─ TextOutW → 白色字形於黑色背景
  ├─ 提取 R 通道作為 alpha（GdiFlush 後直接讀像素）
  └─ 1px 邊緣延伸（填補水平間隙）
       └─► DrawUdaAlphaBlend（同 9.1 A路）
```

---

## 10. BIG5 Hook 機制

### 10.1 Hook 安裝

```c
/* 取得 BIG5 codepage 結構 */
PHB_CODEPAGE cdp = hb_cdpFind( "BIG5" );

/* 替換 wcharGet（BIG5 → Unicode 轉換）*/
s_pOrigWcharGet = cdp->wcharGet;
cdp->wcharGet   = usrfont_wcharGet;

/* 替換 wcharPut（Unicode → BIG5 轉換，保持原始）*/
/* 替換 wcharLen（字元長度判斷）*/
```

### 10.2 Hook 邏輯（usrfont_wcharGet）

```
輸入：BIG5 串流中的一個字元位置

高位元組 hi：
  ├─ 0x7F → 輸出 U+007F（ASCFONT 特殊符號）
  ├─ FA–FE + 有效低位元組 →
  │    ├─ lookupIdx = Big5ToLookupIdx(hi, lo)
  │    ├─ s_aBig5Lookup >= 0 → 輸出 E000+idx（讓 ScanAndDrawUda 畫）
  │    ├─ s_aDynUnicode != 0 →
  │    │    ├─ IsNativeFullwidth → 輸出真實 Unicode（GTWVT 直渲）
  │    │    └─ 否則 → 輸出 E000+idx
  │    └─ 首次遇到 → 查 CP950 → 建立映射 → SaveDynMap
  └─ 其他 → 委派給原始 BIG5 handler
```

### 10.3 wcharGet 特殊案例

| 情況 | 處理 |
|------|------|
| `0x7F` (DEL) | 強制輸出 `U+007F`（避免 BIG5 handler 崩潰）|
| Lead byte 在最後 | 輸出 U+0020（保持欄位寬度）|
| FA–FE 但無效 lo | 委派原始 handler |
| CJK 全形 Unicode | 直接輸出（GTWVT 處理全寬）|

---

## 11. UDA 索引計算

### 11.1 BIG5 UDA 範圍分佈

```
高位元組 FA → lookupIdx  0–156  (FA40–FAFE 共 157 字)
高位元組 FB → lookupIdx 157–313 (FB40–FBFE 共 157 字)
高位元組 FC → lookupIdx 314–470 (FC40–FCFE 共 157 字)
高位元組 FD → lookupIdx 471–627 (FD40–FDFE 共 157 字)
高位元組 FE → lookupIdx 628–784 (FE40–FEFE 共 157 字)

有效低位元組：
  40–7E：63 個（loOfs 0–62）
  A1–FE：94 個（loOfs 63–156）
  無效：7F–A0（跳過）
```

### 11.2 換算公式

```c
/* BIG5 → lookupIdx */
hiOfs    = hi - 0xFA;          /* 0–4 */
loOfs    = (lo <= 0x7E) ? (lo - 0x40) : (lo - 0xA1 + 63);
lookupIdx = hiOfs * 157 + loOfs;

/* lookupIdx → BIG5 */
hiOfs = lookupIdx / 157;
loOfs = lookupIdx % 157;
hi    = 0xFA + hiOfs;
lo    = (loOfs < 63) ? (0x40 + loOfs) : (0xA1 + loOfs - 63);

/* lookupIdx → Unicode PUA */
unicodePUA = 0xE000 + lookupIdx;   /* E000–E30F */
```

---

## 12. 與 ASCFONT 的比較

| 比較項目 | USRFONT | ASCFONT |
|----------|---------|---------|
| 字元集 | BIG5 FA40–FEFE（雙位元組） | ASCII < 0x20, 0x7F（單位元組）|
| 字形數量 | 84（24M）+ 最多 785（動態）| 13（固定）|
| 字型檔 | USRFONT.24M（CMEX_PTN 格式）| 無（直接 TrueType 擷取）|
| 對照表（.UMAP） | USRFONT.UMAP（1570 bytes raw）| ASCFONT.UMAP（512 bytes raw）|
| 點陣快取（.BCACHE）| USRFONT.BCACHE（UFNT 格式）| ASCFONT.BCACHE（ASCF 格式）|
| Alpha 格式 | 0 or 255（1-bit 放大）或 TrueType 抗鋸齒 | TrueType 抗鋸齒 |
| 字形寬度 | 雙寬（nCellW × 2）| 單寬（nCellW）|
| 快取 key | lookupIdx（0–784）| asc_byte（0–255, 13 個有效）|
| SSAA 路徑 | DrawDynUnicodeGlyph（PUA 自參照）| DrawAscFontDirect |
| 觸發條件 | usChar 在 E000–E30F | usChar < 0x0020 or == 0x7F |

### 12.1 統一命名慣例（.UMAP / .BCACHE）

兩套系統採用一致的副檔名命名規則：

| 副檔名 | 意義 | 格式 | Magic |
|--------|------|------|-------|
| `.UMAP` | 對照表（Lookup Map）| Raw Binary Array | 無 |
| `.BCACHE` | 點陣快取（Bitmap Cache）| 自訂二進位格式 | 有 |

| 檔名 | 用途 | 大小 | Magic |
|------|------|------|-------|
| `USRFONT.UMAP` | BIG5 UDA → Unicode 對照（785 slots × 2 bytes）| 1570 bytes（固定）| 無 |
| `USRFONT.BCACHE` | USRFONT alpha mask 快取（UFNT 格式）| 動態 | "UFNT" |
| `ASCFONT.UMAP` | ASCII byte → Unicode 對照（256 slots × 2 bytes）| 512 bytes（固定）| 無 |
| `ASCFONT.BCACHE` | ASCFONT alpha mask 快取（ASCF 格式）| 動態 | "ASCF" |

**設計原則**：
- `.UMAP` 結構簡單（flat array），無格式版本問題，可安全手動比較/備份
- `.BCACHE` 含 Magic + cell 尺寸，驗證失敗時自動重建，**刪除無損**
- 兩份 `.UMAP` 都不含 Magic Header，是純 Raw binary，**不可互換格式**

---

## 13. 重要陷阱與注意事項

### 13.1 GDI 物件清理順序（關鍵！）

```c
/* 錯誤（GDI 洩漏）：hbmT 仍被 hdcT 選取，DeleteObject 失敗 */
DeleteObject( hbmT );   /* ❌ */
DeleteDC( hdcT );

/* 正確：先刪 DC（自動取消選取 hbmT），再刪 bitmap */
DeleteDC( hdcT );       /* ✓ */
DeleteObject( hbmT );
```

**症狀**：GDI 物件耗盡 → CreateDIBSection 失敗 → 顯示黑方塊或系統字型亂碼

### 13.2 USRFONT.UMAP 格式鎖定

- **絕對不可**將 USRFONT.UMAP 改成其他格式
- 此檔案是無 Magic Header 的 Raw HB_WCHAR[] 陣列
- 若要存 alpha bitmap 快取，必須使用獨立的 USRFONT.BCACHE

### 13.3 C1 控制字元（U+0080–U+009F）

- GDI `TextOutW` 對 C1 控制字元無效（顯示空白）
- 必須使用 `GetGlyphIndicesW + ETO_GLYPH_INDEX` 方可渲染
- 適用於 ASCFONT 中的 U+009E（▲）和 U+009F（▼）

### 13.4 ScanAndDrawUda 的 s_pFontData 守衛

```c
if( ! s_pFontData ) return;  /* 此行同時阻止 ASCFONT 渲染！*/
```

若 USRFONT.24M 載入失敗（s_pFontData = NULL），整個 ScanAndDrawUda 函式提早返回，**ASCFONT 也不會渲染**。

### 13.5 雙寬字元跳格

```c
col++;  /* UDA 雙寬字形佔據下一個 col，必須跳過 */
```

忘記此行會導致右半格被覆蓋渲染。

### 13.6 cell 尺寸變更時快取失效

USRFONT.BCACHE 與 cell 尺寸綁定（wW, wH 儲存在 header）。視窗縮放或字型大小改變時，LoadUsrFontUmap 發現尺寸不符，會清除全部快取並觸發重建（Lazy 模式，首次掃描才生成）。

### 13.7 PUA 自參照的過濾

`EnsureUfntAlpha` 中有以下判斷：

```c
if( wc < UDA_UNICODE_BASE || wc > (UDA_UNICODE_BASE + BIG5_LOOKUP_SIZE - 1) )
    pAlpha = CaptureGlyphAlpha( wc, nW, nH );
```

PUA 自參照（`wc` 在 E000–E30F 範圍）會被過濾，不呼叫 `CaptureGlyphAlpha`，改以 `DrawDynUnicodeGlyph`（SSAA 路徑）渲染，讓 HuaKang W5A 字型的 PUA 字形正確顯示。

---

*本文件由 Claude Code 根據 `usrfont.prg` 原始碼自動產生，最後更新：2026-04-18（v2，統一 .UMAP/.BCACHE 命名慣例）*
