M7: CSS system font names draw in macOS's System Font

`system-ui`, `-apple-system`, `BlinkMacSystemFont` and
`.AppleSystemUIFont` join the names `apple_alias` resolves to the
installed System Font, as SF Pro and SF UI already do. SVG import keeps
a family the SVG lists when a font answers to its name, so on a Mac
`font-family="-apple-system, system-ui, sans-serif"` keeps
`-apple-system` and draws in System Font; elsewhere the generic
stand-ins apply as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MortenHustedandClaude Opus 5.5 committed 2026-10-28 08:35:46 +01:00
1 parent 90054a89c4
commit 2ff69183d7
4 files changed
+59 -18

No files matched your search

+16 -11
View File
@@ -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;
+32
View File
@@ -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<FontFamily> {
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");
}
+5 -5
View File
@@ -1069,12 +1069,12 @@ fn public_family(family: &str) -> Option<String> {
/// 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,
})
+6 -2
View File
@@ -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");