LinuxSir.cn,穿越时空的Linuxsir!

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

在 XFree86 窗口系统中实现对 GB18030 的支持[转帖]

[复制链接]
发表于 2002-8-28 19:52:34 | 显示全部楼层 |阅读模式

前言:这篇文章有难度和深度,我们了解一下就OK了。喜欢专门研究的弟兄,也是一个比较不错的资料。

来源:IBM  
内容:

1.1 GB18030 简介
1.2 XFree86 国际化原理简介
作者简介: 苏哲 (suzhe@gnuchina.org)

Turbo Linux公司软件工程师
2001 年 9 月

GB18030 是最新的汉字编码字符集国家标准, 向下兼容 GBK 和 GB2312 标准。本文将向您讲述如何在 XFree86 窗口系统中实现对GB18030 标准的支持。
1. 简介

1.1 GB18030 简介
GB18030 是最新的汉字编码字符集国家标准, 向下兼容 GBK 和 GB2312 标准。 GB18030 编码是一二四字节变长编码。 一字节部分从 0x0~0x7F 与 ASCII 编码兼容。 二字节部分, 首字节从 0x81~0xFE, 尾字节从 0x40~0x7E 以及 0x80~0xFE, 与 GBK标准基本兼容。 四字节部分, 第一字节从 0x81~0xFE, 第二字节从 0x30~0x39, 第三和第四字节的范围和前两个字节分别相同。 四字节部分覆盖了从 0x0080 开始, 除去二字节部分已经覆盖的所有 Unicode 3.1 码位。也就是说, GB18030 编码在码位空间上做到了与 Unicode 标准一一对应,这一点与 UTF-8 编码类似。

目前最新的 glibc 2.2.x 系列已经全面支持了 GB18030 Locale 和 GB18030 与 UCS-4 之间的编码转换, 也就是说在系统层上 Linux 已经可以支持 GB18030 标准了。 下面问题的关键就是怎样让 XFree86 窗口系统也支持 GB18030 标准。

1.2 XFree86 国际化原理简介。
本文介绍的所有内容都基于 XFree86 4.0.3 及其以后版本, XFree86 早期版本未做测试。

XFree86 窗口系统中涉及到国际化的部分主要有字体管理, 文字绘制, 编码转换以及进程间通讯。 具体的编程接口请参阅相关资料, 本文不作更详细的介绍。

1.2.1 字体管理与文字绘制
早期的 XFree86 由于对国际化问题考虑不足, 在字库接口上仅定义了单字节索引和双字节索引两种字库接口, 分别对应 XDrawString 和 XDrawString16 两组函数。前一组函数使用8位字模索引序列, 后一组函数使用16位字模索引序列。函数定义分别为:

      XDrawString(display, d, gc, x, y, string, length)
             Display *display;
             Drawable d;
             GC gc;
             int x, y;
             char *string;
             int length;

       XDrawString16(display, d, gc, x, y, string, length)
             Display *display;
             Drawable d;
             GC gc;
             int x, y;
             XChar2b *string;
             int length;

注意, 两个函数中的 string 指针指向的并不是编码序列, 而是字模在字库中的索引序列。一般将这个序列使用的编码转化成为字符集(CharSet)编码。对于 ISO8859-1 等8位编码的西方语言来说, 编码和字符集编码是一样的。所以可以直接用 XDrawString 显示显示这些编码的字符串。但对于多字节编码的复杂语言来说就不一样了。 以 GB2312 为例, GB2312 编码(encoding) 的范围为单字节 0x00~0x80, 双字节 0xA1A1~0xFEFE, 而 GB2312 字库的字符集仅覆盖 GB2312 编码的双字节部分, 其编码是将 GB2312 双字节编码中每个字节的第8位置0得来的, 例如, GB2312 编码为 0xA1A1 的字符的字模(glyph)信息在 GB2312 字库中的索引是 0x2121, 而不是 0xA1A1。

早期的 XFree86 系统并不支持 ttf 字体, 对于 XFree86 系统中常用的 BDF/PCF 点阵字体而言, 每种字体使用的字符集编码是固定的, 如中文字库有 GB2312.1980-0, GBK-0, BIG5-0 等字符集, 日文有 JISX0201。1976-0, JISX0208。1983-0 等字符集。 后来人们在 XFree86系统中引入了对 ttf 等矢量化字体的支持, 目前用于 XFree86 4.x 的 ttf 字库模块有 freetype 和 xtt。 由于绝大多数 ttf 字体都直接使用 UCS2-BE 作为字符集编码(字库索引)。 为了和现有 XFree86 系统兼容, 这两个模块都具备编码转换功能, 即可以将传统的字符集编码转换为 UCS2-BE 编码, 从而可以将 ttf 字库虚拟成传统的字库来使用。为了便于字库管理, XFree86 引入了 XLFD (X Logical Font Discription) 机制来描述字体。 关于 XLFD 的详悉介绍请参阅 XFree86 的相关文档。

为了增强 X 窗口系统对国际化的支持, 在 X11R5 版本中引进了一系列新的字符串绘制函数和字体集的概念。 简单来说, 字体集就是将几种不同字符集的字库组合成一个覆盖多个字符集的新字体。例如将一个 ISO8859-1 的英文字库和一个 GB2312.1980-0 的中文字库组合成一个字体集, 就可以覆盖 GB2312 编码的所有字符了。

