字符集与字符编码

全球化,多语言,Unicode,UTF8


问题

在做游戏的全球化时,遇到的一个问题。

玩家名称可以混杂多国语言,但是要进行宽度检测。不同国家的字符宽度不同,比如中文、韩文、日文算2个宽度,其他算1个宽度。根据输入的多语言文本,准确计算宽度。

例如:

1
2
3
4
5
6
7
8
你好 - 4
สวัสดี - 6
こんにちは - 10
Привет - 6
안녕 - 4
Hello - 5
Olá - 3
你好สวัสดีこんにちはПривет안녕HelloOlá 宽度为 38

这里泰文比较特殊一些,สวัสดี 在存储时会转为 สว◌ัสด◌ี,unicode中没有 วั 与 ดี 。



基础概念

字符集

字符集即字符的集合。
字符是各种文字和符号的总称,包含各国文字、标点符号、图形符号、数字等。

常见的字符集

字符集名称 别称 特点 技术特征
ASCII American Standard Code for Information Interchange, 美国信息互换标准编码 主要用于显示现代英语和其他西欧语言,是最通用的单字节编码系统 7bits表示一个字符,共128个字符(0-127),其中32-126是可打印字符
Unicode Universal Multiple-Octet Coded Character Set, 通用多八位编码字符集 支持现今世界各种不同语言的书面文本的交换、处理、显示;对每种语言中的每一个字符都设定了统一且唯一的二进制编码 使用十六进制数字,在书写时前面加上前缀 “U+”
UTF-8 8-bit Unicode Transformation Format, 将Unicode转成8bit格式 / 万国码 便于不同的计算机之间使用网络传输不同语言和编码文字,使得双字节的Unicode能在现存处理单字节系统上正确传输 UTF-8是一种针对Unicode的可变长字符编码;用1-6个字节编码Unicode字符,对应还有 UTF-16 与 UTF-32
GB2312 信息交换用汉字编码字符集·基本集 中国国家标准的简体中文字符集,于1981.5.1日实施 1. 区位码(分区表示);对所收录的汉字进行分区处理,每区含有94个汉字/符号。 2. 双字节表示,即两个字节来编码一个字,高位字节与低位字节有各自范围
GB18030 信息交换用汉字编码字符集基本集的扩充 中国政府于2000年3月17日发布的汉字编码国家标准,2001年8月31日后在中国市场上发布的软件必须符合本标准;解决了汉字、日文假名、朝鲜语和中国少数民族文字组成的大字符集计算机编码问题。与 Unicode 3.0兼容,并且与 GB2312兼容 采用 单字节、双字节、四字节三种方式对字符编码。单字节部分使用 0x00-0x7F(与ASCII码对应)
BIG5 大五码/五大码 收录13053个中文字,在中国台湾使用广泛 双字节表示,即两个字节来编码一个字,高位字节与低位字节有各自范围


字符编码

为什么需要编码?

  • 为了便于存储与传输,需要一个统一的规则来存取信息。(某段二进制代表什么信息)

如何进行字符编码?

  • 编码转码所需元素:
    • 字库表,所有可读/可写的字符数据库
    • 编码字符集,表示一个字符在字库表中的位置
    • 字符编码,编码字符集与字库表的映射关系
  • 过程
    • 编码
      1. 拿到一个字符准备存储
      2. 通过字符编码得到在编码字符集中值
      3. 将得到值存储
    • 转码
      • 得到要显示的值
      • 通过字符编码得到对应字库中的值
      • 将得到值进行显示
  • 通过字符编码可以节省字库表大小


Unicode & UTF-8

UTF-8编码为变长编码,最小编码单位(code unit)为一个字节,每个字节的前1-3个bit为描述性部分,后面的为实际序号部分。

  • 字节以 0 开头,当前字符为单字节字符,占用一个字节的空间;0之后的所有部分代表在Unicode中的序号。
  • 字节以 10 开头,当前字符为多字节字符,当前字节为多字节字符的第二字节,10 后所有部分和第一字节的剩余部分共同组成在Unicode中的序号。
  • 字节以 110 开头,当前字符为双字节字符,当前字节为双字节字符的第一字节,110 后所有部分与第二字节剩余部分共同组成在Unicode中的序号。(第二字节以10开头)
  • 字节以 1110 开头,当前字符为三字节字符,当前字节为三字节字符的第一字节,1110 后所有部分与第二字节剩余部分第三字节剩余部分共同组成在Unicode中的序号。(第二字节与第三字节均以10开头)

