原文地址: https://developer.apple.com/fonts/ttrefman/rm06/chap6cmap.html#Surrogates
总表
cmap表将字符编码映射为 glyph (即字符点阵图)的索引。对于某种字体,选择什么编码依赖于目标平台的默认行为。要想字体运行在使用不同编码的多个平台,需要多个编码表。因此cmap表会包含多个“子表”,每个子表支持一种编码方案。
如果该字符的编码在字体文件中找不到任何glyph与之相对应,则其glyph索引指向0。在字体文件中,这个位置是一个代表“字符缺失”的glyph,通常是一个空白方块。
如果字符编码根本不存在,则映射为glyph索引-1,这是保留给glyph流中被删除的glyph。
cmap表开头是cmap表版本和子表数目。然后是“子表”。
最初,cmap表只能映射传统字符集标准,即每字符编码使用8位,8位/16位混合,或16位。随着ISO/IEC10646-1的引入,以及在Unicode2.0以后surrogates(替代,保留区)的使用,字体文件中的字符需要考虑16位/32位混合编码或32位编码。
最初,对于编码子表类型只使用0-6的cmap,版本号建议设置为0。如果cmap表中包含类型8以上的子表,版本号设置为1——这些类型的子表使用了surrogates,提供了对Unicode更好的支持。
目前,这个建议不再适用,所有的cmap表都应该将版本号设置为0。
Table6: 'cmap'表头
Type |
Name |
Description |
UInt16 |
version |
Version number (Set to zero) |
UInt16 |
numberSubtables |
Number of encoding subtables |
子表
每个cmap子表以platformID开始,用于指定编码所使用的环境。然后是platformSpecificID,用于标识在该platform下的某种编码。例如,MacRomman是MacOS下几个标准的编码方案之一。在'name'表一节中,有一个platformID和platformSpecificID的列表。最后是子表真实的偏移地址。
Table7: 'cmap'子表
Type |
Name |
Description |
UInt16 |
platformID |
Platform identifier |
UInt16 |
platformSpecificID |
Platform-specific encoding identifier |
UInt32 |
offset |
Offset of the mapping table |
cmap子表应按照先platformID后platformSpecificID的顺序排序(升序)。
每个cmap子表可能是7种格式之一:
format 0, format 2, format 4, format 6,format 8.0, format 10.0, and format 12.0 。
cmap格式
将Macintosh标准字符映射为glyph使用format0。format2则支持8位/16混合编码(包含日、中、韩的字符)到glyph的映射。format4用于16位映射。format6用于压缩的16位映射。
Formats 8, 10, and 12(properly 8.0, 10.0, and 12.0) 用于混合16位/32位以及纯32位映射。
译注:utf-16是一种16位/32位混合编码。
这些编码都支持Unicode2.0以后的surrogates(替代)。
format 0
Format 0 适用于编码和glyph索引在单字节范围内的字符。这是标准的苹果字符到glyph的映射方式。
Table8: 'cmap'format 0
Type |
Name |
Description |
UInt16 |
format |
Set to 0 |
UInt16 |
length |
Length in bytes of the subtable (set to 262 for format 0) |
UInt16 |
language |
Language code for this encoding subtable, or zero if language-independent |
UInt8 |
glyphIndexArray[256] |
An array that maps character codes to glyph index values |
format 2
Format 2 适用于包含日、中、韩字符的字体。这种编码通常用于支持亚洲语言的Macintosh系统。这些字体包含8位/16位混合编码,这种编码把2个byte中的第一个byte划出一段范围保留不用,而在第2个byte中,这些值却是可用的。
表9显示format2子表记录的数据结构。subHeaderKeys数组把高字节(第1个byte)的可能值映射到subHeaders数组的成员。这就能够判断是否使用了第2个字节(2字节编码)。另外,这也会用来在glyphIndesArray数组中查找glyph索引。过程如下:
设高字节为i,取值0-255。先取subHeaderKeys[i]的值,再除以8,得到subHeader数组索引k。
如果k等于0,说明i是一个单字节编码,没有第2个字节了。如果k>0,则i是一个双字节编码的高字节,那么他应该还有一个低字节j。
译注:如果k==0;直接返回glyphIndexArray[i]作为glyph索引。
Table9: 'cmap'format 2
Type |
Name |
Description |
UInt16 |
format |
Set to 2 |
UInt16 |
length |
Total table length in bytes |
UInt16 |
language |
Language code for this encoding subtable, or zero if language-independent |
UInt16 |
subHeaderKeys[256] |
Array that maps high bytes to subHeaders: value is index * 8 |
UInt16 * 4 |
subHeaders[variable] |
Variable length array of subHeader structures |
UInt16 |
glyphIndexArray[variable] |
Variable length array containing subarrays |
subHeader的数据类型4个word的结构体,其c语言定义为:
typedef struct {
UInt16 firstCode;
UInt16 entryCount;
int16 idDelta;
UInt16 idRangeOffset;
} subheader;
如果k>0,subHeader[k]中的firstCode和entryCount被用于定义第2个字节j的取值范围:
firstCode<= j < (firstCode + entryCount)
如果j不在范围之内,返回glyph索引0(缺失的字符)。否则,idRangeOffset会被用于指出glyphIndexArray数组的对应范围。glyphIndexArray紧跟在subHeaders数组之后,并且可以简单地把它看作是subHeaders的一个扩充。首先需要计算出低字节字符映射数组的首地址:
subArray=subHeader.idRangOffset+&(subHeader.idRangeOffset)
然后用低字节减去firstCode去索引subArray得到glyph索引初值p。idDelta为索引调整量。如果p==0,直接返回p。如果p!=0,那么p=p+idDelta。在必要的情况下,需要对结果数用65535取模。
译注:这段不好翻译,或者原文描述有问题,我采用了意译。
对于单字节k=0的情况,subHeaders[0]的firstCode=0,entryCount=256,idDelta=0。正如前面所说的,idRangeOffset指向glyphIndexArray首地址。i值被直接用于索引glyphIndex数组:
p =glyphIndexArray[i]
最后p被返回。
format 4
Format 4 是双字节编码。这种格式用于字体文件中的编码分布与几个连续的区域,在区域之间可能有一些保留的空白。而有一些编码可能并不会与字体中的glyph对应。2字节的压缩编码则使用format6。
表头开始是格式编号format、子表长度length及语言language。紧接着是format数据。它分为3个部分:
Table10: Format 4
Type |
Name |
Description |
|
UInt16 |
format |
Format number is set to 4 |
|
UInt16 |
length |
Length of subtable in bytes |
|
UInt16 |
language |
Language code for this encoding subtable, or zero if language-independent |
|
UInt16 |
segCountX2 |
2 * segCount |
|
UInt16 |
searchRange |
2 * (2**FLOOR(log2(segCount))) |
|
UInt16 |
entrySelector |
log2(searchRange/2) |
|
UInt16 |
rangeShift |
(2 * segCount) - searchRange |
|
UInt16 |
endCode[segCount] |
Ending character code for each segment, last = 0xFFFF. |
|
UInt16 |
reservedPad |
This value should be zero |
|
UInt16 |
startCode[segCount] |
Starting character code for each segment |
|
UInt16 |
idDelta[segCount] |
Delta for all character codes in segment |
|
UInt16 |
idRangeOffset[segCount] |
Offset in bytes to glyph indexArray, or 0 |
|
UInt16 |
glyphIndexArray[variable] |
Glyph index array |
segCount指定了有多少段。在format4表中没有直接指定这个参数,但所有的表参数中都间接的用到了segCount。segCount是指字体中连续编码区块的数目。searchRange是小于等于segCount的最大的2的n次方的2倍。
示例: Format 4 子表值计算方式:
segCount |
39 |
不计算,segCount=39 |
searchRange |
64 |
(2 * (largest power of 2 <= 39)) = 2 * 32 |
entrySelector |
5 |
(log2(the largest power of 2 < segCount))= log2 (32)= log2 (25)=5 |
rangeShift |
14 |
(2 * segCount) - searchRange = (2 * 39) - 64 |
每个段都有一个statrCode、endCode、idDelta和idRangeOffset,用于描述该段的字符映射。段以endCode值进行排序(升序)。
字符编码进行映射时,首先查找到第1个endCode大于或等于它的段。如果该字符码大于该段的startCode,将使用idDelta和idRangeOffset去查找glyph索引,否则返回“丢失的字符”。为了标志段表结束,最后一个段的endCode必需设置为0xffff。这个段不需要包含任何映射。它简单地将字符码0xffff映射为“丢失的字符”,即glyph0。
如果idRangeOffset值不为0,该字符编码的映射依赖于glyphIndexArray。字符编码到
startCode的偏移的量被用于加在idRangeOffset值中。最终这个数字用于表示从idRangeOffset自身地址到正确的glyphIdArray索引的偏移量。使用这种方法是因为在字体文件中,glyphIdArray是紧跟在idRangeOffset地址之后。下面的公式列出glyph索引地址的计算:
glyphIndexAddress= idRangeOffset[i] + 2 * (c - startCode[i]) + (Ptr) &idRangeOffset[i]
公式中乘以2的原因是需要把值转换成字节数。
当然,也可以使用下列公式:
glyphIndex= *( &idRangeOffset[i] + idRangeOffset[i] / 2 + (c - startCode[i]) )
这种方式是由于idRangeOffset是一个Uint16数组。
如果 idRangeOffset为0,idDelta加上字符编码即glyph索引。
glyphIndex= idDelta[i] + c
注意:所有的idDelta[i]都是65535的模数。
以下表为例,演示了字符编码10-20,30-990,100-153的glyph索引映射过程。这里,segCount被指定为4。这是一个format4子表映射变量的示例,演示从字符到glyph索引是如何计算的。假设段表的segCountX2是8,searchRange是8,entrySelector是2,rangeShift是0。
Name |
Segment 1 |
Segment 2 |
Segment 3 |
Segment 4 |
endCode |
20 |
90 |
153 |
0xFFFF |
startCode |
10 |
30 |
100 |
0xFFFF |
idDelta |
-9 |
-18 |
-27 |
1 |
idRangeOffset |
0 |
0 |
0 |
0 |
下面是映射过程:
10被映射为10-9=1;20 被映射为20-9=11;30被映射为30-18=12;90被映射为90-18=72;等等。
format 6
Format 6 用于映射16位(2字节)的字符为glyph索引。有时称之为精简表映射。这种格式用于字符编码在一个连续范围的字体文件。非压缩的2字节编码映射应采用format4。format6格式如下表所示:
Table11: 'cmap'format 6
Type |
Name |
Description |
UInt16 |
format |
Format number is set to 6 |
UInt16 |
length |
Length in bytes |
UInt16 |
language |
Language code for this encoding subtable, or zero if language-independent |
UInt16 |
firstCode |
First character code of subrange |
UInt16 |
entryCount |
Number of character codes in subrange |
UInt16 |
glyphIndexArray[entryCount] |
Array of glyph index values for character codes in the range |
子表中的firstCode和entryCount指定了字符编码的可能范围。这个范围从firstCode开始,长度等于entryCount。在这个范围之外的编码被映射为“丢失的字符”或者glyph索引0。在这个范围内的编码,用编码减去firstCode将直接作为glphyIndexArray的下标,从而得到glyph索引。
format 8.0–混合 16-位 and 32-位
Format 8.0 有点像format 2,字符编码是可变长度的。如果字体中包含Unicdoe“替代”,那么它也会包含16位unicode的其它规则。就像format2 允许混合8位/16位编码一样,也需要一种16位/32位混合编码的映射。这种混合编码假设:在32位字符编码中的高16位使用了和任何16位编码都不一样。这意味着要么有一部分字符编码是16位数,要么32位数的前端由固定编码构成,不代表任何意义。
下表位format 8子表格式:
Type |
Name |
Description |
Fixed32 |
format |
Subtable format; set to 8.0 |
UInt32 |
length |
Byte length of this subtable (including the header) |
UInt32 |
language |
Language code for this encoding subtable, or zero if language-independent |
UInt8 |
is32[65536] |
Tightly packed array of bits (8K bytes total) indicating whether the particular 16-bit (index) value is the start of a 32-bit character code |
UInt32 |
nGroups |
Number of groupings which follow |
接下来是分组。每个分组由下列结构构成:
Type |
Name |
Description |
UInt32 |
startCharCode |
First character code in this group; note that if this group is for one or more 16-bit character codes (which is determined from the is32 array), this 32-bit value will have the high 16-bits set to zero |
UInt32 |
endCharCode |
Last character code in this group; same condition as listed above for the startCharCode |
UInt32 |
startGlyphCode |
Glyph index corresponding to the starting character code |
请注意,endCharCode并不是编码个数,因为比照不同组时通常使用一个已有的字符编码进行的,使用endCharCode就不用在组的基础上进行加法运算。
压缩的bit数组(is32[])用于判断哪些16位数被保留给32位字符编码作为高字节,以及哪些特定16位数开头的编码能在字体文件中映射到glyph。
因为系统软件需要知道还有多少字节才到下一个字符,尤其是当前字符映射到“缺失的字符”时。
Format 8.0非常适合作为使用surrogates的Unicode文本,其他字符集编码也很容易使用这种格式。
要判断某个16位(cp)是否是32位编码的高字节,可以使用公式:
is32[cp / 8 ] & ( 1 << ( cp % 8 ) ) )
如果得到的不是0,该16位数是一个32位编码的前半部分。0则反之。一个字体文件可以既没有编码为0x0000的glyph,也可以没有高16位为0x0000的glyph。
format 10.0–Trimmed array
Format 10.0 有点像format 6,里面定义了一个trimmed数组(去掉了数组中的空值),用于表示32位字符编码的范围:
:
Type |
Name |
Description |
Fixed32 |
format |
Subtable format; set to 10.0 |
UInt32 |
length |
Byte length of this subtable (including the header) |
UInt32 |
language |
0 if don't care |
UInt32 |
startCharCode |
First character code covered |
UInt32 |
numChars |
Number of character codes covered |
UInt16 |
glyphs[] |
Array of glyph indices for the character codes covered |
format 12.0–Segmented coverage
Format 12.0 有点像format 4, 但它使用32位整数定义段结构,这是子表结构:
Type |
Name |
Description |
Fixed32 |
format |
Subtable format; set to 12.0 |
UInt32 |
length |
Byte length of this subtable (including the header) |
UInt32 |
language |
0 if don't care |
UInt32 |
nGroups |
Number of groupings which follow |
分组的结构定义:
Type |
Name |
Description |
UInt32 |
startCharCode |
First character code in this group |
UInt32 |
endCharCode |
Last character code in this group |
UInt32 |
startGlyphCode |
Glyph index corresponding to the starting character code |
这里再一次使用了endCharCode而不是Code的个数,原因同format8.0中所述。
Mac OS兼容信息
所有的子表格式Mac 0SX10.2(及以后)都支持。对于任何特定的cmap子表,MacOS不针对某种特定格式。
Newton-兼容信息
Newton 字体使用较老的format 0,2,4,6子表格式,不支持format 8.0,10.0,12.0。
依赖
cmap表指向glyph索引。因此glyph索引对某种字体而言必需是有效的,而且不能超过glyphs的大小,glypns大小在maximumpfoile表中描述 maximumprofile table。
工具
对cmap表进行编辑主要使用ftxdumperfuser进行。注意ftxdumperfuser支持全部的7种子表格式,以及使用统一码标量值的增补Unidcode字符。
附: Unicode 和Surrogates
最初的Unicode标准允许所有字符都使用16个bit进行编码。这最多能包括65354个字符(Unicode编码规定U+FFFE和U+FFFF保留,不能用于字符编码,更多细节请参考Unicode标准)。Unicode字符集与其它字符集编码不同,有一些编码,其所有字符都使用8位编码,另一些则部分字符使用8位部分字符使用16位。
在Unicode 2.0研发过程中,发现一个明显的事实——没有足够的编码来覆盖人类所有字符。为解决这个问题,使用了一个扩展机制——surrogates。surrogates(替代)指一些特殊的Unicode编码,它们是成对的,包括高位替代(U+D800到U+DBFF)和低位替代(U+DC00到U+DFFF)。将一对替代映射为单个的32位整数称作单值,代表单个字符。
Unicode 2.0 and 3.0 并没有哪个字符使用替代,但2001年3月发表的Unicode3.1包含了40000个字符就用到了替代。之后,更多的字符使用替代进行编码。
Unicode字符使用16位编码,并且在UTF-16引入了替代。cmapformat 8.0适用于UTF-16编码。注意,0x0000总是一个单独的编码,它后面永远不会出现32位中的另外一半。
Unicode技术委员会已经采用了32位Unicode编码,每个字符将用32位编码代表,即UTF-32。format10.0和12.0适用于UTF-32。
还有8位编码的实现,如UTF-8。UTF-8常用于C字符串的交换协议,通常以0字节作为结尾。没有哪种格式适用于UTF-8。