# BCACHE 離線備援系統完整參考手冊

日期：2026-04-26
專案：HBSTK (GTWVT Console Mode)
作者：開發團隊 + Claude AI 協作

---

## 一、系統概述

### 問題背景

STK 庫存管理系統使用華康楷書W5A 的造字區（Big5 UDA FA40-FEFE）顯示自定義字形。
當程式部署到沒有華康字型的電腦時，造字無法顯示。

### 解決方案

BCACHE（Binary Cache）離線備援系統：
- 在有華康字型的開發機預先渲染所有造字字形
- 將渲染結果儲存為 BCACHE 檔案
- 打包進 EXE，部署到任何電腦都能顯示造字
- 針對不同螢幕解析度預備多套 BCACHE

---

## 二、字形檔案說明

### 造字相關檔案

| 檔案 | 大小 | 用途 | 共用/獨立 |
|------|------|------|-----------|
| USRFONT.24M | 6 KB | 84 個造字原始點陣（24×24 1-bit bitmap） | 共用 |
| USRFONT.UMAP | 1.5 KB | 785 格 Big5 UDA → Unicode PUA 動態對照表 | 共用 |
| USRFONT.BCACHE | ~190 KB | 造字 alpha mask 快取（8-bit grayscale） | 各站獨立 |
| ASCFONT.15 | 100 KB | 13 個控制字元原始字形資料 | 共用 |
| ASCFONT.UMAP | 512 B | 256 格 ASCII → Unicode 對照表 | 共用 |
| ASCFONT.BCACHE | ~10 KB | 控制字元 alpha mask 快取 | 各站獨立 |

### BCACHE 檔案格式

```
Header (10 bytes):
  [0-3]  Magic: "UFNT" (USRFONT) 或 "ASCF" (ASCFONT)
  [4-5]  Width: cell 寬度 (WORD, Little-Endian)
  [6-7]  Height: cell 高度 (WORD, Little-Endian)
  [8-9]  Count: 字形數量 (WORD, Little-Endian)

Entries (每個字形):
  USRFONT: [2 bytes lookupIdx] + [Width × Height bytes alpha data]
  ASCFONT: [1 byte asc_byte] + [Width × Height bytes alpha data]
```

### 多套 BCACHE（bcache/ 子目錄）

檔名格式：`USRFONT_{fullW}x{cellH}_{螢幕寬}x{螢幕高}.BCACHE`

```
fullW = cellW × 2（CJK 全寬）
cellH = GTWVT 實際 cell 高度（不是理論值）
```

實際對照表（80×25 模式）：

| 螢幕解析度 | USRFONT (fullW×H) | ASCFONT (halfW×H) | 生成方式 |
|-----------|-------------------|-------------------|---------|
| 1024×768 | 24×27 | 12×27 | Native |
| 1280×720 | 30×25 | 15×25 | Native |
| 1280×1024 | 30×37 | 15×37 | Native |
| 1366×768 | 32×27 | 16×27 | Native |
| 1440×900 | 34×32 | 17×32 | Native |
| 1536×864 | 38×32 | 19×32 | Native |
| 1600×900 | 38×32 | 19×32 | Native (=1536×864) |
| 1920×1080 | 46×40 | 23×40 | Native (最大化) |
| 1920×1080 | 38×39 | 19×39 | Dev (非最大化) |
| 1920×1200 | 48×46 | 24×46 | Native |

注意：理論計算 (螢幕高-40)/25 與 GTWVT 實際值不同（標題列、邊框影響）。
檔名使用 GTWVT 實際值，不是理論值。

---

## 三、字形渲染流程

### 三種字形來源

