GeekRicardo
0504e3918b
fix(terminal): 自定义字体解析失败时兜底到等宽,避免掉进比例字体毁掉网格
在设置页「侧边卡片」→ 终端卡片填入自定义「终端字体」后,若该族名在浏览器里
解析不到,终端不是退化成"没有想要的字体",而是整个网格崩掉:字符间距被撑开、
字形变成比例字形、Nerd Font 图标全部消失。
用户实测(macOS + Chrome,填 `Maple Mono NF CN`):
| 「终端字体」的值 | DevTools Rendered Fonts | 结果 |
| --- | --- | --- |
| `Maple Mono NF CN` | **PingFang SC** | cell 12.09px @ 13px 字号(0.93em),网格崩坏 |
| `Maple Mono NF CN, monospace` | **Menlo** | 间距恢复正常 |
`resolveTerminalFont` 对用户值(以及主题 `--ds-font-family-code` token 值)
原样返回,只有内置的 `DEFAULT_TERMINAL_FONT_FAMILY` 末尾带 `monospace`。
字体栈若不以通用族收尾,浏览器解析不到任何具名字体时会落到**标准字体**
(standard font,macOS 中文环境是 PingFang SC)——它是**比例字体**。
xterm 用它的推进宽度量 cell(0.93em,等宽字体应为 0.6em),网格随即失真。
这条路径比想象中容易踩:终端里跑的 shell 在 host 上,字体却由**浏览器所在机器**
提供,两者不同机时(远程/容器化的 DSH)用户按 host 侧装的字体填名字必然解析失败。
新增 `withMonospaceFallback()`,在 `resolveTerminalFont` 出口保证字体栈以通用族收尾。
- **不做字体嗅探**:终端字体设计文档 §2 把"字体合法性嗅探"列为非目标;本改动
不检测具名字体是否存在,只补一个通用族尾项。
- **已有通用族则原样保留**:包括用户主动写的非等宽通用族(`sans-serif` 等)
——那是用户的选择,不该被覆盖。
- **主题 token 值同样兜底**:它掉进比例字体的后果与用户值完全一样。
- **CSS 全局关键字单独处理**:`inherit` / `initial` / `unset` / `revert` /
`revert-layer` 追加逗号后是非法 CSS,会被 CSSOM 整条丢弃,反而让 xterm
一个字体都拿不到;这类值直接回落内置栈。
- 字体栈按**顶层逗号**切分并感知引号、转义与括号,避免拆坏 `"Foo, Bar"`
与 `var(--x, y)`;通用族匹配大小写不敏感且只认未加引号的项。
- `tests/terminal-font.spec.ts` 4 → 13 例(兜底追加、已有通用族免动、大小写与
引号边界、顶层逗号切分、幂等性、CSS 全局关键字、空值)
- `pnpm typecheck` 零错误
- 全量 `pnpm test`:941 passed(改动前基线 932 passed,+9 为本次新增);
另有 31 例失败在本分支基线(干净 main)上同样失败,与本改动无关
互补、不重叠。#190 解决"字体解析成功、但栈里没显式列 Nerd Font 导致补充平面
PUA 图标缺字";本 PR 解决"字体压根没解析到、栈又没有通用族尾项导致落进比例
字体"。两者都落在 `terminal-font.ts` 的同一出口,合并时按先后 rebase 即可。
2026-08-29 15:49:35 +08:00
..
2026-08-28 21:42:39 +08:00
2026-08-18 20:29:14 +08:00
2026-08-16 22:32:17 +08:00
2026-08-27 21:44:04 +08:00
2026-08-10 21:43:58 +08:00
2026-08-26 18:33:52 +08:00
2026-08-15 00:28:59 +08:00
2026-08-24 16:41:02 +08:00
2026-08-21 23:49:13 +08:00
2026-08-12 18:27:28 +08:00
2026-08-20 02:09:36 +08:00
2026-08-11 19:28:48 +08:00
2026-08-11 19:28:48 +08:00
2026-08-24 17:54:33 +08:00
2026-08-18 01:22:28 +08:00
2026-08-18 01:22:28 +08:00
2026-08-20 02:09:36 +08:00
2026-08-14 21:28:41 +08:00
2026-08-21 01:28:17 +08:00
2026-08-22 23:13:35 +08:00
2026-08-28 04:20:36 +08:00
2026-08-24 18:40:04 +08:00
2026-08-11 11:16:36 +08:00
2026-08-24 18:40:04 +08:00
2026-08-24 18:40:04 +08:00
2026-08-24 18:40:04 +08:00
2026-08-29 01:52:55 +08:00
2026-08-18 18:05:01 +08:00
2026-08-22 22:50:13 +08:00
2026-08-24 17:54:33 +08:00
2026-08-23 10:28:05 +08:00
2026-08-23 02:50:37 +08:00
2026-08-16 23:35:57 +08:00
2026-08-16 23:35:57 +08:00
2026-08-22 23:11:27 +08:00
2026-08-21 00:45:24 +08:00
2026-08-24 14:18:18 +08:00
2026-08-25 11:04:43 +08:00
2026-08-16 23:35:57 +08:00
2026-08-12 01:16:20 +08:00
2026-08-24 17:54:33 +08:00
2026-08-13 09:06:55 +08:00
2026-08-21 23:41:33 +08:00
2026-08-21 23:41:33 +08:00
2026-08-14 22:30:34 +08:00
2026-08-15 15:05:06 +08:00
2026-08-23 20:58:33 +08:00
2026-08-20 02:09:36 +08:00
2026-08-13 10:26:57 +08:00
2026-08-24 12:05:14 +08:00
2026-08-24 12:05:14 +08:00
2026-08-23 22:02:47 +08:00
2026-08-24 18:40:04 +08:00
2026-08-23 22:02:47 +08:00
2026-08-24 12:05:14 +08:00
2026-08-18 01:22:28 +08:00
2026-08-23 01:50:07 +08:00
2026-08-18 01:22:28 +08:00
2026-08-22 23:19:17 +08:00
2026-08-14 14:53:03 +08:00
2026-08-22 23:19:17 +08:00
2026-08-22 23:19:17 +08:00
2026-08-24 01:39:53 +08:00
2026-08-11 11:22:28 +08:00
2026-08-16 23:35:57 +08:00
2026-08-26 21:40:32 +08:00
2026-08-16 23:35:57 +08:00
2026-08-14 23:04:25 +08:00
2026-08-24 17:54:33 +08:00
2026-08-24 16:46:02 +08:00
2026-08-16 23:06:44 +08:00
2026-08-21 00:48:16 +08:00
2026-08-18 19:56:04 +08:00
2026-08-24 18:40:04 +08:00
2026-08-13 01:39:59 +08:00
2026-08-23 23:44:18 +08:00
2026-08-16 16:24:28 +08:00
2026-08-21 01:28:17 +08:00
2026-08-20 02:09:36 +08:00
2026-08-24 01:19:43 +08:00
2026-08-24 16:41:02 +08:00
2026-08-22 00:15:06 +08:00
2026-08-22 15:34:05 +08:00
2026-08-22 15:34:05 +08:00
2026-08-21 15:41:14 +08:00
2026-08-22 15:34:05 +08:00
2026-08-27 21:44:04 +08:00
2026-08-26 18:27:32 +08:00
2026-08-21 18:15:49 +08:00
2026-08-22 15:34:05 +08:00
2026-08-21 18:15:49 +08:00
2026-08-13 09:06:55 +08:00
2026-08-21 18:15:49 +08:00
2026-08-21 18:15:49 +08:00
2026-08-26 18:42:34 +08:00
2026-08-23 23:44:18 +08:00
2026-08-23 23:44:18 +08:00
2026-08-26 21:40:32 +08:00
2026-08-17 00:06:20 +08:00
2026-08-29 15:49:35 +08:00
2026-08-27 21:44:32 +08:00
2026-08-16 16:37:11 +08:00
2026-08-21 01:28:17 +08:00
2026-08-27 21:44:04 +08:00
2026-08-18 23:23:17 +08:00
2026-08-24 01:36:29 +08:00
2026-08-20 18:28:57 +08:00
2026-08-21 01:28:17 +08:00