查看: 4372|回复: 3

CEGUI中的汉字显示实现

[复制链接]

该用户从未签到

发表于 2009-8-10 21:41:22 | 显示全部楼层 |阅读模式
几日前,用 CEGUI做界面,发现无法应用CEGUI的window中setText()函数直接显示中文。上网google一下,原来经过简单的字符转化可以显示中文(偷着乐,CEGUI太方便了)。

方法如下(引用):http://blog.csdn.net/kun1234567/archive/2008/04/11/2282761.aspx
CEGUI使用utf8编码格式。这就意味着我们可以很简单的就显示中文。

1、弄个包含中文的字体,在这里我借用大多数例子里的 “C:/windows/Font/simhei.ttf”文件。把这个文件拷贝到Datafiles文件夹的Font文件夹里。

2、随便照着一个 .Font文件,自己写一个simhei.font文件。可以用TXT写,然后保存,有的朋友说需要保存为utf8编码格式,实际上是不需要的。

3、同时注意修改你加载到程序里的scheme文件,将里面的字体文件设置成simhei.ttf。你也可以继续使用FirstWindow这个例子,这样的话直接修改源代码里的字体为simhei.tff。

4、现在在程序里进行字符编码转换,我拿代码说明问题:

std::wstring aa = L"123中文abcあいうえお";
char buff[128] = "";
WideCharToMultiByte( CP_UTF8, 0, aa.c_str(), aa.size(), buff, sizeof(buff), 0, 0);
button1->setText ( CEGUI::String ( CEGUI::utf8* )buff );

原理是这样的,对于utf8来说,英文字符和ansi编码 在内存布局上没什么区别,都是一个UCHAR。但是对于非英文字符,则是UCHAR+UCHAR+UCHAR。如果我们手工进行编码格式转换,会比较烦琐。

比较偷懒的方法就是,我们先用WCHAR(unicode内存布局,UCHAR+UCHAR+UCHAR+UCHAR)来储存需要显示的字符串,然后调用Win32API来帮我们把宽字符转换成char(多字节字符集内存布局)。

这就是基本方法了,然后我们可以根据这个转换方针,利用Win32API随意的转换字符编码格式,从而满足程序中的各种需求。


        通过此方法可以显示中文,还没来得急高兴就发现了第二个问题:这种方法显示中文速度太慢(显示几十个字需要等上7、8秒左右)。难道没有高效的方法吗?
于是继续Google(我很懒,别人能做的事情从来不麻烦自己,懒得跟踪代码),结果还真让我找到了两篇相关的文章:一份是千里马肝的《游戏中汉字显示的实现与技巧》,另一份是免费打工仔的《让OGRE支持中文》。从中找到了原因:
        原来在游戏中,是将点阵字库或tif字体里的文字写进纹理,根据需求贴到指定的位置。英文的显示非常简单,只有26个字母,就算再加一些标点、符号什么的,用一张位图,就可以足以显示所有的单词了。而中文要像处理英文那样,把所有的汉字都保存在一张位图里,那么每一种字体都要生成一个巨型位图。在GB2312中,一共有6000多个汉字,就算是用16*16,据说会有2.5M!(马肝兄说的,我没算过)
        继续Google,也没有找到解决问题的直接办法,唉,再懒也得自己上阵了。
通过跟踪调试,发现了问题所在,原来罪魁祸首就是他:

const FontGlyph *Font::getGlyphData (utf32 codepoint)
{
        if (codepoint > d_maxCodepoint)
                return 0;

                if (d_glyphPageLoaded)
                {
                        uint page = codepoint / GLYPHS_PER_PAGE;
                        uint mask = 1 << (page & (BITS_PER_UINT - 1));
                        if (!(d_glyphPageLoaded [page / BITS_PER_UINT] & mask))
                        {
                                d_glyphPageLoaded [page / BITS_PER_UINT] |= mask;
                                rasterize (codepoint & ~(GLYPHS_PER_PAGE - 1),
                                        codepoint | (GLYPHS_PER_PAGE - 1));
                        }
                }

                CodepointMap::const_iterator pos = d_cp_map.find (codepoint);
                return (pos != d_cp_map.end()) ? &pos->second : 0;
}

原来CEGUI根据Unicode字符的编码顺序,为每256个字符分配一张纹理(例如编码0-255存放在纹理一,编码768-1023 存放在纹理四)。英文很容易搞定了,那么几个字符一张纹理就够了,可中文就得靠运气了,有时显示几个字就要生成几张纹理,还要将每张纹理用不需要的相邻字填满,劳民伤财!
发现了问题,我便按照千里马肝的思想对函数进行了改造,将使用的文字放入一张纹理中,因为纹理最大承载256个字,所以,当汉字超过256个时,则将不常用的去掉,将新的字符写入。
后来我发现汉字的引用没有太多的规律,常用的一千多汉字出现的概率没有那么悬殊(废话,要不怎么是常用呢!),没有办法很好地按照使用的频率将汉字限制在256个字以内,写进纹理,就索性一旦满了就将字全部释放掉,重新写入。(也需有我没找到,还请高手指教)
代码如下:
const FontGlyph *Font::getGlyphData (utf32 codepoint)
{
        if (codepoint > d_maxCodepoint)
                return 0;

        if(codepoint < 256)  //决定保留一张纹理放英文和字符
        {
                if (d_glyphPageLoaded)
                {
                        uint page = codepoint / GLYPHS_PER_PAGE;
                        uint mask = 1 << (page & (BITS_PER_UINT - 1));
                        if (!(d_glyphPageLoaded [page / BITS_PER_UINT] & mask))
                        {
                                d_glyphPageLoaded [page / BITS_PER_UINT] |= mask;
                                rasterize (codepoint & ~(GLYPHS_PER_PAGE - 1),
                                        codepoint | (GLYPHS_PER_PAGE - 1));
                        }
                }

                CodepointMap::const_iterator pos = d_cp_map.find (codepoint);
                return (pos != d_cp_map.end()) ? &pos->second : 0;
        }
        else         //显示汉字啦
        {
                CodepointMap::const_iterator pos;

                pos = d_hz_map.find (codepoint);

                if(pos != d_hz_map.end())
                {
                        return (pos != d_hz_map.end()) ? &pos->second : 0;
                }
                else
                {
                        rasterizeHZ(codepoint);

                        pos = d_hz_map.find (codepoint);
                        return (pos != d_hz_map.end()) ? &pos->second : 0;
                }
        }
}