```
1. .24M 字形（84 個）
   來源：USRFONT.24M 原始 24×24 1-bit 點陣
   渲染：ScaleGlyph1bit 即時縮放到目前 cell 尺寸
   品質：任何解析度都完美（從原始資料生成）
   不需要 BCACHE。

2. 動態字形（47+ 個）
   來源：華康楷書W5A TrueType 字型
   渲染：CaptureGlyphAlpha 以華康字型在目前 cell 尺寸渲染
   離線：無華康時從 BCACHE 載入（品質取決於 BCACHE 匹配度）
   需要 BCACHE 作為離線備援。

3. ASCFONT（13 個）
   來源：華康楷書W5A 的控制字元符號（♠♥♦♣☺等）
   渲染：DrawAscFontDirect / DrawAscFontFromCache
   離線：從 ASCFONT.BCACHE 載入
```

### ScanAndDrawUda 渲染流程

```
每次 WM_PAINT:
  1. 掃描螢幕所有 cell
  2. 遇到 UDA 字元 (U+E000~U+E30F):
     a. s_aUfntAlpha[idx] 已有快取 → DrawUdaAlphaBlend（直接用）
     b. s_aBig5Lookup[idx] >= 0 → ScaleGlyph1bit 從 .24M 生成
     c. s_aDynUnicode[idx] != 0 → CaptureGlyphAlpha 從華康生成
  3. 遇到控制字元 (< 0x20 或 0x7F):
     → DrawAscFontGlyph
  4. s_nUfntDirty=1 時 → SaveUsrFontUmap 存回 BCACHE
```

### LoadUsrFontUmap 載入流程

```
LoadUsrFontUmap(nW, nH):
  1. 開檔驗證 header（不先清空 cache）     [FIX-2]
  2. 寬高相符 → 載入字形（跳過 .24M 字形） [Step 1]
  3. 寬高不符 → 從 BCACHE 縮放載入          [FIX-2 SCALE]
  4. 檔案不存在 → return FALSE → EnsureUfntAlpha 逐一生成
```

---

## 四、BCACHE 保護機制（6 道防線）

### [FIX-1] USRFONT_LOAD 不清 alpha cache

```
位置：usrfont.prg BEGINDUMP, USRFONT_LOAD 函式
問題：原本每次載入 .24M 都清空 alpha cache → 離線備援全毀
修正：保留 alpha cache，只重建 glyph mapping table
```

### [FIX-2] LoadUsrFontUmap 先驗證再清 + 不符就縮放

```
位置：usrfont.prg BEGINDUMP, LoadUsrFontUmap 函式
問題：原本先清空再驗證 → 載入失敗時 cache 已空
修正：先驗證 header → 相符才清空載入 → 不符就從 BCACHE 縮放
```

### [FIX-3] SaveUsrFontUmap 新數量 < 舊數量時不覆寫

```
位置：usrfont.prg BEGINDUMP, SaveUsrFontUmap 函式
問題：cell 尺寸變化時重新生成少量字形 → 覆寫掉完整的 BCACHE
修正：寫入前比對檔案中的字形數，新 < 舊則跳過
```

### [FIX-4] USRFONT_LOAD 不清 ASCFONT cache

```
位置：usrfont.prg BEGINDUMP, USRFONT_LOAD 函式
問題：每次載入 .24M 都清空 ASCFONT cache
修正：保留 ASCFONT cache
```

### [FIX-5] LoadAscFontUmap 不符時從 BCACHE 縮放

```
位置：usrfont.prg BEGINDUMP, LoadAscFontUmap 函式
問題：cell 尺寸不符時呼叫 GenerateAscFontCells 重新生成
      → 沒華康就生成失敗 → ASCFONT.BCACHE 被覆寫成殘缺版
修正：不符時從 BCACHE 縮放載入（跟 USRFONT 同邏輯）
```

### [overwrite=0] STK_EXTRACT_RES BCACHE 不覆寫

```
位置：usrfont.prg BEGINDUMP, STK_EXTRACT_RES 函式
問題：原本 overwrite=1 → 每次啟動從 EXE 資源覆寫 BCACHE
      → 工作站收集的新字形被 EXE 內嵌的舊版蓋掉
修正：BCACHE 改為 overwrite=0 → 檔案已存在就不覆寫
```

