Merge pull request #5580 from dotCooCoo/fix/kerning-mixed-modes

Type: Kerning menu lists Auto and Optical for mixed kerning modes
This commit is contained in:
BFlat
2026-11-04 18:52:45 +07:00
committed by GitHub
2 changed files with 29 additions and 2 deletions
+9 -2
View File
@@ -316,6 +316,14 @@ fn caret_kerning(app: &VectorcraftApp, ui: &Ui) -> Option<(f64, bool)> {
Some((value, true))
}
/// The check marks of the Kerning menu's Auto and Optical entries for the selection's kerning
/// `mode` (Optical on, manual value). A selection that mixes modes checks neither, and the menu
/// still lists both.
fn kerning_methods(mode: Option<(bool, Option<f64>)>) -> (Option<bool>, Option<bool>) {
let (automatic, optical) = mode.map_or((false, false), |(optical, manual)| (!optical && manual.is_none(), optical));
(Some(automatic), Some(optical))
}
/// The faces Type › Bold and Type › Italic switch to.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Face {
@@ -756,8 +764,7 @@ pub(crate) fn metrics_grid(app: &mut VectorcraftApp, ui: &mut Ui, id: &str, s: &
let caret = text_editing(app).is_some_and(|(_, a, b)| a == b);
let at_caret = if caret { caret_kerning(app, ui) } else { None };
let kerning = at_caret.map(|(v, _)| v).or_else(|| mode.map(|(_, manual)| manual.unwrap_or(0.0)));
let automatic = mode.map(|(optical, manual)| !optical && manual.is_none());
let optical = mode.map(|(optical, _)| optical);
let (automatic, optical) = kerning_methods(mode);
match widgets::spin_kerning(
ui,
"ch-kern",
@@ -258,6 +258,26 @@ fn character_kerning_setter_and_automatic_pair_value() {
assert_eq!(shared(&app, |s| (s.optical_kerning, s.kerning)), None, "a mixed range shows a blank Kerning field");
}
/// The Kerning menu lists Auto and Optical for a selection that mixes kerning modes, with
/// neither checked, so one mode can be set on all of it.
#[test]
fn kerning_menu_lists_auto_and_optical_for_mixed_modes() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 300, "height": 200})).unwrap();
let a = app.run("text.create", json!({"x": 20, "y": 50, "text": "AV", "size": 12})).unwrap()["id"].clone();
let b = app.run("text.create", json!({"x": 20, "y": 90, "text": "AV", "size": 12})).unwrap()["id"].clone();
app.run("select.set", json!({"ids": [b.clone()]})).unwrap();
app.run("text.setFormat", json!({"kerning": "optical"})).unwrap();
app.run("select.set", json!({"ids": [a.clone()]})).unwrap();
assert_eq!(kerning_methods(shared(&app, |s| (s.optical_kerning, s.kerning))), (Some(true), Some(false)), "Auto");
app.run("select.set", json!({"ids": [b.clone()]})).unwrap();
assert_eq!(kerning_methods(shared(&app, |s| (s.optical_kerning, s.kerning))), (Some(false), Some(true)), "Optical");
app.run("select.set", json!({"ids": [a, b]})).unwrap();
let mode = shared(&app, |s| (s.optical_kerning, s.kerning));
assert_eq!(mode, None, "the selection mixes Auto and Optical");
assert_eq!(kerning_methods(mode), (Some(false), Some(false)), "both entries are listed, neither checked");
}
/// Highlight Kerning and Highlight Tracking show the Character panel and give its field the
/// keyboard focus with the value selected: what is typed replaces it, and Enter applies it.
/// Highlight Font Name opens the font family menu with its search field focused. Without