# ASCFONT 系統完整交接文件
# ── 給下一位 AI 閱讀，不需要使用者重新解釋 ──

**專案**: STK 庫存管理系統 Clipper → Harbour GTWVT 轉換  
**主程式**: `E:\convertfw\hbstk\compat\usrfont.prg`（BIG5 編碼）  
**建置**: `cd E:\ConvertFW\HBSTK\build && call go32.bat`  
**日期**: 2026-04-18（最後更新：DirectDC capture 模式 100% 完成）

---

## 【一】你接手時最重要的事

**ASCFONT 系統已全部完成（100%）。**

所有框線字元垂直與水平均完美銜接，不需要再修改 ASCFONT 相關邏輯。  
如需了解系統架構，直接看【二】～【八】。如需建置，看【十一】。

---

## 【二】ASCFONT 系統是什麼

### 目的
Clipper 原始程式用 chr(0)～chr(31)、chr(127) 顯示框線與特殊符號（如 ♠♥♦♣☺☻¶§ 等）。
轉換到 Harbour GTWVT 後，這些控制字元需要**覆蓋顯示**為華康楷書W5A 字型的對應符號。

進入系統後 DUMP ASCII(0-255)，會看到 chr(0-31) 顯示改變——**這是正常預期行為**。

### 字元對照表（共 13 個）

| asc_byte | Unicode | 符號 | 備註 |
|----------|---------|------|------|
| 0x00 | U+00B6 | ¶  | |
| 0x13 | U+2660 | ♠  | 下框線 |
| 0x14 | U+009E | ▲  | C1控制字元，需 ETO_GLYPH_INDEX |
| 0x15 | U+263A | ☺  | |
| 0x16 | U+25CB | ○  | 水平連接線 |
| 0x17 | U+25AC | ▬  | |
| 0x18 | U+263B | ☻  | |
| 0x19 | U+00A7 | §  | 右框線 |
| 0x1C | U+2666 | ♦  | |
| 0x1D | U+2665 | ♥  | |
| 0x1E | U+2663 | ♣  | |
| 0x1F | U+203C | ‼  | |
| 0x7F | U+009F | ▼  | C1控制字元，需 ETO_GLYPH_INDEX |

---

## 【三】設計演進史（必讀，這是改造的根本原因）

### 第一階段：固定 96×96 UMAP（已廢棄）
- UMAP 固定存 96×96 的 alpha mask
- A路徑播放時用 StretchBlt 縮放到實際 cell 大小
- **問題**：StretchBlt 造成失真，框線接不起來

### 第二階段：發現 B路徑（DirectDC）是最完美的渲染方式
- B路徑 = 直接在視窗 DC 上用 `CreateFontW(-nH)` + `TextOutW` 渲染
- 直接 DC，沒有中間緩衝層，沒有色彩轉換損耗
- **結論**：B路徑渲染效果最完美，完全不失真

### 第三階段：以 B路徑邏輯為基礎，改造 UMAP 生成（目標狀態）
- `DrawAscFontDirect()` 就是那個「DirectDC 函式」
- 讓 A路徑和 B路徑**都呼叫同一個函式**，只是參數不同：
  - A路徑：傳入 `pAlpha`（從 UMAP 讀出），播放預渲染結果
  - B路徑：`pAlpha=NULL`，即時渲染到視窗 DC
  - **UMAP 生成**：傳入記憶體 DC（新參數），用 B路徑邏輯渲染到記憶體 DC，抽取 alpha 存 UMAP
- UMAP 改為動態大小（nW×nH = 實際 cell 大小），A路徑 1:1 播放，**不需要 StretchBlt**

```
演進鏈總結：
固定96×96 + StretchBlt（失真）
  → B路徑 DirectDC 最完美
  → DrawAscFontDirect 加 BITMAP capture 參數
  → GenerateAscFontCells 用同一函式渲染到記憶體DC
  → UMAP 存實際 nW×nH（動態）
  → A路徑 1:1 播放，零失真
```

---

## 【四】現在需要做的事（具體實作）

### 4.1 目前程式碼狀況（全部完成）

| 項目 | 狀態 | 說明 |
|------|------|------|
| UMAP 動態大小（nW×nH）| ✅ 完成 | LoadAscFontUmap 驗證尺寸，不符即重生成 |
| A路徑 1:1 blend+BitBlt | ✅ 完成 | 無 StretchBlt，零失真 |
| B路徑 DirectDC（-nH, yOff）| ✅ 完成 | CreateFontW(-nH) + tmInternalLeading 補償 |
| `DrawAscFontDirect` 加 BITMAP capture 參數 | ✅ 完成 | HDC hdcCapture, BYTE* pCaptureBits |
| `GenerateAscFontCells` 改用 DrawAscFontDirect | ✅ 完成 | 拆掉 4×SSAA，改用 capture 模式 |
| 水平框線間隙修正 | ✅ 完成 | 1px 左右延伸後處理（alpha mask 延伸）|
| 垂直框線接續 | ✅ 完成 | -nH + tmInternalLeading 補償已完全對齊 |

### 4.2 需要修改的函式：DrawAscFontDirect

**檔案位置**：`usrfont.prg` 約第 1303 行

**現在的簽名**：
```c
static void DrawAscFontDirect( HDC hdc, int nX, int nY, int nW, int nH,
                               HB_WCHAR wc, const BYTE * pAlpha,
                               COLORREF crFG, COLORREF crBG )
```

**改造後的簽名**（加兩個 capture 參數）：
```c
static void DrawAscFontDirect( HDC hdc, int nX, int nY, int nW, int nH,
                               HB_WCHAR wc, const BYTE * pAlpha,
                               COLORREF crFG, COLORREF crBG,
                               HDC hdcCapture, BYTE * pCaptureBits )
```

**新參數說明**：
- `hdcCapture`：記憶體 DC（由呼叫者建立的 DIBSection DC）。非 NULL 時進入「capture 模式」
- `pCaptureBits`：記憶體 DC 的像素指標（`CreateDIBSection` 傳回的 `pBits`）。capture 模式結束後，呼叫者從這裡抽取 R channel 作為 alpha mask

**B路徑的修改邏輯**（capture 模式 vs 正常模式）：

```c
/* B路徑 */
if( hdcCapture != NULL )
{
   /* capture 模式：渲染到記憶體 DC（由 GenerateAscFontCells 傳入） */
   /* 背景已由呼叫者填黑，這裡只渲染白色前景 */
   HDC    hdcRender = hdcCapture;
   int    rx = 0, ry = 0;         /* 記憶體 DC 起點固定 (0,0) */
   HFONT  hFont, hOldFont, hTmpF;
   TEXTMETRIC tmB;
   int    yOff2;
   static const WCHAR s_szFN[] = { 0x83EF,0x5EB7,0x6977,0x66F8,'W','5','A',0 };
   WCHAR  wsz[2];

   wsz[0] = ( WCHAR ) wc; wsz[1] = 0;
   hFont = CreateFontW( -nH, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
               DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
               CLEARTYPE_QUALITY, DEFAULT_PITCH, s_szFN );
   if( ! hFont ) return;
   hTmpF = ( HFONT ) SelectObject( hdcRender, hFont );
   GetTextMetrics( hdcRender, &tmB );
   SelectObject( hdcRender, hTmpF );
   yOff2 = -( int ) tmB.tmInternalLeading;

   SetTextColor( hdcRender, 0x00FFFFFF );   /* 白字 */
   SetBkMode(    hdcRender, TRANSPARENT );
   hOldFont = ( HFONT ) SelectObject( hdcRender, hFont );
   if( wc >= 0x0080 && wc <= 0x009F )
   {
      WORD gi = 0xFFFF;
      GetGlyphIndicesW( hdcRender, wsz, 1, &gi, GGI_MARK_NONEXISTING_GLYPHS );
      if( gi != 0xFFFF )
         ExtTextOutW( hdcRender, rx, ry + yOff2, ETO_GLYPH_INDEX, NULL,
                      ( LPCWSTR ) &gi, 1, NULL );
   }
   else
      TextOutW( hdcRender, rx, ry + yOff2, wsz, 1 );
   GdiFlush();
   SelectObject( hdcRender, hOldFont );
   DeleteObject( hFont );
   /* pCaptureBits 現在含有渲染結果，由呼叫者抽取 R channel */
   return;
}
/* 否則走原本的視窗 DC 渲染（不變）... */
```