void FreeTypeFont::rasterizeHZ (utf32 codepoint)
{
        int num;
        uint texsize = 512;

        if(d_hz_map.size() < 256)
        {
                float adv = d_fontFace->glyph->metrics.horiAdvance * float(FT_POS_COEF);

                d_hz_map[codepoint] = FontGlyph (adv);
        }
        else
        {
                d_hz_map.clear();

                ImagesetManager::getSingleton ().destroyImageset (hzImageset->getName ());

                hzImageset = ImagesetManager::getSingleton ().createImageset (
                        d_name + "_auto_glyph_images_" ,
                        System::getSingleton ().getRenderer ()->createTexture ());

                d_glyphImages.push_back (hzImageset);

                float adv = d_fontFace->glyph->metrics.horiAdvance * float(FT_POS_COEF);

                d_hz_map[codepoint] = FontGlyph (adv);
        }

        CodepointMap::const_iterator hzInter  = d_hz_map.find(codepoint);

        if (!hzInter->second.getImage())
        {
                // Render the glyph
                if (FT_Load_Char (d_fontFace, hzInter->first, FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT |
                        (d_antiAliased ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO)))
                {
                        std::stringstream err;
                        err << "Font::loadFreetypeGlyph - Failed to load glyph for codepoint: ";
                        err << static_cast<unsigned int> (hzInter->first);
                        err << ".  Will use an empty image for this glyph!";
                        Logger::getSingleton ().logEvent (err.str (), Errors);

                        // Create a 'null' image for this glyph so we do not seg later
                        Rect area(0, 0, 0, 0);
                        Point offset(0, 0);
                        String name;
                        name += hzInter->first;
                        hzImageset->defineImage(name, area, offset);
                        ((FontGlyph &)hzInter->second).setImage(&hzImageset->getImage(name));
                }
                else
                {
                        uint glyph_w = d_fontFace->glyph->bitmap.width + INTER_GLYPH_PAD_SPACE;
                        uint glyph_h = d_fontFace->glyph->bitmap.rows + INTER_GLYPH_PAD_SPACE;

                        // Check if glyph right margin does not exceed texture size
                        uint x_next = m_nHZX + glyph_w;
                        if (x_next > texsize)
                        {
                                m_nHZX = INTER_GLYPH_PAD_SPACE;
                                x_next = m_nHZX + glyph_w;
                                m_nHZY = m_nHZYB;
                        }

                        // Check if glyph bottom margine does not exceed texture size
                        uint y_bot = m_nHZY + glyph_h;
                       

                        // Copy rendered glyph to memory buffer in RGBA format
                        drawGlyphToBuffer (hzmem_buffer + (m_nHZY * texsize) + m_nHZX, texsize);

                        // Create a new image in the imageset
                        Rect area(static_cast<float>(m_nHZX),
                                static_cast<float>(m_nHZY),
                                static_cast<float>(m_nHZX + glyph_w - INTER_GLYPH_PAD_SPACE),
                                static_cast<float>(m_nHZY + glyph_h - INTER_GLYPH_PAD_SPACE));

                        Point offset(d_fontFace->glyph->metrics.horiBearingX * static_cast<float>(FT_POS_COEF),
                                -d_fontFace->glyph->metrics.horiBearingY * static_cast<float>(FT_POS_COEF));

                        String name;
                        name += hzInter->first;
                        hzImageset->defineImage (name, area, offset);
                        ((FontGlyph &)hzInter->second).setImage (&hzImageset->getImage (name));

                        // Advance to next position
                        m_nHZX = x_next;
                        if (y_bot > m_nHZYB)
                        {
                                m_nHZYB = y_bot;
                        }
                }
        }

        // Copy our memory buffer into the texture and free it
        hzImageset->getTexture ()->loadFromMemory (hzmem_buffer, texsize, texsize, Texture:F_RGBA);

}

        OK,问题搞定,打完收工。试试,效果还不错,可以洗洗睡了。特将自己的一点体会写出来,给新手提供个捷径,也希望高手批评指教。

本文来自新浪博客,转载请标明出处:http://blog.sina.com.cn/s/blog_613d5bdc0100er4f.html

该用户从未签到

发表于 2009-8-10 22:37:56 | 显示全部楼层
不错~~~~~~~

该用户从未签到

发表于 2010-3-20 23:33:36 | 显示全部楼层
顶一下

该用户从未签到

发表于 2011-1-15 15:36:48 | 显示全部楼层
再顶一下
您需要登录后才可以回帖 登录 | 注册

本版积分规则

OSG中国官方论坛-有您OSG在中国才更好

网站简介:osgChina是国内首个三维相关技术开源社区,旨在为国内更多的技术开发人员提供最前沿的技术资讯,为更多的三维从业者提供一个学习、交流的技术平台。

联系我们

  • 工作时间:09:00--18:00
  • 反馈邮箱:1315785073@qq.com
快速回复 返回顶部 返回列表