写点什么

一文读懂 Font 文件

  • 2025-10-25
    北京
  • 本文字数:4838 字

    阅读完需:约 16 分钟

一、引言

在开始阅读本文之前,推荐先阅读字符(Character)、字形(Glyph)、字体的区别理解基本概念。

如果你对字符与 Unicode 的相关概念还不理解,推荐阅读字符与编码


前文,我们介绍了字符(Character)、字形(Glyph)、字体的区别,这里我们再来实际分析一个字体文件中到底有什么,这有利于我们后续理解文字排版引擎的工作原理和流程。


macOS 上系统字体路径一般为/System/Library/Fonts/,可以看到有文件后缀有ttcttf,二者有什么区别呢?


1).ttf (TrueType Font)


ttf表示这是一个单字体文件,每个 .ttf 文件通常只对应一个字体样式(例如 Microsoft YaHei Regular


2).ttc (TrueType Collection)


ttc表示这是一个字体集合文件,内部可以包含多个 TrueType 字体(多个 .ttf 打包在一起),这些字体通常共享某些表(比如 glyph 轮廓、cmap),减少冗余,提高存储效率,常用于一个 Typeface 的多个变体(Regular, Bold, Italic, Light…)


上面提到了 TrueType,与之对应的还有 OpenType,二者其实都是字体类型标准,简单理解就是 OpenType 是 TrueType 的扩展,OpenType 支持更多的特性,比如:连字、RTL、上下标等。


OpenType 一般以otf为后缀,但也不能简单的根据文件名后缀区分二者,文件扩展名只是习惯,并不能完全说明内部格式,真正的区别还是要看字体表结构,比如 OpenType 有 GSUB、GPOS、GDEF 等扩展表。


下面,我们来真正解析一个字体文件,看里面有什么,可以通过如下命令行将字体解析成 XML。


# 对于ttf文件ttx NewYork.ttf# ttc文件是个字体集合,需要明确指明要提取哪个index的字体ttx -y 0 Times.ttc
复制代码

二、Font 文件解析

我们以 NewYork.ttf 文件为例,如下是 NewYork.ttf 中的表


2.1 GlyphOrder

<GlyphOrder>  <GlyphID id="0" name=".notdef"/>  <GlyphID id="1" name=".null"/>  <GlyphID id="2" name="nonmarkingreturn"/>  <GlyphID id="3" name="space"/>  <GlyphID id="4" name="A"/>  ...</GlyphOrder>
复制代码


GlyphOrder 定义 glyphID 与 glyphName 的映射。

2.2 head

Font Header,存储一些全局信息;关注几个值:


<head>  <unitsPerEm value="2048"/>  ...</head>
复制代码


1)unitsPerEm


字体表里的数值一般都很大(见后文),其单位并不是像素值,而是 em unit<unitsPerEm value="2048"/>表示2048 units = 1 em = 设计的字高,比如当字体在屏幕上以 16px 渲染时,1 em = 16px,其他数值可按比例换算

2.3 hhea

Horizontal Typesetting Header,横向排版信息,关注几个值


<hhea>  <!-- MacOS一般使用hhea里的ascent、descent;OS_2表里还有几个ascent、descent,一般在Windows或专业设计上使用 -->  <ascent value="1950"/>  <descent value="-494"/>  <lineGap value="0"/>  <advanceWidthMax value="2818"/>  <minLeftSideBearing value="-693"/>  <minRightSideBearing value="-693"/>  ...</hhea>
复制代码


1)ascent & descent


假设字体大小 16,unitsPerEm 如上为 2048,则按比例换算:ascent = 1950/2048 * 16 ≈ 15.2descent ≈ 494/2048 * 16 ≈ 3.8


需要注意,OS_2 表中也有 ascent、descent 的定义,这是因为不同平台会读取不同表中的 ascent、descent,比如 macOS、iOS 一般使用 hhea 中的值,Windows 一般使用 OS_2 表中的 usWinAscent、usWinDescent,专业排版软件(如 InDesign)一般用 OS_2 表中的 sTypoAscender、sTypoDescender。


Q:对于同一个 Font,ascent、descent 的值是固定的吗?


这个问题的答案需要加定语,对于同一个 Font,在同一个平台上,ascent、descent 是固定的。


Q:为什么 descent 值是负数?


可以理解成规范,TrueType/OpenType 的规范里,descent 是负数,表示基线(baseline)以下延伸的高度。

