Skip to content

支持回滚字体(fallback font)设置#284

Open
Praying wants to merge 4 commits into
zerx-lab:mainfrom
Praying:main
Open

支持回滚字体(fallback font)设置#284
Praying wants to merge 4 commits into
zerx-lab:mainfrom
Praying:main

Conversation

@Praying

@Praying Praying commented Jun 26, 2026

Copy link
Copy Markdown

关联Issue

#283

Describe the solution you'd like

OS: EndeavourOS x86_64
DE: KDE Plasma 6.7.0

  • 设置自定义字体后(例如我设置了Monaco Nerd Font Mono) 后,由于该字体缺少中文支持,在开启Claude Code和Codex等TUI软件后,其中文字体渲染异常,参考下图:
Image Image
  • 解决方案之一是就将字体换成 Noto Sans Mono CJK SC ,但该字体下英文渲染又不太美观
Image Image Image
  • 解决方案:支持回滚字体设置,可以实现终端字体使用设置的Monaco Nerd Font Mono渲染,而无法支持的中文字体则可以回滚到Noto Sans Mono CJK SC进行渲染,从而做到两者兼顾。(JetBrains系列IDE也有类似设置选项)
image image

@zerx-lab zerx-lab left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(详见 inline comments 和下方总结 comment)

— 由 Claude Routine 自动生成;如需复评请 @ 维护者人工触发。


Generated by Claude Code


self.line.push(chr);
self.character_index_to_cell_map.push(column);
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

性能问题self.fallback_font_family? 的提前返回检查发生在 select_font + glyph_for_char 之后(第 2734 行)。这意味着即使用户没有配置回退字体(fallback_font_family == None),每个字符仍然会触发一次 font_cache.select_font() 和一次 font_cache.glyph_for_char()。在 ligature 渲染路径中,append_character 对每行每列字符都会调用此函数,10,000 字符/帧的终端窗口就是 20,000 次额外哈希查找/帧,即便回退字体功能完全未启用。

建议将 ? 守卫前置:

fn fallback_font_family_for_char(&self, chr: char) -> Option<FamilyId> {
    let fallback_font_family = self.fallback_font_family?; // 未配置时立即返回
    let primary_font = self.font_cache.select_font(
        self.current_style.font_family,
        self.current_style.properties,
    );
    if self.font_cache.glyph_for_char(primary_font, chr, false).is_some() {
        return None;
    }
    let fallback_font = self.font_cache.select_font(fallback_font_family, self.current_style.properties);
    self.font_cache.glyph_for_char(fallback_font, chr, false).map(|_| fallback_font_family)
}

这样未配置回退字体时,整个函数仅一次 Option 解包即可返回,不引入任何额外开销。


Generated by Claude Code

}

let mut items = self
.available_families

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

潜在的过滤 bugname != &MonospaceFontName::default_value() 过滤的是主字体的默认值("Hack"),而非回退字体的默认值("")。这会导致 "Hack" 从回退字体下拉列表中消失——用户如果主字体用 "Monaco Nerd Font Mono"、回退字体想选 "Hack" 来处理 CJK 以外的补充字符,就无法选择。

回退字体下拉列表应当能显示所有已安装字体(包括 "Hack");如果需要排除某项,应过滤 MonospaceFallbackFontName::default_value()(即空字符串),而空字符串本已通过 default_fallback_font_item() 在最顶部作为"系统默认"选项单独呈现,所以实际上不需要在 filter_map 里再额外排除任何字体。


Generated by Claude Code

.glyph_cache
.entry((char, font_id))
.or_insert_with(|| font_cache.glyph_for_char(font_id, char, true))
*self.glyph_cache.entry((char, font_id)).or_insert_with(|| {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question:缓存键是 (char, font_id)(主字体的 FontId),但当主字体没有该字形、回退字体有时,缓存值存储的是来自回退字体的 (GlyphId, FontId)。如果用户在设置中更换回退字体,CellGlyphCache 中针对 CJK 字符的条目仍指向旧回退字体的 FontId,可能导致渲染错误(字形 ID 来自已不再使用的字体)。

set_terminal_fallback_font_family 会调用 ctx.invalidate_all_views(),这应当触发终端视图重绘并重建 CellGlyphCache。请确认 CellGlyphCache 的生命周期是每次 render call 新建,还是作为 view 的持久字段——如果是后者,需要在 TerminalFallbackFontFamilyChanged 事件处理中显式清空缓存,否则回退字体切换后的第一帧会渲染错误的字形。


Generated by Claude Code

Copy link
Copy Markdown
Owner

Review 总结

整体判断:Request Changes

功能方向正确,实现覆盖面广(settings 持久化、渲染层非 ligature / ligature 双路径、事件通知、测试),但有一个性能回归和一个功能 bug 需要处理。


阻塞问题

1. fallback_font_family_for_char? 守卫位置错误,造成性能回归grid_renderer.rs

self.fallback_font_family? 的提前返回在 select_font + glyph_for_char 两次调用之后。这意味着即使用户未配置回退字体,ligature 渲染路径中对每个字符仍会触发两次额外的哈希查找。修复只需将 let fallback_font_family = self.fallback_font_family?; 移至函数第一行(见 inline comment 内的示例代码)。

2. 回退字体下拉列表错误过滤了 "Hack"appearance_page.rs

let include_in_dropdown = name != &MonospaceFontName::default_value() 使用的是主字体默认值 "Hack" 作为排除条件,导致 "Hack" 不会出现在回退字体列表中。用户完全可能想用 "Hack" 作为 CJK 字体的回退。回退列表不应复用主字体的过滤规则;default_fallback_font_item()("系统默认")已单独呈现,filter_map 里不需要额外排除任何字体名。


建议(不阻塞)

Question — CellGlyphCache 缓存键与回退字体切换时的有效性cell_glyph_cache.rs

缓存键为 (char, primary_font_id),但当主字体缺少字形、回退字体补足时,缓存值存储的是来自回退字体的 (GlyphId, FontId)。若 CellGlyphCache 是 view 的持久字段(而非每帧新建),切换回退字体后第一帧可能用旧回退字体的 FontId 渲染字形。请确认 invalidate_all_views() 是否能触发 CellGlyphCache 重建;如不能,需要在 TerminalFallbackFontFamilyChanged 处理中显式 clear()


看起来不错的地方

  • get_or_load_optional_font_family 对空字符串的处理简洁清晰,正确地将"未配置"映射为 None
  • fonts.rs 中对"已缓存为 None + include_fallback_fonts = true"时绕过缓存重新搜索的修改,逻辑完整,解决了用户配置回退字体后旧缓存阻断查找的根因。
  • 非 ligature 路径(render_cell_glyph)和 ligature 路径(AttributedStringBuilder)都做了覆盖,没有半途而废。
  • 三个 i18n 文件(en / zh-CN / ja)同步更新,无遗漏。
  • 新增单元测试覆盖 terminal_fallback_font_family 的 get/set 行为。

— 由 Claude Routine 自动生成;如需复评请 @ 维护者人工触发。


Generated by Claude Code

@Praying

Praying commented Jun 26, 2026

Copy link
Copy Markdown
Author

修改后效果
image

image

@Praying Praying requested a review from zerx-lab June 26, 2026 03:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants