总表
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个部分:
- 4 word(UInt16)的头部,指定用于加快分段表查找的参数。
- 4个数组,用于描述段(段是一个连续的编码范围)。
- glyphID数组
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
Chars 10-20
|
Segment 2
Chars 30-90
|
Segment 3
Chars 100-153
|
Segment 4
Missing Glyph
|
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不针对某