diff --git a/crates/svg/src/css.rs b/crates/svg/src/css.rs index 34e74269e..ca4b5d8ca 100644 --- a/crates/svg/src/css.rs +++ b/crates/svg/src/css.rs @@ -81,7 +81,12 @@ pub(crate) fn font_descriptor(st: &CharStyle) -> (u16, bool) { /// (600 for Semibold, 300 for Light), `bold` for 700 and left out for 400. pub(crate) fn font_props(st: &CharStyle, len: &dyn Fn(f64) -> String) -> Props { let mut p = Props::new(); - let fam = if st.font_family.contains(|c: char| c.is_whitespace() || c == ',') { format!("'{}'", st.font_family) } else { st.font_family.clone() }; + // 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() + }; p.push(("font-family", fam)); p.push(("font-size", len(st.size * st.v_scale / 100.0))); let (weight, italic) = font_descriptor(st); diff --git a/crates/svg/tests/css_rules.rs b/crates/svg/tests/css_rules.rs index 2b4a203d9..9e088b6d4 100644 --- a/crates/svg/tests/css_rules.rs +++ b/crates/svg/tests/css_rules.rs @@ -259,3 +259,15 @@ fn groups_stand_for_their_objects_and_hidden_ones_are_left_out() { assert_eq!(sheet.rules.len(), 1, "one visible rectangle, once: {sheet:?}"); assert_eq!(sheet.text(), sheet.rules[0].text()); } + +#[test] +fn font_family_with_quote_or_backslash_is_escaped() { + for (family, want) in [("Bob's Font", r"'Bob\'s Font'"), (r"Back\slash Font", r"'Back\\slash Font'"), ("Bob's", r"'Bob\'s'")] { + let mut d = doc(); + let st = CharStyle { font_family: family.into(), size: 18.0, ..CharStyle::default() }; + let n = text(&mut d, "Hello", st); + let id = add(&mut d, n); + let r = one(&d, id, &CssOptions::default()); + assert_eq!(prop(&r, "font-family"), Some(want)); + } +}