计划

字节 标准格式 容纳位数 16进制范围
单字节 0xxx xxxx 0-7 0x0000 - 0x007F
双字节 110x xxxx 10xx xxxx 8-11 0x0080 - 0x07FF
三字节 1110 xxxx 10xx xxxx 10xx xxxx 12-16 0x0800 - 0xFFFF

实际应用

实际字符 在Unicode字库中十六进制 在Unicode字库中二进制 UTF-8编码后的二进制 UTF-8编码后十六进制
H 0048 100 1000 0100 1000 48
Ĉ 0108 1 0000 1000 1100 0100 1000 1000 C4 88
4F60 100 1111 0110 0000 1110 0100 1011 1101 1010 0000 E4 BD A0


Lua & Unicode

Lua是否支持Unicode的呢?
在官网FAQ中,有一些讨论: Can I use unicode strings? or Does Lua support unicode?
主要意思就是Lua并不知道存储/处理的是否为Unicode,但Lua字符串是一个任意序列的字节序列,可以存储任意二进制的数据,包括Unicode;然后根据处理需求可自行扩展相应逻辑。

处理UTF-8的第三方文件:


方案

按照需求,可定流程

  1. 求出字符串的unicode值
  2. 查找unicode值区间,判断所属国家
  3. 根据国家来进行不同长度计算

PS: 整个utf8相关方法,在文章最下方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
-- http://www.unicode.org/charts/nameslist/
local UnicodeNationRangeList = {
{tip = "数字", range = {{0x30, 0x39}}},
{tip = "英文", range = {{0x41, 0x5A}, {0x61, 0x7a}}},
{tip = "中文", range = {{0x2E80, 0x2FDF}, {0x3400, 0x4dbf}, {0x4E00, 0x9FFF}}},
{tip = "韩文", range = {{0x1100, 0x11FF}, {0x3130, 0x318F}, {0xAC00, 0xD7AF}}},
{tip = "日文", range = {{0x3040, 0x30FF}, {0x31F0, 0x31FF}}},
{tip = "泰文", range = {{0x0E00, 0x0E7F}}},
{tip = "俄文", range = {{0x0400, 0x052F}}},
{tip = "法文/德文", range = {{0x00C0, 0x00FF}}},
{tip = "阿拉伯文", range = {{0x0600, 0x06FF}, {0x0750, 0x077F}}},
}

--[[DESC: 分析字符属于哪个国家文字

c: string
字符

return: number, string
number: 国家索引
string: 国家简述
]]
function Xutf8.analyseCharBelong(c)
local nationIdx = -1
local tip = "不识别"

if Xutf8.len(c) ~= 1 then return nationIdx, tip end

local hexStr, hexStrArr, hexValArr = Xutf8.utf8_to_unicode(c)
local valueC = hexValArr[1]

for i, cfg in ipairs(UnicodeNationRangeList) do
local rangeList = cfg.range
for _, rangeCfg in ipairs(rangeList) do
if valueC >= rangeCfg[1] and valueC <= rangeCfg[2] then
nationIdx = i
tip = cfg.tip
break
end
end

if nationIdx ~= -1 then break end
end

return nationIdx, tip
end

--[[DESC: 返回字符串长度
中文、日文、韩文,一个字符算两个长度

注意:有些会占据双字节,所以长度会有所偏差
สวัสดี = สว◌ัสด◌ี -- 长度为6

str: string
字符串

return:
int - 返回具体长度
]]
function Xutf8.getSpecialLength(s)
local l = 0

local realLen = Xutf8.len(s)
for i = 1, realLen do
local c = Xutf8.sub(s, i, i)
local idx, tip = Xutf8.analyseCharBelong(c)
if idx == 3 or idx == 4 or idx == 5 then
l = l + 2
else
l = l + 1
end
end

return l
end




参考文章: