业精于勤,荒于嬉
全部博文(763)
分类: C/C++
2010-05-28 13:55:26
经过这两天的摸索和实践,终于做好了第一版点阵字库生成工具,开心 ^o^
之前做TFT Driver的时候,一直期望能有一个比较好用的点阵字库生成工具,但是从网上找到的都是需要注册之后才可以生成任意大小的字库~不方便!
其实老早就想自己写一个了,但是苦于一直没有太多精力~
前几天西藏大学的学生咨询有关藏文字库的问题,突然感觉,是时候自己写一个字库生成工具了~虽然目前还不能对藏文的编码等有了解,但是做一个简体汉字字库的生成工具,也是必要的了~~ 也是希望能为像我一样苦于不能找到一个简单易用的字库生成器的朋友们提供一个免费开放的小软件 :-)
言归正传。
MFC提供了一些API帮助用户方便的从各种系统字库中提取需要的点阵数据。大致上是使用这个函数:
DWORD GetGlyphOutline( UINT nChar,
UINT nFormat,
LPGLYPHMETRICS lpgm,
DWORD cbBuffer,
LPVOID lpBuffer,
const MAT2 FAR* lpmat2 ) const;
这个函数的各个参数的用法可以去查MSDN,利用这个函数的典型应用如下:
int FontSize = 48; CString FontName = "宋体"; int ch = 'A'; ////////////////////////////////////////////////////// // 获取字符ch的点阵数据的大小 unsigned char *pBuf = new unsigned char [len]; // 保存点阵数据 dcScreen.GetGlyphOutline(ch, GGO_BITMAP, &pGL, len, pBuf, &mat2); // 最后记得释放掉pBuf,并还原Screen DC的字体 dcScreen.SelectObject(poldfont); |
使用上面的代码可以很方便的拿到字符ch的点阵数据。这里需要注意的是,获取到的点阵数据是横向取点,第一个点保存在第一个字节的最高位,另外,点阵数据的每一行都是要做4字节对齐的。
也就是说,48*48的点阵数据,它的每一行实际是8个字节,而不是48/8=6字节!
为了做4字节对齐,可以使用这样的方法来得到每一行的宽度:int FontWidth = ((FontSize + 31) >> 5) << 2;
上面的公式在网上随处可见,也是典型的有效的快速做对齐的方法。
由于在写这个软件的时候可能不仅仅这一个地方需要做对齐的操作,所以索性就写了一个对齐函数:
static const int sc_PowerMap[] = {
0, 0, 1, 0, 2, 0, 0, 0, 3,
};
int Power(int n)
{
if((n > 0) && (n < sizeof(sc_PowerMap) / sizeof(int)))
return sc_PowerMap[n];
return -1;
}
#define ALIGN(num, align) (((((num) + (align) * 8 - 1) >> (3 + Power(align)))) << Power(align))
利用宏ALIGN就可以求得num的align字节对齐的结果,比如:ALIGN(32, 4)即可得到32做4字节对齐
当然,这里写的这个宏是有一定的局限性的,它只能做2的n次幂个字节的对齐,即,只能做1字节、2字节、4字节、8字节等对齐操作。不过,一般情况下这已经足够用了,因为很少有人变态到在二进制的计算机里使用3字节或5字节对齐这样的数据存储方式~~
得到字符的点阵数据之后,问题接踵而来。正如之前转的那个《根据所选择的 TrueType 字体生成点阵数据》里叙述的那样,GetGlyphOutline()函数得到的点阵数据仅仅是下图中的BlackBox的范围内的数据。
而通常情况下我们希望得到的点阵数据是完整的lfHeight*gmCellIncX这么大范围内的数据。所以,有必要将BlackBox这幅“点阵图像”按照Origin所指示的原点给它移动到我们希望的字符范围的中间。
在阅读《根据所选择的 TrueType 字体生成点阵数据》一文时,其实并没有搞清楚上面的图中的各个参数是如何得到的。通过GetGlyphOutline()函数中的LPGLYPHMETRICS参数,可以得到BlackBoxX和BlackBoxY,以及gmCellIncX和gmCellIncY,以及Origin坐标等信息。但是在调试的过程中发现,gmCellIncY为0 ?由于机器上的MSDN出了问题,没办法查它的意义,所以只好硬着头皮尝试。另外还发现,Origin坐标中的y坐标看起来并不是以字符外框的左上角为原点计算的。后来以为是以左下角为原点计算,所以只需要:
off_x = Origin.x;
off_y = lfHeight - Origin.y;
就可以得到字符在框框中的起始坐标了,但是实际操作的时候发现,其实并不是这样的……因为在对某个字符的数据跟踪的时候发现,BlackBoxY的值是16(16号字体下),Orign.y是14,这简直是不可能的!因为off_y = 16-14=2,而如果起始y坐标是2的话,那么BlackBoxY已经是16个像素高了,再向下移动2个像素就会超出字符的外框啊~
结果这个问题导致了调试过程中经常由于内存操作越界而导致堆block损坏!
后来再回头去看《根据所选择的 TrueType 字体生成点阵数据》一文发现,原来Origin坐标的计算方法不是这样的,需要配合另外一个函数GetTextMetrics()来实现。最后,完整的获取点阵数据的函数如下:
int CHZKCreatorDlg::GetFontData(int ch, CString FontName, int FontSize, void *pBuf/*=NULL*/, int x_off/*=0*/, int y_off/*=0*/) { // 小于4号的字体不再输出 if(FontSize < 4) { if(pBuf == NULL) return FontSize; else { memset(pBuf, 0, FontSize); return FontSize; } } size_t RetSize = 0; ////////////////////////////////////////////////////// CDC dcScreen; dcScreen.CreateDC("DISPLAY", NULL, NULL, NULL); CFont newfont; newfont.CreateFont(FontSize, 0, 0, 0, 0, 0, 0, 0, DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH|FF_DONTCARE, FontName); CFont *poldfont=dcScreen.SelectObject(&newfont); ////////////////////////////////////////////////////// TEXTMETRIC tm; GLYPHMETRICS pGL; MAT2 mat2 = { {0, 1}, {0, 0}, {0, 0}, {0, 1}, }; dcScreen.GetTextMetrics(&tm); int len =dcScreen.GetGlyphOutline(ch, GGO_BITMAP, &pGL, 0, NULL, &mat2); ////////////////////////////////////////////////////// //int Truelen = (ch & 0xff00) != 0 ? ALIGN(FontSize, 4) : ALIGN(FontSize / 2, 4); int Truelen = (ch & 0xff00) != 0 ? ALIGN(FontSize, 1) : ALIGN(FontSize / 2, 1); int Fontlen = ALIGN(pGL.gmBlackBoxX, 4); int TransLen = Truelen > Fontlen ? Fontlen : Truelen; int FontOffY = tm.tmAscent - pGL.gmptGlyphOrigin.y; int FontOffX = pGL.gmptGlyphOrigin.x < 0 ? 0 : pGL.gmptGlyphOrigin.x; RetSize = Truelen * FontSize; if(pBuf != NULL) { memset(pBuf, 0, RetSize); if(len > 0) { unsigned char *pSrc = new unsigned char [len]; unsigned char *pDest = (unsigned char *)pBuf; dcScreen.GetGlyphOutline(ch, GGO_BITMAP, &pGL, len, pSrc, &mat2); for(int i = 0; i < len / Fontlen; i++) { memcpy(pDest + i * Truelen, pSrc + i * Fontlen, TransLen); } delete []pSrc; ConvertMetrux(pDest, Truelen, FontSize, x_off + FontOffX, y_off + FontOffY); } } dcScreen.SelectObject(poldfont); return RetSize; } |
中间的循环memcpy的过程是将GetGlyphOutline()得到的数据copy至输出Buffer中,在这个过程中需要注意,由于希望输出的点阵数据的每行是1字节对齐而不是4字节对齐,所以copy长度需要计算一下。
最后,使用ConvertMetrux()函数来完成对点阵数据的偏移操作,以便将点阵数据放到外框的中间位置上去。其中,可以注意到,FontOffX和FontOffY就是根据Origin计算得到的需要做的偏移量。另外,x_off和y_off是希望留出一个比较灵活的结果,让用户可以对字体在点阵中的位置进行微调,最后,将Origin和用户微调的程度结合起来送给ConvertMetrux函数来完成数据在矩阵内的搬移。有关数据的搬移,今天就先不介绍啦,哈,卖个关子先~(嘿嘿,其实是怕出丑啦,代码写的比较烂)
利用上面的这个函数就可以比较好的获取字符ch的点阵数据了。剩下的工作就是做一个循环,把所有的ch都调用一下这个函数,然后将得到的数据写入到文件里就OK啦~^o^
先把工具放出来让大家看看吧:-)
目前支持简体中文GB2312和英文ASCII