From 0978b44385c2908f2573dcec5fedc84b9090dcd0 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 24 Oct 2026 14:02:50 -0700 Subject: [PATCH] M4: text on a path takes a fill binding made through `fills` The bindings that move with the path's fills and effects are now found with list_binding, which reads `fills` as the first fill's color, as style_sets in cmd/styles.rs does. --- crates/engine/src/cmd/text.rs | 6 +++++- crates/engine/src/draw_tools_tests.rs | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/cmd/text.rs b/crates/engine/src/cmd/text.rs index 0334a72a..813a6036 100644 --- a/crates/engine/src/cmd/text.rs +++ b/crates/engine/src/cmd/text.rs @@ -148,7 +148,11 @@ fn on_path(s: &mut Session, p: &Value) -> Result { let (transform, w, h) = (pn.transform, pn.width, pn.height); let fills = pn.fills.iter().any(|f| f.visible).then(|| (pn.fills.clone(), pn.styles.fill.clone())); let effects = (!pn.effects.is_empty()).then(|| (pn.effects.clone(), pn.styles.effect.clone())); - let moved = |k: &str| (fills.is_some() && (k == "fill" || k.starts_with("fills/"))) || (effects.is_some() && k.starts_with("effects/")); + let moved = |k: &str| match super::lists::list_binding(k) { + Some((super::lists::BoundList::Fills, ..)) => fills.is_some(), + Some((super::lists::BoundList::Effects, ..)) => effects.is_some(), + _ => false, + }; let bound: Vec<(String, VariableId)> = pn.bound.iter().filter(|(k, _)| moved(k)).map(|(k, v)| (k.clone(), v.clone())).collect(); let mut tp = TextPath::new(&geom); tp.start = num(p, "start").filter(|v| v.is_finite()).unwrap_or(0.0).clamp(0.0, 1.0); diff --git a/crates/engine/src/draw_tools_tests.rs b/crates/engine/src/draw_tools_tests.rs index d33a48ff..726ee884 100644 --- a/crates/engine/src/draw_tools_tests.rs +++ b/crates/engine/src/draw_tools_tests.rs @@ -142,6 +142,20 @@ fn text_put_on_a_path_with_an_empty_effect_style_keeps_its_own_effects() { assert_eq!((&n.effects, &n.styles.effect), (&text.effects, &text.styles.effect), "a path whose effect style draws no effects"); } +#[test] +fn text_put_on_a_path_takes_a_fill_binding_made_through_fills() { + let mut s = Session::new(); + let (t, v) = text_and_curve(&mut s, json!({"fill": "#FF0000"})); + s.execute("variable.bind", &json!({"ids": [v], "field": "fills", "variable": "shadow"})).unwrap(); + s.execute("variable.bind", &json!({"ids": [t], "field": "fills", "variable": "ink"})).unwrap(); + let path = s.doc().unwrap().node(v).unwrap().clone(); + assert!(path.bound.contains_key("fills")); + s.execute("text.onPath", &json!({"ids": [t, v]})).unwrap(); + let n = s.doc().unwrap().node(t).unwrap(); + assert_eq!(n.bound, path.bound, "the path's `fills` binding replaces the text's"); + assert_eq!(n.fills, path.fills); +} + #[test] fn pattern_fills_tile_their_source_live() { let mut s = Session::new();