文字の形を自分で作る ― フォントを自作して描画するC言語の実験

## 初めに
文字表示はあまりにも当たり前すぎて、普段は意識しない

本記事の狙い：「文字ってどう表示されてるの？」を一から見直す

実験の概要：「HELLO WORLD」を自作フォントで表示

## そもそもフォント（字体）とは？
文字コード（ASCIIなど）は「どの文字か」を決めているだけ
「どう描くか」はフォント（字体）の仕事

## 自作フォントを定義する（8×8ドット文字）
ドット絵とは？ビットで表現する方法

実際のフォントデータ（例：'H'や'R'の定義）

各ビットが画素に対応している仕組み

## ドット絵を描く処理
SetPixel() で画素単位の描画を行う

1文字ずつ描く仕組み

1行の文字列「HELLO WORLD」を並べて描く方法

## プログラム
<details>
<summary>custom_font.c</summary>

```custom_font.c
#include <windows.h>

#define CHAR_WIDTH 8
#define CHAR_HEIGHT 8
#define CHAR_SPACING 2

// HELLO WORLD に使うすべての文字のビットマップ定義（8x8）
const unsigned char FONT_H[8] = {
    0b10000001,
    0b10000001,
    0b10000001,
    0b11111111,
    0b10000001,
    0b10000001,
    0b10000001,
    0b00000000
};

const unsigned char FONT_E[8] = {
    0b11111111,
    0b10000000,
    0b10000000,
    0b11111110,
    0b10000000,
    0b10000000,
    0b11111111,
    0b00000000
};

const unsigned char FONT_L[8] = {
    0b10000000,
    0b10000000,
    0b10000000,
    0b10000000,
    0b10000000,
    0b10000000,
    0b11111111,
    0b00000000
};

const unsigned char FONT_O[8] = {
    0b01111110,
    0b10000001,
    0b10000001,
    0b10000001,
    0b10000001,
    0b10000001,
    0b01111110,
    0b00000000
};

const unsigned char FONT_W[8] = {
    0b10000001,
    0b10000001,
    0b10000001,
    0b10000001,
    0b10011001,
    0b10100101,
    0b01000010,
    0b00000000
};

const unsigned char FONT_R[8] = {
    0b11111100,
    0b10000010,
    0b10000010,
    0b11111100,
    0b10001000,
    0b10000100,
    0b10000010,
    0b00000000
};

const unsigned char FONT_D[8] = {
    0b11111110,
    0b10000001,
    0b10000001,
    0b10000001,
    0b10000001,
    0b10000001,
    0b11111110,
    0b00000000
};

const unsigned char FONT_SPACE[8] = {
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000,
    0b00000000
};

// "HELLO WORLD" という文字列を表示するために、
// 各文字に対応するドット絵データ（フォント）をポインタで並べたもの
// 各フォントデータは、文字を画面に描画するための8×8ドットのビットパターンを表している
const unsigned char* MESSAGE[] = {
    FONT_H, FONT_E, FONT_L, FONT_L, FONT_O,
    FONT_SPACE,
    FONT_W, FONT_O, FONT_R, FONT_L, FONT_D
};

#define MESSAGE_LEN (sizeof(MESSAGE) / sizeof(MESSAGE[0]))

void draw_char(HDC hdc, int x, int y, const unsigned char* font) {
    for (int row = 0; row < CHAR_HEIGHT; row++) {
        for (int col = 0; col < CHAR_WIDTH; col++) {
            if (font[row] & (1 << (7 - col))) {
                SetPixel(hdc, x + col, y + row, RGB(0, 0, 0));
            }
        }
    }
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        int x = 10;
        int y = 10;
        for (int i = 0; i < MESSAGE_LEN; i++) {
            draw_char(hdc, x, y, MESSAGE[i]);
            x += CHAR_WIDTH + CHAR_SPACING;
        }

        EndPaint(hwnd, &ps);
        break;
    }
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow) {
    const char CLASS_NAME[] = "CustomFontWindow";

    WNDCLASS wc = { };
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);

    RegisterClass(&wc);

    HWND hwnd = CreateWindowEx(
        0,
        CLASS_NAME,
        "Custom HELLO WORLD",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 400, 200,
        NULL, NULL, hInstance, NULL
    );

    if (hwnd == NULL) return 0;

    ShowWindow(hwnd, nCmdShow);

    MSG msg = { };
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}
```
</details>

