Text: the variable-font test picks an axis that moves the advance

`style_variations_change_advances_and_outlines` picked `wdth`, else `wght`, else the first axis
with a range, and then asserted that the advance moves. That assumes the chosen axis changes
advances, which a monospaced family does not: Google Sans Code - the first variable face on a
machine where it is installed, and the one this check met on Linux - moves its outlines along
`wght` but keeps its advances fixed by design, so the test failed with "wght 300..800: advances
780 vs 780" while `measure` and the font were both right.

The test now tries the axes in order and uses the first one whose advance actually moves (Google
Sans Code answers on its own MONO axis, Bahnschrift on `wdth`), and skips with a message on a
machine whose variable faces never move an advance - the same shape as the "no variable font
installed" skip next to it. The outline assertion keeps its teeth: the axis is picked for moving
the advance, but whether the outline follows it is still checked.

On Linux with Google Sans Code and Bahnschrift installed: `cargo test -p effectcraft-text`
reported 43 passed / 1 failed before and 44 passed after. `cargo fmt --all --check` and
`cargo clippy -p effectcraft-text --all-targets -- -D warnings` are clean.
This commit is contained in:
FeiLiuEM committed 2026-10-09 20:21:05 +08:00
1 parent d5f78f3fc0
commit 55ba002acf
1 file changed
+20 -6
+20 -6
View File
@@ -197,26 +197,40 @@ mod tests {
eprintln!("no variable font installed: skipped");
return;
};
// An axis that changes widths (wdth or wght, else the first that moves).
let a = axes.iter().find(|a| a.tag == "wdth" && a.max > a.min).or_else(|| axes.iter().find(|a| a.tag == "wght" && a.max > a.min)).unwrap_or(&axes[0]);
let info = crate::fonts::face(face).info.clone();
let st = |v: f32| crate::TextStyle {
// An axis that moves the advance: `wdth` and `wght` usually do, but a monospaced family
// keeps its advances fixed along `wght` (Google Sans Code does) and moves them along its
// own axis instead, so try the axes in order, take the first one that moves, and skip a
// machine whose variable faces never do.
let st = |a: &FontAxis, v: f32| crate::TextStyle {
family: info.family.clone(),
style: info.style.clone(),
size: 100.0,
variations: vec![(a.tag.clone(), v)],
..Default::default()
};
let (lo, hi) = (crate::layout::measure("Hamburgefonts", &st(a.min)), crate::layout::measure("Hamburgefonts", &st(a.max)));
let mut moving = None;
for a in axes.iter().filter(|a| a.max > a.min) {
let (lo, hi) = (crate::layout::measure("Hamburgefonts", &st(a, a.min)), crate::layout::measure("Hamburgefonts", &st(a, a.max)));
if (lo - hi).abs() > 1.0 {
moving = Some((a.clone(), lo, hi));
break;
}
}
let Some((a, lo, hi)) = moving else {
eprintln!("no variable axis installed here moves the advance: skipped");
return;
};
if crate::resolve(&info.family, &info.style).face != face {
eprintln!("variable face not reachable by family/style: skipped");
return;
}
// The axis was picked because it moves the advance; the outline has to follow it too.
assert!((lo - hi).abs() > 1.0, "{} {}..{}: advances {lo} vs {hi}", a.tag, a.min, a.max);
let ink = |v: f32| {
let l = crate::layout::layout("H", &st(v), &crate::ParagraphStyle::default());
let l = crate::layout::layout("H", &st(&a, v), &crate::ParagraphStyle::default());
l.glyphs.iter().map(|g| crate::glyph_outline(g).area().abs()).sum::<f64>()
};
assert!((ink(a.min) - ink(a.max)).abs() > 1.0);
assert!((ink(a.min) - ink(a.max)).abs() > 1.0, "{} {}..{}: outlines do not move", a.tag, a.min, a.max);
}
}