在游戏开发过程中,经常遇见显示中文的问题,如果你使用图形渲染中介绍的方法,会发现显示的文字是乱码,之所以是这样是因为 SDL 文字显示接口的用法决定的,这篇文章就简单的介绍一下 SDL 显示中文的具体过程。

在 vs2015 以后,vs 编辑器支持不同的文字编码,可以使用 u8 前缀标记字符串为 UTF8 编码,所以你可以使用下面的方式在 SDL 程序中显示中文:const char* test = u8"测试中文";。具体相关信息可以查看 MSDN

在原来介绍 SDL 文字渲染的内容中,我们介绍了 SDL 扩展库 SDL_ttf 的使用方法。其中使用了接口函数 TTF_RenderText_Blended 显示文字,不过这个接口仅仅支持西方语言的文本显示,如果想要显示其它类型的文本则需要 SDL_ttf 中的其它接口。

SDL_ttf 文字相关的接口一共有三类:

  • 包含 Text 文本的接口,如:TTF_RenderText_Blended、TTF_SizeText。
  • 包含 UTF8 文本的接口,如:TTF_RenderUTF8_Blended、TTF_SizeUTF8。
  • 包含 UNICODE 文本的接口,如:TTF_RenderUNICODE_Blended、TTF_SizeUNICODE。

第一类函数接受的是 Latin1 编码的字符,Latin1 是 ISO-8859-1 的别名,有些环境下写作 Latin-1。Latin1 编码是单字节编码,向下兼容 ASCII,其编码范围是 0x00-0xFF,0x00-0x7F 之间完全和 ASCII 一致,0x80-0x9F 之间是控制字符,0xA0-0xFF 之间是文字符号。

第二类函数接受的是 UTF-8 编码的字符,UTF-8 是一种 Unicode 编码的实现方式,UTF-8 最大的特点是支持变长编码。

第三类函数接受的是 Unicode 编码的字符。Unicode 本身是一项规范,规定了符号对应的二进制码,但是并没有规定如何存储这些编码,所以才会出现 UTF-8、UTF-16、UTF-32 等不同的编码方式。

Unicode 字符集为每个字符分配一个码位,例如 “知” 的码位是 30693,十六进制表示为 0x77E5,所以记作 U+ 77E5。

UTF-8 用的一套 8 位为一个编码单位的边长编码方式,所以 UTF-8 可以用 C 语言的 char 型数组存储,其中每个字符可能用 1 到 4 个字节表示。

上边第三类函数接受的 Unicode 编码是只 UTF-16LE 的方式,LE 只的是小端,对应的还存在 UTF-16BE,而 UTF-8 是 UTF-8 BOM,BOM 存在 是为了区分 UTF-16LE、UTF-16BE 和 UTF-8,因为三种编码可能共存。GB2312 是中国早期给简体中文指定的编码,后来加入了繁体变为 GBK,后来融入日韩变为 GB18030。

1. 选择字体

说了这么多,其实如果准备使用 SDL 显示中文,你需要使用后两种接口,推荐使用 UTF8,因为在其它非 Windows 平台大多以 UTF8 为默认字符集。

要想正确的显示你的中文文本,你需要先选择一个支持中文编码的字体:

中文字体

因为中文的字符较多,所以大多数中文字体的大小都比较大,例如常见的黑体,有 9M 之多,与之相对的 常用的等宽字体 Courier New 大小只有 500kb 左右。

2. 转换编码

一般我们操作系统默认是中文显示,所以默认的编码都是 GBK2312。这里需要先将 GBK2312 转换为 UTF-8 编码方式:

int GBKToUTF8(const char* gbk, int gbklen, char* utf8, int utf8len)
{
	if (!(gbk && utf8)) {
		return 0;
	}

	//转换为宽字符
	int bufWLen = MultiByteToWideChar(CP_ACP, 0, gbk, gbklen, NULL, 0);
	if (bufWLen == 0) {
		return -1;
	}

	WCHAR* bufW = malloc(sizeof(WCHAR) * bufWLen);
	if (!bufW) {
		return -2;
	}

	int ret = MultiByteToWideChar(CP_ACP, 0, gbk, gbklen, bufW, bufWLen);
	if (ret == 0) {
		free(bufW);
		return -3;
	}

	//转换为 UTF8
	int bufMLen = WideCharToMultiByte(CP_UTF8, 0, bufW, -1, NULL, 0, NULL, NULL);
	if (bufMLen == 0) {
		free(bufW);
		return -4;
	}

	CHAR* bufM = malloc(bufMLen);
	if (!bufM) {
		free(bufW);
		return -5;
	}

	ret = WideCharToMultiByte(CP_UTF8, 0, bufW, -1, bufM, bufMLen, NULL, NULL);
	if (ret == 0) {
		free(bufW);
		free(bufM);
		return -6;
	}

	//空间不够,不截取...
	if (bufMLen > utf8len) {
		free(bufW);
		free(bufM);
		return -7;
	}

	//复制
	memset(utf8, 0, utf8len);
	memcpy(utf8, bufM, bufMLen);

	free(bufW);
	free(bufM);

	return bufMLen;
}

3. 文字显示

最后就是真正的显示阶段,首先加载支持汉字的字体:

TTF_Font* font = TTF_OpenFont("msyh.ttc", 48);

接着渲染显示文字的纹理图片:

SDL_Surface* surf = TTF_RenderUTF8_Blended(font, test, color);
SDL_Texture* tex = SDL_CreateTextureFromSurface(pRenderer, surf);

最后就是显示:

SDL_RenderCopy(pRenderer, ftex, NULL, NULL);

注意上面代码中使用的是 TTF_RenderUTF8_Blended 函数,如果你需要获取文本大小同样需要使用 TTF_SizeUTF8 函数。