SDL & Object Pascal (Delphi) [2] 显示中文字符
一些基础的问题我不再赘述了,查阅帮助文档(Object Pascal SDL Doc.chm)显然比在这看我胡扯合适得多。那些画像素,获取像素信息,显示BMP文件多数时候只要把那些代码复制过来基本就能工作。下面说的是怎样显示中文。
其实SDL加上ttf支持时(需要将对应的dll文件添加到工程目录里)是完全可以显示中文的,简体和繁体都没问题(Object Pascal版本也不缺功能),但是只能用Unicode编码。这就是说,你必须选择一个Unicode编码完整的字体文件(或者说至少你需要的那些字符是完整的)。Windows下面这样的字体有很多,我推荐的有:
简体中文:宋体大字符集,微软雅黑;
繁体中文:细明体,微软正黑,标楷体。
这些基本是中文地区最重要的几种字体。一个缺憾就是用于显示简体中文的楷体(和其他字体)优秀的并不多。
【注意:目前unicode部分较完整的字体已经很多了,例如华文系列,中易系列均可以】
输出中文时,可以先写这样的一个子程作为基础:- procedure DrawText(word: PUint16; x_pos, y_pos: integer; color: Uint32);
- var
- text: PSDL_Surface;
- dest: TSDL_Rect;
- begin
- text := TTF_RenderUNICODE_blended(font, word, TSDL_Color(Color));
- dest.x := x_pos;
- dest.y := y_pos;
- SDL_BlitSurface(text,nil, screen, @dest);
- SDL_FreeSurface(text);
- end;
复制代码 screen就是已经定义好的那个主屏幕的标记。font是一个指向字体文件的指针,可以这样定义和初始化:
全局变量:在初始化状态的子程中:- TTF_Init;
- font:=TTF_OpenFont('kaiu.ttf', 20);
复制代码 当然font的定义和初始化写在DrawText里面也一样,只是写在全局变量里面就可以只初始化一次,可能会效率高一些。同样把DrawText里面用的那个text写成全局变量也是一个选择。在程序结束时,还要释放这些资源。
kaiu就是标楷体的文件名字,20是字号,需要把字体文件也放在工程目录里面。
TTF_RenderUNICODE_blended这个函数是以blended效果输出文字,以TTF_RenderUNICODE开头的函数有3个,对应的是不同的效果。
SDL_BlitSurface大致可以理解为在“表面”上再贴一层。
调用DrawText的方法如下:- str: WideString;
- ......
- str:=' 这是一个显示中文的测试';
- DrawText(@str[1], 100, 100, $FFFFFF);
- Sdl_UpdateRect(screen, 0, 0, screen.w, screen.h);
复制代码 注意一定要用WideString(或PWideChar)类型!
str前面我添加了一个空格。这是因为我发现有些字体中的某些字符似乎包含自动缩进(原因不明),它们在作为首字符时整个字符串会右移,于是我干脆把首字符全都设成了空格(如果有人知道原因以及更好的解决方案,还请不吝赐教)。100,100,$FFFFFF就是位置和颜色了,这里我是随便写了一个颜色(应该是白的)。最后那句就是更新屏幕上的一个矩形区域了,不更新是不会有效果的。
如果用WideString类型的话,str的第0位保存的是字串长度,所以用的是str[1],由于DrawText的第一个参数是指针所以加了@。这个调用方法似乎有点别扭,当然如果在DrawText里多写几行代码也可以有其他的封装方案。
这样做基本上显示中文就可以了。但是有时你可能面对已经做好的资源文件,里面使用了其他的编码方案,比如繁体地区常用的Big5码。这时需要用到一个API函数 MultiByteToWideChar 。
这个API的参数还是有点麻烦的,可以写一个函数将它封装:- function Big5ToUnicode(str Char): widestring;
- var
- len: integer;
- begin
- len:=MultiByteToWideChar(950,0,PChar(str),-1,nil,0);
- setlength(result,len-1);
- MultiByteToWideChar(950, 0, PChar(str), -1, pwidechar(result), len+1);
- result:=' '+result;
- end;
复制代码- MultiByteToWideChar(950, 0, PChar(str), -1, pwidechar(result), len+1) 各个参数含义:
- 950:码表,950即Big5和Unicode的对应表;
- 0:这一位写0就可以了;
- PChar(str):字符串的地址(这里又用PChar强行转换了一次,应该没必要);
- -1:字符串长度,如果是-1就会自动计算长度并转换到字符串结束;
- pwidechar(result):保存结果的地址;
- len+1:缓冲区大小,如果为零函数就返回需要的缓冲区大小。
复制代码 在Big5ToUnicode函数中MultiByteToWideChar使用了两次,第一次是计算所需缓冲区大小,第二次才是正式的转换。结果的长度设成多少需要实验一下,因为结尾或许有不想要的字符。但是不能不设或设成0,这样result并没有获得地址分配,转换会出错。至于后面在结果前又加了个空格,就看效果而定了。
在这里形参并没有用String而是PChar,是因为从资源文件中读出的数据不一定是读到String里面,甚至可能读到整数数组里,使用@算符传入地址就可以了。后面还可以利用这两个函数再写一个DrawBig5Text。
如果是GB2312,码表号是936。大部分的转码软件都是使用了这个API,GB2312与Big5的互相转换也是通过Unicode进行的。
同样还有一个WideCharToMultiByte的API,用法是类似的(Char和WideChar的位置当然相反),但是后面的参数多了两个,如果不知是干什么的可以都写成nil。
这个API是Windows提供的,所以这是在Windows下面比较简单的实现方法。但是如果作跨平台开发就不适合了。
最后是整个程序的代码。需要注意的是这份代码并未对打开设备失败进行处理。在你正式编写一个游戏时,判断一下经常是必要的。- unit myfunctions;
- interface
- uses
- sysutils,
- windows,
- sdl,
- sdl_ttf;
- procedure Run;
- procedure DrawText(word: PUint16; x_pos, y_pos: integer; color: Uint32);
- implementation
- var
- screen: PSDL_Surface;
- event: TSDL_event;
- font: PTTF_Font;
- procedure Run;
- var
- str: WideString;
- begin
- SDL_Init(SDL_INIT_VIDEO);
- screen := SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE or SDL_DOUBLEBUF);
- TTF_Init;
- font := TTF_OpenFont('kaiu.ttf', 20);
- str := ' 这是一个显示中文的测试';
- DrawText(@str[1], 100, 100, $FFFFFF);
- Sdl_UpdateRect(screen, 0, 0, screen.w, screen.h);
- while SDL_PollEvent(@event) >= 0 do
- begin
- if event.type_ = SDL_QUITEV then break;
- Sdl_delay(10);
- end;
- TTF_Quit;
- SDL_Quit;
- exit;
- end;
- procedure DrawText(word: PUint16; x_pos, y_pos: integer; color: Uint32);
- var
- text: PSDL_Surface;
- dest: TSDL_Rect;
- begin
- text := TTF_RenderUNICODE_blended(font, word, TSDL_Color(Color));
- dest.x := x_pos;
- dest.y := y_pos;
- SDL_BlitSurface(text, nil, screen, @dest);
- SDL_FreeSurface(text);
- end;
- end.
复制代码 |