From 20553865cc3db5f15306501bae15a1e65a6cab08 Mon Sep 17 00:00:00 2001 From: neo Date: Tue, 20 Oct 2026 00:03:59 -0700 Subject: [PATCH] Fonts: a variable font's named instances answer to their Windows family names Documents name non-RIBBI weights by their Windows family ("Montserrat ExtraBold" / Regular). The bundled Montserrat is variable, so its instances had no Windows names; the installed static files that do carry them were skipped as already present, and the lookup fell back to Inter Regular without reporting the font missing. Each named instance other than Regular, Italic, Bold and Bold Italic now also answers to " " with a Regular or Italic style, as Windows lists them. Co-Authored-By: Claude Opus 5.5 --- crates/fonts/src/fontdb.rs | 18 +++++++++++++++++- crates/fonts/src/tests.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/crates/fonts/src/fontdb.rs b/crates/fonts/src/fontdb.rs index 751f36ca..58f7e1cd 100644 --- a/crates/fonts/src/fontdb.rs +++ b/crates/fonts/src/fontdb.rs @@ -393,6 +393,22 @@ fn legacy_names(f: &skrifa::FontRef<'_>, family: &str) -> Option<(String, String Some((legacy, name(f, &[StringId::SUBFAMILY_NAME]).unwrap_or_else(|| "Regular".into()))) } +/// The Windows family and style a variable font's named instance answers to. Windows lists each +/// instance other than Regular, Italic, Bold and Bold Italic as a family of its own, as the +/// family's static files name themselves: "Montserrat ExtraBold" / "Regular" for the ExtraBold +/// instance, "Montserrat ExtraBold" / "Italic" for ExtraBold Italic. +fn instance_legacy_names(family: &str, style: &str) -> Option<(String, String)> { + if style.contains('{') { + return None; + } + let weight: Vec<&str> = style.split_whitespace().filter(|w| !matches!(norm(w).as_str(), "italic" | "oblique")).collect(); + let weight = weight.join(" "); + if matches!(norm(&weight).as_str(), "" | "regular" | "bold") { + return None; + } + Some((format!("{family} {weight}"), if style_italic(style) { "Italic" } else { "Regular" }.into())) +} + /// The tables skrifa reads glyph outlines from: `glyf` (TrueType), `CFF `, `CFF2` and `VARC`. For a /// face with none of them (bitmaps only, or outlines in Apple's `hvgl` table), [`FontDb::outline`] /// returns an empty path for every glyph. @@ -686,7 +702,7 @@ fn make_face(bytes: FontBytes, index: u32, family: String, style: String, coords .map(|v| v >= 0.5) .or(axis(b"slnt").map(|v| v.abs() > 0.1)) .unwrap_or(!matches!(a.style, skrifa::attribute::Style::Normal) || style_italic(&style)); - let legacy = if coords.is_empty() { legacy_names(&f, &family) } else { None }; + let legacy = if coords.is_empty() { legacy_names(&f, &family) } else { instance_legacy_names(&family, &style) }; Some(FontFace { id: NEXT_ID.fetch_add(1, Ordering::Relaxed), family, diff --git a/crates/fonts/src/tests.rs b/crates/fonts/src/tests.rs index bb7b726a..a6fca4c4 100644 --- a/crates/fonts/src/tests.rs +++ b/crates/fonts/src/tests.rs @@ -147,6 +147,36 @@ fn windows_family_names_find_their_faces() { } } +#[test] +fn variable_font_instances_answer_to_their_windows_names() { + // Windows lists a variable font's named instances other than Regular, Italic, Bold and + // Bold Italic as families of their own, and documents name them that way ("Montserrat + // ExtraBold"). The bundled Montserrat is variable, so these names found no face and drew + // in Inter Regular, even with the family's static files installed (their faces are + // skipped as already present). + let db = FontDb::with_font_dirs(Vec::new()); + db.set_system_fallback(false); + if !db.is_loaded("Montserrat") { + return; // built without craft-fonts + } + for (name, style, weight, italic) in [ + ("Montserrat ExtraBold", "Regular", 800.0, false), + ("Montserrat SemiBold", "Regular", 600.0, false), + ("Montserrat Medium", "Regular", 500.0, false), + ("Montserrat Medium", "Italic", 500.0, true), + ("Montserrat Thin", "Bold", 100.0, false), + ] { + assert!(db.has_family(name), "{name}"); + let f = db.face(name, style); + assert_eq!((f.family.as_str(), f.weight, f.italic), ("Montserrat", weight, italic), "{name} {style}"); + assert_eq!(db.document_face(name, style).family, "Montserrat", "{name}: installed, so not Calibri"); + } + // Regular and Bold are the family itself, not families of their own. + assert!(!db.has_family("Montserrat Regular")); + assert!(!db.has_family("Montserrat Bold")); + assert_eq!(db.face("Montserrat", "Bold").weight, 700.0); +} + #[test] fn faces_without_outlines_are_not_cataloged() { // Rename the Semibold's `glyf` table, as in fonts whose glyphs are bitmaps or are stored in