字体里的隐藏开关
字体文件里藏着许多可由 CSS 控制的开关。报表数字列对不齐、tab 缩进的代码过宽、中英混排间距过紧、fi / fl 未连成连字 —— 这些问题无需更换字体、无需 JS,几个鲜为人知的 CSS 属性即可解决。
先用两页地基:讲清字体本身(character → cmap → glyph → outline:你存的是码位,字形是控制点描出的轮廓)、以及轮廓如何被光栅化 / 抗锯齿填到像素(subpixel vs grayscale,-webkit-font-smoothing),再分两条线:拨字体开关(OpenType features) —— 先用 font-variant-numeric 处理数字(等宽对齐 / 老式数字 / 斜杠零 / 分数);下沉到底层 font-feature-settings 看 4 字符 tag 与高层属性的关系;text-autospace 给 CJK 与西文自动插「四分之一空格」;tab-size 调 <pre> 里制表符宽度;再用 font-variant-caps 演示小型大写系列变体。字体的度量与尺寸 —— em-square / ascender / descender 怎么决定元素高度与 line-height: normal(font metrics),再用 text-box-trim 把这层多出来的行内空白裁掉(按钮标签 / 标题居中),以及 font-size-adjust 如何按 x-height 对齐 fallback、抑制布局偏移。末尾番外与加餐:字体反爬把码位↔字形的错位当锁;unicode-range 按字符把一个 family 切给不同字体;font-variant-east-asian / font-variant-alternates / font-stretch 三个较少使用的 variant,以及 font-synthesis —— 缺字面时浏览器伪造粗体 / 斜体的兜底开关。每页都能拨开关 / 拖滑块,实时观察同一段文字的变化。
取材自知乎「CSS 有哪些暖心却鲜为人知的属性」(灵祁)、iamvdo 的字体度量深潜、web.dev 的 font-size-adjust 与斗鱼字体反爬实战,以 MDN 的 font-variant-numeric / -caps / font-feature-settings / text-autospace / tab-size / font-size-adjust 文档与 OpenType feature 注册表校准。开关类样本用带齐全 OpenType 表的 Inter,度量类用 Canvas measureText() 实测,效果取决于字体是否含对应字形。
地基
字体到底是什么:从码位到轮廓
本系列各页都在「拨开关」「量高度」,但字体本身是什么?字体文件 = 一组字形轮廓 + 一张编码表。你存储的是码位('o'=U+006F),cmap 查成字形编号,字形编号指向一组控制点,浏览器照点描出贝塞尔轮廓填黑。demo 把一个小写 o 拆成可拖动的 on-curve / off-curve 控制点,拖动即可看到字形变形——这就是字体文件里真正存储的内容。是「字体度量」与「字体反爬」两页的前置。
字形如何变成像素:光栅化与抗锯齿
接「字体到底是什么」:轮廓画好后,浏览器要把它填到像素网格上——这一步叫光栅化,边缘的灰阶过渡叫抗锯齿。默认的 subpixel antialiasing(借像素内 RGB 三条子像素模拟更细的横向网格)在低 DPI 屏上更锐利,但在 Retina 上反而让字偏重、带彩边、和 Figma 不一致;-webkit-font-smoothing: antialiased / -moz-osx-font-smoothing: grayscale 切到 grayscale antialiasing 更轻更干净。放大的像素网格并排模拟两种方式(可切字形 / 网格精细度 / 深浅底),并实测一段真实文字的 on/off 差异。仅 macOS 生效(Windows 走不可覆盖的 ClearType)。
拨字体开关 · OpenType features
数字的对齐与字形:font-variant-numeric
做报表 / 价格列 / 倒计时时,默认比例宽度数字让每列对不齐——tabular-nums 把每个数字撑成等宽,列即对齐。再试 oldstyle-nums(有升降部的「老式数字」)、slashed-zero(给零划一道防混淆)、diagonal-fractions(1/2 变真分数)、ordinal(1st 上标)。拨动开关即可看到同一组数字实时变形。
OpenType 原始开关:font-feature-settings
font-variant-numeric 这类高层属性,底层都是 OpenType 的 4 字符 feature tag。font-feature-settings 直接拨动这些原始开关:连字 liga / dlig、小型大写 smcp、花体 swsh、风格集 ss01… ss20、上下标 sups / subs。逐个勾选查看字形变化,并讲清高层语义属性 vs 底层 raw tag 的取舍——能用 font-variant-* 时就不要用它。
中西文之间的自动间距:text-autospace
「今天 PM 2.5 是 36」——中文与拉丁字母 / 数字紧贴时显得局促,过去只能手动敲空格或借助 pangu.js。CSS Text Module Level 4 的 text-autospace 让浏览器在 CJK ↔ 非 CJK 边界自动插入「四分之一空格」。切换 normal / no-autospace 观察间距出现 / 消失(新属性,附兼容性提示)。
制表符到底多宽:tab-size
有人贴一大段用 Tab 缩进的代码,浏览器默认一个 tab 占 8 个空格宽,缩进宽得难以阅读。tab-size 一行即可解决:设为 2 或 4 便规整了。拖动滑块在 <pre> 里实时调整,同时看清它只对真正的制表符 U+0009 生效、且需要 white-space 保留 tab。
大写字母的变体:small-caps 系列
正文里夹一串大写缩写(OAUTH / HTML5)显得突兀——小型大写把它们降到约 x-height 高度、保留大写字形,既区分专名又不打断正文节奏。拨动 small-caps / all-small-caps / petite-caps / unicase / titling-caps,还能切换「真字形 vs 浏览器合成」,看 font-synthesis 如何反映字体的真实能力。
字体的度量与尺寸
字体度量:为什么 100px 不是 100px
同样 font-size: 100px,只换 font-family,渲染高度却各不相同。用 Canvas measureText() 实测,把 em-square / content-area / baseline / cap-height / x-height 全画在字上,看清 line-height: normal 怎么从字体度量算出——以及为什么 line-height: 1 常常导致相邻行重叠。
把多余的行内空白裁掉:text-box-trim 与 strut
承接「字体度量」一讲:content-area 常比 em 高、上下还不对称——给按钮标签 / 标题做垂直居中时文字总像「浮」着,padding 跨字体调不平。text-box-trim / text-box-edge 一行 CSS 把行盒裁到 cap / alphabetic 等度量线,文字盒紧贴字形;旧浏览器用 ::before 负 margin(basekick)做 fallback。切字体 / 切修法,叠加层实测 cap 顶 / baseline 到内壁的「多余空白」看它如何归零。
font-size-adjust:fallback 不再时大时小
web 字体未加载完时由系统字体顶替,但不同字体 x-height 差异较大 → 同 font-size 看起来却时大时小,引起布局偏移(CLS)。font-size-adjust 把字号锚到 x-height 比例。两栏同字号实时对比,把目标字体的 aspect(from-font)传给 fallback,观察它如何放大 / 缩小以贴合。
番外
收尾
加餐
unicode-range:按字符切字体
一个 font-family 名字背后可以是多张 @font-face 切片:unicode-range 声明每张切片负责的码位区间,浏览器逐字符挑字体——Latin 落 local("Georgia")、汉字落 local("PingFang SC"),一段混排文本各取所需,逐字符高亮看归属。再看 font-family 列表本身的 per-character fallback,以及 Google Fonts 把 CJK 字体拆成上百个 woff2 按需下载的真实用途。
variant 番外:east-asian / alternates / stretch
集中演示三个较少使用的 variant:font-variant-east-asian 在 jis78…jis04 / traditional / simplified 间切换汉字字形(葛 飴 辻 的年代差);@font-feature-values 给 ss01 / salt 起语义名、再用 font-variant-alternates 按名引用;font-stretch(font-width)拖动可变字体 Archivo 的 wdth 轴实时看宽窄。末尾简述 text-size-adjust。
浏览器伪造的粗体与斜体:font-synthesis
给文字设 bold / italic 时字体若缺对应字面,浏览器会算法伪造:把轮廓膨胀成 faux bold、把直立字形倾斜成 faux oblique。font-synthesis 统管这层兜底——weight / style / small-caps / position 四类合成的白名单。切「只有 Regular」与「有真 bold/italic」两种字体,对比真字面 / 合成 / 退回三态,并讲清为何图标字体必须 font-synthesis: none。与 font-variant-caps 页的小型大写合成同源。
加餐 · 轮廓深潜:从描摹到 GPU
轮廓从哪来:位图描摹成矢量
字形是控制点描出的轮廓——这些点从一张位图自动描摹(tracing)而来时,通用描摹器(potrace)只求轮廓相似,字体却要结构合理:控制点最少、节点钉在极值点让手柄轴对齐、直线保持笔直、拐点标记结构。demo 把同一个 o 交给笨拙描摹(随分辨率膨胀的楼梯)与结构化描摹(img2bez 风格),并演示为何结构对齐对可变字体插值至关重要。是「字体到底是什么」的延伸。
在 GPU 上直接画贝塞尔字形
轮廓上屏有两条路:预先栅格化成纹理图集(放大就糊、每字号重烤),或像 Slug 那样把贝塞尔曲线交给 GPU,在像素着色器里实时求每个像素的内 / 外与覆盖率(任意缩放都锐利)。demo 对照缩放下的两者,并用 Dynamic Dilation 演示为何要把包围多边形向外扩张才不漏掉边缘像素。是「字形如何变成像素」的 GPU 侧续篇。
应用
相关链接
📐 规范 / 文档
- CSS Fonts Module Level 4 drafts.csswg.org · CSSWG 规范本体:font-variant-numeric / -caps、font-feature-settings、font-synthesis、font-size-adjust、font-width(font-stretch 别名)、unicode-range 均在此定义。
- CSS Text Module Level 4 drafts.csswg.org · CSSWG text-autospace(§Automatic Contextual Spacing)与 tab-size(§Tab Character Size)的规范出处。
- CSS Inline Layout Module Level 3 drafts.csswg.org · CSSWG text-box-trim / text-box-edge 的规范本体:把行盒裁到 cap / alphabetic 等度量线。
- Can I use · font-variant-numeric caniuse.com 数字字形高层属性的实时浏览器支持矩阵。
- Can I use · font-size-adjust caniuse.com font-size-adjust 的实时浏览器支持矩阵(Firefox / Chrome / Safari 近版本已可用)。
- Can I use · text-box-trim caniuse.com text-box / text-box-trim / text-box-edge 的实时浏览器支持矩阵(Chromium 与 Safari 已支持,Firefox 未支持)。
- CSS 有哪些暖心却鲜为人知的属性? zhihu.com · 灵祁 本系列选题出处:tab-size、font-variant-numeric、font-feature-settings 等好用却偏门的属性。
- font-variant-numeric · MDN developer.mozilla.org 数字字形的高层语义属性:figure(lining/oldstyle)、spacing(tabular/proportional)、fraction、ordinal、slashed-zero。
- font-feature-settings · MDN developer.mozilla.org 底层 OpenType feature 开关,4 字符 tag + 0/1;以及为什么应优先用 font-variant-* 高层属性。
- text-autospace · MDN developer.mozilla.org CSS Text Module Level 4:CJK 与非 CJK 文本间自动间距;normal / no-autospace 取值与浏览器支持现状。
- tab-size · MDN developer.mozilla.org 制表符 U+0009 的渲染宽度(整数 = 空格数,或长度值);需 white-space 保留 tab 才生效。
- font-variant-caps · MDN developer.mozilla.org 小型大写一家子:small-caps / all-small-caps / petite-caps / unicase / titling-caps 与等价 OpenType tag;配合 font-synthesis 看真字形 vs 浏览器合成。
- font-synthesis · MDN developer.mozilla.org 缺字面时浏览器是否合成伪粗体 / 伪斜体 / 小型大写 / 上下标;简写白名单 + weight / style / small-caps / position 四个 longhand。
- Correcting Subpixel on Web santos.fyi · Marcos Santos 光栅化 / 抗锯齿页蓝本:为什么字在 web 上比 Figma 渲得重、subpixel 当年为何存在、Retina 上为何反成累赘,以及那一行 -webkit-font-smoothing 修法与细字重小字号注意点。
- Deep dive CSS: font metrics, line-height and vertical-align iamvdo.me · Vincent De Oliveira 字体度量页蓝本:em-square / ascender / descender / line-gap 如何决定 content-area 与 line-height:normal。(vertical-align / IFC 的后半截在姊妹系列 inline-block。)
- font-size-adjust is now in Chrome web.dev font-size-adjust 用 x-height 对齐 fallback 字体、from-font 关键字与两值语法,减少字体交换造成的 CLS。
- Never use px for font sizes joshcollinsworth.com 正文字号该用 rem 而非 px:px 无视用户的浏览器字号偏好、损害可访问性;rem 跟随根字号缩放。
- 斗鱼爬虫与字体反爬 cjdev.me 番外页蓝本:用私有字体把码位↔字形错排实现反爬,以及如何解析 cmap / 字形轮廓还原真实数据。
- OpenType Layout feature tag registry learn.microsoft.com 全部 OpenType feature 4 字符 tag 的官方注册表:liga / dlig / smcp / onum / tnum / frac / ss01… 各自含义。
- What is a Glyph in Typography? fontfabric.com 字符 character vs 字形 glyph 的基础概念:一个字符可对应多个字形,本系列的 feature 开关就是在这些字形间切换。另见应用页里 font 作为「数据类型」的交互拆解。
- Vectro Type Foundry vectrotype.com 专长 variable fonts 与 OpenType features 的字体工作室:WHOA 用四个轴把轮廓拉进三维空间,Kablammo (Google Fonts) 的 Morph 轴在四套 alternates 间变形 —— 本系列所讲 feature/axis 的极致应用示例。