Fonts: bound named instances per file, index axis settings in style strings (#2215)

* fix(fonts): bound named-instance listing and index variable-style settings

* address review: bound inspected named-instance records file-wide
This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-11-03 23:26:17 -05:00
1 parent 5309d7d81a
commit a4017a3cda
3 files changed
+34 -3

No files matched your search

+14 -1
View File
@@ -329,17 +329,30 @@ pub(super) fn enumerate_faces(data: &[u8]) -> Vec<Found> {
Ok(FileRef::Collection(c)) => c.len().min(MAX_FACES),
Err(_) => 0,
};
/// Named instances listed from one file, all its faces together: each is a face of its own,
/// and a collection's faces can share one table of thousands.
const MAX_INSTANCES: usize = 4096;
let mut out = Vec::new();
let mut instances = 0usize;
// Records looked at, kept or not: duplicate or unnamed ones cost a name lookup too.
let mut inspected = 0usize;
for i in 0..count {
let Ok(f) = skrifa::FontRef::from_index(data, i) else { continue };
let Some((family, style)) = face_names(&f) else { continue };
let axes = f.axes();
let mut named = Vec::new();
// The styles taken, ASCII-lowercased: a lookup, not a search of those before.
let mut taken = std::collections::HashSet::new();
for ni in f.named_instances().iter() {
if instances >= MAX_INSTANCES || inspected >= MAX_INSTANCES {
break;
}
inspected += 1;
let Some(style) = name(&f, &[ni.subfamily_name_id()]) else { continue };
if named.iter().any(|(_, s, _): &(u32, String, _)| s.eq_ignore_ascii_case(&style)) {
if !taken.insert(style.to_ascii_lowercase()) {
continue;
}
instances += 1;
let coords: Vec<([u8; 4], f32)> = axes.iter().zip(ni.user_coords()).map(|(a, v)| (a.tag().to_be_bytes(), v)).collect();
named.push((i, style, coords));
}
+14 -2
View File
@@ -325,6 +325,14 @@ impl ScopedFonts<'_> {
return Some(f.clone());
}
let mut coords = base.coords.clone();
// The font's own axes, and where each tag is in `coords`: a setting for an axis the font
// doesn't have changes nothing, so it isn't kept (a style string is document input).
let axes: std::collections::HashSet<[u8; 4]> =
base.skrifa().map(|f| f.axes().iter().map(|a| a.tag().to_be_bytes()).collect()).unwrap_or_default();
let mut at: std::collections::HashMap<[u8; 4], usize> = std::collections::HashMap::new();
for (i, (t, _)) in coords.iter().enumerate() {
at.entry(*t).or_insert(i);
}
for kv in style[open + 1..].trim_end_matches('}').split(',') {
let (k, v) = kv.split_once(':')?;
let k = k.trim().as_bytes();
@@ -333,9 +341,13 @@ impl ScopedFonts<'_> {
return None;
}
let tag = [k[0], k[1], k[2], k[3]];
match coords.iter_mut().find(|(t, _)| *t == tag) {
match at.get(&tag).and_then(|i| coords.get_mut(*i)) {
Some(c) => c.1 = v,
None => coords.push((tag, v)),
None if axes.contains(&tag) => {
at.insert(tag, coords.len());
coords.push((tag, v));
}
None => {}
}
}
let f = make_face(base.bytes.clone(), base.source.clone(), base.index, base.family.clone(), style.to_string(), coords)?;
+6
View File
@@ -479,6 +479,12 @@ mod tests {
let custom = db.face(&fam, &format!("{} {{wght:{mid}}}", light.style));
assert!(custom.is_variable() && custom.coords.iter().any(|(t, v)| t == b"wght" && *v == mid));
assert!(!db.styles(&fam).iter().any(|s| s.contains('{')), "instances aren't listed");
// Settings for axes the font hasn't got are dropped, however many a style string names.
let bogus: Vec<String> = (0..20_000).map(|i| format!("q{:03}:1", i % 1000)).collect();
let t = std::time::Instant::now();
let odd = db.face(&fam, &format!("{} {{wght:{mid},{}}}", light.style, bogus.join(",")));
assert!(t.elapsed() < std::time::Duration::from_secs(2), "{:?}", t.elapsed());
assert_eq!(odd.coords.len(), custom.coords.len(), "only the font's own axes are kept");
let static_face = db.face(DEFAULT_FAMILY, "Regular {wght:700}");
assert_eq!(static_face.style, "Regular", "static fonts ignore axis settings");
}