Fonts

#默认字体

每个应用都从主题自带的一套 UI 字体和等宽字体开始:

用途字体字号
UI 文本.SystemUIFont16px
代码/等宽macOS:Menlo,Windows:Consolas,Linux:DejaVu Sans Mono13px

编辑器使用 mono_font_familymono_font_size 绘制代码,详见 Editor

应用主题时会对照系统已安装的字体检查这两个默认值:等宽默认字体缺失时换成已安装的备选;当 .SystemUIFont 解析到的是 GPUI 回退栈里的某个字体而不是系统字体本身(Linux 桌面通常没有 GPUI 映射到的那个字体),主题会直接记下该字体名,让文本查找一直命中缓存。你自己设置的字体保持不变。

#系统字体

桌面应用可以直接按名称使用操作系统已安装的任意字体,无需打包、无需配置。GPUI 会实时向系统字库解析(macOS 用 CoreText,Windows 用 DirectWrite,Linux 用 fontconfig)。

div().font_family("Segoe UI")

Editor::new(&editor).font_family("JetBrains Mono")

各平台常见字体举例:

  • macOS:SF ProHelveticaArialTimes New RomanMenloMonaco
  • Windows:Segoe UIArialConsolasCourier New
  • Linux:Noto SansDejaVu SansLiberation SansDejaVu Sans Mono

如果名称与已安装字体不匹配,GPUI 会静默回退——请在每个目标平台上确认准确的 family 名称。

#通过 Theme 修改字体

Theme 全局量上设置应用级字体,然后同步到底层:

Theme::global_mut(cx).font_family = "Inter".into();
Theme::global_mut(cx).mono_font_family = "JetBrains Mono".into();
Theme::global_mut(cx).font_size = px(18.);
Theme::sync_base(cx);
window.refresh();

font_size 同时是应用缩放控制——Root 会调用 window.set_rem_size(cx.theme().font_size),因此基于 rem 的间距会跟随缩放。详见编码指南

#元素级覆盖

任何元素都可以在不改动主题的情况下覆盖字体:

div()
    .font_family("JetBrains Mono")
    .text_size(px(15.))
    .font_weight(FontWeight::BOLD)

这些就是普通的 Styled 方法,与样式链的其余部分组合使用。

#打包自定义字体

用户系统中没有的字体必须打包,并在首帧之前注册到文本系统:

cx.text_system()
    .add_fonts(vec![Cow::Borrowed(
        include_bytes!("../fonts/MyFont-Regular.ttf").as_slice(),
    )])
    .expect("Failed to load fonts");

之后照常用 family 名称引用:

Theme::global_mut(cx).font_family = "MyFont".into();
Theme::sync_base(cx);

Web 版画廊就是这样打包 InterJetBrains MonoNoto Sans SCIBM Plex Sans 的,参见 crates/story-web/src/lib.rs

#主题 JSON 配置

字体与字号也可以来自主题文件:

{
    "font.family": "Inter",
    "font.size": 16,
    "mono_font.family": "JetBrains Mono",
    "mono_font.size": 13
}

ThemeRegistry 加载:

ThemeRegistry::watch_dir(PathBuf::from("./themes"), cx, move |cx| {
    if let Some(theme) = ThemeRegistry::global(cx).themes().get(&theme_name).cloned() {
        Theme::global_mut(cx).apply_config(&theme);
    }
});

完整配置说明参见 Theme

#WebAssembly 说明

浏览器不会向 WASM 应用暴露系统字体。在 gpui-kit.com/gallery/ 运行的 story-web 画廊必须打包它用到的每一种字体,并在 Theme::change 之后重新 声明,否则文本系统会 panic。桌面应用完全不需要这一步。

打包字体画不出来的文字仍然可以交给浏览器绘制。Web 平台会用 Canvas 2D 和访问者本机的字体渲染 emoji,应用不必再打包 emoji 字体。回退策略在构造 平台时选定,之后不能更改:

CanvasFontFallback由浏览器绘制的内容
Emoji(默认)emoji,包括肤色、旗帜、键帽和 ZWJ 序列
EmojiAndCjkemoji,外加横排的汉字、假名和现代谚文
Disabled不回退,只使用打包字体

gpui_kit::application()gpui_kit::platform::single_threaded_web() 沿用默认策略。要放宽范围,就自己构造平台:

use gpui_kit::web::{CanvasFontFallback, WebBackendPreference, WebPlatform};

let platform = Rc::new(WebPlatform::new_with_backend_and_font_fallback(
    false,
    WebBackendPreference::Auto,
    CanvasFontFallback::EmojiAndCjk,
));
let http_client = Arc::new(platform.fetch_http_client());
let app = Application::with_platform(platform).with_http_client(http_client);

只要打包字体里有对应字形,就仍然优先使用打包字体。回退是逐个字素独立绘制的, 所以这样渲染的 CJK 文字以可读为先,不保证精确的间距和字体特性,外观也取决于 访问者机器上安装的字体。画廊选择了 EmojiAndCjk:它打包的字体只包含 故事本身用到的字形,访问者在输入框里键入的其他文字否则都会显示成方块。