diff --git a/crates/svg/src/css.rs b/crates/svg/src/css.rs index e9b4ef67a..7d58e1c79 100644 --- a/crates/svg/src/css.rs +++ b/crates/svg/src/css.rs @@ -109,11 +109,10 @@ fn generic_fallback(family: &str) -> &'static str { pub(crate) fn font_props(st: &CharStyle, len: &dyn Fn(f64) -> String) -> Props { let mut p = Props::new(); // A quoted family escapes `\` and `'`, which would otherwise end the string early. - let fam = if st.font_family.contains(|c: char| c.is_whitespace() || matches!(c, ',' | '\'' | '\\')) { - format!("'{}'", st.font_family.replace('\\', "\\\\").replace('\'', "\\'")) - } else { - st.font_family.clone() - }; + // Only a plain identifier (no leading digit) can be written bare; anything else is quoted. + let bare = st.font_family.chars().next().is_some_and(|c| !c.is_ascii_digit() && c != '-') + && st.font_family.chars().all(|c| c.is_alphanumeric() || matches!(c, '-' | '_')); + let fam = if !bare { format!("'{}'", st.font_family.replace('\\', "\\\\").replace('\'', "\\'")) } else { st.font_family.clone() }; p.push(("font-family", format!("{fam}{}", generic_fallback(&st.font_family)))); p.push(("font-size", len(st.size * st.v_scale / 100.0))); let (weight, italic) = font_descriptor(st); @@ -695,3 +694,23 @@ fn is_upright_rectangle(path: &PathData) -> bool { && b.width() > 0.0 && b.height() > 0.0 } + +#[cfg(test)] +mod tests { + use super::*; + + fn family(name: &str) -> String { + let st = CharStyle { font_family: name.into(), ..CharStyle::default() }; + let p = font_props(&st, &|v| format!("{v}px")); + p.into_iter().find(|(k, _)| *k == "font-family").unwrap().1 + } + + #[test] + fn font_family_quoted_unless_plain_identifier() { + assert!(family("04b03").starts_with("'04b03'"), "{}", family("04b03")); + assert!(family("a;b").starts_with("'a;b'")); + assert!(family("a\"b").starts_with("'a\"b'")); + assert!(family("a(b").starts_with("'a(b'")); + assert!(family("Arial").starts_with("Arial,")); + } +}