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 "<family> <weight>" with a Regular or Italic style,
as Windows lists them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
neo
2026-10-27 00:10:58 -07:00
committed by neo
co-authored by Claude Opus 5.5
parent ee875abffa
commit 20553865cc
2 changed files with 47 additions and 1 deletions
+17 -1
View File
@@ -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,
+30
View File
@@ -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