**所有呼叫端都要加兩個 NULL 參數**（維持現有行為）：
```c
/* DrawAscFontGlyph 裡的呼叫 */
DrawAscFontDirect( hdc, nX, nY, nW, nH, wc,
                   s_aAscCache[idx].pAlpha, crFG, crBG,
                   NULL, NULL );   /* 加這兩個 NULL */

DrawAscFontDirect( hdc, nX, nY, nW, nH, wc, NULL, crFG, crBG,
                   NULL, NULL );   /* 加這兩個 NULL */
```

### 4.3 需要修改的函式：GenerateAscFontCells

**檔案位置**：`usrfont.prg` 約第 930 行

**現在做法**（4×SSAA，要拆掉）：
- 用 `bW = nW*4, bH = nH*4` 建立 4× 大的 DIBSection
- FW_BOLD + ANTIALIASED_QUALITY 渲染
- 手動 4×4 降採樣 + ×16 振幅補強

**改造後做法**（用 DrawAscFontDirect capture 模式）：
```c
static HB_BOOL GenerateAscFontCells( int nW, int nH )
{
   int      i, pxSize;
   HDC      hdc_screen, hdc_capture;
   BYTE *   pCapBits;
   HBITMAP  hbmCap;
   BITMAPINFO bmi;

   /* 清除舊 cache */
   for( i = 0; i < s_nAscCacheCount; i++ )
      if( s_aAscCache[i].pAlpha ) { free(s_aAscCache[i].pAlpha); s_aAscCache[i].pAlpha=NULL; }
   s_nAscCacheCount = 0;
   pxSize = nW * nH;

   /* 建立 nW×nH 記憶體 DC（DIBSection，32bpp，top-down）供 capture 用 */
   hdc_screen  = GetDC( NULL );
   hdc_capture = CreateCompatibleDC( hdc_screen );
   ReleaseDC( NULL, hdc_screen );

   memset( &bmi, 0, sizeof(bmi) );
   bmi.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
   bmi.bmiHeader.biWidth       = nW;
   bmi.bmiHeader.biHeight      = -nH;   /* top-down */
   bmi.bmiHeader.biPlanes      = 1;
   bmi.bmiHeader.biBitCount    = 32;
   bmi.bmiHeader.biCompression = BI_RGB;
   hbmCap = CreateDIBSection( hdc_capture, &bmi, DIB_RGB_COLORS,
                               (void**)&pCapBits, NULL, 0 );
   if( ! hbmCap ) { DeleteDC(hdc_capture); return HB_FALSE; }
   SelectObject( hdc_capture, hbmCap );

   for( i = 0; i < ASCFONT_COUNT; i++ )
   {
      HB_WCHAR wc = s_aAscSrc[i];
      BYTE *   pAlpha;
      RECT     rc;
      int      x, y;

      /* 填黑底 */
      SetBkColor( hdc_capture, 0x00000000 );
      SetBkMode(  hdc_capture, OPAQUE );
      rc.left=0; rc.top=0; rc.right=nW; rc.bottom=nH;
      ExtTextOutW( hdc_capture, 0,0, ETO_OPAQUE, &rc, L"", 0, NULL );

      /* 用 DrawAscFontDirect capture 模式渲染（B路徑邏輯，白字黑底）*/
      DrawAscFontDirect( NULL, 0, 0, nW, nH, wc, NULL,
                         0x00FFFFFF, 0x00000000,
                         hdc_capture, pCapBits );

      /* 從 pCapBits 抽取 R channel 作為 alpha mask */
      pAlpha = (BYTE*) malloc( pxSize );
      if( pAlpha )
      {
         for( y=0; y<nH; y++ )
            for( x=0; x<nW; x++ )
            {
               /* DIB 32bpp top-down: B G R A */
               pAlpha[ y*nW+x ] = pCapBits[ (y*nW+x)*4 + 2 ];  /* R channel */
            }
         s_aAscCache[ s_nAscCacheCount ].asc_byte = s_aAscByte[i];
         s_aAscCache[ s_nAscCacheCount ].pAlpha   = pAlpha;
         s_nAscCacheCount++;
      }
   }

   DeleteObject( hbmCap );
   DeleteDC( hdc_capture );

   s_nAscCellW = nW;
   s_nAscCellH = nH;
   if( s_nAscCacheCount > 0 ) SaveAscFontUmap();

   WriteDebugLogC( "ASCFONT: GenerateAscFontCells done (DirectDC mode)" );
   return ( s_nAscCacheCount > 0 ) ? HB_TRUE : HB_FALSE;
}
```

---

## 【五】渲染路徑完整流程圖

