最小化的实例

下面的方式就可以绘制出中文字体,先不说它的问题,我们先看到在游戏窗口看到中文再说。

它的问题是码点(codepoints) 很少 (这东西是什么,后面紧接着就会介绍) ,我只能很少量的中文,比如下面的代码,字体里面只有 6 个码点 ,如果绘制其他内容 "生而为人,我…" 就要创建新的字体,并加载对应的码点才能满足需求。

它的解决办法可能会这样想,缺码点是吧。那我就多加载些,大不了….

可问题在于中文汉字有几万个,把全部中文一次性读入显存显然不是一个很理智的做法。一般来说载入7000常用汉字,但是碰到生僻字依然抓瞎。所以需要一个动态机制,遇到没有的码点时,更新字体。这些都是后话了,我们先来看第一个有问题但是最简洁的例子。

 2025-09-03 at 05.36.10.png

#include <raylib.h>
#include <string>

int main() {
  SetWindowState(FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI);
  InitWindow(1200, 800, "game");
  SetTargetFPS(60);

  // Get font data from file system to memory
  int font_data_size;
  unsigned char* font_data =
      LoadFileData("path/to/your/zh-ch.ttf", &font_data_size);

  // Text to display (Draw)
  std::string text = "你好,世界。";

  // Convert UTF8 text to unicode 
  int codepoint_count;
  int* codepoints = LoadCodepoints(text.c_str(), &codepoint_count);

  // Load Font
  Font font = LoadFontFromMemory(".ttf", font_data, font_data_size, 64,
                                 codepoints, codepoint_count);

  UnloadCodepoints(codepoints);

  while (!WindowShouldClose()) {
    BeginDrawing();
    ClearBackground(WHITE);

    int fs = 32;             // font size
    Vector2 pos = {20, 20};  // text position
    DrawTextEx(font, text.c_str(), pos, fs, 1, BLACK);

    EndDrawing();
  }
  UnloadFont(font);
  CloseWindow();
  return 0;
}

Unicode & UTF-8

UTF-8 是一种针对 Unicode 的可变长字符编码。

UTF-8 specs from <https://www.ietf.org/rfc/rfc3629.txt>

Char. number range  |        UTF-8 octet sequence
  (hexadecimal)     |              (binary)
--------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

**举个栗子 🌰 :**中文字符 → Unicode U+7231 → Unicode 7231 转为二进制 01110010 00110001(共 16 位),然后 → 按三字节填充,最终的 UTF-8 编码为 为 E7 88 B1 (十六进制显示)

const char* text 里面就是 UTF-8 的二进制编码,可以这样不严谨地简单打印检查一下。

 2025-09-03 at 05.43.11.png

/**
 * @brief Load UTF-8 text encoded as codepoints array
 *
 * @param text 
 * @param count :point to store codepoint count result
 * @return an int array that contains the Unicode of each char of text.
 */
int *LoadCodepoints(const char *text, int *count) {
  int textLength = TextLength(text);

  int codepointSize = 0;
  int codepointCount = 0;

  // Allocate a big enough buffer to store as many codepoints as text bytes
  int *codepoints = (int *)RL_CALLOC(textLength, sizeof(int));

  for (int i = 0; i < textLength; codepointCount++) {
    codepoints[codepointCount] = GetCodepointNext(text + i, &codepointSize);
    i += codepointSize;
  }

  // Re-allocate buffer to the actual number of codepoints loaded
  codepoints = (int *)RL_REALLOC(codepoints, codepointCount * sizeof(int));

  *count = codepointCount;

  return codepoints;
}

/** @brief Unload codepoints data from memory */
void UnloadCodepoints(int *codepoints) { RL_FREE(codepoints); }

// Get total number of characters(codepoints) in a UTF-8 encoded text, until
// '\\0' is found NOTE: If an invalid UTF-8 sequence is encountered a '?'(0x3f)
// codepoint is counted instead
int GetCodepointCount(const char *text) {
  unsigned int length = 0;
  const char *ptr = text;

  while (*ptr != '\\0') {
    int next = 0;
    GetCodepointNext(ptr, &next);

    ptr += next;

    length++;
  }

  return length;
}

int GetCodepointNext(const char *text, int *codepointSize) {
  const char *p = text;
  int codepoint = 0x3f; // Codepoint (defaults to '?')
  *codepointSize = 1;

  // Get current codepoint and bytes processed
  if (0xf0 == (0xf8 & p[0])) {
    // 4 byte UTF-8 codepoint
    if (((p[1] & 0xC0) ^ 0x80) || ((p[2] & 0xC0) ^ 0x80) ||
        ((p[3] & 0xC0) ^ 0x80)) {
      return codepoint;
    } // 10xxxxxx checks
    codepoint = ((0x07 & p[0]) << 18) | ((0x3f & p[1]) << 12) |
                ((0x3f & p[2]) << 6) | (0x3f & p[3]);
    *codepointSize = 4;
  }
  else if (0xe0 == (0xf0 & p[0])) {
    // 3 byte UTF-8 codepoint */
    if (((p[1] & 0xC0) ^ 0x80) || ((p[2] & 0xC0) ^ 0x80)) {
      return codepoint;
    } // 10xxxxxx checks
    codepoint =
        ((0x0f & p[0]) << 12) | ((0x3f & p[1]) << 6) | (0x3f & p[2]);
    *codepointSize = 3;
  }
  else if (0xc0 == (0xe0 & p[0])) {
    // 2 byte UTF-8 codepoint
    if ((p[1] & 0xC0) ^ 0x80) {
      return codepoint;
    } // 10xxxxxx checks
    codepoint = ((0x1f & p[0]) << 6) | (0x3f & p[1]);
    *codepointSize = 2;
  } else if (0x00 == (0x80 & p[0])) {
    // 1 byte UTF-8 codepoint
    codepoint = p[0];
    *codepointSize = 1;
  }

  return codepoint;
}


FontCache LRU Cache

https://www.cnblogs.com/evencai/p/18842502

Solusion