From c79fd5c13245c66a2ab338265f09525e0c38ff77 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Mon, 2 Nov 2026 05:58:45 -0800 Subject: [PATCH] Character Styles: Style Options leaves Figure unset for figure tags no menu entry matches A style whose features turn on pnum or lnum alone, or pair a figure tag with one the shown entry turns off (onum with lnum, tnum with pnum), showed a Figure entry that does not match its tags. Choosing that entry then left the field unchanged, so the tags could not be cleared. Figure now opens unset for these lists, and choosing Default Figure writes -tnum, -onum, -pnum and -lnum. --- .../ui-egui/src/dialogs/char_style_options.rs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/dialogs/char_style_options.rs b/crates/ui-egui/src/dialogs/char_style_options.rs index e2427af62..38c425a81 100644 --- a/crates/ui-egui/src/dialogs/char_style_options.rs +++ b/crates/ui-egui/src/dialogs/char_style_options.rs @@ -189,7 +189,8 @@ fn bare(tag: &str) -> &str { } /// The OpenType Features fields for a style's `features`: each control is unset unless the list -/// names one of its tags, on or off. +/// names one of its tags, on or off. Figure is also unset when the list turns one of its tags on +/// or off against every Figure menu entry, for example `pnum` alone. fn opentype_fields(features: Option<&Value>) -> Map { let tags: Vec<&str> = features.and_then(Value::as_array).into_iter().flatten().filter_map(Value::as_str).collect(); let names = |set: &[&str]| tags.iter().any(|t| set.contains(&bare(t))); @@ -199,7 +200,11 @@ fn opentype_fields(features: Option<&Value>) -> Map { let last = tags.iter().rev().find(|t| bare(t) == *tag); fields.insert((*field).into(), last.map_or(Value::Null, |t| json!(!t.starts_with('-')))); } - let figure = names(&FIGURE_TAGS).then(|| FIGURE_VALUES.get(crate::panels::opentype::figure(&f))).flatten(); + let last_on = |tag: &str| tags.iter().rev().find(|t| bare(t) == tag).map(|t| !t.starts_with('-')); + let figure = names(&FIGURE_TAGS) + .then(|| FIGURE_VALUES.get(crate::panels::opentype::figure(&f))) + .flatten() + .filter(|v| figure_tags(v).iter().all(|t| last_on(bare(t)).is_none_or(|on| on != t.starts_with('-')))); fields.insert("ot_figure".into(), figure.map_or(Value::Null, |v| json!(v))); let position = names(&crate::panels::opentype::POSITION_TAGS).then(|| crate::panels::opentype::position(&f).and_then(|i| POSITION_VALUES.get(i))).flatten(); @@ -1310,6 +1315,21 @@ mod tests { assert_eq!(edited_attrs(&d).unwrap(), Map::new()); } + #[test] + fn figure_tags_no_menu_entry_matches_leave_the_figure_unset() { + for features in [json!(["pnum"]), json!(["lnum"]), json!(["onum", "lnum"]), json!(["tnum", "pnum"])] { + let mut d = dialog_for(json!({ "features": features })); + assert!(d.fields["ot_figure"].is_null(), "{features}"); + assert_eq!(edited_attrs(&d).unwrap(), attrs(json!({ "features": features })), "{features}"); + d.fields.insert("ot_figure".into(), json!("default")); + assert_eq!(edited_attrs(&d).unwrap()["features"], json!(["-tnum", "-onum", "-pnum", "-lnum"]), "{features}"); + } + for (features, shown) in [(json!(["tnum"]), "tabularLining"), (json!(["-pnum"]), "default"), (json!(["pnum", "lnum"]), "proportionalLining")] + { + assert_eq!(dialog_for(json!({ "features": features })).str("ot_figure"), shown, "{features}"); + } + } + #[test] fn color_tint_and_weight_outside_their_limits_are_errors() { let linked = json!({"type": "solid", "color": {"model": "gray", "k": 0.5}, "swatch": "Ink"});