1 引言 我們在介紹 MiniGUI 體系結構的第一篇文章中提到,MiniGUI 采用了面向對象的技術實現了 GAL、IAL 以及多字體和多字符集的支持。字體和字符集的支持,對任何一個 GUI 系統來講都是不可缺少的。不過,各種 GUI 在實現多字體和多字符集的支持時,采用不同的策略。比如,對多字符集的支持,QT/Embedded采用 UNICODE 為基礎實現,這種方法是目前比較常用的方法,是一種適合於通用系統的解決方案。然而,這種方法帶來許多問題,其中最主要就是 UNICODE 和其他字符集之間的轉換碼表會大大增加 GUI 系統的尺寸。這對某些嵌入式系統來講是不能接受的。 MiniGUI 在內部並沒有采用 UNICODE 為基礎實現多字符集的支持。MiniGUI的策略是,對某個特定的字符集,在內部使用和該字符集完全一致的內碼表示。然後,通過一系列抽象的接口,提供對某個特定字符集文本的一致分析接口。該接口既可以用於對字體模塊,也可以用來實現多字節字符串的分析功能。如果要增加對某個字符集的支持,只需要實現該字符集的接口即可。到目前為止,MiniGUI 已經實現了 ISO8859-x 的單字節字符集支持,以及 GB2312、BIG5、EUCKR、UJIS 等多字節字符集的支持。 和字符集類似,MiniGUI 也針對字體定義了一系列抽象接口,如果要增加對某種字體的支持,只需實現該字體類型的接口即可。到目前為止,MiniGUI 已經實現了對 RBF 和 VBF 字體(這是 MiniGUI 定義的兩種光柵字體格式)、TrueType 和 Adobe Type1 字體等的支持。 在多字體和多字符集的抽象接口之上,MiniGUI 通過邏輯字體為應用程序提供了一致的接口。 本文重點介紹 MiniGUI 的邏輯字體、多字體和多字符集的實現,並以 EUCKR(韓文)字符集和 Adobe Type1 字體為例,說明如何在 MiniGUI 中實現一種新的字符集支持和新的字體類型支持。 2 邏輯字體、設備字體以及字符集之間的關系 在 MiniGUI 中,每個邏輯字體至少由一個單字節的設備字體組成。設備字體是直接與底層字體相關聯的數據結構。每個設備字體有一個操作集(即 font_ops),其中包含了 get_char_width、get_char_bitmap 等抽象接口。每個 MiniGUI 所支持的字體類型,比如等寬光柵字體(RBF)、變寬光柵字體(VBF)、TrueType 字體、Adobe Type1 字體等均對應一組字體操作集。通過這個字體操作集,我們就可以從相應的字體文件中獲得某個字符的點陣(對光柵字體而言)或者輪廓(對矢量字體而言)。之後,MiniGUI 上層的繪圖函數就可以將這些點陣輸出到屏幕上,最終就可以看到顯示在屏幕上的文字。 圖 1 給出了邏輯字體、設備字體以及字符集之間的關系。 在設備字體結構中,還有一個字符集操作集(即 charset_ops),其中包含了 len_first_char、char_offset、len_first_substr 等抽象接口。每個 MiniGUI 所支持的字符集,比如 ISO8859-x、GB2312、BIG5 等字符集均對應一組字符集操作集。通過這個字符集操作集,我們就可以對某個多種字符集混合的字符串進行文本分析。比如在“ABC中文”這個字符串中,頭三個字符是屬於 ISO8859 的字符,而“中文”是屬於 GB2312 的字符。通過調用這兩個字符集操作集中的函數,我們就可以了解該字符串中哪些字符是屬於 ISO8859 的字符,哪些字符是屬於 GB2312 的字符,甚至可以進行更加復雜的分析。比如,MiniGUI 中的 GetFirstWord 函數可以從這種字符串中獲得第一個單詞。比如“ABC DEF 中文”字符串中的第一個單詞是“ABC”,而第二個單詞是“DEF”,第三個單詞和第四個單詞分別是“中”和“文”。該函數的實現如下: int GUIAPI GetFirstWord (PLOGFONT log_font, const char* mstr, int len, WORDINFO* word_info) { DEVFONT* sbc_devfont = log_font->sbc_devfont; DEVFONT* mbc_devfont = log_font->mbc_devfont; if (mbc_devfont) { int mbc_pos; mbc_pos = (*mbc_devfont->charset_ops->pos_first_char) (mstr, len); if (mbc_pos == 0) { len = (*mbc_devfont->charset_ops->len_first_substr) (mstr, len); (*mbc_devfont->charset_ops->get_next_word) (mstr, len, word_info); return word_info->len + word_info->nr_delimiters; } else if (mbc_pos > 0) len = mbc_pos; } (*sbc_devfont->charset_ops->get_next_word) (mstr, len, word_info); return word_info->len + word_info->nr_delimiters; } 該函數首先判斷該邏輯字體是否包含多字節設備字體(mbc_devfont是否為空),如果是,則調用多字節字符集對應的操作函數 pos_first_char、len_first_substr、get_next_word 等函數獲得第一個單詞信息,並填充 word_info 結構。如果該邏輯字體只包含單字節設備字體,則直接調用單字節字符集對應的操作函數 get_next_word。一般而言,在 GetFirstWord 等函數中,我們首先要進行多字節字符集的某些判斷,比如 pos_first_char 返回的是字符串中屬於該字符集的第一個字符的位置。如果返回值不為零,表明第一個字符是單字節字符;如果為零,才會調用其他函數進行操作。 有了這樣的邏輯字體、設備字體和字符集結構定義,當我們需要新添加一種字符集或者字體支持時,只需按照我們的字體操作集和字符集操作集定義對應的新操作集結構即可,而對上層程序沒有任何影響。 3 MiniGUI 中的字符集支持 3.1 字符集操作集 在 MiniGUI 中,每個特定的字符集由對應的字符集操作集來表示。字符集操作集的定義如下(include/gdi.h。前面的數字表示在該文件中的行數,下同): 250 typedef struct _CHARSETOPS 251 { 252 int nr_chars; // 該字符集中字符的個數 253 int bytes_per_char; // 每個字符的平均字節數 254 int bytes_maxlen_char; // 字符的最大字節數 255 const char* name; // 字符集名稱 256 char def_char [MAX_LEN_MCHAR]; // 默認字符 257 258 int (*len_first_char) (const unsigned char* mstr, int mstrlen); 259 int (*char_offset) (const unsigned char* mchar); 260 261 int (*nr_chars_in_str) (const unsigned char* mstr, int mstrlen); 262 263 int (*is_this_charset) (const unsigned char* charset); 264 265 int (*len_first_substr) (const unsigned char* mstr, int mstrlen); 266 const unsigned char* (*get_next_word) (const unsigned char* mstr, 267 int strlen, WORDINFO* word_info); 268 269 int (*pos_first_char) (const unsigned char* mstr, int mstrlen); 270 271 #ifndef _LITE_VERSION 272 unsigned short (*conv_to_uc16) (const unsigned char* mchar, int len); 273 #endif /* !LITE_VERSION */ 274 } CHARSETOPS; 其中,前幾個字段(nr_chars、bytes_per_char、bytes_maxlen_char、name、def_char 等)表示了該字符集的一些基本信息,具體含義參見注釋。這裡需要對 bytes_maxlen_char 和 def_chat 作進一步解釋: bytes_maxlen_char 用來表示該字符集中字符的最長字節數。通常情況下,一個字符集中的每個字符的長度一般是定長的,但是也有許多例外,比如在 GB18303、UNICODE 等字符集中,字符的最長字節數可能超過 4 字節。 def_char 用來表示該字符集中的默認字符。該字段主要和字體配合使用。當某個針對該字符集的字體中缺少一些字符的定義時,就需要用默認字體替代這些缺少的字符。 在上述字符集的操作集定義中,後幾個字段定義為函數指針,它們均由邏輯字體接口用來進行文本分析: len_first_char 返回多字節字符串中第一個屬於該字符集的字符的長度。若不屬於該字符集,則返回 0。 char_offset 返回某個字符在該字符集中的位置。該信息可以由設備字體使用,用來從一個字體文件中獲取該字符對應的寬度或點陣。 nr_chars_in_str 計算字符串中屬於該字符集的字符個數並返回。注意,傳入的字符串必須均為該字符集字符。 is_this_charset 判斷給定的用來表示字符集的名稱是否指該字符集。因為對某種特定的字符集,其名稱不一定和 name 字段所定義的名稱匹配。比如,對 GB2312 字符集,就可能有 gb2312-1980.0、GB2312_80 等各種不同的名稱。該函數可以幫助正確判斷一個名稱是否指該字符集。 len_first_substr 返回某個多字節字符串中屬於該字符集的子字符串長度。如果第一個字符不屬於該字符集,則返回為 0。 get_next_word 返回多字節字符串中屬於該字符集的字符串中下一個單詞的信息。對歐美語言來說,單詞之間由空格、標點符號、制表符等相隔;對亞洲語言來說,單詞通常定義為字符。 pos_first_char 該函數返回多字節字符串中屬於該字符集的第一個字符的位置。 conv_to_uc16 該函數將某個屬於該字符集的字符,轉換為 UNICODE 的 16 位內碼。該函數主要用來從 TrueType 字體中獲得字符的輪廓信息。因為 TrueType 字體使用 UNICODE 定位