支持国际化的字符串绘制函数主要分为两类 XmbDrawString 和 XwcDrawString。 它们的定义为:

void XmbDrawString(display, d, font_set, gc, x, y, string,
       num_bytes)
             Display *display;
             Drawable d;
             XFontSet font_set;
             GC gc;
             int x, y;
             char *string;
             int num_bytes;

       void XwcDrawString(display, d, font_set, gc, x, y, string,
       num_wchars)
             Display *display;
             Drawable d;
             XFontSet font_set;
             GC gc;
             int x, y;
             wchar_t *string;
             int num_wchars;






前一类用于绘制多字节编码的字符串, 后一类用于绘制宽字节编码的字符串。使用这两个函数可以直接绘制当前系统语言编码的多字节编码字符串或者 wchar_t 类型的字符串。但实际上这两个函数还是将多字节字符串或宽字节字符串拆分并转换成多段不同字符集的编码串, 然后调用 XDrawString 或者 XDrawString16 来显示。所使用的字库就是字体集中对应的字库。

例如使用 XmbDrawString 绘制 "ABC大家好" 这个GB2312编码的多字节字符串, XmbDrawString 首先会把这个字符串拆分成两部分, 一部分是 "ABC" 对应 ISO8859-1 字符集, 将用字体集中的 ISO8859-1 英文字库来绘制; 另一部分是 "大家好" 对应 GB2312.1980-1 字符集, 将用字体集中的中文字库来绘制。然后会将这两个字符串分别转换为对应字符集的编码, 转换后的结果为 "ABC" (未变) 和 0x73 0x34 0x52 0x3C 0x43 0x3A (即 "大家好" 的 GB2312.1980-0 字符集编码, 也就是字库索引)。 然后 XmbDrawString 就会分别调用 XDrawString 和 XDrawString16 来显示这两个字符串。

1.2.2 进程间通讯
X 窗口系统的进程间通讯主要涉及到不同进程间的文字拷贝和粘贴, 不同进程间的数据通讯以及程序的数据输入等方面。 由于 X 窗口系统为 Client/Server 结构的分布式网络窗口系统, 为了在保证进程间通讯的网络透明性和可恢复性的同时, 提供国际化支持, 在 X 窗口系统内部引入了复合文本编码 (Compound Text)。这个编码其实是以 ISO-2022(一种早期的多语言编码框架标准) 为基础制定的。 它并不是一种真正意义上的编码, 而仅是一个能将多种不同编码的字符流组合成一个字符流的框架协议。进程间在使用 Compound Text 通讯的时候可以将各种不同编码的字符流汇集在一起然后再传输, 到达目的进程后再按照一定规则解码成多个字符流进行处理。 简单来说, Compound Text 就是利用不同的状态字符串来标志各个编码字符串的起止位置, 利用一种统一的编码规则对字符串进行编码, 以免引起歧义。

Compound Text 标准中为 ISO8859, EUC-CN, EUC-JP 等许多常用编码规定了标准的编码规则和状态字符串, 另外还定义了几种扩展规则, 用于将更新的编码溶入 Compound Text 标准。 Compound Text 标准过于复杂, 而且所能包含的编码数量也很有限。 随着 ISO10646/Unicode 标准的不断发展和完善, X 系统已经开始逐步用 UTF-8 编码替换 Compound Text。 为了保持与旧系统的兼容性, 在 XFree86 系统中 UTF-8 编码的字符串可以用一种特殊的编码规则编码成 Compound Text。

1.2.3 编码转换
前面已经提到了很多种文字编码和标准, 简单总结如下:

多字节/宽字节编码 (Multibytes/Wide Char): 通俗的说, 多字节编码就是外码, 一般为可变长编码, 主要用于信息存储和交换; 宽字节编码就是内码, 为定长码, 通常一个字符对应4个字节, 主要用于信息处理。 在 POSIX 标准中定义多字节编码的数据类型为 char, 宽字节编码的数据类型为 wchar_t。 在目前 linux 常用的 glibc 2.2.x 版本中, 使用 UCS-4 作为宽字节编码, 并遵循 Unicode 标准 (最新的 glibc 2.2.4 已经完全支持 Unicode 3.1 标准)。 常见的多字节编码有 UTF-8, ISO8859 系列, GB2312, GBK, EUC-JP 等。
字符集 (charset): 即一系列字汇(glyphs)的有序组合。字符集一般使用固定长度的编码。 常见的字符集有 Unicode (它即定义了编码的标准, 又定义了字符集的标准), ISO8859-1, GB2312.1980-0, GBK-0 等。
复合文本编码 (Compound Text): 是一种可以将多个不同编码的字符流汇集在一起的编码标准, 常用于 X 应用程序之间的进程通讯和文字输入。
既然有这么多不同的编码和标准, 那么 X 系统就必须能够正确的处理并转换它们。 在 X 的底层采用了一种模块化的机制来处理所有编码的转换与翻译工作。这些转换模块被直接编译到了 X11 Library 中 (具体源代码请参阅 XFree86 源码中的 xc/lib/X11/lc* 等文件)。

首先, 在 X11 Library (以下简称 libX11) 中为它所支持的编码标准定义了标准名称 (见 XlcPublic。h 文件):

