mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-26 11:26:50 -06:00
M7: missing fonts: follow each font variable's aliases once (#752)
A file whose font family variables alias each other in several modes made the missing-fonts walk branch in every mode, billions of steps before the depth limit stopped it, freezing the app on open and `figcraft-cli info`. The walk now visits each variable once. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a86897dc91
commit
85010e491a
@@ -64,16 +64,20 @@ pub fn missing(doc: &Document) -> Vec<Missing> {
|
||||
used.into_values().filter(|(family, _)| !figcraft_text::has_family(family)).map(|(family, layers)| Missing { family, layers }).collect()
|
||||
}
|
||||
|
||||
/// Every string value of variable `id` in any mode, following aliases (at most 32 deep).
|
||||
/// Every string value of variable `id` in any mode, following aliases (each variable once).
|
||||
fn string_values(vars: &Variables, id: &str) -> Vec<String> {
|
||||
let mut out = Vec::new();
|
||||
let mut todo = vec![(id.to_string(), 0)];
|
||||
while let Some((id, depth)) = todo.pop() {
|
||||
let mut seen = BTreeSet::new();
|
||||
let mut todo = vec![id.to_string()];
|
||||
while let Some(id) = todo.pop() {
|
||||
if !seen.insert(id.clone()) {
|
||||
continue;
|
||||
}
|
||||
let Some(var) = vars.get(&id) else { continue };
|
||||
for v in var.values.values() {
|
||||
match v {
|
||||
VariableValue::String(s) => out.push(s.clone()),
|
||||
VariableValue::Alias(a) if depth < 32 => todo.push((a.clone(), depth + 1)),
|
||||
VariableValue::Alias(a) => todo.push(a.clone()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,6 +119,25 @@ fn blank_families_and_aliases_are_handled() {
|
||||
assert_eq!(families(&mut s), vec![("FigCraft Missing F".to_string(), 1), ("FigCraft Missing G".to_string(), 1)]);
|
||||
}
|
||||
|
||||
/// Aliases in several modes are followed once per variable: a chain that branches in every mode
|
||||
/// would otherwise grow to billions of steps (a file can hold one).
|
||||
#[test]
|
||||
fn branching_alias_chains_are_walked_once_per_variable() {
|
||||
let mut s = Session::new();
|
||||
s.execute("variable.createCollection", &json!({"name": "Type"})).unwrap();
|
||||
for i in 0..32 {
|
||||
s.execute("variable.create", &json!({"collection": "Type", "name": format!("V{i}"), "type": "string", "value": "x"})).unwrap();
|
||||
}
|
||||
let doc = s.doc_mut().unwrap();
|
||||
let ids: Vec<String> = doc.variables.variables.iter().map(|v| v.id.clone()).collect();
|
||||
for (i, var) in doc.variables.variables.iter_mut().enumerate() {
|
||||
let next = ids.get(i + 1).cloned().unwrap_or_else(|| "FigCraft Missing H".into());
|
||||
let leaf = |v: String| if i + 1 < ids.len() { VariableValue::Alias(v) } else { VariableValue::String(v) };
|
||||
var.values = [("m1".to_string(), leaf(next.clone())), ("m2".to_string(), leaf(next))].into();
|
||||
}
|
||||
assert_eq!(string_values(&doc.variables, &ids[0]), vec!["FigCraft Missing H", "FigCraft Missing H"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn needs_an_open_document() {
|
||||
let mut s = Session::empty();
|
||||
|
||||
Reference in New Issue
Block a user