CEGUI::String类算是一个不错的类,但是说实话,这个类让非英文国度的IT们不是很舒服。比如,自我们学习C++编程以来,我们一直使用这样的输出语句:
std::cout << "名字: " << szName << std::endl;
其中"名字:"使用的是多字节编码(ASCII编码的扩充版本)。我们的控制台能正确显示这些中文文本。但是当我们初次了解并使用CEGUI的时候,却无法使用这样简单的功能:
CEGUI::String strName = "徐杰"; pNameWindow->setText( strName );
不是说编译不过,而是显示不正确。求其根源有两个原因:
- CEGUI 使用 utf32 编码方案,这个并不重要。重要的是 CEGUI::String 如何将 const char* 这样参数转换成其内部编码方式。只要转换正确,CEGUI会按照自己的方式,将utf32 编码方案的文本显示出来。但不幸的是,eddy博士的团队并不认为 const char* 会指向非英文字符串,所以 CEGUI::String 接受 const char* 的构造函数,根本就没有进行多字节编码到 utf32 编码的转换。
- 其次,即使我们将多字节编码正确转换成了 utf32 编码,传给了CEGUI::String,我们也无法让空间显示出中文。这个问题主要是字体文件的问题,正确的字符编码必须使用正确字体文件,才能正确显示出中文。(一开始,我还以为是CEGUI::String本身的问题,后来到网上查了查,CEGUI::String的utf8构造函数是没有问题的,只是需要正确的字体文件)
我们根据这两个问题,分别给出解决方案:
-
CEGUI::String 接受几种字符串类型,通过查看源码CEGUI::String只支持其他三种字符串类型:
String(const char* chars, size_type chars_len) { init(); assign(chars, chars_len); } String(const std::string& std_str, size_type str_idx, size_type str_num = npos) { init(); assign(std_str, str_idx, str_num); } String(const utf8* utf8_str, size_type chars_len) { init(); assign(utf8_str, chars_len); }
第一种和第二种其实是一样的,std::string只不过是 const char* 的封装类,内部还是使用的多字节编码。第三种是接受一个utf8编码的字符串。之前我们讲过,CEGUI::String 根本不对 const char* 进行适当的编码转换,所以如果我们想传入中文文本,那么,我们唯一的出路是使用 utf8 编码的字符串。这样我们就需要构造 utf8 字符串了。Window SDK有响应的函数可以帮助我们完成多字节编码到utf8编码的转换。下面是我写的转换函数:#define MB2UTF8( str ) ( const CEGUI::utf8* )( MultiByteToUtf8( ( str ) ).c_str() ) std::string MultiByteToUtf8( const char* pszMultiByte ) { std::string strUtf8; if( NULL == pszMultiByte ) { return strUtf8; } int iNumCharacter = 0; //convert from MultiByte to WideChar std::wstring wcsText; iNumCharacter = MultiByteToWideChar( CP_ACP, 0, pszMultiByte, -1, NULL, 0 ); wcsText.resize( iNumCharacter + 1 );//addition 1 for '\0' MultiByteToWideChar( CP_ACP, 0, pszMultiByte, -1, &wcsText[ 0 ], iNumCharacter ); //convert from WideChar to utf8 iNumCharacter = WideCharToMultiByte( CP_UTF8, 0, &wcsText[ 0 ], -1, NULL, 0, NULL, NULL ); strUtf8.resize( iNumCharacter + 1 ); WideCharToMultiByte( CP_UTF8, 0, &wcsText[ 0 ], -1, &strUtf8[ 0 ], iNumCharacter, NULL, NULL ); return strUtf8; } void setWindowText( CEGUI::Window* pWindow, const char* pszText ) { if( NULL == pWindow || NULL == pszText ) { return; } pWindow->setText( MB2UTF8( pszText ) ); }
-
给出正确的字体文件。
- 首先到 C:\WINDOWS\Fonts 路径下找到一个带有中文字符的字体文件 ”simhei",拷贝一下,然后放到 CEGUI SDK 的 datafiles\fonts 路径下。
- 复制 CEGUI SDK 的 datafiles\fonts 下的一个.font文本,然后粘贴一下(出来一个该文件的副本),更改文件名为simhei.font。然后用写字板或者记事本打开,如下文本:
<?xml version="1.0" ?> <Font Name="Batang-26" Filename="batang.ttf" Type="FreeType" Size="26" NativeHorzRes="1024" NativeVertRes="768" AutoScaled="true"/>
修改 Name(代表程序中使用什么名字表示该字体) 和 Filename(该字体对应的字体文件名)字段:<?xml version="1.0" ?> <Font Name="simhei" Filename="simhei.ttf" Type="FreeType" Size="26" NativeHorzRes="1024" NativeVertRes="768" AutoScaled="true"/>
- 修改对应的.scheme文件,打开TaharezLook.scheme文件,将
<Font Filename="DejaVuSans-10.font" />
修改为:<Font Filename="simhei.font" />
最后还是建议,直接修改CEGUI::String的源码,将ASCII正确地转换成 utf32 编码,这样效率更高一点,使用也更加舒服。例如,添加下列代码到CEGUI::String类中可实现直接传递宽字符:
String( const wchar_t* utf16 ) { init(); assign( utf16 ); } String& assign( const wchar_t* utf16 ) { if( NULL == utf16 ) { return *this; } int iNumCharacter = wcslen( utf16 ); grow( iNumCharacter ); utf32* pData = ptr(); for( int i = 0; i < iNumCharacter; ++i ) { pData[ i ] = utf16[ i ]; } setlen( iNumCharacter ); return *this; }因为utf16绝大部分是utf32的子集,所以可以直接赋值(至少对于汉字来说可以)。
更多关于字符编码的问题,网友可以查看本人博客:彻底搞懂字符编码(unicode,mbcs,utf-8,utf-16,utf-32,big endian,little endian...)