diff --git a/crates/engine/src/cmd/graph/tests.rs b/crates/engine/src/cmd/graph/tests.rs index 36c9589cd..66f04f0ef 100644 --- a/crates/engine/src/cmd/graph/tests.rs +++ b/crates/engine/src/cmd/graph/tests.rs @@ -2588,6 +2588,18 @@ fn formatting_the_whole_graphs_text_survives_it_being_drawn_again() { assert_eq!(spec(&s, id).label_styles, vectorcraft_doc::LabelStyles::default()); } +/// A stroke given to all of a graph's text without a weight is kept at 1 pt, as `paint.setStroke` keeps it. +#[test] +fn a_stroke_given_to_the_whole_graphs_text_is_kept_at_1_pt() { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 800, "height": 800})).unwrap(); + let id = graph(&mut s, "column"); + s.execute("select.set", &json!({"ids": [id.0]})).unwrap(); + s.execute("text.setStyle", &json!({"stroke": "#ff0000"})).unwrap(); + let legend = spec(&s, id).label_styles.legend.unwrap(); + assert_eq!((legend.stroke.color().map(|c| c.to_hex()), legend.stroke_width), (Some("#ff0000".into()), 1.0)); +} + #[test] fn formatting_one_label_keeps_that_kind_of_label() { let mut s = Session::new(); diff --git a/crates/engine/src/cmd/typecmd.rs b/crates/engine/src/cmd/typecmd.rs index 9ee14708a..d4a58c34d 100644 --- a/crates/engine/src/cmd/typecmd.rs +++ b/crates/engine/src/cmd/typecmd.rs @@ -123,7 +123,7 @@ pub fn specs() -> Vec { "Character", [], None, - "{ids?|id?, font?, style? (font alone: the style is kept when the new family has it, else its closest), size?: pt, leading?: pt|\"auto\", tracking?: 1/1000 em, justify?: \"auto\" (the start of each paragraph's direction)|\"left\"|\"center\"|\"right\"|\"justifyAll\", fill?: colour|\"none\", stroke?: colour|\"none\", keepModel?: bool (fill and stroke, as paint.setFill), strokeWidth?: pt (the character stroke's weight, in document points as stroke.set), features?: [\"dlig\", \"-liga\", …] OpenType, featureChanges?: [\"tnum\", \"-onum\", …] (OpenType features turned on, or with - off, on top of each character's own; its other features are kept), start?: byte, end?: byte} (size/leading are document points after object scaling; with a range: the character attributes style that range and justify the paragraphs it touches; without: all the text) → {ids, overflow?: [id…] (area type and type on a path whose text no longer fits; left out when all fits), warning?: text (the font or style set isn't available: its type is drawn in the fallback font or the family's closest style)}", + "{ids?|id?, font?, style? (font alone: the style is kept when the new family has it, else its closest), size?: pt, leading?: pt|\"auto\", tracking?: 1/1000 em, justify?: \"auto\" (the start of each paragraph's direction)|\"left\"|\"center\"|\"right\"|\"justifyAll\", fill?: colour|\"none\", stroke?: colour|\"none\" (characters without a stroke weight get 1 pt), keepModel?: bool (fill and stroke, as paint.setFill), strokeWidth?: pt (the character stroke's weight, in document points as stroke.set), features?: [\"dlig\", \"-liga\", …] OpenType, featureChanges?: [\"tnum\", \"-onum\", …] (OpenType features turned on, or with - off, on top of each character's own; its other features are kept), start?: byte, end?: byte} (size/leading are document points after object scaling; with a range: the character attributes style that range and justify the paragraphs it touches; without: all the text) → {ids, overflow?: [id…] (area type and type on a path whose text no longer fits; left out when all fits), warning?: text (the font or style set isn't available: its type is drawn in the fallback font or the family's closest style)}", has_doc, set_style ), @@ -858,6 +858,10 @@ fn set_style(s: &mut Session, p: &Value) -> Result { st.fill = f.clone(); } if let Some(f) = &stroke { + // A painted stroke on a run without a weight gets 1 pt, as with paint.setStroke. + if !f.is_none() { + super::gradient::run_stroke_weight(st); + } st.stroke = f.clone(); } if !stroke_weight.is_empty() { @@ -920,6 +924,9 @@ fn set_style(s: &mut Session, p: &Value) -> Result { st.fill = f.clone(); } if let Some(f) = &stroke { + if !f.is_none() { + super::gradient::run_stroke_weight(st); + } st.stroke = f.clone(); } if !stroke_weight.is_empty() { diff --git a/crates/engine/src/tests/textedit.rs b/crates/engine/src/tests/textedit.rs index ea5b38ca4..c6c3587b4 100644 --- a/crates/engine/src/tests/textedit.rs +++ b/crates/engine/src/tests/textedit.rs @@ -175,6 +175,23 @@ fn set_style_sets_the_character_stroke() { assert!(obj(&s, id).runs.iter().all(|r| (r.style.stroke_width - 1.0).abs() < 1e-9), "{:?}", paints(&s)); } +/// `text.setStyle {stroke}` without `strokeWidth` gives type with no stroke weight 1 pt, as +/// `paint.setStroke` does, and keeps a weight the type already has. +#[test] +fn set_style_stroke_without_a_weight_draws_1_pt() { + use vectorcraft_color::Color; + let mut s = session(); + let id = text(&mut s, "Hello"); + assert_eq!(obj(&s, id).runs[0].style.stroke_width, 0.0); + s.execute("text.setStyle", &json!({"ids": [id.0], "stroke": "#ff0000"})).unwrap(); + let st = &obj(&s, id).runs[0].style; + assert_eq!((st.stroke.color(), st.stroke_width), (Some(Color::rgb8(255, 0, 0)), 1.0)); + assert!(st.has_stroke()); + s.execute("text.setStyle", &json!({"ids": [id.0], "strokeWidth": 3})).unwrap(); + s.execute("text.setStyle", &json!({"ids": [id.0], "stroke": "#0000ff"})).unwrap(); + assert_eq!(obj(&s, id).runs[0].style.stroke_width, 3.0, "an existing weight is kept"); +} + #[test] fn optical_command_manual_pair_undo_redo_and_native_reopen() { let mut s = session(); diff --git a/docs/mcp.md b/docs/mcp.md index 24f8a1780..c0aa26a31 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -2360,7 +2360,8 @@ characters as its place relative to the Characters row says. To stroke some characters, use `text.setRangeStyle {id, start, end, strokeOptions: {weight?, cap?, join?, miterLimit?, align?, dash?, dashOffset?, alignDashes?}}`. `text.setStyle {ids?, stroke?, strokeWidth?, start?, end?}` sets the stroke color and weight of the characters of each object given (without `start` and `end`: all of its text), with its other keys in -one undo step; `strokeWidth` is in document points, as `stroke.set` gives the weight of scaled type. +one undo step; `strokeWidth` is in document points, as `stroke.set` gives the weight of scaled type. A `stroke` color +without `strokeWidth` gives characters that have no stroke weight 1 pt, as `paint.setStroke` does. `align: "inside"|"outside"` keeps the characters' stroke on that side of the glyph outlines, drawn at twice the weight and clipped, while the type stays editable (beyond Illustrator); SVG export writes the text live and that stroke as glyph outlines over it. `inspect_document` reports each object's stroke as `strokeOptions` (type: its first