|
好久以前接到过一个任务,给 minigui 的 incore 模式添加一种字体,当时做了一点笔记。今天心情不太好,不想看书,又不想写程序,就用这种枯燥的工作来缓解心情。由于原文用 LaTeX 书写,把它改成论坛使用的格式简直就像疯人院的疯子在撕纸,所以如果我遗漏了什么,或者有什么错误,都是不可避免的。
再说一句,我不是 minigui 的员工或者参与开发者,因此如果哪里说得不对,权当是笑话吧。
minigui 嵌入资源模式(incore)下的字体研究
作者:于宁(yuningdodo@tom.com)
欢迎转载,但请注明原作者与出处。
* 摘要 *
在本文中,我们将讨论 \minigui 在 incore 模式下内嵌字体的格式,并将演示如何添加一个 16 点阵的中文字体。
* 关键词 *
minigui,incore,字体
* minigui *
minigui 是由国人开发的一个基于 FrameBuffer 的图形用户接口,可以运行于 Linux 与 Windows 下,它具有体积小,省资源,运行效率高的特点,而且对中日韩字体支持良好,在嵌入式领域有很好的应用前景。
minigui 做为运行库,可以有两种存在方式,即普通的资源外置式与资源内嵌(in-core)式。资源外置式可以在运行时载入外部的诸如字体等资源,但是在嵌入式应用环境下,很多时候资源如金,这时候我们可以使用资源内嵌式,它的特点是不使用外部资源,而只把必需的少量资源直接编译在运行库中。内嵌资源虽然节省资源,但是其内嵌的资源太少,很多时候又无法满足我们的需求。这时我们就要考虑如何定制其内嵌的资源。本文中我们就将讨论 minigui 的内嵌字体格式,并实际展示了如何加入一种 16x16 字体。
这里我们采用 minigui-1.3.3,这是目前 minigui 采用 GPL 协议发布的最高版本(关于 minigui 的授权协议请自行查找相关资料),而编译器我们采用 gcc-3.3(用 arm-linux-gcc-3.3 也测试通过)。
* 字体格式 *
minigui 在 incore 模式下将把所有的资源都嵌入在二进制目标,也就是应用程序调用的连接库中,而内嵌的字体都在 src/font/in-core/ 目录下。默认的内嵌中文字体为 12 点阵,对应的文件为 song-12-gb2312.c。
我们可以先来看看它的内容
[PHP]
/* src/font/in-core/song-12-gb2312.c */
#include <stdio.h>
#include <stdlib.h>
#include "common.h"
#include "minigui.h"
#include "gdi.h"
#ifdef _RBF_SUPPORT
#ifdef _INCORE_RES
#include "rawbitmap.h"
static const unsigned char font_data [] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* ...... */
0x02, 0x20, 0xff, 0xe0, 0x48, 0x00, 0x8b, 0xf0
};
/* Exported structure definition. */
INCORE_RBFINFO song12_gb2312 = {
"rbf-fixed-rrncnn-12-12-GB2312.1980-0",
font_data
};
#endif /* _INCORE_RES */
#endif /* _RBF_SUPPORT */
[/PHP]
忽略引用的头文件与条件编译宏,其核心的内容分为两部分,一个是 font_data 这个一维数组,其内容为字形的数据;另一个是 song12_gb2312 这个 INCORE_RBFINFO 结构,它的作用是导出字体信息,以便在程序中使用。
* INCORE_RBFINFO *
它定义在 src/font/rawbitmap.h 中。
[PHP]
/* src/font/rawbitmap.h : INCORE_RBFINFO */
typedef struct
{
const char* name;
const unsigned char* data;
} INCORE_RBFINFO;
[/PHP]
关于字体信息,其定义在 src/font/fontname.c 中,其说明如下
[PHP]
/* src/font/fontname.c : font format */
/*
* Font name format:
* type-family-style-width-height-charset-encoding1[,encoding2,...]
*/
[/PHP]
对照 song_gb2312 中的内容,我们得到如下的关系
type rbf
family fixed
style rrncnn
width 12
height 12
charset-encoding GB2312.1980-0
关于 style,我们可以根据 src/font/fontname.c 中 fontGetStyleFromName() 函数来分析
[PHP]
/* src/font/fontname.c : fontGetStyleFromName() */
DWORD fontGetStyleFromName (const char* name)
{
int i;
const char* style_part = name;
for (i = 0; i < NR_LOOP_FOR_STYLE; i++) {
if ((style_part = strchr (style_part, '-')) == NULL)
return 0xFFFFFFFF;
if (*(++style_part) == '\0')
return 0xFFFFFFFF;
}
return fontConvertStyle (style_part);
}
[/PHP]
此函数中的循环的目的是为了保证格式的正确,关键的判断在 fontConvertStyle() 中
[PHP]
/* src/font/fontname.c : fontConvertStyle() */
DWORD fontConvertStyle (const char* style_part)
{
DWORD style = 0;
switch (style_part [0]) {
case FONT_WEIGHT_BLACK:
style |= FS_WEIGHT_BLACK;
break;
case FONT_WEIGHT_BOLD:
style |= FS_WEIGHT_BOLD;
break;
case FONT_WEIGHT_BOOK:
style |= FS_WEIGHT_BOOK;
break;
case FONT_WEIGHT_DEMIBOLD:
style |= FS_WEIGHT_DEMIBOLD;
break;
case FONT_WEIGHT_LIGHT:
style |= FS_WEIGHT_LIGHT;
break;
case FONT_WEIGHT_MEDIUM:
style |= FS_WEIGHT_MEDIUM;
break;
case FONT_WEIGHT_REGULAR:
style |= FS_WEIGHT_REGULAR;
break;
case FONT_WEIGHT_ALL:
style |= FS_WEIGHT_MASK;
default:
return 0xFFFFFFFF;
}
switch (style_part [1]) {
case FONT_SLANT_ITALIC:
style |= FS_SLANT_ITALIC;
break;
case FONT_SLANT_OBLIQUE:
style |= FS_SLANT_OBLIQUE;
break;
case FONT_SLANT_ROMAN:
style |= FS_SLANT_ROMAN;
break;
case FONT_SLANT_ALL:
style |= FS_SLANT_MASK;
break;
default:
return 0xFFFFFFFF;
}
switch (style_part [2]) {
case FONT_SETWIDTH_BOLD:
style |= FS_SETWIDTH_BOLD;
break;
case FONT_SETWIDTH_CONDENSED:
style |= FS_SETWIDTH_CONDENSED;
break;
case FONT_SETWIDTH_SEMICONDENSED:
style |= FS_SETWIDTH_SEMICONDENSED;
break;
case FONT_SETWIDTH_NORMAL:
style |= FS_SETWIDTH_NORMAL;
break;
case FONT_SETWIDTH_ALL:
style |= FS_SETWIDTH_MASK;
break;
default:
return 0xFFFFFFFF;
}
switch (style_part [3]) {
case FONT_SPACING_MONOSPACING:
style |= FS_SPACING_MONOSPACING;
break;
case FONT_SPACING_PROPORTIONAL:
style |= FS_SPACING_PROPORTIONAL;
break;
case FONT_SPACING_CHARCELL:
style |= FS_SPACING_CHARCELL;
break;
case FONT_SPACING_ALL:
style |= FS_SPACING_MASK;
break;
default:
return 0xFFFFFFFF;
}
switch (style_part [4]) {
case FONT_UNDERLINE_LINE:
style |= FS_UNDERLINE_LINE;
break;
case FONT_UNDERLINE_ALL:
style |= FS_UNDERLINE_MASK;
break;
case FONT_UNDERLINE_NONE:
style &= FS_UNDERLINE_MASK;
break;
default:
return 0xFFFFFFFF;
}
switch (style_part [5]) {
case FONT_STRUCKOUT_LINE:
style |= FS_STRUCKOUT_LINE;
break;
case FONT_STRUCKOUT_ALL:
style |= FS_STRUCKOUT_MASK;
break;
case FONT_STRUCKOUT_NONE:
style &= FS_STRUCKOUT_MASK;
break;
default:
return 0xFFFFFFFF;
}
return style;
}
[/PHP]
大意还是可以从命名来猜出来的。猜不出也无所谓,即使不知道具体含义我们也有办法:对于普通的字体来说,默认的“rrncnn”是非常合适的选择。因此我们可以为我们预期的 16 点阵字体进行如下的设置
[PHP]
/* src/font/in-core/song-16-gb2312.c : song16_gb2312 */
INCORE_RBFINFO song16_gb2312 = {
"rbf-fixed-rrncnn-16-16-GB2312.1980-0",
font_data
};
[/PHP]
* font_data *
这是用来保存字形数据的一维数组。首先我们猜测它的存储格式为每一比特对应一个点,1 对应字形,0 对应空白。这样一个字节可以保存 8 个点的数据。对于 12 点阵来说,每行有 12 个点,那么究竟它是忽略字节与字节间的差异,用 1.5 个字节存储一行(3 个字节存 2 行),还是采用对齐的方式,用 2 个字节存储一行而剩下的 4 比特忽略或置 0 呢?我们继续猜测它会照顾处理速度而采用对齐的方式,这样就可以知道数组中的第二列(字节)的后半字节(低 4 位)为 0。在 vim 中用
来查找,无匹配,证明以上猜测都可能是正确的。
下面我们将用一个小程序来验证我们的猜测,它的功能是把字形数据从数组中提取出来并以可视的形式打印出来。
[PHP]
/* TEST/show.c */
#include "font.h"
#include <stdio.h>
int main()
{
unsigned char * p = (unsigned char *)font_data;
int w = 12, h = 12;
int x, y;
int n = 22842 /* total 22842 lines */
* 8 /* 8 char per line */
/ 2 /* 12x12: 2 char is 1 line */
/ 12; /* 12 lines */
while (n-- > 0) {
printf("-+-+-+-+-+-+-+-+-+-+-+-+\n");
y = 0;
for (y = 0; y < h; ++y) {
for (x = 0; x < w; x += 8) {
int t;
for (t = 0; t < 8; ++t) {
if ((*p << t) & 0x80) {
printf(" *");
} else {
printf(" ");
}
}
++p;
}
printf("\n");
}
}
printf("-+-+-+-+-+-+-+-+-+-+-+-+\n");
return 0;
}
[/PHP]
注意这里我们要把 font_data 单独保存一个文件 font.h 中,以免这样一个小程序还要连接 minigui 库。
[PHP]
/* TEST/font.h */
static const unsigned char font_data [] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* ...... */
0x02, 0x20, 0xff, 0xe0, 0x48, 0x00, 0x8b, 0xf0
};
[/PHP]
将上面的程序编译,运行,将其输出导出到某文件中
- $ gcc -o show show.c
- $ ./show > shape
复制代码
查看 shape 文件,其中的部分内容如下
- -+-+-+-+-+-+-+-+-+-+-+-+
- * *
- * * * * * * * * * * * *
- * * * * *
- * * * * * * * *
- * * * * * *
- * * * * * * *
- * * * * * *
- * * * * * * * *
- * * * * *
- * *
- * * *
- * * *
- -+-+-+-+-+-+-+-+-+-+-+-+
复制代码
可以看出,这是汉字“啊”,证明我们以上的猜测全部是正确的。
我们已经知道了一个字的保存方式,下面我们来研究这些字的位置关系,换句话说,我们如何能够找到我们指定的某个字对应的字形。
为此我们要先了解一些关于编码的东西。
简体中文在计算机中有多种编码,常见有的 GB2312,GBK,GB18030,UTF-8 等。在刚才对字体信息的分析中,我们已经知道它采用的编码格式为 GB2312。它的编码规则是:
* 一个汉字用两字节表示,前一位称为区码,后一位称为位码;
* 每个字节的范围从 0xA1 至 0xFE,
但从编码来说,则称为 01 至 94。
从对应关系来说,编码值+0xA0=字节值;
* 具体来说,区码分为几段来表示不同的内容,
> 01-09区为特殊符号;
> 16-55区为一级汉字,按拼音排序;
> 56-87区为二级汉字,按部首/笔画排序;
> 10-15区及88-94区则未有编码。
* 编码存在的区中,位码从 01 至 94。
这里我们猜测 minigui 会按照区位码的顺序来存储数据,即从 0101 开始,接着存储 0102……
但是我们还要判断 minigui 中是否跳过了区码中无编码的部分。通过简单的测试,我们得到结论:minigui 中仅保存了有编码的三个部分,因此其码表关系如下:
0101 .... 0194
0201 .... 0294
: .... :
: .... :
0901 .... 0994
1601 .... 1696
: .... :
: .... :
8601 .... 8694
8701 .... 8794
为此我们可以将刚才输出字形用的小程序稍加修改,变为如下形式
[PHP]
/* TEST/show2.c */
#include "font.h"
#include <stdio.h>
unsigned char * p = (unsigned char *)font_data;
int w = 12, h = 12;
void output(int from, int to)
{
int a, b;
int x, y;
for (a = from; a <= to; ++a) {
for (b = 01; b <= 94; ++b) {
printf("[%02d][%02d]: %c%c\n", a, b, a + 0xA0, b + 0xA0);
for (y = 0; y < h; ++y) {
for (x = 0; x < w; x += 8) {
int t;
for (t = 0; t < 8; ++t) {
if ((*p << t) & 0x80) {
printf(" *");
} else {
printf(" ");
}
}
++p;
}
printf("\n");
}
}
}
}
int main()
{
output( 1, 9);
output(16, 87);
return 0;
}
[/PHP]
还是按照刚才同样的方法编译,运行,查看输出
- [16][01]: 啊
- * *
- * * * * * * * * * * * *
- * * * * *
- * * * * * * * *
- * * * * * *
- * * * * * * *
- * * * * * *
- * * * * * * * *
- * * * * *
- * *
- * * *
- * * *
复制代码
证明上面全部猜测均是正确的。
* 生成新字体 *
根据以上的分析,我们可以着手开始生成我们需要的 16 点阵字体。字体信息在上面的分析中已经确定了,剩下的就是生成字形数据。这可以有几种方法,直接生成需要的 .c 文件或者先生成某种格式的字形数据再通过其它的某种方式修改为 .c 格式。这里用后者比较简单,因为我们可以在想办法生成字形时不用太关注输出的格式。
为了获得一个字的字形,有不同的方法可以实现。最直接的方法是从字体文件中直接读取字形,进行必要的处理后输出。这种方法的难度较大。另一种方法是通过某种方式将字输出或打印成图片,再通过对图片的处理得到数据。这种方法显然比较简单。流程描述如下
- void output(int from, int to)
- {
- /* similar as output()@show.c */
- w = h = 16;
- for (a = from; a <= to; ++a) {
- for (b = 1; b <= 94; ++b) {
- draw font[a + 0xA0][b + 0xA0] to image at (0, 0);
- for (y = 0; y < h; ++y) {
- for (x = 0; x < w; ++x) {
- get pixel[y][x] from image;
- }
- }
- }
- }
- make every 8 pixels into one char;
- output 8 chars into one line in the font_data format;
- }
- int main(void)
- {
- output( 1, 9);
- output(16, 87);
- }
复制代码
这里,如何显示一个字符及如何读取一个像素点取决于具体的编程工具。你可以用 GTK,QT,Delphi,VB,VC++ 甚至是一个非 in-core 型的 minigui 来完成这样的工作。我生成字体的时候是用 VB 来完成的,主要目的是想获得中文宋体,但是用 minigui 显然是一个更好的选择。
注意:无论是用 minigui 还是用任何一种 Windows 下的开发工具,他们都没有授权你使用它们的字体。即使你用 GTK 或者 QT,如果你所使用的字体为 Windows 下的字体(比如宋体 simsun.ttf,这是很多人的选择),那么你同样面临着版权的问题。因此用一些开源字体来实现这样的工作才是完全之策。“文泉驿”是一个优秀的开源中文点阵字体,你可以考虑利用。只是想生成它的字体,你恐怕要使用 GTK 或者 QT 来编程,也可以直接调用字体库的相应函数。
* 添加字体 *
下面我们来考虑如何将生成的字体加入工程。
一个简单的方法便是首先看看系统原来自带的 song-12 字体都在代码的哪些部分出现。在代码根目录中用如下的命令
- $ make distclean
- $ grep -R song *
复制代码
除了 src/font/in-core/song-12-gb2312.c 中的东西不要动,其它 song-12 出现的地方都模仿着把咱们的 song-16 也加上即可。
经过这样的处理后,应该就可以了。按照正常的方法配置,编译,安装
- $ ./configure --enable-incore
- $ make
- $ make install
复制代码
再用下面的小程序试一下
[PHP]
/* TEST/hello.c */
#include <stdio.h>
#include <minigui/common.h>
#include <minigui/minigui.h>
#include <minigui/gdi.h>
#include <minigui/window.h>
static int HelloWinProc(HWND hWnd, int message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
switch (message) {
case MSG_CLOSE:
DestroyMainWindow (hWnd);
PostQuitMessage (hWnd);
return 0;
}
return DefaultMainWinProc(hWnd, message, wParam, lParam);
}
int MiniGUIMain (int argc, const char* argv[])
{
MSG Msg;
HWND hMainWnd;
MAINWINCREATE CreateInfo;
#ifdef _LITE_VERSION
SetDesktopRect(0, 0, 1024, 768);
#endif
CreateInfo.dwStyle = WS_VISIBLE | WS_BORDER | WS_CAPTION;
CreateInfo.dwExStyle = WS_EX_NONE;
CreateInfo.spCaption = "Hello, world";
CreateInfo.hMenu = 0;
CreateInfo.hCursor = GetSystemCursor(0);
CreateInfo.hIcon = 0;
CreateInfo.MainWindowProc = HelloWinProc;
CreateInfo.lx = 0;
CreateInfo.ty = 0;
CreateInfo.rx = 320;
CreateInfo.by = 240;
CreateInfo.iBkColor = COLOR_lightwhite;
CreateInfo.dwAddData = 0;
CreateInfo.hHosting = HWND_DESKTOP;
hMainWnd = CreateMainWindow (&CreateInfo);
/* default font is 12x12 size */
TextOut (hdc, 100, 100, "Hello, world! 中文");
/* set font to 16x16 size */
PLOGFONT logfont;
logfont = CreateLogFont (NULL, "song", "GB2312",
FONT_WEIGHT_REGULAR, FONT_SLANT_ROMAN, FONT_SETWIDTH_NORMAL,
FONT_SPACING_CHARCELL, FONT_UNDERLINE_NONE, FONT_STRUCKOUT_NONE,
16, 0);
SelectFont (HDC_SCREEN, logfont);
TextOut (HDC_SCREEN, 100, 160, "Hello, world! 中文");
if (hMainWnd == HWND_INVALID)
return -1;
ShowWindow(hMainWnd, SW_SHOWNORMAL);
while (GetMessage(&Msg, hMainWnd)) {
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
MainWindowThreadCleanup (hMainWnd);
return 0;
}
#ifndef _LITE_VERSION
#include <minigui/dti.c>
#endif
[/PHP]
编译,运行
- $ gcc-3.3 -lminigui -lm -lpthread -ljpeg -lpng hello.c -o hello
- $ ./hello
复制代码
效果如图。 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有帐号?注册
x
|