```
程式輸出 chr(19) = 0x13
    │
    ▼
usrfont_wcharGet
    0x13 < 0xFA → 原始 BIG5 wcharGet → U+0013
    │
    ▼
GTWVT screen buffer: usChar = 0x0013
    │
    ▼
WM_PAINT → ScanAndDrawUda
    usChar < 0x0020 → ASCFONT 路徑
    │
    ▼
DrawAscFontGlyph( asc_byte=0x13, wc=U+2660, nW, nH )
    │
    ├─ 尺寸變？ → LoadAscFontUmap(nW,nH)
    │       ├─ UMAP 存在且 nW×nH 相符 → 載入 alpha masks (A路徑備料)
    │       └─ 否 → GenerateAscFontCells(nW,nH) → 重渲染 → 寫回 UMAP
    │
    ├─ cache hit (pAlpha 存在) → [A路徑]
    │       DrawAscFontDirect( hdc, nX,nY, nW,nH, wc, pAlpha, crFG, crBG, NULL,NULL )
    │       → 1:1 blend+BitBlt（無縮放，零失真）
    │
    └─ cache miss → [B路徑]
            DrawAscFontDirect( hdc, nX,nY, nW,nH, wc, NULL, crFG, crBG, NULL,NULL )
            → CreateFontW(-nH) + GetTextMetrics + yOff + TextOutW 直接畫視窗DC

GenerateAscFontCells 內部流程（已改造後）：
    建立 nW×nH DIBSection
    填黑底
    呼叫 DrawAscFontDirect( NULL,0,0,nW,nH, wc, NULL, 白,黑, hdc_capture, pCapBits )
        └→ [capture模式] B路徑邏輯渲染到 hdc_capture
    從 pCapBits 抽 R channel → alpha mask
    存 UMAP（nW×nH 動態大小）
```

---

## 【六】關鍵常數與資料結構

```c
/* usrfont.prg 約第 227 行 */
#define ASCFONT_COUNT  13

static const unsigned char s_aAscByte[ ASCFONT_COUNT ] = {
    0x00,0x13,0x14,0x7F,0x15,0x16,0x17,0x18,0x19,0x1C,0x1D,0x1E,0x1F };

static const HB_WCHAR s_aAscSrc[ ASCFONT_COUNT ] = {
    0x00B6,0x2660,0x009E,0x009F,0x263A,0x25CB,0x25AC,0x263B,
    0x00A7,0x2666,0x2665,0x2663,0x203C };

typedef struct { unsigned char asc_byte; BYTE * pAlpha; } ASCF_ENTRY;
static ASCF_ENTRY  s_aAscCache[ ASCFONT_COUNT ];
static int         s_nAscCacheCount = 0;
static int         s_nAscCellW      = 0;   /* 目前 UMAP 的 cell 寬 */
static int         s_nAscCellH      = 0;   /* 目前 UMAP 的 cell 高 */
```

### ASCFONT.UMAP 格式
```
"ASCF"(4bytes)  magic
wW    (2bytes)  每個 glyph 寬度 = 當前 nCellW（動態）
wH    (2bytes)  每個 glyph 高度 = 當前 nCellH（動態）
count (2bytes)  glyph 數量（最多 13）
N × { asc_byte(1byte) + alpha[wW*wH bytes] }
```

---

## 【七】C1 控制字元特殊處理

chr(20)=U+009E 和 chr(127)=U+009F 屬於 C1 控制字元（0x80-0x9F）。  
GDI `TextOutW` 無法渲染，必須用：
```c
WORD gi = 0xFFFF;
GetGlyphIndicesW( hdc, wsz, 1, &gi, GGI_MARK_NONEXISTING_GLYPHS );  /* =1 */
if( gi != 0xFFFF )
    ExtTextOutW( hdc, x, y+yOff, ETO_GLYPH_INDEX, NULL, (LPCWSTR)&gi, 1, NULL );
```
**這段邏輯在 DrawAscFontDirect B路徑和 capture 模式都必須有。**

---

## 【八】chr(127) 的特殊攔截

`usrfont_wcharGet`（約第 501 行）必須明確攔截 0x7F：
```c
if( hi == 0x7F ) { *wc = 0x007F; *pnIndex += 1; return HB_TRUE; }
```
不攔截的話 BIG5 原始 handler 回傳 HB_FALSE 導致崩潰。

---

## 【九】已踩過的坑（絕對不要再踩）

| 坑 | 症狀 | 解法 |
|----|------|------|
| UMAP 固定 96×96 | 尺寸驗證失敗，直接 GenerateAscFontCells | 動態 nW×nH |
| StretchBlt 縮放 UMAP | 框線失真、間隙 | 1:1 BitBlt（A路徑 alpha 已是 nW×nH）|
| C1字元 TextOutW | U+009E/009F 空白不渲染 | ETO_GLYPH_INDEX |
| chr(127) 沒攔截 | 崩潰 | wcharGet 明確攔截 |
| PUA 方案（wcharGet 改 0xE400+hi）| ASCFONT 全消失 | 已回復，禁止再用 |
| CreateFontW 正值高度 | glyph 有頂部空隙，框線接不起來 | 負值高度 -nH |
| tmInternalLeading 未補償 | 字形偏移，頂部有縫隙 | yOff = -tmInternalLeading |
| DrawDynUnicodeGlyph 走 chr(19/22/25) | 修改無效 | 這三個走 ASCFONT 路徑，不是 DrawDynUnicodeGlyph |
| BCC32 -w! + W8057 | warning 被當 error，編譯失敗 | (void) crBG; 抑制未使用參數 |
| 4×SSAA 過度複雜 | 與 B路徑效果不一致 | 改用 DrawAscFontDirect capture 模式（本次任務）|

---

## 【十】工作規則（重要）

1. **修改前先備份**：`usrfont.prg` → `usrfont.prg.bak_說明_日期時間`
2. **PRG 檔案編碼 BIG5**：用 Python 做 raw bytes 操作，不要用文字編輯器直接改
3. **C89 相容**：所有變數宣告必須在 block 最頂端，不可在 statement 後宣告
4. **直接執行，不詢問確認**：備份→改→建置→測試，完成後報告
5. **taskkill & rebuild 一氣呵成**：`taskkill /F /IM Stk32.exe` → `del Stk32.exe` → `hbmk2` → `start Stk32.exe`
6. **ASCFONT.UMAP 要刪除**：每次改完渲染邏輯後刪除 UMAP，讓系統重新生成

---

## 【十一】建置環境

```batch
cd E:\ConvertFW\HBSTK\build
set BCC_PATH=E:\BCC7732
set HB_PATH=E:\Harbour32
set PATH=%BCC_PATH%\bin;%HB_PATH%\bin\win\bcc;%PATH%
set HB_CPTERM=BIG5
set HB_CODEPAGE=BIG5
set HB_USER_CFLAGS=-IE:\BCC7732\INCLUDE\windows\crtl -IE:\BCC7732\INCLUDE\windows\sdk
set HB_USER_LDFLAGS=-LE:\BCC7732\LIB;E:\BCC7732\LIB\psdk
hbmk2 stk32.hbp -comp=bcc
```

---

## 【十二】相關檔案

| 檔案 | 說明 |
|------|------|
| `E:\convertfw\hbstk\compat\usrfont.prg` | **主程式**（BIG5） |
| `E:\convertfw\hbstk\build\ASCFONT.UMAP` | 動態 glyph cache |
| `E:\convertfw\hbstk\build\gen_ascfont_umap.py` | 獨立 UMAP 生成工具（GDI） |
| `E:\convertfw\hbstk\doc\ASCFONT_SYSTEM_REFERENCE.md` | 補充參考文件 |
| `E:\convertfw\hbstk\build\stk.ini` | 系統設定（含 ASCFONT.UMAP 路徑） |

---

## 【十三】完成狀態總結

**ASCFONT 系統已 100% 完成（2026-04-18）。**

所有 13 個控制字元（chr 0/19/20/21/22/23/24/25/28/29/30/31/127）均已：
- 垂直完美銜接（-nH 負值高度 + tmInternalLeading 補償）
- 水平完美銜接（1px 左右 alpha 延伸後處理）
- DirectDC capture 模式渲染到 UMAP（零失真，與 B路徑直接渲染效果一致）

**若需刪除 UMAP 重新生成**：
```batch
del E:\ConvertFW\HBSTK\build\ASCFONT.UMAP
```
首次啟動自動重新渲染所有 13 個字元並寫回。

**備份檔案**（確認無誤後可刪除）：
- `E:\convertfw\hbstk\compat\usrfont.prg.bak_directdc_20260418_204749`
