stb_easy_font.hのご紹介

:::note info
この記事はC++ Advent Calendar 2022の10日目の記事です。
:::

# TL;DR & 前書き

- この記事は、stb_easy_font.h についての使い方の説明になります。

## 簡単な自己紹介
画像処理系の組み込みエンジニアです。OpenCV communityにもちょくちょくコメントしています。よろしくお願いします！

本当は C++20の「モジュール」について記事を書こうとしたのですが、時間が無く、ゆるふわネタです。ごめんなさい。

## 煽られてしまいましたか…

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">（ふぉんとえんじんのきじがよみたいです）</p>&mdash; dandelion (@dandelion1124) <a href="https://twitter.com/dandelion1124/status/1597570353391603712?ref_src=twsrc%5Etfw">November 29, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

フォントエンジン・・・フォントエンジンの記事が読みたいとな！！

ということで、STBのフォントエンジン記事を書きますかね？

:::note alert
ただし、STBはSTBでも、stb_truetype.h ではなく、stb_easy_font.h です！
:::

# そもそもSTBとは何ぞや？

see https://github.com/nothings/stb ...

> single-file public domain (or MIT licensed) libraries for C/C++

「ヘッダファイル１つで機能を実現するライブラリ」ですかね。

例えば、画像データを読み込みたいならば `stb_image.h`、書き込みたいなら`stb_image_write.h` をincludeすれば、この中に実行部分も入っているのですぐに使えます。

OpenCV 5.0以後では、stb_truetype.h を使って、UTF-8で日本語なんかも書けるようになっちゃいます！さて、それはそれとして……

## stb_easy_font.h とは？

ヘッダファイルによると「3D Rendering向けのbitmap font」とのこと。

https://github.com/nothings/stb/blob/master/stb_easy_font.h

```c++
// stb_easy_font.h - v1.1 - bitmap font for 3D rendering - public domain
// Sean Barrett, Feb 2015
//
//    Easy-to-deploy,
//    reasonably compact,
//    extremely inefficient performance-wise,
//    crappy-looking,
//    ASCII-only,
//    bitmap font for use in 3D APIs.
//
```

## 解説

本体部分はたった３行である。

```c++
    char buffer[99999];
    char text[]="C++ Advent Calendar 2022\nHappy New Year !!!\nWith stb_easy_font";
    int num_quads = stb_easy_font_print(0, 0, text, NULL, buffer, sizeof(buffer));
```

この３行を実行すると、`buffer`の中に、指定した文字列を描画するための座標情報が保持される。

| Offset| 内容                |
|------:|:--------------------|
|  +0x0 | X1座標(Float)        |
|  +0x4 | Y1座標(Float)        |
|  +0x8 | Z1座標(Float, 0固定) |
|  +0xC | 色情報              |
| +0x10 | X2座標(Float)        |
| +0x14 | Y2座標(Float)        |
| +0x18 | Z2座標(Float, 0固定) |
| +0x1C | 色情報              |
| +0x20 | X3座標(Float)        |
| +0x24 | Y3座標(Float)        |
| +0x28 | Z3座標(Float, 0固定) |
| +0x2C | 色情報              |
| +0x30 | X4座標(Float)        |
| +0x34 | Y4座標(Float)        |
| +0x38 | Z4座標(Float, 0固定) |
| +0x3C | 色情報              |

## 本来は...
当該データをOpenGLで処理して・・・ということができていたらしい。

## 今回は...

これに対して、テスト結果を確認するために、画像を描画しなければならない。

- pixel()関数 - フレームバッファに画素を書き出す
- line()関数 - フレームバッファに直線を書き出す
- PBMでの画像出力。

## 出力結果

こんな感じで、無事に文字出力ができました、と。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89434/6e5c0894-4468-31de-d03e-d9bd15daeb06.png)

以上となります。

## おまけ：サンプルコードを以下に示す。

```c++:サンプルコード
// g++ main.cpp -o a.out
// ./a.out > test.pbm

#include <iostream>
#include "stb_easy_font.h"

const int screen_height = 60;
const int screen_width  = 200;
uint32_t fb[screen_height][screen_width] = { 0 };

void pset(int x, int y)
{
  if ( ( x < 0 ) || ( screen_width  < x ) ) { return ; }
  if ( ( y < 0 ) || ( screen_height < y ) ) { return ; }
  fb[y][x] = 1;
}

void line(float x1, float y1, float x2, float y2 )
{
  int x,y;
  float sx, sy, ex, ey;

  if ( x1 == x2 )
  {
    // Vertial line
    if( y1 < y2 )
    {
      x = x1; sy = y1; ey = y2;
    }
    else
    {
      x = x1; sy = y2; ey = y1;
    }

    for( y = sy; y <= ey; y++ )
    {
      pset(x,y);
    }
  }else{
    // Non vertial line
    if( x1 < x2 )
    {
      sx = x1; ex = x2; sy = y1; ey = y2;
    }
    else
    {
      sx = x2; ex = x1; sy = y2; ey = y1;
    }

    for( x = sx; x <= ex; x++ )
    {
      y = sy + ( x - sx )  * ( ey - sy ) / ( ex - sx );
      pset(x,y);
    }
  }
}

int main()
{
    // Drawing
    char buffer[99999];
    char text[]="C++ Advent Calendar 2022\nHappy New Year !!!\nWith stb_easy_font";
    int num_quads = stb_easy_font_print(0, 0, text, NULL, buffer, sizeof(buffer));

    // Rendering
    for(int i = 0 ; i < num_quads; i++ )
    {
        const float x1 = *(float*)&( buffer[i * 64 + 16 * 0 + 0 ] );
        const float y1 = *(float*)&( buffer[i * 64 + 16 * 0 + 4 ] );
        const float x2 = *(float*)&( buffer[i * 64 + 16 * 1 + 0 ] );
        const float y2 = *(float*)&( buffer[i * 64 + 16 * 1 + 4 ] );
        const float x3 = *(float*)&( buffer[i * 64 + 16 * 2 + 0 ] );
        const float y3 = *(float*)&( buffer[i * 64 + 16 * 2 + 4 ] );
        const float x4 = *(float*)&( buffer[i * 64 + 16 * 3 + 0 ] );
        const float y4 = *(float*)&( buffer[i * 64 + 16 * 3 + 4 ] );

        line(x1, y1,  x2, y2 );
        line(x2, y2,  x3, y3 );
        line(x3, y3,  x4, y4 );
        line(x4, y4,  x1, y1 );
    }

    // Export to PBM
    std::cout << "P1 " << screen_width << " " << screen_height << std::endl; ;
    for(int iy=0; iy < screen_height; iy++ )
    {
      for(int ix=0; ix < screen_width; ix++ )
      {
        std::cout << ( fb[iy][ix] == 0 ? 0 : 1) << " ";
      }
      std::cout << std::endl;
    }
    return 0;
}
```