#define XlcNMultiByte           "multiByte"       /* 多字节编码 */
#define XlcNWideChar            "wideChar"        /* 宽字节编码 */
#define XlcNCompoundText        "compoundText"    /* 复合文本编码 */
#define XlcNString              "string"          /* 传统字符串编码, 即 ASCII 编码 */
#define XlcNUtf8String          "utf8String"      /* UTF-8 编码 */
#define XlcNCharSet             "charSet"         /* 字符集编码 */
#define XlcNCTCharSet           "CTcharSet"
#define XlcNFontCharSet         "FontCharSet"     /* 字库使用的字符集编码 */
#define XlcNChar                "char"            /* 单个字符 */
#define XlcNUcsChar             "UCSchar"         /* UCS-4 编码的字符 */






其中 XlcNCharSet 和 XlcNFontCharSet 在绝大多数情况下是一样的, 目前仅在处理 Unicode 的时候有区别。 在 UTF-8 Locale 下, XlcNCharSet 使用的是 UTF-8 编码, 而 XlcNFontCharSet 使用的是 UCS-2 Big Endian 编码。

在程序初始化的时候, libX11 将调用 _XlcInitLoader() 函数来初始化所有的编码转换模块, 程序段如下:

void
_XlcInitLoader()
{
#ifdef USE_GENERIC_LOADER
    _XlcAddLoader(_XlcGenericLoader, XlcHead);
#endif

#ifdef USE_DEFAULT_LOADER
    _XlcAddLoader(_XlcDefaultLoader, XlcHead);
#endif

#ifdef USE_UTF8_LOADER
    _XlcAddLoader(_XlcUtf8Loader, XlcHead);
#endif

#ifdef USE_EUC_LOADER
    _XlcAddLoader(_XlcEucLoader, XlcHead);
#endif

#ifdef USE_SJIS_LOADER
    _XlcAddLoader(_XlcSjisLoader, XlcHead);
#endif

#ifdef USE_JIS_LOADER
    _XlcAddLoader(_XlcJisLoader, XlcHead);
#endif

#ifdef USE_DYNAMIC_LOADER
    _XlcAddLoader(_XlcDynamicLoader, XlcHead);
#endif
}


在以上所有转换模块中, Generic 转换模块 (_XlcGenericLoader) 是最重要的一个模块。 它使用 XLC_LOCALE 描述文件所定义的标准转换方法完成 XlcNWideChar, XlcNCharSet, XlcNCompoundText 等编码之间的相互转换。 Utf8 转换模块 (_XlcUtf8Loader) 则专门用于 UTF-8 Locale 下的各种编码转换。 Dynamic 模块用于动态调入独立的编码转换模块。 但在目前的 XFree86 4.x (X11R6.5) 中, 这个模块是无效的。

下面就以 Utf8 模块 (lcUTF8.c) 为例, 来介绍一下编码转换模块的构造:

虽然常用的编码标准仅有 XlcNCharSet, XlcNMultiByte, XlcNWideChar 等几种, 但经过排列组合, 需要处理的编码转换还是非常多的。 下面仅以 XlcNCharSet 到 XlcNMultiByte 转换为例来说明问题。

首先, 在 lcUTF8.c 中定义了一个负责将 XlcNCharSet 编码的字符串转换为 XlcNMultiByte 的字符串的函数:

(注意, 在 UTF-8 Locale 中, MultiByte 编码即为 UTF-8, 所以 XlcNMultiByte 和 XlcNUtf8String 其实是一样的)

/* from XlcNCharSet to XlcNUtf8String */

static int
cstoutf8(
    XlcConv conv,
    XPointer *from,
    int *from_left,
    XPointer *to,
    int *to_left,
    XPointer *args,
    int num_args)
{
....... (略)
}

关于数据结构 XlcConv 的定义请参见 XlcPublic。h 文件。然后定义了一个 XlcConvMethodsRec 对象和一个初始化函数:

static XlcConvMethodsRec methods_cstoutf8 = {
    close_converter,
    cstoutf8,
    NULL
};

static XlcConv
open_cstoutf8(
    XLCd from_lcd,
    const char *from_type,
    XLCd to_lcd,
    const char *to_type)
{
    lazy_init_all_charsets();
    return create_conv(from_lcd, &methods_cstoutf8);
}


最后还要在初始化函数 _XlcAddUtf8Converters() 和 _XlcUtf8Loader() 中分别加上:

_XlcSetConverter(lcd, XlcNCharSet, lcd, XlcNUtf8String, open_cstoutf8);

和:

_XlcSetConverter(lcd, XlcNCharSet, lcd, XlcNMultiByte, open_cstoutf8);

至此, UTF-8 Locale 的 XlcNCharSet 到 XlcNMultiByte 和 XlcNUtf8String 的转换函数就完成了。 当 X 应用软件在 UTF-8 Locale 下需要进行 CharSet 到 MultiByte 或 Utf8String 转换的时候, libX11 会自动调用 函数 cstoutf8 进行转换。

大家读过 lcUTF8.c 后也许会发现里面并没有定义 Compound Text 和其他编码之间的转换方法。 其实在 lcCT.c 中已经定义了 Compound Text 和各种标准 CharSet 以及已在 XLC_LOCALE 中定义的非标准 CharSet 之间的转换函数, libX11 会利用这些函数完成 MultiByte, Utf8String 等编码和 Compound Text 之间的间接转换。

由于 libX11 中所有的编码转换都倚赖于这些编码转换模块, 所以要想让 libX11 支持一种新的编码(即一种新的 Locale), 就必需在 libX11 中加入相应的编码转换模块。 在 X 窗口系统中实现对 GB18030 的支持的难点就在于此了。
 楼主| 发表于 2002-8-28 19:52:45 | 显示全部楼层
在 XFree86 窗口系统中实现对 GB18030 的支持(二)   

内容:

1. 在 libX11 中实现 GB18030 编码转换模块
2. GB18030 Locale 的 XLC_LOCALE 文件
3. 改造 xtt 模块

本文是《在 XFree86 窗口系统中实现对 GB18030 的支持》的第二篇,将具体介绍如何在XFree86中实现对 GB18030的支持。
1. 在 libX11 中实现 GB18030 编码转换模块
前面已经介绍了在 XFree86 中支持一种文字编码的关键是在 libX11 中实现支持这种编码的一系列转换模块。 但是由于 GB18030 是单双四字节混和编码, 所以和其它常用的单双字节编码相比, 处理起来比较麻烦。 下面就简要介绍一下在 libX11 中实现 GB18030 编码转换函数的原理。 大家可以参考 XFree86 的源码和相关文档, 以便更好的理解以下内容。

1.1 如何将 GB18030 编码成 Compound Text
看过 Compound Text 文档, 大家就会知道它的最初用途是用来承载单字节或双字节编码的, 并不适合承载编码 GB18030 这样的多字节可变长度编码。其实 Compound Text 所能够承载的编码并不是通常意义的文本编码(encoding),而是字符集编码(CharSet encoding)。而 XFree86 系统最多仅支持双字节宽度的字符集编码。

要想用 Compound Text 承载 GB18030 编码的文本, 就必须先把 GB18030 转换成 Compound Text 所支持的编码。 目前可行的有以下两种方案:

方案一、将 GB18030 拆分成两个双字节编码
首先需要注意的是 GB18030 编码中 0x0~0x7F 范围的单字节部分用 Compound Text 编码后就变成了 ISO8859-1:GL (GL 表示左半部分编码, 即 ISO8859-1 的七位码部分), 所以在拆分 GB18030 编码的时候, 这部分是不计入内的。

拆分 GB18030 的最直接的办法就是把其双字节部分作为一个独立的编码, 然后把四字节部分再编码成双字节, 成为另一个独立的编码。 这样我们就可以将 GB18030 文本分解成一个单字节部分, 和两个双字节部分编码成 Compound Text 了。 GB18030 的双字节部分其实基本就是原来的 GBK 双字节编码部分, 它们之间仅有几十个码位的区别。 也就是说我们可以象处理 GBK 编码那样处理 GB18030 的双字节部分。 下面的任务就是处理四字节部分了。

将 GB18030 四字节部分编码成双字节最简单的办法就是把这部分编码从第一个码开始按顺序重新编码, 重新编码后 GB18030 的第一个四字节码 0x81308130 就变成了 0。 依次类推, 我们可以将 GB18030 四字节编码中与 Unicode 3.0 (即 ISO10646 第一平面) 相对应的部分编码成双字节。 但由于这种编码方式最多只能有 65536 个码位, 所以我们不可能将 GB18030 中与 Unicode 3.1 对应的所有码位都编成双字节码。 这也是这种编码方案最大的不足。 另外还有一种办法就是直接将 GB18030 四字节编码转换成对应的 UCS2 编码, 同样可以实现以上目的。

在实际实现时, 我们并没有使用将 GB18030 拆分成两个双字节编码的方案。 因为这种方案实现起来过于复杂, 而且还具有无法向 Unicode 3.1 标准过渡等问题。 实际使用的是下面一种方案。

方案二、将 GB18030 编码成 UTF-8
前面已经介绍过,为了使 XFree86 平稳的从 Compound Text 过渡到 UTF-8,已经在标准的 Compound Text 中加入了一个特殊的模式用来编码 UTF-8文本。在 XFree86 中 Compound Text 编码转换函数会对这种编码模式做特别处理,以便能够正确转换编码在 Compound Text 中的 UTF-8 文本。详细细节请参见 XFree86 的源码(xc/lib/X11/lcCT.c)。

另外前面也介绍过,GB18030 在字汇上和 Unicode 3.0 标准是一一对应的,在码位上也给 Unicode 3.1 标准预留了足够的空间。也就是说,GB18030 编码和 Unicode 3.1 编码是一一对应关系,它们之间可以做双向转换而不损失任何信息。所以我们完全可以将 GB18030 编码的文本先转换成 UTF-8 编码,然后再转换成 Compound Text,而不丢失任何信息,反之亦然。

