# ASCFONT 系統技術參考手冊

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

---

## 目錄

1. [系統目的](#1-系統目的)
2. [字元對照表（13 個）](#2-字元對照表13-個)
3. [兩種檔案格式（詳細）](#3-兩種檔案格式詳細)
4. [渲染架構（A + B 雙路）](#4-渲染架構a--b-雙路)
5. [字元路由流程](#5-字元路由流程)
6. [核心函式說明](#6-核心函式說明)
7. [與 USRFONT 的命名統一對照](#7-與-usrfont-的命名統一對照)
8. [踩過的坑](#8-踩過的坑)
9. [建置指令](#9-建置指令)

---

## 1. 系統目的

ASCFONT 系統的核心目的有三：

1. **ASCII TABLE 覆蓋**  
   將 Clipper 原始系統所用的 `chr(0)`～`chr(31)`、`chr(127)` 控制字元，覆蓋顯示為華康楷書W5A 的對應符號（框線、牌號、特殊符號等）。  
   進入系統後 DUMP ASCII(0-255) 會看到 chr(0-31) 的字元顯示已被改變，這是**設計預期行為**。

2. **無失真渲染**  
   字形以**系統字型實際 cell 大小**渲染並存入 BCACHE，不做二次縮放（StretchBlt），確保最原味不失真。

3. **離線備援**  
   客戶系統若未安裝華康楷書W5A，ASCFONT.BCACHE 仍提供完整字形，不依賴執行時字型。

---

## 2. 字元對照表（13 個）

對照表存於 `ASCFONT.UMAP`（256×2 bytes raw array，`s_aAscUmap[asc_byte]` = Unicode）。

| asc_byte | Unicode | 顯示 | 說明 |
|----------|---------|------|------|
| 0x00 (chr 0)   | U+00B6 | ¶  | 段落符號 |
| 0x13 (chr 19)  | U+2660 | ♠  | 下框線/黑桃 |
| 0x14 (chr 20)  | U+009E | ▲  | C1 控制字元（需 GlyphIndex）|
| 0x15 (chr 21)  | U+263A | ☺  | 白臉 |
| 0x16 (chr 22)  | U+25CB | ○  | 水平連接線/空心圓 |
| 0x17 (chr 23)  | U+25AC | ▬  | 矩形 |
| 0x18 (chr 24)  | U+263B | ☻  | 黑臉 |
| 0x19 (chr 25)  | U+00A7 | §  | 右框線/段符 |
| 0x1C (chr 28)  | U+2666 | ♦  | 紅磚 |
| 0x1D (chr 29)  | U+2665 | ♥  | 愛心 |
| 0x1E (chr 30)  | U+2663 | ♣  | 梅花 |
| 0x1F (chr 31)  | U+203C | ‼  | 雙驚嘆號 |
| 0x7F (chr 127) | U+009F | ▼  | C1 控制字元（需 GlyphIndex）|

---

## 3. 兩種檔案格式（詳細）

ASCFONT 系統使用兩個獨立檔案，命名規則與 USRFONT 完全統一：

---

### 3.1 ASCFONT.UMAP — 字元對照表（Raw Array 格式）

**用途**：儲存 ASCII byte → Unicode codepoint 的對照關係  
**大小**：256 × 2 = **512 bytes**（固定）  
**格式**：純 Raw Binary，**無 Magic Header**（與 USRFONT.UMAP 相同哲學）  
**首次執行**：若檔案不存在，程式自動以預設值建立

#### 整體結構

```
┌──────────────────────────────────────────────┐
│  s_aAscUmap[0]   (2 bytes, LE WORD)          │  ← ASCII 0x00 的 Unicode
├──────────────────────────────────────────────┤
│  s_aAscUmap[1]   (2 bytes, LE WORD)          │  ← ASCII 0x01 的 Unicode
├──────────────────────────────────────────────┤
│  ...（共 256 個 WORD）...                    │
├──────────────────────────────────────────────┤
│  s_aAscUmap[255] (2 bytes, LE WORD)          │  ← ASCII 0xFF 的 Unicode
└──────────────────────────────────────────────┘
```

#### 項目值意義

| 值 | 意義 |
|----|------|
| `0x0000` | 無映射（不渲染）|
| `0x00B6`、`0x2660` 等 | 對應 Unicode codepoint，由 DrawAscFontDirect 渲染 |
| `0x009E`、`0x009F` | C1 控制字元，需 `GetGlyphIndicesW + ETO_GLYPH_INDEX` |

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

```c
/* 讀取（LoadAscMap）*/
HB_WCHAR tmpMap[ 256 ];
ReadFile( hFile, tmpMap, 256 * 2, &dwRead, NULL );
memcpy( s_aAscUmap, tmpMap, sizeof( s_aAscUmap ) );

/* 從 s_aAscUmap[] 重建 s_aAscByte[] / s_aAscSrc[] */
n = 0;
for( i = 0; i < 256 && n < ASCFONT_COUNT; i++ )
    if( s_aAscUmap[i] != 0 )
    {
        s_aAscByte[n] = (unsigned char) i;
        s_aAscSrc [n] = s_aAscUmap[i];
        n++;
    }

/* 寫入（SaveAscMap）*/
WriteFile( hFile, s_aAscUmap, 256 * 2, &dwWritten, NULL );
```

#### 預設值（程式內建，首次建立時寫入）

```c
static const unsigned char s_aAscByteDefault[ 13 ] = {
    0x00, 0x13, 0x14, 0x7F, 0x15, 0x16, 0x17,
    0x18, 0x19, 0x1C, 0x1D, 0x1E, 0x1F };
static const HB_WCHAR s_aAscSrcDefault[ 13 ] = {
    0x00B6, 0x2660, 0x009E, 0x009F, 0x263A, 0x25CB, 0x25AC,
    0x263B, 0x00A7, 0x2666, 0x2665, 0x2663, 0x203C };
```

---

### 3.2 ASCFONT.BCACHE — Alpha Mask 離線快取（ASCF 格式）

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

#### 整體結構

```
┌──────────────────────────────────────────────┐
│  Magic: "ASCF" (4 bytes, ASCII)              │
├──────────────────────────────────────────────┤
│  Cell Width  wW (2 bytes, LE WORD)           │  ← = nCellW（半寬）
├──────────────────────────────────────────────┤
│  Cell Height wH (2 bytes, LE WORD)           │  ← = nCellH
├──────────────────────────────────────────────┤
│  Count wCount (2 bytes, LE WORD)             │  ← 快取字形數（最多 13）
├──────────────────────────────────────────────┤
│  ENTRY #0                                     │
│    asc_byte (1 byte)                          │  ← ASCII 控制字元值
│    alpha[wW × wH] (wW*wH bytes)              │  ← 0=BG, 255=FG, 中間=抗鋸齒
├──────────────────────────────────────────────┤
│  ENTRY #1 ...                                 │
└──────────────────────────────────────────────┘
```

#### ASCFONT.BCACHE 的 key 結構

注意：ASCFONT 使用 `asc_byte`（1 byte）而非 `lookupIdx`（2 bytes）作為 key，  
因為 ASCII 只需 0–255 的空間。

#### 讀取驗證（LoadAscFontUmap）

```c
1. Magic == "ASCF"       → 否則 GenerateAscFontCells
2. wW == 目前 nCellW      → 否則 GenerateAscFontCells（視窗縮放）
3. wH == 目前 nCellH      → 否則 GenerateAscFontCells
4. wCount <= ASCFONT_COUNT → 否則 GenerateAscFontCells
```

驗證失敗 → 捨棄舊 BCACHE，重新渲染並寫入新尺寸版本

#### 與 USRFONT.BCACHE 格式對照

| 屬性 | ASCFONT.BCACHE | USRFONT.BCACHE |
|------|----------------|----------------|
| Magic | `"ASCF"` | `"UFNT"` |
| Key 欄位 | `asc_byte` (1 byte) | `lookupIdx` (2 bytes) |
| Key 範圍 | 0–255 (ASCII) | 0–784 (BIG5 UDA) |
| 每格寬度 | `nCellW`（半寬）| `nCellW×2`（全寬）|
| Alpha 格式 | 8-bit grayscale | 8-bit grayscale |

---

### 3.3 兩種格式對照表

| 屬性 | ASCFONT.UMAP | ASCFONT.BCACHE |
|------|--------------|----------------|
| 用途 | ASCII → Unicode 對照表 | Alpha 渲染快取 |
| Magic | 無（Raw Array）| `"ASCF"` |
| 大小 | 固定 512 bytes | 動態（依 cell 尺寸）|
| 內容 | 256 × HB_WCHAR | N × (1 byte key + alpha mask) |
| 可刪除？ | 不可（對照資料）| 可（會自動重建）|
| 首次建立 | 程式自動從預設值建立 | 首次渲染後自動建立 |
| 重建條件 | 不重建（使用者可手動修改）| cell 尺寸改變時重建 |

---

## 4. 渲染架構（A + B 雙路）

```
ScanAndDrawUda（WM_PAINT 時掃描所有 cell）
    │
    ├── usChar in E000–E30F  → USRFONT 系統（見 USRFONT 文件）
    │
    ├── usChar < 0x0020 || == 0x007F  → DrawAscFontGlyph（ASCFONT 路徑）
    │       │
    │       ├── 尺寸改變？ → LoadAscFontUmap(nCellW, nCellH)
    │       │       ├── BCACHE 存在且尺寸相符 → 載入 alpha masks
    │       │       └── 否 → GenerateAscFontCells → 寫回 BCACHE
    │       │
    │       ├── cache hit（pAlpha 存在）→ [A 路] DrawAscFontDirect(pAlpha)
    │       │       ├─ CPU alpha blend（FG×alpha + BG×(255-alpha)）
    │       │       └─ BitBlt 到視窗 DC（無 StretchBlt，零失真）
    │       │
    │       └── cache miss              → [B 路] DrawAscFontDirect(NULL)
    │               ├─ CreateFontW(-nH, HuaKang W5A)
    │               ├─ GetTextMetrics → yOff = -tmInternalLeading
    │               ├─ TextOutW 或 ETO_GLYPH_INDEX（C1 字元）
    │               └─ CreateRectRgn clip 防止溢出
    │
    └── usChar in E311–F8FF → DrawDynUnicodeGlyph（IME 動態字元）
```

### 4.1 A 路徑（BCACHE 快取）

條件：`s_aAscCache[idx].pAlpha != NULL`

```
1. 讀取 nW × nH alpha mask（0=背景, 255=前景）
2. CPU 混色：pixel.R = BG.R*(255-a)/255 + FG.R*a/255
3. 寫入暫存 DIBSection（nW×nH 32-bit）
4. BitBlt 到視窗 DC（無縮放，無失真）
```

### 4.2 B 路徑（即時字型渲染）

條件：`pAlpha == NULL`（cache 未載入或首次渲染）

```
1. CreateFontW(-nH, 0, ..., DEFAULT_CHARSET, HuaKang W5A)
   負號 = character height（不含 internal leading）
2. GetTextMetrics → tmInternalLeading
3. yOff = -tmInternalLeading（讓字形 body 從 y=0 開始）
4. C1 字元（0x80-0x9F）: GetGlyphIndicesW + ETO_GLYPH_INDEX
   一般字元: TextOutW(nX, nY + yOff, wsz, 1)
5. CreateRectRgn 裁切，防止字形溢出 cell 邊界
```

### 4.3 GenerateAscFontCells（DirectDC 擷取模式）

```
1. CreateCompatibleDC + 建立 nW×nH 32-bit DIBSection（黑底）
2. CreateFontW(-nH, 0, ..., CLEARTYPE_QUALITY, HuaKang W5A)
3. GetTextMetrics → yOff = -tmInternalLeading
4. 黑底白字：SetTextColor(0x00FFFFFF)
5. C1 字元用 ETO_GLYPH_INDEX；一般字元用 TextOutW
6. GdiFlush → 讀取 R channel → 1px 水平邊緣延伸
7. 結果存入 s_aAscCache[n].pAlpha（nW×nH alpha mask）
8. 完成後 SaveAscFontUmap() → 寫入 ASCFONT.BCACHE
```

---

## 5. 字元路由流程

### chr(0-31) 進入 ASCFONT 路徑

```
Harbour 輸出 chr(19)（= 0x13）
    │
    ▼ usrfont_wcharGet 攔截
      hi = 0x13，不在 FA-FE 範圍 → 委派原始 BIG5 wcharGet
      BIG5 wcharGet: 0x13 < 0xA1 → 直接回傳 U+0013
    │
    ▼ GTWVT screen buffer 儲存 usChar = 0x0013
    │
    ▼ WM_PAINT → ScanAndDrawUda
      usChar = 0x0013 < 0x0020 → TRUE
      → 搜尋 s_aAscByte[]: 找到 0x13 → index=1
      → DrawAscFontGlyph(asc_byte=0x13, wc=U+2660, nCellW, nCellH, ...)
    │
    ▼ A 路 或 B 路渲染至視窗
```

### chr(127) 特殊處理

```
BIG5 原始 wcharGet 對 0x7F 會回傳 HB_FALSE → 程式崩潰
→ usrfont_wcharGet 明確攔截：
   if( hi == 0x7F ) { *wc = 0x007F; return HB_TRUE; }
→ ScanAndDrawUda: usChar == 0x007F → ASCFONT 路徑
```

### C1 控制字元（U+009E / U+009F）

GDI `TextOutW` 無法渲染 C1 控制字元（0x80–0x9F），必須改用：

```c
WORD gi = 0xFFFF;
GetGlyphIndicesW( hdc, wsz, 1, &gi, GGI_MARK_NONEXISTING_GLYPHS );
if( gi != 0xFFFF )
    ExtTextOutW( hdc, x, y, ETO_GLYPH_INDEX, NULL, (LPCWSTR)&gi, 1, NULL );
```

---

## 6. 核心函式說明

| 函式 | 說明 |
|------|------|
| `LoadAscMap()` | 讀取 ASCFONT.UMAP → `s_aAscUmap[]`，重建 `s_aAscByte[]`/`s_aAscSrc[]`；不存在則建立預設值 |
| `SaveAscMap()` | 將 `s_aAscUmap[]` 寫入 ASCFONT.UMAP（256×2 bytes）|
| `AscCacheFindIdx(byte_val)` | 在 `s_aAscCache[]` 中找 asc_byte 的快取索引 |
| `SaveAscFontUmap()` | 將 alpha cache 寫入 ASCFONT.BCACHE（ASCF 格式）|
| `LoadAscFontUmap(nW, nH)` | 載入 ASCFONT.BCACHE；尺寸不符則呼叫 GenerateAscFontCells |
| `GenerateAscFontCells(nW, nH)` | DirectDC 擷取：渲染所有 13 字元到 alpha mask，存入 BCACHE |
| `DrawAscFontGlyph(...)` | ASCFONT 渲染入口：判斷 A/B 路，呼叫 DrawAscFontDirect |
| `DrawAscFontDirect(...)` | A路（alpha blend BitBlt）或 B路（即時 TrueType）|

### 靜態變數

| 變數 | 型別 | 說明 |
|------|------|------|
| `s_aAscUmap[256]` | `HB_WCHAR` | ASCII byte → Unicode（從 ASCFONT.UMAP 載入）|
| `s_aAscByte[13]` | `unsigned char` | 有映射的 13 個 ASCII 值（從 UMAP 重建）|
| `s_aAscSrc[13]` | `HB_WCHAR` | 對應的 13 個 Unicode（從 UMAP 重建）|
| `s_aAscCache[13]` | `ASCF_ENTRY` | alpha mask 快取陣列 |
| `s_nAscCacheCount` | `int` | 已快取字形數量 |
| `s_nAscCellW/H` | `int` | 快取建立時的 cell 尺寸 |
| `s_szAscMapFile[]` | `char[MAX_PATH]` | ASCFONT.UMAP 路徑（對照表）|
| `s_szAscUmapFile[]` | `char[MAX_PATH]` | ASCFONT.BCACHE 路徑（bitmap cache）|

---

## 7. 與 USRFONT 的命名統一對照

兩套系統採用完全一致的命名規則與設計哲學：

| 對應角色 | ASCFONT | USRFONT |
|----------|---------|---------|
| **對照表檔案** | `ASCFONT.UMAP`（512 bytes raw）| `USRFONT.UMAP`（1570 bytes raw）|
| **Bitmap cache** | `ASCFONT.BCACHE`（ASCF 格式）| `USRFONT.BCACHE`（UFNT 格式）|
| **原始字型** | 無（直接 TrueType 擷取）| `USRFONT.24M`（CMEX_PTN 格式）|
| **對照表格式** | `s_aAscUmap[256]`（indexed by ASCII）| `s_aDynUnicode[785]`（indexed by lookupIdx）|
| **對照表載入** | `LoadAscMap()` | `LoadDynMap()` |
| **對照表儲存** | `SaveAscMap()` | `SaveDynMap()` |
| **Cache 載入** | `LoadAscFontUmap()` | `LoadUsrFontUmap()` |
| **Cache 儲存** | `SaveAscFontUmap()` | `SaveUsrFontUmap()` |
| **Alpha 確保** | `GenerateAscFontCells()` | `EnsureUfntAlpha()` |
| **Alpha 渲染** | `DrawAscFontDirect()` | `DrawUdaAlphaBlend()` |
| **字元寬度** | 半寬（nCellW）| 全寬（nCellW × 2）|
| **Cache Magic** | `"ASCF"` | `"UFNT"` |
| **Cache Key** | `asc_byte`（1 byte）| `lookupIdx`（2 bytes）|

### 設計哲學一致點

1. **.UMAP = 對照表**（Raw Array，無 Magic，固定大小，可手動編輯）
2. **.BCACHE = Bitmap 快取**（有 Magic，與 cell 尺寸綁定，可刪除重建）
3. **首次執行自動建立**（UMAP 和 BCACHE 都在需要時自動建立）
4. **Lazy 渲染**（遇到字元才渲染，不預先全部生成）

---

## 8. 踩過的坑

| 坑 | 症狀 | 解法 |
|----|------|------|
| BCACHE 固定 96×96 | LoadAscFontUmap 驗證失敗 | 改動態 nW×nH |
| C1 字元 TextOutW 不渲染 | U+009E/009F 顯示空白 | GetGlyphIndicesW + ETO_GLYPH_INDEX |
| PUA 方案（wcharGet 改 0xE400+hi）| ASCFONT 全消失 | 回復備份，移除 PUA 攔截 |
| CreateFontW 正值高度 | 字形頂部有空隙，框線接不上 | 負值高度 + tmInternalLeading 補償 |
| chr(127) BIG5 wcharGet 回傳 HB_FALSE | 程式崩潰 | usrfont_wcharGet 明確攔截 0x7F |
| 對照表寫死在程式碼 | 修改需重新編譯 | 外部化至 ASCFONT.UMAP（與 USRFONT 統一）|
| ASCFONT/USRFONT 命名混亂 | UMAP 語意不一致 | 統一：.UMAP=對照表，.BCACHE=bitmap cache |

---

## 9. 建置指令

```batch
REM 終止程式
taskkill /F /IM Stk32.exe
taskkill /F /IM PrnAsk.exe

REM 強制重新渲染 BCACHE（可選）
del E:\ConvertFW\HBSTK\build\ASCFONT.BCACHE

REM 重設對照表（可選，會以預設值重建）
del E:\ConvertFW\HBSTK\build\ASCFONT.UMAP

REM 建置
cd E:\ConvertFW\HBSTK\build
call go32.bat
```

首次啟動（或 BCACHE 不存在時）：
1. `LoadAscMap()` 讀取 ASCFONT.UMAP（不存在則建立預設值）
2. `LoadAscFontUmap()` 讀取 ASCFONT.BCACHE
3. 尺寸不符或不存在 → `GenerateAscFontCells(nCellW, nCellH)`
4. DirectDC 擷取所有 13 個字元的 alpha mask
5. 寫入 ASCFONT.BCACHE（含 cell 尺寸 header）

---

## 相關檔案

| 檔案 | 說明 |
|------|------|
| `E:\ConvertFW\HBSTK\compat\usrfont.prg` | 主程式（ASCFONT + USRFONT 合併）|
| `E:\ConvertFW\HBSTK\build\ASCFONT.UMAP` | 對照表（256×2 raw，512 bytes）|
| `E:\ConvertFW\HBSTK\build\ASCFONT.BCACHE` | Bitmap 快取（ASCF 格式，動態大小）|
| `E:\ConvertFW\HBSTK\doc\USRFONT_SYSTEM_REFERENCE.md` | USRFONT 對應文件 |

---

*本文件由 Claude Code 根據 `usrfont.prg` 原始碼產生，日期：2026-04-18*
