GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
feat: targeted CJK/emoji fallback fonts + resolvable generic families
Bundled fonts cover Latin only, so CJK and emoji shaped to tofu in every
app unless $CCE_LOAD_SYSTEM_FONTS pulled in the whole system set.
build_font_system now probes a short list of well-known fallback files
(Noto Sans CJK .ttc, Noto Color Emoji — Arch/Debian/Fedora system paths
plus ~/.local/share/fonts) and loads whichever exist: cosmic-text's unix
script fallback matches them by family name ('Noto Sans CJK SC',
'Noto Color Emoji'). $CCE_NO_FALLBACK_FONTS opts out.
Loading the emoji font exposed a latent resolution hole: fontdb's
generic-family defaults name Windows faces (Arial/Times New Roman), so
Family::SansSerif/Monospace never resolved and EVERY glyph of
generic-family text rode the per-glyph fallback chain — where Noto Color
Emoji sits high (cosmic common_fallback) and hijacked spaces and digits
with emoji metrics (2x-wide spaces, mismatched digits). The generics are
now pinned to loaded faces: monospace -> Berkeley Mono, sans-serif ->
Noto Sans CJK SC. Visible side effect: 'sans-serif' text (e.g. mail
bodies) renders in a real sans instead of the accidental serif fallback.
Verified in cce-email with a JP/zh/KR + emoji mail: all scripts render,
color emoji inline at text size, spacing and digits normal.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/lib.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/src/lib.rs b/src/lib.rs
index 894d2a5..469a53c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -56,9 +56,39 @@ pub fn create_font_system_with_system_fonts() -> glyphon::FontSystem {
build_font_system(true)
}
+/// Targeted script-fallback faces loaded alongside the bundled house fonts.
+/// The bundled set covers Latin; anything else shaped to tofu unless
+/// `$CCE_LOAD_SYSTEM_FONTS` pulled in the entire system set. Probing a short
+/// list of well-known files keeps startup cheap while giving cosmic-text's
+/// unix script fallback (family names "Noto Sans CJK *", "Noto Color Emoji")
+/// real faces to land on. `$CCE_NO_FALLBACK_FONTS` opts out.
+fn load_fallback_fonts(db: &mut glyphon::cosmic_text::fontdb::Database) {
+ if std::env::var("CCE_NO_FALLBACK_FONTS").is_ok() {
+ return;
+ }
+ let home = std::env::var("HOME").unwrap_or_default();
+ let candidates = [
+ // CJK (Arch noto-fonts-cjk; Debian/Fedora paths for good measure)
+ "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc".to_string(),
+ "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc".to_string(),
+ "/usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Regular.ttc".to_string(),
+ // emoji (Arch noto-fonts-emoji; other distros; per-user install)
+ "/usr/share/fonts/noto/NotoColorEmoji.ttf".to_string(),
+ "/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf".to_string(),
+ "/usr/share/fonts/google-noto-emoji-color-fonts/NotoColorEmoji.ttf".to_string(),
+ format!("{home}/.local/share/fonts/NotoColorEmoji.ttf"),
+ ];
+ for path in &candidates {
+ if std::path::Path::new(path).exists() {
+ let _ = db.load_font_file(path);
+ }
+ }
+}
+
fn build_font_system(load_system_fonts: bool) -> glyphon::FontSystem {
let mut db = glyphon::cosmic_text::fontdb::Database::new();
db.load_fonts_dir(fonts_dir());
+ load_fallback_fonts(&mut db);
if load_system_fonts || std::env::var("CCE_LOAD_SYSTEM_FONTS").is_ok() {
db.load_system_fonts();
}
@@ -70,6 +100,24 @@ fn build_font_system(load_system_fonts: bool) -> glyphon::FontSystem {
db.load_system_fonts();
}
+ // Pin the generic families to faces that actually exist. fontdb's defaults
+ // name Windows faces ("Arial"/"Times New Roman"), so Family::SansSerif /
+ // Monospace never resolved here and every glyph of generic-family text
+ // dropped into the per-glyph fallback chain — where Noto Color Emoji sits
+ // high (cosmic-text common_fallback) and hijacked spaces and digits with
+ // emoji metrics. Berkeley Mono is the house mono; Noto Sans CJK SC (the
+ // targeted fallback face above) doubles as a full Latin sans.
+ fn has_family(db: &glyphon::cosmic_text::fontdb::Database, fam: &str) -> bool {
+ db.faces()
+ .any(|f| f.families.iter().any(|(n, _)| n == fam))
+ }
+ if has_family(&db, "Berkeley Mono") {
+ db.set_monospace_family("Berkeley Mono");
+ }
+ if has_family(&db, "Noto Sans CJK SC") {
+ db.set_sans_serif_family("Noto Sans CJK SC");
+ }
+
// Validate configured custom fonts
let font_getters = vec![
("list_font", crate::layout::list_font_parsed().0),