由于 libX11 中已经提供了通用的函数完成 CharSet encoding 与 Compound Text 之间的转换,并且支持 UTF-8 编码。所以我们只需要提供 GB18030 <-> UTF-8 的转换函数,就可以利用 libX11 的间接编码转换功能实现 GB18030 <-> Compound Text 的相互转换。这一过程对于应用程序来说是完全透明的,只要应用程序使用 libX11 所提供的标准的 Compound Text 处理函数,就可以正确无误的处理 GB18030 编码。目前常用的应用程序开发库,如 gtk+、QT 等都是这样做的。但不幸的是有少数应用软件是自己处理 Compound Text 的,如 emacs 和 xemacs。所以这种方案不能应用于这类软件。另外,gtk+ 从 1.2.9 开始便引入了一个错误,它在处理 Compound Text 文本时画蛇添足的做了一下过滤,把 0x80~0xFF 之间的一些控制字符过滤掉了,就破坏了编码成 UTF-8 的 Compound Text 文本。在最新发布的 TurboLinux 7.0 中已经修正了这个错误。


至此,将 GB18030 编码成 Compound Text 的问题已经解决了。接下来就要解决 GB18030 与其它编码之间的转换问题,以及显示的问题了。

1.2 GB18030 与其它编码之间的相互转换
以上介绍了如何将 GB18030 编码成 Compound Text 的问题,解决了这个问题后我们就可以在 GB18030 Locale 下运行的两个应用程序之间利用 Compound Text 交换数据了。但这还远远不够。

目前最常用的中文编码是 GB2312 和 GBK,台湾和香港地区常用的则是 BIG5 和 BIG5-HKSCS。GB18030 作为最新的汉字编码标准其实已经涵盖了前面这几种编码标准的绝大部分字汇,是名副其实的超集。因此,在使用传统中文编码的应用程序和使用 GB18030 的应用程序之间交换数据,是一个非常有用的功能。至少需要实现从传统编码程序到 GB18030 程序的单向数据传输。因此我们必须在 libX11 中实现把 GB2312、GBK、BIG5 等编码转换成 GB18030 编码的功能。更广泛的讲,GB18030 标准已经具备表示所有 Unicode 3.0 甚至 Unicode 3.1 字汇的能力,也就是说 GB18030 可以编码各国文字。所以将非中文编码转换成 GB18030 编码也是有意义的,尤其是日文和韩文。

