Add a generic family to the font-family of SVG export and CSS (#3135)

This commit is contained in:
MarekSpodBiedry
2026-10-20 03:41:17 +07:00
committed by bflatastic
parent 7c47692b32
commit 0f855be77f
4 changed files with 40 additions and 5 deletions
+1 -1
View File
@@ -48,7 +48,7 @@ fn type_gives_font_properties_and_gradients_css_gradients() {
let mut s = session();
let t = run(&mut s, "text.create", json!({"x": 20, "y": 50, "text": "Title", "size": 24}))["id"].as_u64().unwrap();
let text = css(&run(&mut s, "css.selection", json!({"ids": [t]}))).to_string();
for decl in ["font-family: 'Source Sans 3';", "font-size: 24px;", "color: #"] {
for decl in ["font-family: 'Source Sans 3', sans-serif;", "font-size: 24px;", "color: #"] {
assert!(text.contains(decl), "{decl} in {text}");
}
run(&mut s, "shape.rectangle", json!({"x": 0, "y": 100, "width": 100, "height": 50}));
+17 -1
View File
@@ -85,6 +85,22 @@ pub(crate) fn font_stretch(st: &CharStyle) -> Option<f64> {
st.font_axes.get("wdth").copied().filter(|w| w.is_finite() && *w > 0.0)
}
/// The generic family to list after `family`, with its leading comma, so a viewer without the font draws
/// the kind of face it was (the font's class, as the font menu's filters know it) rather than its default
/// serif. Nothing when `family` is a generic family itself.
fn generic_fallback(family: &str) -> &'static str {
const GENERIC: [&str; 5] = ["serif", "sans-serif", "monospace", "cursive", "fantasy"];
if GENERIC.iter().any(|g| family.trim().eq_ignore_ascii_case(g)) {
return "";
}
match vectorcraft_text::FontDb::global().family_traits(family).class {
vectorcraft_text::FontClass::Serif => ", serif",
vectorcraft_text::FontClass::Monospaced => ", monospace",
vectorcraft_text::FontClass::Script => ", cursive",
_ => ", sans-serif",
}
}
/// The font of character style `st`: family, size (the vertical scale; writers stretch the
/// horizontal one), weight and style, and its Variable Font settings, lengths written by `len`.
/// The weight is the style's number (600 for Semibold, 300 for Light) or the `wght` setting,
@@ -98,7 +114,7 @@ pub(crate) fn font_props(st: &CharStyle, len: &dyn Fn(f64) -> String) -> Props {
} else {
st.font_family.clone()
};
p.push(("font-family", fam));
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);
match weight {
+21 -2
View File
@@ -163,7 +163,7 @@ fn type_gives_font_properties() {
let id = add(&mut d, n);
let r = one(&d, id, &CssOptions::default());
assert_eq!(r.selector, ".type");
assert_eq!(prop(&r, "font-family"), Some("'Source Sans 3'"));
assert_eq!(prop(&r, "font-family"), Some("'Source Sans 3', sans-serif"));
assert_eq!(prop(&r, "font-size"), Some("18px"));
assert_eq!(prop(&r, "font-weight"), Some("bold"));
assert_eq!(prop(&r, "font-style"), Some("italic"));
@@ -281,9 +281,28 @@ fn groups_stand_for_their_objects_and_hidden_ones_are_left_out() {
assert_eq!(sheet.text(), sheet.rules[0].text());
}
#[test]
fn font_family_ends_with_a_generic_family_for_viewers_without_the_font() {
let family = |name: &str| {
let mut d = doc();
let st = CharStyle { font_family: name.into(), size: 18.0, ..CharStyle::default() };
let n = text(&mut d, "Hello", st);
let id = add(&mut d, n);
prop(&one(&d, id, &CssOptions::default()), "font-family").map(str::to_string)
};
// Source Sans 3 is a sans face; an unknown family gets the same default; a generic family stands alone.
assert_eq!(family("Source Sans 3").as_deref(), Some("'Source Sans 3', sans-serif"));
assert_eq!(family("No Such Font").as_deref(), Some("'No Such Font', sans-serif"));
assert_eq!(family("serif").as_deref(), Some("serif"));
}
#[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'")] {
for (family, want) in [
("Bob's Font", r"'Bob\'s Font', sans-serif"),
(r"Back\slash Font", r"'Back\\slash Font', sans-serif"),
("Bob's", r"'Bob\'s', sans-serif"),
] {
let mut d = doc();
let st = CharStyle { font_family: family.into(), size: 18.0, ..CharStyle::default() };
let n = text(&mut d, "Hello", st);
+1 -1
View File
@@ -300,7 +300,7 @@ fn lines_without_moved_characters_are_written_as_before() {
}
let open = |x: &str, anchor: &str| {
format!(
r##"<text transform="matrix(1 0 0 1 20 20)" x="{x}" y="12" xml:space="preserve" font-family="&apos;Source Sans 3&apos;" font-size="12" fill="#000000"{anchor}>"##
r##"<text transform="matrix(1 0 0 1 20 20)" x="{x}" y="12" xml:space="preserve" font-family="&apos;Source Sans 3&apos;, sans-serif" font-size="12" fill="#000000"{anchor}>"##
)
};
let words =