### STK_SELECT_BCACHE 字形數保護

```
位置：TLIB.PRG, STK_SELECT_BCACHE 函式
問題：每次啟動從 bcache/ 複製匹配版本 → 覆蓋工作站收集的新字形
修正：複製前檢查本機 BCACHE 字形數
      → 本機字形數 ≥ bcache/ 版本 + 尺寸相符 → 跳過（保留本機）
      → 否則覆蓋（用 bcache/ 版本）
```

---

## 五、EXE 打包與解壓

### stk32.rc 資源定義

```rc
// 核心檔案（overwrite=1 每次覆寫）
101 RCDATA ".\USRFONT.24M"        // 造字原始點陣
102 RCDATA ".\USRFONT.UMAP"       // Unicode 對照表
104 RCDATA ".\ASCFONT.15"         // ASCFONT 原始字形
105 RCDATA ".\ASCFONT.UMAP"       // ASCFONT 對照表

// BCACHE（overwrite=0 不覆寫）
103 RCDATA ".\USRFONT.BCACHE"     // 預設 BCACHE
106 RCDATA ".\ASCFONT.BCACHE"     // 預設 ASCFONT BCACHE

// 多套 BCACHE（overwrite=1 每次覆寫，由 STK_EXTRACT_MULTI_BCACHE 處理）
201~210 RCDATA "./bcache/USRFONT_*.BCACHE"  // 10 套 USRFONT
301~310 RCDATA "./bcache/ASCFONT_*.BCACHE"  // 10 套 ASCFONT

// 設定檔（overwrite=0 不覆寫）
107 RCDATA ".\stk.ini"
108 RCDATA ".\system.ini"
```

### 解壓覆蓋規則