要想实现从各种传统编码向 GB18030 编码转换可以借助于 glibc 的 iconv 模块,但这种方法的最大缺陷就是效率太低。幸运的是,在 libX11 中已经包含了许多常用字符集编码和 UCS4 之间的转换函数(参见 xc/lib/X11/lcUTF8.c 和 xc/lib/X11/lcUniConv/*)。所以我们可以利用这些函数通过 UCS4 编码做中介,实现 GB18030 和这些传统编码之间的转换。

1.3 GB18030 编码字符串的显示
前面已经提到,XFree86 仅支持双字节的字符集编码,所以直接用 GB18030 编码做字符集编码进行字符串显示是不行的。因此我们需要将 GB18030 编码的字符串先转换成多段单字节或双字节编码的字符串,然后才能送去显示。这个问题的解决办法其实已经在 1.1 节中提到了。

对于第一个方案,我们已经把 GB18030 编码的字符串分解为一个单字节编码部分和两个双字节编码部分,因此可以直接送去显示。单字节部分就是 ISO8859-1 编码,可以使用 ISO8859-1 编码的英文字库进行显示;双字节部分则需要中文字库的支持。在现有的应用软件中,mozilla 其实就是使用这种方法来显示 GB18030 文本的。mozilla 使用了 1.1 中所述的第一种方法编码 GB18030 文本,并将 GBK 兼容的双字节编码部分称做 gb18030.2000-0,将原四字节编码部分称做 gb18030.2000-1。因为 mozilla 不使用字符集来显示字符串,它使用自己的一套处理机制来完成类似于字符集的功能,因此只要 XFree86 支持这两种编码的字库,mozilla 就可以正确显示 GB18030 文本了。关于在 XFree86 中支持这两种字库的问题,我们将在后面介绍。

对于第二个方案,就更简单了。我们只需要用一个 iso10646-1 (UCS2-BE) 编码的 GB18030 中文字库就可以完成字符串的显示工作。而且现在所有符合 GB18030 标准的 TrueType 字库都使用了 Unicode (UCS2-BE) 编码作为字库索引,XFree86 的 xtt/freetype 字库模块可以直接访问这种字库而不用做任何编码转换。但是由于中文 TrueType 字库包含的字模数目太大,在 XFree86 中用变宽模式使用这种字库,速度太慢。所以我们只能用固定宽度的字符元(Char Cell)模式来使用中文字库,由此导致的直接后果就是所有英文字符变成全宽的了,效果非常难看。

解决这个问题的方法其实很简单,即使用字符集。将 GB18030 编码文本中 0x0~0x7F 的部分分离出来,使用半宽的英文字库进行显示。其余部分仍然使用中文字库进行显示。在实践中发现,由于 GB18030 还覆盖了 Unicode 中 0x80~0xFF 之间的编码,即 ISO8859-1 的右半部分。而这部分也是半宽的,所以同样不能使用中文字库进行显示。因此这部分也需要被分离出来使用英文字库进行显示。

TrueType 字库在显示低点阵字符的时候效果比较差,目前又没有可用的 GB18030 点阵字库,所以在实际应用中我们还把 GB18030 中常用的 GB2312 部分编码分离了出来单独用 GB2312 字库来显示,这样在低点阵的时候我们就可以用 GB2312 编码的点阵字库来显示这部分汉字,以获得比较好的显示效果。

1.4 实现 GB18030 编码转换模块
上面说了两种显示办法都需要将 GB18030 编码的字符串切割成多段单字节或双字节字符集编码的字符串才能调用相应的字库进行显示。好在 libX11 库中已经提供了一套现成的函数来完成类似的工作,这就是 libX11 中的 lcUTF8.c 模块。这个模块不仅实现了一整套传统字符集编码与UTF-8编码和UCS4编码之间的转换函数,还提供了将 Unicode 编码的字符串切分并转换成多段单、双字节字符集编码的函数。这个模块使用静态的转换码表来实现 Unicode 编码和传统字符集编码之间的转换。与使用 glibc 的 iconv 模块相比它的速度更快,而且占用的内存更小,因为所有 XFree86 软件仅在内存中共享一套转换码表。

前面已经介绍过,GB18030 编码和 Unicode 编码在字汇上有一一对应的关系,因此它和 UTF-8 编码也同样存在一一对应关系。而 lcUTF8.c 模块所提供的功能正是我们的 GB18030 编码转换模块所需要的。所以很自然的就可以想到我们可以在 lcUTF8.c 的基础上实现 GB18030 编码转换模块。

实际上我们并没有编写独立的 GB18030 模块,而是在 lcUTF8.c 中直接实现了对 GB18030 的支持。这样做的好处是不言而喻的。我们仅在 lcUTF8.c 中增加了几百行代码就解决了上面讲到的所有问题。

参照 lcUTF8.c 对 UTF-8 编码的处理方法,我们只需完成 GB18030<->UCS4 的相互转换工作就行了。GB18030<->Compound Text,GB18030<->CharSet encoding 等转换函数其实就是先将 GB18030 转换为 UCS4 然后在转换成 Compound Text 或 CharSet encoding。而 UCS4<->Compound Text,UCS4<->CharSet encoding 等转换函数和 lcUTF8.c 中原有的函数没有任何区别,可以共享同样的代码。因此关键问题就是如何实现 GB18030<->UCS4 的转换。解决这个问题的最直接的方法其实就是使用 glibc 的编码转换模块。由于使用 GB18030 编码的 X 应用软件都会将 Locale 设置为 GB18030,所以我们可以在 libX11 中直接使用 mbtowc 和 wctomb 两个函数在 libX11 中实现 GB18030 多字节字符和宽字节字符之间的转换。而且 glibc 中 wchar_t 使用的正是 UCS4 编码。由于 GB18030 是无状态编码,所以在 libX11 中使用 mbtowc 和 wctomb 是线程安全的。

在此仅列出 CharSet encoding 到 GB18030 编码(也就是 MultiByte 编码)的转换函数以供参考:
/* from XlcNCharSet to XlcNMultiByte */
               
static int      
iconv_cstombs(conv, from, from_left, to, to_left, args, num_args)
    XlcConv conv;   
    XPointer *from;     
    int *from_left;     
    XPointer *to;      
    int *to_left;   
    XPointer *args;
    int num_args;
{   
    XlcCharSet charset;
    char *name;
    Utf8Conv convptr;
    int i;
    unsigned char const *src;
    unsigned char const *srcend;
    unsigned char *dst;
    unsigned char *dstend;
    int unconv_num;

    if (from == NULL || *from == NULL)
        return 0;

    if (num_args < 1)
        return -1;

    charset = (XlcCharSet) args[0];
    name = charset->encoding_name;
    /* not charset->name because the latter has a ":GL"/":GR" suffix */

    for (convptr = all_charsets, i = all_charsets_count-1; i > 0; convptr++, i--)
        if (!strcmp(convptr->name, name))
            break;
    if (i == 0)
        return -1;

    src = (unsigned char const *) *from;
    srcend = src + *from_left;
    dst = (unsigned char *) *to;
    dstend = dst + *to_left;
    unconv_num = 0;

    while (src < srcend) {
        ucs4_t wc;
        int consumed;
        int count;

         /* 调用 lcUTF8.c 提供的 CharSet encoding -> UCS4 编码转换函数
            将 CharSet 编码字符转换成 UCS4 编码 */
        consumed = convptr->cstowc(conv, &wc, src, srcend-src);
        if (consumed == RET_ILSEQ)
            return -1;
        if (consumed == RET_TOOFEW(0))
            break;

         /* 调用 glibc 中的 wctomb 函数将 UCS4 字符转换成 GB18030 多字节字符 */
        count = wctomb(dst, wc);
        if (count == 0)
            break;
        if (count == -1) {
            count = wctomb(dst, BAD_WCHAR);
            if (count == 0)
                break;
            unconv_num++;
        }
        src += consumed;
        dst += count;
    }

    *from = (XPointer) src;
    *from_left = srcend - src;
    *to = (XPointer) dst;
    *to_left = dstend - dst;

    return unconv_num;
}




另外,lcUTF8.c 中原先是没有 GBK<->UCS4 和 BIG5-HKSCS<->UCS4 转换模块的,我们参照 xc/lib/X11/lcUniConv 目录中其它转换模块的格式为 lcUTF8.c 增加了这两个模块。

至此,我们就在 libX11 中实现了 GB18030 的编码转换模块,XFree86 的 GB18030 内功已经修炼完成,下面就要开始修炼 Locale 描述文件、字库接口等外功了。

2. GB18030 Locale 的 XLC_LOCALE 文件
*nix 系统中编写国际化程序的关键就是底层 libc 库对 locale 的支持。每一种语言编码和国家/地域都对应一个 locale,其中提供了编码转换信息、日期货币格式、字符排序规则等信息。同样,要想让 XFree86 支持一个 locale,就必须有一个相应的 XLC_LOCALE 文件,其中描述了这种 locale 使用的文字编码、所有字符集编码以及字体集等信息。

GB18030 locale 对应的 XLC_LOCALE 文件内容如下:
#     XFree86 NLS for Chinese encoding GB18030
#                Modified from xc/nls/XLC_LOCALE/en_US.UTF-8
#            by James Su

#
#        XLC_FONTSET category
#
XLC_FONTSET

on_demand_loading        True

object_name              generic

#        We leave the legacy encodings in for the moment, because we don't
#        have that many ISO10646 fonts yet.

#        以下定义了 GB18030 locale 使用的所有字库和对应的字符集编码
#        字体集 fs0, fs1 对应单字节部分的 0x0~0x7F 和 0x80~0xFF
#        fs0 class (7 bit ASCII)
fs0      {
         charset         {
                 name        ISO8859-1:GL
         }
         font        {
                 primary         ISO8859-1:GL
                 vertical_rotate         all
         }
}

#        fs1 class (ISO8859 families)
fs1      {
         charset         {
                 name        ISO8859-1:GR
         }
         font        {
                 primary         ISO8859-1:GR
         }
}

#        字符集 fs2, fs3 对应其它部分
#        fs2 class (Chinese Han Character)
fs2      {
         charset         {
                 name        GB2312.1980-0:GL
         }
         font        {
                 primary         GB2312.1980-0:GL
         }
}

#        fs3 class
fs3      {
         charset         {
                 name        ISO10646-1
         }
         font        {
                 primary         GB18030-0
                 substitute GBK2K-0
         }
}
END XLC_FONTSET

#
#        XLC_XLOCALE category
#
XLC_XLOCALE

encoding_name                GB18030
mb_cur_max               4
state_depend_encoding        False

#        下面定义的是 GB18030 locale 使用的所有字符集的信息
#        cs0 class
cs0      {
         side                GLefault
         length              1
         ct_encoding         ISO8859-1:GL
}

#        cs1 class
cs1      {
         side            GRefault
         length          1
         ct_encoding     ISO8859-1:GR
}

#        cs2 class
cs2      {
         side            GR
         length          2
         ct_encoding     GB2312.1980-0:GL; GB2312.1980-0:GR
}

#        cs3 class
cs3      {
         side                none
         ct_encoding         ISO10646-1
}

END XLC_XLOCALE



注意字符集 fs2 的定义,字符集编码使用的是 ISO10646-1,也就是 UCS2-BE,而字体是 GB18030-0。其实 GB18030-0 只是我们在 xtt 字库模块中增加的一个 ISO10646-1 的别名而已,目的是将 GB18030 字库与其它 ISO10646-1 字库区别开来。

字符集定义 cs0 ~ cs3 与字体集 fs0 ~ fs3 并没有直接的联系,它们的定义是独立的。定义 cs0 ~ cs3 的用途是告诉 libX11,在将 GB18030 字符串编码成 Compound Text 前先拆分成使用 ISO8859-1:GL,ISO8859-1:GR,GB2312.1980-0 以及 UTF-8 等不同字符集编码的四段字符串,然后在编码成一个 Compound Text 数据流。这样做的好处是,我们可以从 GB18030 locale 的应用程序向 GB2312 locale 的应用程序传输英文字符和 GB2312 中文字符;或向英文 locale 的应用程序传输纯 ASCII 码数据。

现在 XFree86 支持 GB18030 的工作已经基本完成,最后就差在 xtt 字体模块中添加对 gb18030-0 以及 gb18030.2000-0 和 gb18030.2000-1 的支持了。

3. 改造 xtt 模块
XFree86 中包含了两个常用的 TrueType 字体模块,xtt 和 freetype。相对而言,freetype 模块使用标准码表来实现对各种字符集编码的支持,改造起来相对简单些。但由于性能以及功能较 xtt 弱,所以人们常用 xtt 模块。xtt 模块采用编码转换模块来支持各种编码的字体。所有编码转换模块都被编译成动态库文件,可以在使用的时候动态加载和释放。下面就介绍一下怎样让 xtt 支持上述三种字体。

支持 gb18030-0 非常简单,因为它就是 ISO10646-1 (UCS2-BE编码)的一个别名而已。支持 gb18030.2000-0 也比较简单,只需把 xtt 原有的 GBK 模块拿来稍加改造就行了。gb18030.2000-1 就比较复杂了,因为它是 mozilla 专用模块,采用采用顺序编码的方式把 GB18030 的四字节部分重新编码,我们必须建立这种新编码和 UCS2-BE 编码之间的映射表才能编写 gb18030.2000-1 模块。好在 mozilla 的源码中有这张表,只需要转换一下格式就行了。按照 xtt 中其它编码转换模块的格式,我们可以将三种字体的代码写到一个模块中,主程序(main.c)的代码如下:
/* ===EmacsMode: -*- Mode: C; tab-width:4; c-basic-offset: 4; -*- === */
/* ===FileName: ===
   Copyright (c) 1998 Takuya SHIOZAKI, All Rights reserved.
   Copyright (c) 1998 X-TrueType Server Project, All rights reserved.
   (省略注释)
Notice===
*/

#include "xttversion.h"

static char const * const releaseID =
    _XTT_RELEASE_NAME;

#include "xttcommon.h"
#include "xttcap.h"
#include "xttcconv.h"
#include "xttcconvP.h"

typedef enum
{
    GB18030_0,
    GB18030_2000_0,
    GB18030_2000_1
} CharSetMagic;

static CharSetRelation const charSetRelations[] = {
    { "gb18030",  "2000", "0", GB18030_2000_0, { 0x40, 0xff, 0x81, 0xfe, 0x8140 } },
    { "gb18030",  "2000", "1", GB18030_2000_1, { 0x00, 0xff, 0x00, 0x99, 0x0000 } },
    { "gb18030",    NULL, "0", GB18030_0,      { 0x00, 0xff, 0x00, 0xff, 0x3000 } },
    { "gbk2k",      NULL, "0", GB18030_0,      { 0x00, 0xff, 0x00, 0xff, 0x3000 } },
    { NULL, NULL, NULL, 0, { 0, 0, 0, 0, 0 } }
};

CODECONV_TEMPLATE(cc_gb18030_2000_0_to_ucs2);
CODECONV_TEMPLATE(cc_gb18030_2000_1_to_ucs2);
CODECONV_TEMPLATE(cc_font_gbk2k_to_ucs2);
static MapIDRelation const mapIDRelations[] = {
    { GB18030_2000_0,  EPlfmISO,     EEncISO10646,
                                     cc_gb18030_2000_0_to_ucs2,      NULL },
    { GB18030_2000_0,  EPlfmUnicode, EEncAny,
                                     cc_gb18030_2000_0_to_ucs2,      NULL },
    { GB18030_2000_0,  EPlfmMS,      EEncMSUnicode,
                                     cc_gb18030_2000_0_to_ucs2,      NULL },
    { GB18030_2000_1,  EPlfmISO,     EEncISO10646,
                                     cc_gb18030_2000_1_to_ucs2,      NULL },
    { GB18030_2000_1,  EPlfmUnicode, EEncAny,
                                     cc_gb18030_2000_1_to_ucs2,      NULL },
    { GB18030_2000_1,  EPlfmMS,      EEncMSUnicode,
                                     cc_gb18030_2000_1_to_ucs2,      NULL },
    { GB18030_0,       EPlfmISO,     EEncISO10646,
                                     cc_font_gbk2k_to_ucs2,          NULL },
    { GB18030_0,       EPlfmUnicode, EEncAny,
                                     cc_font_gbk2k_to_ucs2,          NULL },
    { GB18030_0,       EPlfmMS,      EEncMSUnicode,
                                     cc_font_gbk2k_to_ucs2,          NULL },
    { -1, 0, 0, NULL, NULL }
};

STD_ENTRYFUNC_TEMPLATE(GB18030_entrypoint)

ft_char_code_t /* result charCodeDest */
cc_font_gbk2k_to_ucs2(ft_char_code_t codeSrc)
{
    return codeSrc;
}
/* end of file */



可以看到 gb18030-0 到 UCS2-BE 的转换函数就是原封不动的将数据返回。gb18030.2000-0和gb18030.2000-1的转换函数由于太长就不列出来了。

至此,我们对 XFree86 的现代化改造已经彻底完工。接下来的任务就是要购买和安装符合 GB18030 标准的 TrueType 字库了,相信大家都会,就不做介绍了。

参考文献:

glibc 的 infopage 中编码转换,locale 等章节;
XFree86 的相关文档,XLFD,CTEXT,i18n 等,可以在 XFree86 的源码包中找到;
XFree86 中 libX11 和 xtt 模块的源码(xc/lib/X11/*,xc/extras/X-TrueType/*)。

另一篇

http://www.linuxsir.cn/forum.php?mod=viewthread&tid=5631
发表于 2002-8-29 12:13:50 | 显示全部楼层
XFree86 窗口系统中 GB18030 支持 如何成只支持 GB2312 ?
Redhat7.3下, 我改了一下 /etc/sysconfig/i18n, LANG="GB18030"改成了LANG="zh_CN.GB2312"后,用root进X后,locale显示还是GB18030, 而用普通用户进X却是GB2312. WHY?! 怎样才能使root用户进X也为GB2312?
 楼主| 发表于 2002-8-29 21:12:41 | 显示全部楼层
不太清楚,请弟兄们研究研究吧。
发表于 2002-8-30 19:00:03 | 显示全部楼层
好文。我买了一本《LINUX程序设计权威指南》是于明俭、方汉等写的,书中只写了实际部分GB2312的方法,这回把这篇文章拷下了,研究研究先。
发表于 2002-8-30 23:29:45 | 显示全部楼层
是.太深奥了.
我也在论坛找几本书看看.
发表于 2002-8-31 09:53:18 | 显示全部楼层

曾经在水木见识过Suzhe

当初偶还初学Linux,不知Suzhe何人,曾在水木和他舌战。后来得知他是Turbolinux的牛人,才知自己真是不知天高地厚!呵呵
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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