2.4 maxp

<maxp>  <numGlyphs value="1811"/>  ...</maxp>
复制代码


定义字体里 glyph 的数量,以及一些最大值参数。

2.5 OS_2

<OS_2>  <!-- 下标的大小和偏移 -->  <ySubscriptXSize value="650"/>  <ySubscriptYSize value="600"/>  <ySubscriptXOffset value="0"/>  <ySubscriptYOffset value="75"/>    <!-- 上标的大小和偏移 -->  <ySuperscriptXSize value="650"/>  <ySuperscriptYSize value="600"/>  <ySuperscriptXOffset value="0"/>  <ySuperscriptYOffset value="350"/>
<!-- 删除线的粗细和垂直位置 --> <yStrikeoutSize value="12"/> <yStrikeoutPosition value="620"/>
<!-- ulUnicodeRange表示字体支持的Unicode范围,用ulUnicodeRange1 … ulUnicodeRange4 这 4 个 32 位字段来表示,总共 128 个 bit,对应 128 个 Unicode Block,如果某 bit = 1,表示字体支持该区块中的至少一些字符, 映射表见:https://learn.microsoft.com/en-us/typography/opentype/spec/os2#ur --> <ulUnicodeRange1 value="10100001 00000000 00000010 11111111"/> <ulUnicodeRange2 value="00000010 00000000 00100000 01011110"/> <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/> <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
<!-- 专业排版(比如 InDesign)一般使用sTypoAscender、sTypoDescender --> <sTypoAscender value="1950"/> <sTypoDescender value="-494"/> <sTypoLineGap value="0"/>
<!-- Windows一般使用sTypoAscender、sTypoDescender --> <usWinAscent value="1950"/> <usWinDescent value="494"/> ...</OS_2>
复制代码


参见Apple文档,关注几个值:


1)ySubscriptXSize & ySubscriptYSize & ySubscriptXOffset & ySubscriptYOffset


下标的大小和偏移


2)ySuperscriptXSize & ySuperscriptYSize & ySuperscriptXOffset & ySuperscriptYOffset


上标的大小和偏移


3)yStrikeoutSize & yStrikeoutPosition


删除线的粗细和垂直位置


4)ulUnicodeRange1 & ulUnicodeRange2 & ulUnicodeRange3 & ulUnicodeRange4


ulUnicodeRange 表示该字体支持的 Unicode 范围,用 ulUnicodeRange1 … ulUnicodeRange4 这 4 个 32 位字段来表示,总共 128 个 bit,对应 128 个 Unicode Block,如果某 bit = 1,表示字体支持该区块中的至少一些字符,映射表见:https://learn.microsoft.com/en-us/typography/opentype/spec/os2#ur


Windows 系统通常用 ulUnicodeRange 来看一个字体是否支持某 Unicode;macOS/iOS 系统一般用 cmap 表(精确的字符映射),ulUnicodeRange 只作为辅助信息;浏览器排版一般直接查 cmap,但 ulUnicodeRange 有时也用于字体 fallback 策略。


5)sTypoAscender & sTypoDescender & usWinAscent & usWinDescent


如前文所述,不同系统会取不同的值作为 ascent、descent

2.6 hmtx

<hmtx>  <mtx name=".notdef" width="2048" lsb="199"/>  <mtx name=".null" width="0" lsb="0"/>  <mtx name="A" width="1244" lsb="-16"/>  ...</hmtx>
复制代码


Horizontal Metrics,记录每个 glyph 的 advance width 和 left side bearing。


简单理解排版引擎绘制字形的流程是:将字形放在当前点 + lsb 偏移位置进行绘制,画完后,将光标向右移动 advanceWidth,准备绘制下一个字形。

2.7 cmap

<cmap>  <tableVersion version="0"/>  <cmap_format_4 platformID="0" platEncID="3" language="0">    <!-- A的Unicode code point是0x41 -->    <map code="0x41" name="A"/><!-- LATIN CAPITAL LETTER A -->    <map code="0x42" name="B"/><!-- LATIN CAPITAL LETTER B -->    <map code="0x43" name="C"/><!-- LATIN CAPITAL LETTER C -->    <map code="0x44" name="D"/><!-- LATIN CAPITAL LETTER D -->    <map code="0x45" name="E"/><!-- LATIN CAPITAL LETTER E -->    <map code="0x46" name="F"/><!-- LATIN CAPITAL LETTER F -->    <map code="0x47" name="G"/><!-- LATIN CAPITAL LETTER G -->    <map code="0x48" name="H"/><!-- LATIN CAPITAL LETTER H -->    ...  </cmap_format_4>  ...</cmap>
复制代码


