C0EEBDA1

在梦中,我是有超能力的。。。

【MFC应用】文件关联 »

点阵字库生成工具

经过这两天的摸索和实践,终于做好了第一版点阵字库生成工具,开心 ^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';

//////////////////////////////////////////////////////
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);
//////////////////////////////////////////////////////
GLYPHMETRICS pGL;
MAT2 mat2 = {
  {0, 1},
  {0, 0},
  {0, 0},
  {0, 1},
};

// 获取字符ch的点阵数据的大小
int len =dcScreen.GetGlyphOutline(ch, GGO_BITMAP, &pGL, 0, NULL, &mat2);
// 申请空间

unsigned char *pBuf = new unsigned char [len];

// 保存点阵数据

  dcScreen.GetGlyphOutline(ch, GGO_BITMAP, &pGL, len, pBuf, &mat2);
// 接下来就可以利用pBuf里的数据来显示了

// 最后记得释放掉pBuf,并还原Screen DC的字体

dcScreen.SelectObject(poldfont);
delete []pBuf;

 

使用上面的代码可以很方便的拿到字符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^

先把工具放出来让大家看看吧:-)

工具下载:http://leajian.9k9k.cn/upload/HZKCreator.rar

目前支持简体中文GB2312和英文ASCII

  • 相关文章:
  • quote 1.qwerty
  • 收藏了。比那些收费的字库转换工具还好用!
    有空再完善一下,支持各种数据排列方向,再支持对若干指定的文字生成.c字模文件,就基本上完美了~
    lijian 于 2008-8-4 20:02:22 回复
    :-)
    之前在黑龙江培训的时候还有老师提意见让增加指定文字字模生成功能呢,看来是事在必行哈,有空再弄一弄,呵呵
  • 2008-8-4 12:45:49 回复该留言
  • quote 2.yoga67
  • http://mobile.0110.cn
  • 你好,首先感谢你开发出如此小巧好用的点阵字体生成
    软件,方便了很多人
    事情是这样的,我现在为没落的西门子手机做字体补丁
    需要用到你的软件生成的BMP文件
    但是西门子手机限制只能使用黑白两色bmp
    这样虽然一些字体生成的bmp很完美
    却在做补丁过程中被强制转换为单色位图
    导致很多细节丢失,字体残缺
    请问能否做些小小的改动
    把生成的BMP调成单色位图
    而且可以自己调整让部分颜色加深
    一面丢失过多细节
    十分感谢
    lijian 于 2009-3-30 11:46:23 回复
    呵呵,生成BMP没问题,不过不知道您说的“让部分颜色加深”具体是指什么?怎么操作?^_^
  • 2009-3-30 10:32:00 回复该留言
  • quote 3.Skyboy
  • 无意中闯入你的Blog,发现你的技术很牛。
    这个字库软件能支持Unicode编码的字库就更好了!!期待新版本。。。
    lijian 于 2009-5-7 11:15:33 回复
    您好,不知道您说的支持Unicode是什么意思?是指按照Unicode编码的顺序生成字库吗? ^_^
    Skyboy 于 2009-6-8 11:01:50 回复
    对,就是按照Unicode编码的顺序生成字库。

    GB2312的字库,是按照汉字拼音排序的,而Unicode字库是按照汉字笔画排序的。
    (ps:好久没上网了。。。所以才给你回信)
    Skyboy 于 2009-6-8 11:11:01 回复
    Unicode可以支持很多国家的字符,手机短信就是Unicode编码


    我的电邮wjj16(at)163.com,咱们交流下。。。
  • 2009-5-5 13:17:31 回复该留言
  • quote 4.Skyboy
  • 哈哈,我发现你的C0EEBDA1,原来是这意思
    GB2312(0xC0EE)=李=Unicode(0x4E67)
    GB2312(0xBDA1)=健=Unicode(0x6550)
    lijian 于 2009-5-7 11:16:37 回复
    哈哈 被发现了 ^_^
    的确,是这个意思^_^
    当初建博客的时候,也正是我做这个字库生成工具的时期,所以就取了这个名字^_^
    Skyboy 于 2009-6-8 11:12:38 回复
    也只有咱们搞技术的才能发现这隐藏的boss,哈哈
  • 2009-5-5 13:27:56 回复该留言
  • quote 6.AQI
  • http://csaqi.spaces.live.com/blog/
  • 非常感谢您的文章!!!
    尤其是关于origin的定位给了我很大帮助。
    int FontOffY = tm.tmAscent - pGL.gmptGlyphOrigin.y;

    不过貌似有个小问题,tm.tmAscent获取的是逻辑坐标,而pGL.gmptGlyphOrigin.y获取的是设备坐标。如果不转化一下不能够对齐。
    可以使用DPtoLP和LPtoDP进行转换。
  • 2010-8-23 16:23:13 回复该留言
  • quote 7.冰雪新空
  • 谢谢你的代码和工具,有个弱弱的问题,调用GetFontData函数获取英文字符的点阵数据直接传入就可以,但如果是汉字的话,传什么参数?最近在做一个汉字字库转换,正在入门...
  • 2012-1-13 10:05:07 回复该留言

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

日历

最新评论及回复

最近发表

Powered By Z-Blog 1.8 Spirit Build 80722 Code detection by Codefense  theme by BokeZhuti

Copyright 2008-2009 C0EEBDA1. Some Rights Reserved. 备案号:京ICP备09020681号