Align Stroke on live type: tests for the Type tool's selection, docs (#2294)

The Stroke panel enables Align Stroke while the Type tool edits text, and
styles the selected characters. docs: mcp.md (stroke.set and strokeOptions
align on type), type-parity and target-app-parity rows.
This commit is contained in:
Gert Pellin
2026-10-16 15:27:54 +07:00
committed by bflatastic
parent ca0673e0bc
commit 065e237f7f
6 changed files with 42 additions and 16 deletions
+3 -3
View File
@@ -20,7 +20,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Stroke Options",
["Window", "Stroke"],
None,
"{weight?: pt, cap?: butt|round|square, join?: miter|round|bevel, miterLimit?, align?: center|inside|outside, dash?: [d,g,…]|null (a 0 dash with a round or projecting cap draws dots or squares; a new pattern keeps the current offset and alignment), dashOffset? (exact dashes only), alignDashes?: bool (true: dashes fitted to corners and path ends, every run between them holding whole periods with a dash centred on each corner and end; false, the default for a new pattern: exact lengths), startArrow?, endArrow?: Arrow|ArrowOpen|Barbed|Concave|DoubleArrow|HalfArrowLeft|HalfArrowRight|Chevron|Feather|Swallowtail|Triangle|TriangleOpen|TriangleReverse|TriangleBar|Kite|Leaf|Drop|Circle|CircleOpen|HalfCircle|Oval|Target|Square|SquareOpen|Tag|TagOpen|Diamond|DiamondOpen|Hexagon|HexagonOpen|Star|Cross|Plus|Bar|DoubleBar|DotOnBar|Slash|DoubleSlash|Bracket|Fork|null (HalfArrowLeft/Right: one barb, left or right of the direction the head points), arrowAlign?: \"extend\" (tip past the end point, default)|\"tip\" (tip on the end point; the stroke is shortened), profile?: \"uniform\"|\"lens\"|\"taperStart\"|\"taperEnd\"|\"pinch\"|\"teardrop\"|\"wave\"|a saved profile's name (stroke.widthProfile.list), item?: stroke item index|null (omitted: the Appearance panel's active item if it is a stroke, else the top stroke, created when missing), ids?|id?, target?: \"object\"|\"contents\"} Without a stroke item, type takes weight, cap, join, miterLimit, align and the dash options as its characters' stroke (align: around the glyph outlines, the type staying live) (every run; text.setRangeStyle `strokeOptions` styles a range), and images and symbol instances (also in groups) are left alone. With nothing selected (and no ids) they set up the next object drawn (appearance.newArt; weight always does)",
"{weight?: pt, cap?: butt|round|square, join?: miter|round|bevel, miterLimit?, align?: center|inside|outside, dash?: [d,g,…]|null (a 0 dash with a round or projecting cap draws dots or squares; a new pattern keeps the current offset and alignment), dashOffset? (exact dashes only), alignDashes?: bool (true: dashes fitted to corners and path ends, every run between them holding whole periods with a dash centred on each corner and end; false, the default for a new pattern: exact lengths), startArrow?, endArrow?: Arrow|ArrowOpen|Barbed|Concave|DoubleArrow|HalfArrowLeft|HalfArrowRight|Chevron|Feather|Swallowtail|Triangle|TriangleOpen|TriangleReverse|TriangleBar|Kite|Leaf|Drop|Circle|CircleOpen|HalfCircle|Oval|Target|Square|SquareOpen|Tag|TagOpen|Diamond|DiamondOpen|Hexagon|HexagonOpen|Star|Cross|Plus|Bar|DoubleBar|DotOnBar|Slash|DoubleSlash|Bracket|Fork|null (HalfArrowLeft/Right: one barb, left or right of the direction the head points), arrowAlign?: \"extend\" (tip past the end point, default)|\"tip\" (tip on the end point; the stroke is shortened), profile?: \"uniform\"|\"lens\"|\"taperStart\"|\"taperEnd\"|\"pinch\"|\"teardrop\"|\"wave\"|a saved profile's name (stroke.widthProfile.list), item?: stroke item index|null (omitted: the Appearance panel's active item if it is a stroke, else the top stroke, created when missing), ids?|id?, target?: \"object\"|\"contents\"} Without a stroke item, type takes weight, cap, join, miterLimit, align (around its glyph outlines; the type stays live) and the dash options as its characters' stroke (every run; text.setRangeStyle `strokeOptions` styles a range), and images and symbol instances (also in groups) are left alone. With nothing selected (and no ids) they set up the next object drawn (appearance.newArt; weight always does)",
has_doc,
stroke_set
),
@@ -275,8 +275,8 @@ impl Session {
impl DocState {
/// Which Stroke panel values the selected objects' strokes (type: every run's) don't share,
/// and whether Align Stroke applies to them (no open path among them). One walk of the selection, so callers that ask
/// every frame cache it by revision.
/// and whether Align Stroke applies to them (no open path among them). One walk of the
/// selection, so callers that ask every frame cache it by revision.
pub fn stroke_mixed(&self) -> StrokeMixed {
let mut nodes = vec![];
for id in &self.selection.objects {
+4 -5
View File
@@ -136,7 +136,7 @@ fn character_stroke_options_round_trip_natively_and_old_files_load() {
/// Align Stroke on live type (Reddit redd.it/1dwhos6): the characters' stroke goes inside or
/// outside the glyphs without creating outlines, for every run or a range, and is kept by the
/// native format, the Eyedropper and Create Outlines.
/// native format and Create Outlines.
#[test]
fn align_stroke_reaches_the_characters_stroke() {
let mut s = session();
@@ -173,17 +173,16 @@ fn align_stroke_reaches_the_characters_stroke() {
assert!(aligns.contains(&StrokeAlign::Inside) && aligns.contains(&StrokeAlign::Outside), "{aligns:?}");
}
/// Copying type's appearance onto a path keeps its characters' stroke alignment (the Eyedropper's
/// own stroke attributes have none, on paths as on type).
#[test]
fn the_eyedropper_carries_the_characters_stroke_alignment() {
fn a_path_copying_types_appearance_takes_its_stroke_alignment() {
let mut s = session();
let id = stroked_text(&mut s);
run(&mut s, "stroke.set", json!({"align": "inside"}));
let rect = run(&mut s, "shape.rectangle", json!({"x": 10, "y": 150, "width": 50, "height": 50}))["id"].as_u64().unwrap();
run(&mut s, "appearance.copyFrom", json!({"source": id.0, "ids": [rect]}));
assert_eq!(s.doc().unwrap().doc.node(NodeId(rect)).unwrap().appearance.stroke().unwrap().align, StrokeAlign::Inside);
run(&mut s, "stroke.set", json!({"ids": [rect], "align": "outside"}));
run(&mut s, "appearance.copyFrom", json!({"source": rect, "ids": [id.0]}));
assert_eq!(text(&s, id).runs[0].style.stroke_align, StrokeAlign::Outside);
}
#[test]