Character to Glyph Mapping,定义 Unicode code point → glyph ID 的映射,cmap 表中能精确的查到该 Font 支持哪些 Unicode。

2.8 glyf

<glyf>    <TTGlyph name="A" xMin="-16" yMin="0" xMax="1260" yMax="1444">    <contour>      <pt x="1086" y="213" on="1"/>      <pt x="1113" y="137" on="0"/>      <pt x="1161" y="50" on="0"/>      <pt x="1219" y="9" on="0"/>      <pt x="1260" y="1" on="1"/>      <pt x="1260" y="0" on="1"/>      <pt x="793" y="0" on="1"/>      <pt x="793" y="1" on="1"/>      <pt x="845" y="7" on="0"/>      <pt x="897" y="54" on="0"/>      <pt x="899" y="143" on="0"/>      <pt x="874" y="213" on="1"/>      <pt x="528" y="1200" on="1"/>      <pt x="528" y="1200" on="1"/>      <pt x="220" y="292" on="1"/>      <pt x="184" y="186" on="0"/>      <pt x="170" y="66" on="0"/>      <pt x="224" y="11" on="0"/>      <pt x="290" y="1" on="1"/>      <pt x="290" y="0" on="1"/>      <pt x="-16" y="0" on="1"/>      <pt x="-16" y="1" on="1"/>      <pt x="27" y="9" on="0"/>      <pt x="89" y="59" on="0"/>      <pt x="151" y="181" on="0"/>      <pt x="193" y="297" on="1"/>      <pt x="614" y="1444" on="1"/>      <pt x="648" y="1444" on="1"/>    </contour>    <contour>      <pt x="290" y="532" on="1"/>      <pt x="294" y="544" on="1"/>      <pt x="859" y="544" on="1"/>      <pt x="860" y="532" on="1"/>    </contour>    <instructions/>  </TTGlyph>  ...</glyf>
复制代码


Glyph Data,真正的字形轮廓(矢量点、轮廓、控制点);cmap 表负责把 Unicode 字符映射到 glyphID,而 glyf 表告诉渲染系统该 glyph 的具体形状。

2.9 name

<name>  <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">    © 2017-2024 Apple Inc. All rights reserved.  </namerecord>  <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">    .New York  </namerecord>  <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">    Regular  </namerecord>  <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">    .New York; 20.0d1e1; 2024-05-06  </namerecord>  <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">    .New York  </namerecord>  <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">    20.0d1e1  </namerecord>  <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">    .NewYork-Regular  </namerecord>  ...</name>
复制代码


name 表中定义的是字体名称、字体家族、PostScript Name、厂商信息等。


nameID 对应的含义如下:



这里需要重点介绍下 PostScript Name:PostScript Name 是字体在一个系统里的唯一标识,是单个字符串,不允许有空格,一般是 FamilyName-StyleName 形式,比如:.NewYork-RegularHelvetica-BoldNotoSansCJKsc-Regular等。


在 CoreText 的 API 里,一般都要求传 PostScript Name,比如:CTFontCreateWithName

2.10 GDEF

Glyph Definition Table,简单理解 GDEF 表就是是 GPOS / GSUB 的辅助表,比如 GPOS 和 GSUB 需要知道「哪些字形是 mark、哪些能连接、哪些有变体」等信息,这些元数据就是放在 GDEF 表里的。

2.11 GPOS

Glyph Positioning Table,控制字形的相对位置(如 kerning、上下标等),比如「A + V」之间要减少间距,或者音标放在元音正上方等。

2.12 GSUB

Glyph Substitution Table,控制字形替换(连字、、阿拉伯文变体、直角引号换弯引号等),比如f + i'quoteleft'等。

2.13 HVAR & MVAR & avar & fvar & gvar...

这几个表是用于转换可变字体的,可变字体不在本文范围内,不再详述。


更多精彩内容欢迎关注🌍公众号:非专业程序员 Ping

发布于: 刚刚阅读数: 2
用户头像

还未添加个人签名 2019-03-12 加入

还未添加个人简介

评论

发布
暂无评论
一文读懂Font文件_ios_非专业程序员Ping_InfoQ写作社区