diff --git a/crates/svg/src/import/text.rs b/crates/svg/src/import/text.rs index 32d37bf10..6c48fd593 100644 --- a/crates/svg/src/import/text.rs +++ b/crates/svg/src/import/text.rs @@ -28,8 +28,11 @@ impl Fonts { fn load() -> Self { Fonts(figcraft_text::fontdb::family_names().into_iter().map(|f| f.to_lowercase()).collect()) } + /// Installed under this name, or under the family it names (`system-ui` on a Mac is its + /// System Font, see `fontdb::apple_alias`). fn has(&self, family: &str) -> bool { - self.0.contains(&family.to_lowercase()) + let key = family.to_lowercase(); + self.0.contains(&key) || figcraft_text::fontdb::apple_alias(&key).is_some_and(|target| self.0.contains(target)) } } @@ -67,8 +70,9 @@ fn generic(name: &str) -> Option<&'static [&'static str]> { }) } -/// The family to use: the first the SVG lists that's installed, else the first it names, else the -/// default. +/// The family to use: the first the SVG lists that's installed (a generic family as the first of +/// its installed stand-ins, unless a font answers to its name: `system-ui` on a Mac), else the +/// first it names, else the default. fn family(list: &[FontFamily], fonts: &Fonts) -> String { let name = |f: &FontFamily| match f { FontFamily::Serif => "serif".to_string(), @@ -80,14 +84,11 @@ fn family(list: &[FontFamily], fonts: &Fonts) -> String { }; for f in list { let n = name(f); - match generic(&n) { - Some(cands) => { - if let Some(c) = cands.iter().find(|c| fonts.has(c)) { - return (*c).to_string(); - } - } - None if fonts.has(&n) => return n, - None => {} + if fonts.has(&n) { + return n; + } + if let Some(c) = generic(&n).and_then(|cands| cands.iter().find(|c| fonts.has(c))) { + return (*c).to_string(); } } list.iter().map(name).find(|n| generic(n).is_none() && !n.is_empty()).unwrap_or_else(|| TextStyle::default().font_family) @@ -339,3 +340,7 @@ fn add_layer( } cx.add(n, parent); } + +#[cfg(test)] +#[path = "text_tests.rs"] +mod tests; diff --git a/crates/svg/src/import/text_tests.rs b/crates/svg/src/import/text_tests.rs new file mode 100644 index 000000000..f267b2947 --- /dev/null +++ b/crates/svg/src/import/text_tests.rs @@ -0,0 +1,32 @@ +//! Which family an SVG's `font-family` list gives a text layer. + +use usvg::FontFamily; + +use super::{Fonts, family}; + +fn fonts(installed: &[&str]) -> Fonts { + Fonts(installed.iter().map(|f| f.to_lowercase()).collect()) +} + +fn named(list: &[&str]) -> Vec { + list.iter() + .map(|n| match *n { + "sans-serif" => FontFamily::SansSerif, + n => FontFamily::Named(n.to_string()), + }) + .collect() +} + +/// The names CSS gives the system font keep their name on a Mac, where they draw in its System +/// Font, and fall back as before without it. +#[test] +fn css_system_font_names_keep_their_name_where_the_system_font_is() { + let mac = fonts(&["System Font", "Inter", "Helvetica"]); + assert_eq!(family(&named(&["-apple-system", "BlinkMacSystemFont", "system-ui", "sans-serif"]), &mac), "-apple-system"); + assert_eq!(family(&named(&["BlinkMacSystemFont", "sans-serif"]), &mac), "BlinkMacSystemFont"); + assert_eq!(family(&named(&["system-ui", "sans-serif"]), &mac), "system-ui"); + assert_eq!(family(&named(&["Helvetica", "system-ui"]), &mac), "Helvetica", "an installed family the SVG lists first still wins"); + let elsewhere = fonts(&["Inter", "DejaVu Sans"]); + assert_eq!(family(&named(&["-apple-system", "BlinkMacSystemFont", "system-ui", "sans-serif"]), &elsewhere), "Inter"); + assert_eq!(family(&named(&["-apple-system"]), &elsewhere), "-apple-system", "nothing installed: the first name it gives"); +} diff --git a/crates/text/src/fontdb.rs b/crates/text/src/fontdb.rs index ced18c815..139871711 100644 --- a/crates/text/src/fontdb.rs +++ b/crates/text/src/fontdb.rs @@ -1069,12 +1069,12 @@ fn public_family(family: &str) -> Option { /// The family Apple's system fonts are installed under, for the names designs use for them /// (lowercase): SF Pro and SF UI are the variable `SFNS.ttf` ("System Font"), its text and display -/// cuts its optical sizes. -fn apple_alias(key: &str) -> Option<&'static str> { +/// cuts its optical sizes, as are the names CSS gives the system font (`system-ui`, +/// `-apple-system`, `BlinkMacSystemFont`, `.AppleSystemUIFont`). +pub fn apple_alias(key: &str) -> Option<&'static str> { Some(match key.trim_start_matches('.') { - "sf pro" | "sf pro text" | "sf pro display" | "sf ui text" | "sf ui display" | "sf ns" | "sf ns text" | "sf ns display" | "san francisco" => { - "system font" - } + "sf pro" | "sf pro text" | "sf pro display" | "sf ui text" | "sf ui display" | "sf ns" | "sf ns text" | "sf ns display" | "san francisco" + | "system-ui" | "-apple-system" | "blinkmacsystemfont" | "applesystemuifont" => "system font", "sf compact text" | "sf compact display" => "sf compact", _ => return None, }) diff --git a/crates/text/src/fontdb_tests.rs b/crates/text/src/fontdb_tests.rs index 85d1f3a78..35b9435b3 100644 --- a/crates/text/src/fontdb_tests.rs +++ b/crates/text/src/fontdb_tests.rs @@ -340,8 +340,8 @@ fn font_bytes_load_into_the_db() { } } -/// Apple's system font answers to the names designs use for it (SF Pro, SF UI Text, …), unless a -/// font of that name is installed. +/// Apple's system font answers to the names designs and CSS use for it (SF Pro, SF UI Text, +/// system-ui, …), unless a font of that name is installed. #[test] fn apple_system_font_names_find_the_system_font() { let mut db = FontDb::default(); @@ -352,7 +352,11 @@ fn apple_system_font_names_find_the_system_font() { for name in ["SF Pro", "SF Pro Text", "SF Pro Display", "SF UI Text", "SF UI Display", ".SF NS Text", "San Francisco"] { assert!(db.has_family(name), "{name}"); } + for name in ["system-ui", "-apple-system", "BlinkMacSystemFont", ".AppleSystemUIFont"] { + assert_eq!(db.key(name), "system font", "{name}"); + } assert!(!db.has_family("SF Mono")); + assert!(!db.has_family("ui-monospace")); db.pending.insert("sf pro".into(), ("SF Pro".into(), Vec::new())); assert_eq!(db.key("SF Pro"), "sf pro", "an installed SF Pro is itself"); assert_eq!(db.key("SF UI Text"), "system font");