LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 2101|回复: 2

[原创] minigui 在 incore 模式下的字体研究

[复制链接]
发表于 2006-12-15 14:22:32 | 显示全部楼层 |阅读模式
好久以前接到过一个任务,给 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 中用

  1.     /^  0x.., 0x.[1-9]
复制代码

来查找,无匹配,证明以上猜测都可能是正确的。

    下面我们将用一个小程序来验证我们的猜测,它的功能是把字形数据从数组中提取出来并以可视的形式打印出来。
[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]
    将上面的程序编译,运行,将其输出导出到某文件中

  1. $ gcc -o show show.c
  2. $ ./show > shape
复制代码

    查看 shape 文件,其中的部分内容如下

  1. -+-+-+-+-+-+-+-+-+-+-+-+
  2.      *               *
  3. * * * * * * * * * * * *
  4. *   *   *   *       *
  5. *   *   * * * * *   *
  6. *   *   *   *   *   *
  7. *   *   * * *   *   *
  8. *   *   *   *   *   *
  9. * * *   *   * * *   *
  10. *   *   * *         *
  11.          *           *
  12.          *       *   *
  13.          *         * *
  14. -+-+-+-+-+-+-+-+-+-+-+-+
复制代码

    可以看出,这是汉字“啊”,证明我们以上的猜测全部是正确的。

    我们已经知道了一个字的保存方式,下面我们来研究这些字的位置关系,换句话说,我们如何能够找到我们指定的某个字对应的字形。

    为此我们要先了解一些关于编码的东西。

    简体中文在计算机中有多种编码,常见有的 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]
    还是按照刚才同样的方法编译,运行,查看输出

  1. [16][01]: 啊
  2.      *               *
  3. * * * * * * * * * * * *
  4. *   *   *   *       *
  5. *   *   * * * * *   *
  6. *   *   *   *   *   *
  7. *   *   * * *   *   *
  8. *   *   *   *   *   *
  9. * * *   *   * * *   *
  10. *   *   * *         *
  11.          *           *
  12.          *       *   *
  13.          *         * *
复制代码

    证明上面全部猜测均是正确的。


* 生成新字体 *
    根据以上的分析,我们可以着手开始生成我们需要的 16 点阵字体。字体信息在上面的分析中已经确定了,剩下的就是生成字形数据。这可以有几种方法,直接生成需要的 .c 文件或者先生成某种格式的字形数据再通过其它的某种方式修改为 .c 格式。这里用后者比较简单,因为我们可以在想办法生成字形时不用太关注输出的格式。

    为了获得一个字的字形,有不同的方法可以实现。最直接的方法是从字体文件中直接读取字形,进行必要的处理后输出。这种方法的难度较大。另一种方法是通过某种方式将字输出或打印成图片,再通过对图片的处理得到数据。这种方法显然比较简单。流程描述如下

  1. void output(int from, int to)
  2. {
  3.     /* similar as output()@show.c */
  4.     w = h = 16;
  5.     for (a = from; a <= to; ++a) {
  6.         for (b = 1; b <= 94; ++b) {
  7.             draw font[a + 0xA0][b + 0xA0] to image at (0, 0);
  8.             for (y = 0; y < h; ++y) {
  9.                 for (x = 0; x < w; ++x) {
  10.                     get pixel[y][x] from image;
  11.                 }
  12.             }
  13.         }
  14.     }
  15.     make every 8 pixels into one char;
  16.     output 8 chars into one line in the font_data format;
  17. }
  18. int main(void)
  19. {
  20.     output( 1,  9);
  21.     output(16, 87);
  22. }
复制代码

    这里,如何显示一个字符及如何读取一个像素点取决于具体的编程工具。你可以用 GTK,QT,Delphi,VB,VC++ 甚至是一个非 in-core 型的 minigui 来完成这样的工作。我生成字体的时候是用 VB 来完成的,主要目的是想获得中文宋体,但是用 minigui 显然是一个更好的选择。

    注意:无论是用 minigui 还是用任何一种 Windows 下的开发工具,他们都没有授权你使用它们的字体。即使你用 GTK 或者 QT,如果你所使用的字体为 Windows 下的字体(比如宋体 simsun.ttf,这是很多人的选择),那么你同样面临着版权的问题。因此用一些开源字体来实现这样的工作才是完全之策。“文泉驿”是一个优秀的开源中文点阵字体,你可以考虑利用。只是想生成它的字体,你恐怕要使用 GTK 或者 QT 来编程,也可以直接调用字体库的相应函数。


* 添加字体 *
    下面我们来考虑如何将生成的字体加入工程。

    一个简单的方法便是首先看看系统原来自带的 song-12 字体都在代码的哪些部分出现。在代码根目录中用如下的命令

  1. $ make distclean
  2. $ grep -R song *
复制代码

    除了 src/font/in-core/song-12-gb2312.c 中的东西不要动,其它 song-12 出现的地方都模仿着把咱们的 song-16 也加上即可。

    经过这样的处理后,应该就可以了。按照正常的方法配置,编译,安装

  1. $ ./configure --enable-incore
  2. $ make
  3. $ 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]
    编译,运行

  1. $ gcc-3.3 -lminigui -lm -lpthread -ljpeg -lpng hello.c -o hello
  2. $ ./hello
复制代码

    效果如图。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
发表于 2008-2-15 16:10:20 | 显示全部楼层
相当有才!收藏学习了
回复 支持 反对

使用道具 举报

发表于 2008-11-26 11:03:52 | 显示全部楼层
学习了,很长见识,呵呵
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表