| 資源 | overwrite | 說明 |
|------|-----------|------|
| USRFONT.24M | 1 (覆寫) | 造字定義隨 EXE 更新 |
| USRFONT.UMAP | 1 (覆寫) | 對照表隨 EXE 更新 |
| USRFONT.BCACHE | 0 (不覆寫) | 保留工作站收集的字形 |
| ASCFONT.15 | 1 (覆寫) | ASCFONT 定義隨 EXE 更新 |
| ASCFONT.UMAP | 1 (覆寫) | ASCFONT 對照表隨 EXE 更新 |
| ASCFONT.BCACHE | 0 (不覆寫) | 保留工作站 ASCFONT 快取 |
| bcache/*.BCACHE | 1 (覆寫) | 多套 BCACHE 隨 EXE 更新 |
| stk.ini | 0 (不覆寫) | 保留使用者設定 |
| system.ini | 0 (不覆寫) | 保留使用者設定 |

### 啟動流程

```
Stk32.exe 啟動
  │
  ▼
1. STK_EXTRACT_RES()
   → 解壓 EXE 內嵌資源到 EXE 所在目錄
   → USRFONT.24M/UMAP/ASCFONT.*: 覆寫（保持最新）
   → USRFONT.BCACHE/ASCFONT.BCACHE: 不覆寫（保留本機）
   → stk.ini/system.ini: 不覆寫（保留設定）
  │
  ▼
2. STK_EXTRACT_MULTI_BCACHE()
   → 解壓 10 套多解析度 BCACHE 到 EXE 目錄
   → 覆寫（每次隨 EXE 更新）
  │
  ▼
3. LoadIni(System.ini) + LoadIni(stk.ini)
  │
  ▼
4. 字型設定
   FontSize=0 → 依螢幕自動計算
   hb_gtInfo(HB_GTI_FONTNAME/FONTSIZE/FONTWIDTH)
   SetMode(Rows, Cols)
  │
  ▼
5. STK_SELECT_BCACHE( 實際cellW×2, 實際cellH )
   → 用 GTWVT 實際 cell 尺寸搜尋 bcache/ 中匹配的檔案
   → 用 FindFirstFile("USRFONT_{nW}x{nH}_*.BCACHE") 搜尋
   → 找到且本機字形數較少 → 複製為 USRFONT.BCACHE
   → 找到但本機字形數 ≥ → 跳過（保留本機收集的）
   → 找不到 → 不動（用現有 BCACHE，可能需要縮放）
  │
  ▼
6. FontDirect 判斷
   FontDirect=0: 自動偵測華康 → 有則用字型，無則用 BCACHE
   FontDirect=1: 強制字型（刪除 BCACHE，用華康渲染）
   FontDirect=2: 強制 BCACHE（不偵測字型）
  │
  ▼
7. UsrFontInit("USRFONT.24M")
   → Step 1: USRFONT_LOAD 載入 .24M（不清 alpha cache）
   → Step 2: HOOKCDP 掛鉤 Big5 codepage
   → Step 2b: HOOKGT 掛鉤 GT Rest
   → Step 3: idle hook 等待視窗建立後 subclass
  │
  ▼
8. 第一次 WM_PAINT → ScanAndDrawUda
   → LoadUsrFontUmap 載入 BCACHE
   → EnsureUfntAlpha 按需生成字形
   → 新字形存入 BCACHE（SaveUsrFontUmap）
```

---

## 六、System.ini 設定詳解

### [Screen] 螢幕/字型

```ini
FontName=DFKai-SB     # 字型名稱（標楷體）
Rows=0                # 螢幕列數（0=自動）
Cols=0                # 螢幕欄數（0=自動）
FontSize=0            # 字型大小（0=依螢幕自動計算）
pad_x=2               # 字元格水平額外空白
pad_y=0               # 字元格垂直額外空白
```

FontSize 優先順序：
1. FontSize > 0 → 使用指定值
2. FontSize = 0 → 自動計算：nFontH = (螢幕高-100) / Rows

### [FontDirect] 造字渲染模式

```ini
FontDirect=0
```

| 值 | 模式 | 說明 |
|----|------|------|
| 0 | 自動 | 偵測華康字型：有→用字型，無→用BCACHE |
| 1 | 強制字型 | 刪除BCACHE，華康即時渲染（無華康=空白） |
| 2 | 強制BCACHE | 不偵測字型，強制用BCACHE |

建議：
- 有華康的開發機：FontDirect=0（自動偵測到華康→最佳品質）
- 沒華康的工作站：FontDirect=0（自動偵測不到→用BCACHE）
- 測試BCACHE機制：FontDirect=2（強制BCACHE即使有華康）

### [BCACHE] 多解析度設定

```ini
BCacheThreshold=80    # 銳化閥值（僅 Python 縮放生成時使用）
```

| 值 | 效果 |
|----|------|
| 0 | 不銳化（保留 anti-aliased 灰階邊緣） |
| 60 | 輕度銳化（保留較多細節） |
| 80 | 預設（中等銳化） |
| 120 | 強力銳化（更銳利但細筆畫可能消失） |
| 255 | 最大（純黑白） |

此值由 gen_bcache_sets.py 讀取，用於從高解析度 BCACHE 縮放生成低解析度版本。
不是 runtime 設定，改變後需重跑 Python + rebuild EXE。

### [UI] 功能鍵

```ini
alt_copy=true          # Alt-C 複製 / Alt-V 貼上
close_window=false     # 右上角 [X] 關閉鈕
```

### [GTWVT] 擴展全形

```ini
ExtWideSymbol=1        # BIG5 擴展全形符號 (CP950 U+2200-26FF)
```

設為 1 時，isWideChar() 額外判定 U+2200-24FF 和 U+25A0-26FF 為全形。
解決 BIG5 特殊符號（如 ☉ A1F3）被當半形渲染的問題。

---

## 七、BCACHE 生成方式

### 方案A：原生生成（最佳品質）

```
P83 選單 → GenBCacheAll
  → 自動 ChangeDisplaySettings 切換螢幕解析度
  → 華康字型在目標尺寸直接渲染
  → 每個解析度都是原生品質
  → 完成後自動恢復原解析度
```

限制：螢幕不支援的解析度無法生成（如 1080p 螢幕無法生成 2560×1440）

### 方案B：Python 縮放（次佳品質）

```
gen_bcache_sets.py
  → 讀取高解析度 BCACHE（38×39）
  → nearest-neighbour 縮放到各目標尺寸
  → 套用 BCacheThreshold 銳化
  → 用於螢幕不支援的解析度
```

### 混合方案（實際使用）

```
方案A 能生成的 → 原生品質
方案A 不能的   → 方案B 補齊
```

### P83 工具選單

```
UDA Tools:
  ShowUDA          → 顯示所有造字（觸發 BCACHE 生成）
  ShowASCFONT      → 顯示 ASCFONT 控制字元
  GenBCacheAll     → 一鍵生成所有解析度 BCACHE
  單一解析度選擇   → 只生成指定解析度
```

### USRFONT_RESETCACHE

```
用途：切換解析度前清空 alpha cache
原因：舊解析度的 alpha mask 在記憶體中，不清空會用到錯誤尺寸的字形
位置：usrfont.prg BEGINDUMP
呼叫：GenBCacheAll / GenBCacheOne 切換解析度前
```

---

## 八、.24M 字形 vs BCACHE 字形

### 載入時跳過 .24M 字形 [Step 1]

```
LoadUsrFontUmap 載入 BCACHE 時：
  if( s_aBig5Lookup[idx] >= 0 ) continue;  // 跳過 .24M 字形
```

原因：.24M 字形有原始 24×24 點陣，ScaleGlyph1bit 可以在任何尺寸完美生成。
不需要依賴 BCACHE，從原始資料生成品質更好。

### 存檔時只存動態字形 [Step 2]

```
SaveUsrFontUmap 存檔時：
  if( s_aUfntAlpha[i] && s_aBig5Lookup[i] < 0 ) wCount++;  // 只計動態字形
```

原因：BCACHE 只需要存沒有 .24M 原始資料的動態字形。
84 個 .24M 字形不存 BCACHE → 檔案更小。

---

## 九、多站部署

### 部署結構

```
W:\                            ← 網路磁碟 (map to server C:\STK)
  S001\                        ← 主機 (server)
    Stk32.exe
    System.ini (FontDirect=0)
    stk.ini
    USRFONT.24M                ← EXE 解壓（overwrite=1）
    USRFONT.UMAP               ← EXE 解壓（overwrite=1）
    USRFONT.BCACHE             ← 本機（overwrite=0，自動成長）
    ASCFONT.*                  ← 同上
    bcache/                    ← 多套 BCACHE（overwrite=1）
    Dbf/                       ← 共用資料庫

  S002\                        ← 工作站 #2
    Stk32.exe
    System.ini
    USRFONT.BCACHE             ← 自己的（可能不同解析度）
    bcache/

  S003\ ~ S012\                ← 工作站 #3~12
    ...同上...
```

### 造字更新流程

```
1. 在開發機修改 USRFONT.24M
2. 用 bat 複製到所有工作站：
   copy USRFONT.24M W:\S001\
   copy USRFONT.24M W:\S002\
   ...
3. 或：rebuild EXE（.24M 打包在 EXE 裡，overwrite=1 會自動更新）
```

### BCACHE 自動成長

```
工作站遇到新造字 → EnsureUfntAlpha 生成 → SaveUsrFontUmap 存入 BCACHE
下次啟動 → STK_SELECT_BCACHE 檢查：
  → 本機字形數 ≥ bcache/ 版本 → 跳過（保留收集的）
  → 本機字形數 < → 用 bcache/ 版本覆蓋
```

---

## 十、BCC ICE 問題記錄

### 症狀

BCC 7.73 編譯 usrfont.prg 的 BEGINDUMP C 碼時，偶爾觸發
Internal Compiler Error（ICE），無錯誤訊息，只有 "1 errors in Compile"。

### 真正原因

1. **Include path 錯誤**（最常見）：
   `-IE:\BCC7732\Include\dinkumware` 是 C++ STL 標頭，
   混入 C 編譯會觸發 ICE。
   正確：`-IE:\BCC7732\INCLUDE\windows\crtl -IE:\BCC7732\INCLUDE\windows\sdk`

2. **C89 語法**：
   BCC 使用 C89 模式，不允許在 for 迴圈內宣告變數（`int i`）。
   需要在函式頂端宣告。

3. **程式碼總量**：
   usrfont.prg BEGINDUMP 約 130KB C 碼，接近 BCC 極限。
   加入少量程式碼可能觸發 ICE。

### 對策

- 新的 C 函式優先放 TLIB.PRG（編譯無問題）
- 遇到 ICE 先用正確 flags 手動編譯確認是否為語法錯誤
- 語法錯誤修正後通常可通過

---

## 十一、關鍵經驗教訓

### 1. FreezeWindow 連續性

```
問題：MenuBar 閃爍
根因：FreezeWindow(.F.) 在 RestScreen 後立即解凍，
      迴圈回頂端重繪前有「裸奔」區間
解法：不解凍，讓 freeze 延續到重繪完成
教訓：問題在邏輯，不在 API。用盡天下的 API 也沒用，
      要先追蹤完整執行流程，找出邏輯缺口。
```

### 2. 逐行流程追蹤法

```
卡關時不要繼續換 API，改用「逐行流程追蹤」：
1. 列出程式碼完整執行順序
2. 每一行旁邊標註關鍵狀態
3. 用 ★ 標記狀態突變位置
4. 邏輯缺口自然浮現
```

### 3. BCACHE 清除鏈

```
USRFONT_LOAD [CLEAR-1] → s_nUfntCellW=0
  ↓
LoadUsrFontUmap [CLEAR-2] → 先清空再驗證
  ↓
SaveUsrFontUmap [WRITE-3] → 無條件覆寫
  ↓
190KB → 12KB

修正：6 道防線全部補上
```

### 4. STK_EXTRACT_RES overwrite 陷阱

```
BCACHE overwrite=1 → 每次啟動從 EXE 覆寫 → 好的 BCACHE 被舊版蓋掉
修正：BCACHE 改 overwrite=0
```

---

## 十二、相關檔案清單

### 原始碼

| 檔案 | 內容 |
|------|------|
| usrfont.prg | USRFONT/ASCFONT 主程式 + C BEGINDUMP |
| main.prg | 啟動流程、字型設定、STK_SELECT_BCACHE 呼叫 |
| TLIB.PRG | FreezeWindow、STK_EXTRACT_MULTI_BCACHE、STK_SELECT_BCACHE、ChangeResolution |
| P83.PRG | ShowUDA、ShowASCFONT、GenBCacheAll、GenBCacheOne |
| stk32.rc | EXE 資源定義（RCDATA） |
| stk32.hbp | 編譯設定 |

### 設定檔

| 檔案 | 內容 |
|------|------|
| System.ini | 螢幕/字型/FontDirect/BCacheThreshold |
| stk.ini | 業務設定（列印、路徑等） |

### 工具

| 檔案 | 用途 |
|------|------|
| gen_bcache_sets.py | Python 縮放生成 BCACHE（方案B） |
| _compile_usrfont.bat | 單獨編譯 usrfont C 碼（避 ICE） |

### 文件

| 檔案 | 內容 |
|------|------|
| BCACHE_Final_Plan.md | 架構計畫 |
| BCACHE_System_Reference.md | 本文件 |
| BCACHE_MAP.log | GenBCacheAll 產出的解析度對照 |
| FONTSIZE.log | 每次啟動記錄的實際字型尺寸 |
| UNDO_LIST.md | 未完成項目 |
