UTF8轉GB2312-C語言實現


UTF8編碼轉換為GB2312編碼字符集時,需要明確以下兩點:

  1. UTF8是編碼格式,而GB2312是字符集,UTF8可以動態的表示1到6字節的編碼范圍,其還原后可以是雙字節Unicode UTF16(USC2)字符集,也可以是四字節Unicode UTF32(USC4)字符集,四字節以上的很少用到可以不做兼容處理。

  2. 這兩種編碼是完全不兼容的,因此他們之間的轉換無法通過既有規則形成公式之間計算,僅能通過LUT(look up table)查找表模式實現;這也就意味着必須先准備一張包含UTF8編碼下的字符對應GB2312字符集下字符的表。

GB2312字符集概述:
GB2312字符集通常指GB2312-80格式,使用2字節(16bit)表示一個字符,分區碼和位碼兩部分,區碼在高字節,位碼在低字節,在解析時需要注意。GB2312-80共編碼7445個字符,其中漢字有6763個,全字符集編碼范圍A1A1-F7FE;其中中文字符編碼范圍B0A1-F7FE,可見該編碼能與單字節的ASCII編碼0x00~0x7F做出很好的區分。
官方資料請移步“GB2312 80信息交換用漢字編碼字符集 基本集”http://www.moe.gov.cn/s78/A19/yxs_left/moe_810/s230/201206/t20120601_136847.html
但該文檔僅2頁,幾乎沒有參考價值:(

UTF8編碼方式概述:

Unicode碼 (hex) UTF8碼 (hex)
0000 0000 0000 007F 0xxxxxxx
0000 0080 0000 07FF 110xxxxx 10xxxxxx
0000 0800 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000 0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

單字節情況:UTF8編碼中單字節最高位為0,UTF8兼容ASCII編碼0x00~0x7F
多字節情況:第一個字節從高位開始連續的1的個數(這里1的個數計算方法為從最高位開始計數,直至遇到當前字節10結束)表示后續還有多少位是連續位;后續數據字節高兩位固定 10,由於第一字節自身可以攜帶部分數據;所以也受數據字節高兩位固定的規則限制。
舉個栗子:
雙字節的Unicode編碼為UTF8格式,110xxxxx 10xxxxxx,閱讀方向從左往右依次為第一字節、第二字節,字節計數基准點為0;第一字節最高位為1表示該字節后還有一個字節的數據;第6,5位為10表示本字節數據段開始,第4,0位為xxxxx表示可以攜帶5比特的數據;第二字節就非常簡單了,高位10打頭,直接取剩下的6比特數據即可。
因此2字節的UTF8編碼僅有11比特的有效數據區,比起直接使用UTF16(USC2)編碼還少了5比特信息范圍,但是UTF8的優勢在於可變長的設計,從解碼規則也可以看出,對於西方國家的ASCII碼,僅需一個字節就能表示,在網絡傳輸方面能省點帶寬;但對於東亞文字,就不那么友好了,兩個字節能表示的數據范圍,編碼到UTF8格式需要3個字節才能表示,可以說有利有弊。

UTF8編碼轉換為GB2312編碼字符集轉換思路:
1.解碼UTF8,還原成Unicode字符集
2.使用Unicode到GB2312字符集映射表找到對應的GB2312字符集編碼
3.對於點陣字體,使用GB2312字符集編碼在字庫中查找字模進行顯示;對於TTF字體,使用GB2312字符集編碼找到字體的繪制path,進行繪制渲染。

值得慶幸的是GB2312字符集所表示的漢字/符號剛好在雙字節的Unicode16(USC2)編碼范圍內,一對Unicode16轉GB2312正好是4個字節,非常方便32位的系統進行對齊尋址,尤其是ARM這種不支持非對其訪問的平台。

代碼實現:

/*
Unicode符號范圍     |        UTF-8編碼方式
(十六進制)        |              (二進制)
----------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
*/
/**
 * @brief utf8編碼轉unicode字符集(usc4),最大支持4字節utf8編碼,(4字節以上在中日韓文中為生僻字)
 * @param *utf8 utf8變長編碼字節集1~4個字節
 * @param *unicode utf8編碼轉unicode字符集結果,最大4個字節,返回的字節序與utf8編碼序一致
 * @return length 0:utf8解碼異常,others:本次utf8編碼長度
 */
uint8_t UTF8ToUnicode(uint8_t *utf8, uint32_t *unicode) {
    const uint8_t lut_size = 3;
    const uint8_t length_lut[] = {2, 3, 4};
    const uint8_t range_lut[] = {0xE0, 0xF0, 0xF8};
    const uint8_t mask_lut[] = {0x1F, 0x0F, 0x07};

    uint8_t length = 0;
    byte b = *(utf8 + 0);
    uint32_t i = 0;

    if(utf8 == NULL) {
        *unicode = 0;
        return PARSE_ERROR;
    }
    // utf8編碼兼容ASCII編碼,使用0xxxxxx 表示00~7F
    if(b < 0x80) {
        *unicode =  b;
        return 1;
    }
    // utf8不兼容ISO8859-1 ASCII拓展字符集
    // 同時最大支持編碼6個字節即1111110X
    if(b < 0xC0 || b > 0xFD) {
        *unicode = 0;
        return PARSE_ERROR;
    }
    for(i = 0; i < lut_size; i++) {
        if(b < range_lut[i]) {
            *unicode = b & mask_lut[i];
            length = length_lut[i];
            break;
        }
    }
    // 超過四字節的utf8編碼不進行解析
    if(length == 0) {
        *unicode = 0;
        return PARSE_ERROR;
    }
    // 取后續字節數據
    for(i = 1; i < length; i++ ) {
        b = *(utf8 + i);
        // 多字節utf8編碼后續字節范圍10xxxxxx‬~‭10111111‬
        if( b < 0x80 || b > 0xBF ) {
            break;
        }
        *unicode <<= 6;
        // ‭00111111‬
        *unicode |= (b & 0x3F);
    }
    // 長度校驗
    return (i < length) ? PARSE_ERROR : length;
}
/**
 * @brief 4字節unicode(usc4)字符集轉utf16編碼
 * @param unicode unicode字符值
 * @param *utf16 utf16編碼結果
 * @return utf16長度,(2字節)單位
 */
uint8_t UnicodeToUTF16(uint32_t unicode, uint16_t *utf16) {
    // Unicode范圍 U+000~U+FFFF
    // utf16編碼方式:2 Byte存儲,編碼后等於Unicode值
    if(unicode <= 0xFFFF) {
		if(utf16 != NULL) {
			*utf16 = (unicode & 0xFFFF);
		}
		return 1;
	}else if( unicode <= 0xEFFFF ) {
		if( utf16 != NULL ) {
		    // 高10位
		    *(utf16 + 0) = 0xD800 + (unicode >> 10) - 0x40;
            // 低10位
		    *(utf16 + 1) = 0xDC00 + (unicode &0x03FF);
		}
		return 2;
	}

    return 0;
}

測試樣例

int main() {
    // 嚴 utf8 E4 B8 A5
    printf("Hello world!\n");
    uint32_t buffer;
    uint8_t utf8[] = "\xE4\xB8\xA5";
    uint16_t utf16[2] = {0};

    uint8_t len = UTF8ToUnicode(utf8, &buffer);

    printf("len:%d\n", len);
    printf("buffer:%x\n", buffer);

    len = UnicodeToUTF16(buffer, utf16);

    printf("len:%d\n", len);
    printf("utf16[0]:%x\n", utf16[0]);
    printf("utf16[0]:%x\n", utf16[1]);

    return 0;
}

輸出:

驗證:

tips:
Unicode 16與GB2312-80的查找表可以使Unicode 16字符集編碼按照升序排列,(即Unicode 16從小到大排,GB2312與之對應,但GB2312是亂的)這樣程序中使用二分法查表,時間復雜度是O(log(n)),比線性查找最壞情況的O(n)要快上許多。

附件:
utf16.bin
utf16->gb2312查找表,其中utf16字符集升序排序,二進制格式
gb2312.bin
gb2312->utf16查找表,其中gb2312字符集升序排序,二進制格式
RawFile.txt
文本格式的UTF16到GB2312對照表,其中gb2312字符集升序排序

https://files.cnblogs.com/files/yanye0xff/fontmap.zip


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM