Rust egui 默认字体 crateepaint_default_fonts:CHANGELOG 演进史与内嵌字体实现解析
【免费下载链接】eguiegui: an easy-to-use immediate mode GUI in Rust that runs on both web and native项目地址: https://gitcode.com/GitHub_Trending/eg/egui
epaint_default_fonts是 egui 生态中负责承载默认字体的基础 crate,它把 Hack、Ubuntu-Light 等字体以字节数组的形式静态内嵌进二进制,供epaint文本渲染层直接消费。本文以该 crate 的 CHANGELOG 为时间主线,结合 lib.rs、font_definitions.rs、special_emojis.rs 等源码,完整梳理这个 crate 的由来、版本演变、feature 配置与字体装配原理,帮助读者理解 egui 默认字体体系,并掌握按需裁剪或替换字体的方法。
一、这个 crate 是什么:面向epaint的内嵌字体库
按 README 与 lib.rs 的说明,epaint_default_fonts是一个"包含内建字体、以字节形式内嵌"的库,并不建议作为独立库使用,而是经由epaintcrate 间接消费。在仓库根 Cargo.toml 中它以 workspace 依赖的形式声明:
epaint_default_fonts = { version = "0.36.2", path = "crates/epaint_default_fonts" }而其自身定义在 crates/epaint_default_fonts/Cargo.toml,描述为 "Default fonts for use in epaint / egui",许可证为(MIT OR Apache-2.0) AND OFL-1.1 AND Ubuntu-font-1.0—— 后半部分正是字体文件自带的 OFL(SIL Open Font License)与 UFL(Ubuntu Font License)。
crate 通过include_bytes!将fonts/目录下的.ttf文件编译进二进制,对外暴露 5 个静态字节切片常量:
| 常量 | 对应字体文件 | 依赖 feature | 说明 |
|---|---|---|---|
HACK_REGULAR | fonts/Hack-Regular.ttf | 无条件 | 面向源代码的等宽字体,x-height 大、字怀宽、低对比度,在 8–14px 常见代码字号下可读性佳 |
UBUNTU_LIGHT | fonts/Ubuntu-Light.ttf | 无条件 | Ubuntu 品牌字体,现代风格,作为比例字体主体 |
EGUI_ICONS | fonts/egui-icons.ttf | 无条件 | 仅含 5 个私有使用区图标字形(约 3.5 kB),见 egui-icons.txt |
NOTO_EMOJI_REGULAR | fonts/NotoEmoji-Regular.ttf | monochrome_emoji_fonts | Noto 家族黑白 emoji,匹配 Google 的 emoji 设计 |
EMOJI_ICON | fonts/emoji-icon-font.ttf | monochrome_emoji_fonts | 实验性图标字体,基于标准化 UNICODE 平面设计,实心字形便于改色 |
前三个字体无条件内嵌,保证 egui 在任何平台上都能渲染基础文本与特殊图标;后两个仅在选择monochrome_emoji_fontsfeature 时加入,以避免在不需要时白白增加二进制体积。
二、从 CHANGELOG 看 crate 的演进时间线
该 crate 的 CHANGELOG 记录了从 0.28.1 到 0.36.2 共十余个版本。虽然绝大多数版本标注为 "Nothing new",但其中有三个里程碑式的条目,构成了这个 crate 的完整故事。
2.1 0.28.1(2024-07-05):默认字体独立成 crate
CHANGELOG 中最早、也最重要的条目是0.28.1:将默认字体移动到新的 crateepaint_default_fonts(PR #4853)。此前这些字体字节直接内嵌在epaint内部,随epaint一起分发;独立之后,字体资源与渲染逻辑解耦,epaint通过可选依赖按需启用,其他需要复用同一套字体的 crate 也可以直接依赖。这解释了为什么该 crate 从 0.28.1 才开始出现——它不是新项目,而是从epaint中拆出的既有资源。
2.2 0.31.0(2025-02-04):许可证规范化
0.31.0 条目为"更新egui_default_fonts许可证(PR #5361)"。这一步对应 Cargo.toml 中的许可证声明:
license = "(MIT OR Apache-2.0) AND OFL-1.1 AND Ubuntu-font-1.0" # OFL and UFL are from the font files themselves.其中 OFL-1.1 与 Ubuntu-font-1.0 分别来自字体文件本身(Hack/Noto/emoji 系列采用 SIL Open Font License,Ubuntu-Light 采用 Ubuntu Font License),发行时需要在fonts/目录下随附OFL.txt与UFL.txt授权文本——该目录中的 OFL.txt 与 UFL.txt 正是这一要求的落实。
2.3 0.34.0(2026-03-26):修复 emoji 图标字体
0.34.0 是最近一次功能性变更:"修复 emoji icon font(PR #7940)"。emoji 图标字体在 egui 中承担特殊图标与装饰性 emoji 的渲染,它的修复直接影响到special_emojis(Android/Apple/GitHub/Windows 商标与git字样)的显示正确性。
2.4 大量 "Nothing new" 版本的含义
从 0.32.0 到 0.36.2 之间的大量版本(0.32.1、0.33.0、0.34.1、0.35.0、0.36.0 等)均标注 "Nothing new"。这说明该 crate 已经高度稳定——它不随 egui 主版本每次都产生代码变更,但仍然会跟随 workspace 版本号统一发布、统一打 tag,以保持版本号与整个 egui 工作区一致。这种"版本号跟随、变更单独记录"的发布策略,保证了epaint_default_fonts 0.36.2一定与epaint 0.36.2等 crate 同时发布、互相兼容。
2.5 CHANGELOG 的生成机制
CHANGELOG 头部提到"自上次发布以来的变更可通过运行scripts/generate_changelog.py脚本生成"。该脚本位于 scripts/generate_changelog.py,通过ghCLI 拉取 PR 的标题、作者与 label,汇总后供人工粘贴进 CHANGELOG,再配合人工编辑。这说明仓库内的各 crate CHANGELOG 是"脚本辅助 + 人工整理"的产物,而非完全自动生成。
三、默认字体的实际装配:FontDefinitions::default()
crate 提供的字节切片最终要进入 egui 的文本渲染体系。装配发生在 crates/epaint/src/text/font_definitions.rs 的FontDefinitions::default()中(在default_fontsfeature 开启时生效):
"Hack"由HACK_REGULAR构造,绑定到FontFamily::Monospace;"Ubuntu-Light"由UBUNTU_LIGHT构造,绑定到FontFamily::Proportional,同时作为等宽族的回退字体(用于覆盖 Hack 缺失的√等字形);"egui-icons"由EGUI_ICONS构造,无条件注册,并通过FontTweak { scale: 0.90 }缩小 10% 以与正文协调;- 启用
monochrome_emoji_fonts时追加"NotoEmoji-Regular"(scale 0.81)与"emoji-icon-font"(scale 0.90)。
关键设计在于回退链(fallback chain):每个字族的末尾都会拼接兜底字体列表——
let fallback_fonts: &[&str] = if cfg!(feature = "monochrome_emoji_fonts") { &["egui-icons", "NotoEmoji-Regular", "emoji-icon-font"] } else { &["egui-icons"] };即egui-icons永远作为最后兜底,保证特殊图标在任何配置下都能渲染;而开启单色 emoji 后,NotoEmoji 与 emoji-icon-font 会覆盖更多平台没有的 emoji 字形。源码注释明确说明了这一设计意图:"在覆盖文本脚本的字体之后,作为最后的兜底"。配套的builtin_font_names()(font_definitions.rs)则按 feature 返回全部内建字体名列表,便于上层 UI(如字体书、调试面板)枚举。
此外,font_definitions.rs 中FontProvider for FontDefinitions的实现保证:FontDefinitions永远是第一个被询问的字体提供者,且从不按需发现字体——配置里写了什么就渲染什么,从而"同一文本在任何机器上看起来都一样"。
四、feature 开关与二进制体积权衡
epaint_default_fonts自身只定义一个 featuremonochrome_emoji_fonts(默认关闭),而epaint侧在 crates/epaint/Cargo.toml 通过两级 feature 组合控制:
default_fonts = ["epaint_default_fonts"] monochrome_emoji_fonts = ["default_fonts", "epaint_default_fonts/monochrome_emoji_fonts"]default_fonts(默认开启):epaint使用include_bytes!内嵌 Hack、Ubuntu-Light、egui-icons 三套字体;如果开发者打算完全自定义字体,可以显式关闭它;monochrome_emoji_fonts:额外内嵌NotoEmoji-Regular.ttf与emoji-icon-font.ttf,官方注释明确指出这两套字体会给二进制增加约 1 MB,仅在"希望所有平台都显示同一套(单色)emoji,而不是使用平台自带的彩色 emoji"时才需要开启。
由于彩色 emoji 由平台字体渲染(对应color_fonts等机制),默认配置下 egui 的 emoji 显示交给系统字体,这正是体积控制的核心:一个 3.5 kB 的egui-icons.ttf解决平台字体完全没有的私有区图标,把需要额外付费体积的完整 emoji 字体留给用户按需选购。
五、特殊 emoji:私有使用区图标的内嵌方案
egui 需要显示一些 Unicode 标准之外的符号——Android、Apple、GitHub、Windows 的商标 logo,以及单词git。这些字形位于私有使用区(Private Use Area),任何平台字体都不会包含它们,因此必须自己内嵌。定义见 special_emojis.rs:
| 常量 | 码点 | 说明 |
|---|---|---|
OS_LINUX | 🐧(U+1F427) | Tux 企鹅,普通 emoji,绝大多数 emoji 字体已覆盖 |
OS_WINDOWS | \u{E61F} | Windows 标志 |
OS_ANDROID | \u{E618} | Android 标志 |
OS_APPLE | \u{F8FF} | Apple 标志 |
GITHUB | \u{E624} | GitHub 标志 |
GIT | \u{E625} | 单词git |
egui-icons.ttf就是从完整的emoji-icon-font.ttf中裁剪出这 5 个字形(U+E618、U+E61F、U+E624、U+E625、U+F8FF)的子集,裁剪与再生成命令记录在 egui-icons.txt 中:
pip install fonttools python3 -m fontTools.subset emoji-icon-font.ttf \ --unicodes=E618,E61F,E624,E625,F8FF \ --no-hinting --desubroutinize \ --output-file=egui-icons.ttf因此即使完全关闭monochrome_emoji_fonts,这套图标仍然无条件内嵌。测试用例 fonts.rs(special_emojis_come_from_the_bundled_icon_font)验证了这一点:对GIT、GITHUB、OS_ANDROID、OS_APPLE、OS_WINDOWS逐一断言它们必须由名为egui-icons的字体提供,否则测试失败。而 fonts.rs 中正是通过epaint_default_fonts::special_emojis引入这些常量,印证了该模块在渲染管线中的实际使用。
六、开发者视角:查看、替换与裁剪默认字体
结合以上源码事实,可总结出几条实用的操作路径:
- 枚举内建字体:直接调用
FontDefinitions::builtin_font_names(),开启/关闭monochrome_emoji_fonts时返回的列表分别为 5 项与 3 项(见 font_definitions.rs); - 完全自定义字体:关闭
epaint的default_fontsfeature,此时FontDefinitions::default()等价于FontDefinitions::empty()(font_definitions.rs),再从零构造自己的font_data与families; - 在默认字体基础上追加:保留
default_fonts,向FontDefinitions::font_data插入新字体,并把字体名追加到对应字族的回退链中;egui-icons建议保留在链尾作兜底; - 体积敏感场景:默认仅约 3.5 kB 的图标子集会被无条件带入,完整单色 emoji 字体(约 1 MB)务必只在跨平台 emoji 一致性成为硬需求时开启;
- 交互式体验:egui 在线演示中的 Font Book 可以浏览当前字体集合里全部 emoji,方便验证字体裁剪效果。
如果希望查看独立示例,仓库的 examples/custom_font 演示了如何在 egui 应用中加载自定义字体数据(其配套字体位于 examples/font_variations/data,展示可变字体的使用)。需要强调的前提是:上述所有路径都依赖本仓库当前版本(epaint_default_fonts 0.36.2)的实际代码结构,若升级到未来版本,请以对应版本的 CHANGELOG 与FontDefinitions源码为准。
总结
epaint_default_fonts的 CHANGELOG 表面上只有零星几条实质记录,但结合源码可以还原出一个清晰的工程决策链条:字体资源从epaint拆分为独立 crate(0.28.1)→ 许可证随字体授权文本规范化(0.31.0)→ emoji 图标字体修复(0.34.0)→ 此后保持长期稳定。其实现上"少量图标无条件内嵌、完整 emoji 按 feature 可选、字体名进回退链"的三层设计,配合FontDefinitions的装配逻辑与测试用例的强制校验,共同构成了 egui 开箱即用、跨平台一致、且体积可控的默认字体体系。
【免费下载链接】eguiegui: an easy-to-use immediate mode GUI in Rust that runs on both web and native项目地址: https://gitcode.com/GitHub_Trending/eg/egui
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考