## 動作
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/fb5756e8-d662-4dc2-93f4-e0c03b215d06.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/9f84eb7c-6088-4c6e-b173-f6192299a3a2.png)

## プログラムの解説
※文字描画に関わる処理のみに絞り、windows固有の書き方については割愛します。

### 定義
- MESSAGEは文字列に相当。ここでHELLO WORLDを設定している。
- MESSAGE_LENはHELLO WORLDの文字数11が入る。
```c
const unsigned char* MESSAGE[] = {
    FONT_H, FONT_E, FONT_L, FONT_L, FONT_O,
    FONT_SPACE,
    FONT_W, FONT_O, FONT_R, FONT_L, FONT_D
};

#define MESSAGE_LEN (sizeof(MESSAGE) / sizeof(MESSAGE[0]))
```

### WndProc関数
- 10,10の座標から文字数分forを回して描画していく。
- MESSAGE配列の中にどの文字を表示するかの情報が入っている
- draw_charを呼び出しピクセル単位で文字を描画する。
- 一文字出力する毎にｘ座標を一文字分の幅8＋間隔2ずつ右へずらす
```c
        int x = 10;
        int y = 10;
        for (int i = 0; i < MESSAGE_LEN; i++) {
            draw_char(hdc, x, y, MESSAGE[i]);
            x += CHAR_WIDTH + CHAR_SPACING;
        }
```

### draw_char関数
- 高さ８、幅８の2重ループで処理
- `(1 << (7 - col))` は、col 番目のビットを1に設定するための式
- `font[row] & (1 << (7 - col))` で、`font[row]` の中で指定された col 番目のビットが 1 かどうかを確認
- 結果が 0 でない場合（つまりビットが 1 であれば）、その位置にピクセル(黒)を描画
```c
    for (int row = 0; row < CHAR_HEIGHT; row++) {
        for (int col = 0; col < CHAR_WIDTH; col++) {
            if (font[row] & (1 << (7 - col))) {
                SetPixel(hdc, x + col, y + row, RGB(0, 0, 0));
            }
        }
    }
```

例：1 行のビット列を描画する流れ

例えば、font[row] の値が次のようなビット列だったとします：

font[0] = 0b10110010;

これは、次のようにビット配置されます（左が上位ビット）：

bit位置:   7 6 5 4 3 2 1 0
値      :  1 0 1 1 0 0 1 0
描画    :  ■ □ ■ ■ □ □ ■ □

この1バイトは、row = 0（上から1行目）に相当します。

| col | `1 << (7 - col)` | `font[0] & ...` | 結果 | 描画位置（x + col, y）         |
|-----|------------------|------------------|------|----------------------------|
| 0   | `0b10000000`     | `1`（true）     | ■    | `(x + 0, y + 0)` ⇒ ピクセル描画 |
| 1   | `0b01000000`     | `0`（false）    | □    | 描画しない                    |
| 2   | `0b00100000`     | `1`             | ■    | `(x + 2, y + 0)`             |
| 3   | `0b00010000`     | `1`             | ■    | `(x + 3, y + 0)`             |
| 4   | `0b00001000`     | `0`             | □    | 描画しない                    |
| 5   | `0b00000100`     | `0`             | □    | 描画しない                    |
| 6   | `0b00000010`     | `1`             | ■    | `(x + 6, y + 0)`             |
| 7   | `0b00000001`     | `0`             | □    | 描画しない                    |