总表

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不针对某种特定格式。

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。