From 8518ade58230503e2261e2ba51046be6a42bfbe3 Mon Sep 17 00:00:00 2001 From: KnottyDyes <47539659+KnottyDyes@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:21:31 -0600 Subject: [PATCH] Fonts: discover Linux user fonts in XDG_DATA_HOME Linux font discovery ignored XDG_DATA_HOME and always scanned ~/.local/share/fonts. Use XDG_DATA_HOME/fonts when the base path is absolute, otherwise keep the default. Preserve ~/.fonts and system directories, with environment-independent regression tests for directory selection. --- crates/fonts/src/fontdb.rs | 18 +++++++--- crates/fonts/src/tests_sysfonts.rs | 58 ++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/crates/fonts/src/fontdb.rs b/crates/fonts/src/fontdb.rs index 14441e14..390669ab 100644 --- a/crates/fonts/src/fontdb.rs +++ b/crates/fonts/src/fontdb.rs @@ -608,15 +608,23 @@ pub fn system_font_dirs() -> Vec { dirs.push(std::path::PathBuf::from(l).join("Microsoft\\Windows\\Fonts")); } } else { - dirs.extend(["/usr/share/fonts", "/usr/local/share/fonts"].map(Into::into)); - if let Some(h) = &home { - dirs.push(h.join(".fonts")); - dirs.push(h.join(".local/share/fonts")); - } + let xdg_data_home = std::env::var_os("XDG_DATA_HOME").map(std::path::PathBuf::from); + dirs.extend(unix_font_dirs(home.as_deref(), xdg_data_home.as_deref())); } dirs } +fn unix_font_dirs(home: Option<&std::path::Path>, xdg_data_home: Option<&std::path::Path>) -> Vec { + let mut dirs: Vec = ["/usr/share/fonts", "/usr/local/share/fonts"].map(Into::into).into(); + if let Some(home) = home { + dirs.push(home.join(".fonts")); + } + let data_home = + xdg_data_home.filter(|path| path.is_absolute()).map(std::path::Path::to_path_buf).or_else(|| home.map(|path| path.join(".local/share"))); + dirs.extend(data_home.map(|path| path.join("fonts"))); + dirs +} + /// One face found in a font file: index, family, style and (variable fonts) the named instance's /// axis settings. type Found = (u32, String, String, Vec<([u8; 4], f32)>); diff --git a/crates/fonts/src/tests_sysfonts.rs b/crates/fonts/src/tests_sysfonts.rs index a9cda7cd..a05be10e 100644 --- a/crates/fonts/src/tests_sysfonts.rs +++ b/crates/fonts/src/tests_sysfonts.rs @@ -6,6 +6,64 @@ use super::*; const FAMILY: &str = "Sysfont Sans3"; +#[cfg(not(any(target_os = "macos", windows)))] +mod user_font_dirs { + use super::*; + + #[test] + fn user_font_dirs_when_xdg_is_absolute() { + let home = Path::new("/home/font-user"); + let xdg = Path::new("/custom/data"); + let dirs = unix_font_dirs(Some(home), Some(xdg)); + assert_eq!(dirs, ["/usr/share/fonts", "/usr/local/share/fonts", "/home/font-user/.fonts", "/custom/data/fonts"].map(PathBuf::from)); + } + + #[test] + fn user_font_dirs_when_xdg_is_unset() { + let home = Path::new("/home/font-user"); + let dirs = unix_font_dirs(Some(home), None); + assert_eq!( + dirs, + ["/usr/share/fonts", "/usr/local/share/fonts", "/home/font-user/.fonts", "/home/font-user/.local/share/fonts"].map(PathBuf::from) + ); + } + + #[test] + fn user_font_dirs_when_xdg_is_relative() { + let home = Path::new("/home/font-user"); + let xdg = Path::new("relative/data"); + let dirs = unix_font_dirs(Some(home), Some(xdg)); + assert_eq!( + dirs, + ["/usr/share/fonts", "/usr/local/share/fonts", "/home/font-user/.fonts", "/home/font-user/.local/share/fonts"].map(PathBuf::from) + ); + } + + #[test] + fn user_font_dirs_when_xdg_is_empty() { + let home = Path::new("/home/font-user"); + let xdg = Path::new(""); + let dirs = unix_font_dirs(Some(home), Some(xdg)); + assert_eq!( + dirs, + ["/usr/share/fonts", "/usr/local/share/fonts", "/home/font-user/.fonts", "/home/font-user/.local/share/fonts"].map(PathBuf::from) + ); + } + + #[test] + fn user_font_dirs_when_home_is_unset_and_xdg_is_absolute() { + let xdg = Path::new("/custom/data"); + let dirs = unix_font_dirs(None, Some(xdg)); + assert_eq!(dirs, ["/usr/share/fonts", "/usr/local/share/fonts", "/custom/data/fonts"].map(PathBuf::from)); + } + + #[test] + fn user_font_dirs_when_home_and_xdg_are_unset() { + let dirs = unix_font_dirs(None, None); + assert_eq!(dirs, ["/usr/share/fonts", "/usr/local/share/fonts"].map(PathBuf::from)); + } +} + /// A bundled Source Sans 3 file renamed [`FAMILY`] (as long as the original name). fn renamed(file: &str) -> Vec { let mut data = std::fs::read(Path::new(env!("CARGO_MANIFEST_DIR")).join("../../assets/fonts").join(file)).unwrap();