From 63c48588b5b8b1bbdb982a29faa04425617b2c5a Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Wed, 4 Nov 2026 22:36:47 -0800 Subject: [PATCH] Render: a moved path reuses the slices of a placed gradient stroke A move also moves where a stroke's gradient is placed, but the slices of a gradient along or across the stroke take only its stops and kind. The moved stroke is now compared with the stroke the slices were made for with the gradient's placement left out, so a placed gradient no longer makes its slices again after every move. --- crates/render/src/renderer/paint.rs | 23 +++++++++++++++++------ crates/render/src/tests/movedstroke.rs | 13 ++++++++++++- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/crates/render/src/renderer/paint.rs b/crates/render/src/renderer/paint.rs index 6d9f0ea3e..9e6080428 100644 --- a/crates/render/src/renderer/paint.rs +++ b/crates/render/src/renderer/paint.rs @@ -395,11 +395,12 @@ impl Renderer { /// The slices of stroke `st` of `node` along `bp` (filled with `rule`), made from the latest /// slices kept under `mk` ([`MovedSliceKey`]) when those were made along their object's own - /// path, their object is `node` changed, its stroke there is `st` and its fill rule `rule`, and - /// `bp` is its path moved (to within rounding): the node they were made from, those slices, - /// and the offset from that node to `bp`, which the slices are drawn moved by. The anchors of - /// the two objects are compared first ([`anchors_moved`]), and the earlier path is made again - /// from its object only when they match. `None` when there are no such slices. + /// path, their object is `node` changed, its stroke there differs from `st` at most in where + /// its gradient is placed, its fill rule is `rule`, and `bp` is its path moved (to within + /// rounding): the node they were made from, those slices, and the offset from that node to + /// `bp`, which the slices are drawn moved by. The anchors of the two objects are compared first + /// ([`anchors_moved`]), and the earlier path is made again from its object only when they + /// match. `None` when there are no such slices. fn moved_slices( &self, mk: MovedSliceKey, @@ -415,7 +416,17 @@ impl Renderer { let Some(AppearanceItem::Stroke(old)) = e.made.appearance.items.get(mk.1) else { return None; }; - if !std::ptr::eq(old, st) && old != st { + // The slices take only the gradient's stops and kind, not where it is placed, which a move + // moves too. + let unplaced = |s: &StrokeLayer| { + let mut s = s.clone(); + if let vectorcraft_color::Paint::Gradient(g) = &mut s.paint { + g.geom = None; + g.angle = 0.0; + } + s + }; + if !std::ptr::eq(old, st) && unplaced(old) != unplaced(st) { return None; } if !anchors_moved(&e.made, node) { diff --git a/crates/render/src/tests/movedstroke.rs b/crates/render/src/tests/movedstroke.rs index 81fa40da8..f08e341c2 100644 --- a/crates/render/src/tests/movedstroke.rs +++ b/crates/render/src/tests/movedstroke.rs @@ -3,7 +3,7 @@ //! offset, with the pixels a new renderer draws. A change other than a move outlines the stroke //! and makes the slices again. -use vectorcraft_color::{Color, Gradient, GradientKind, GradientPaint, GradientStop, Paint}; +use vectorcraft_color::{Color, Gradient, GradientGeom, GradientKind, GradientPaint, GradientStop, Paint}; use vectorcraft_doc::{ Appearance, AppearanceItem, ArrowAlign, Arrowhead, Dash, FillLayer, LineCap, LineJoin, Node, NodeId, NodeKind, StrokeGradientMode, StrokeLayer, WidthProfile, @@ -353,6 +353,17 @@ fn gradient_cases() -> Vec<(String, (Document, NodeId), Point)> { out.push((format!("a short closed path, {mode:?}"), doc(&ring(40, 80.0), only()), Point::new(80.0, 0.0))); out.push((format!("a long open path, {mode:?}"), doc(&zigzag(3000), only()), Point::new(12_000.0, 15.0))); out.push((format!("a long closed path, {mode:?}"), doc(&ring(4000, 5000.0), only()), Point::new(0.0, 5000.0))); + // A placed gradient, whose start and end a move moves too. + let mut st = gradient_stroke(12.0, mode); + if let Paint::Gradient(g) = &mut st.paint { + g.geom = Some(GradientGeom { start: Point::new(0.0, 0.0), end: Point::new(140.0, 30.0), aspect: 1.0, focal: None }); + g.angle = 12.0; + } + out.push(( + format!("a short open path with a placed gradient, {mode:?}"), + doc(&zigzag(20), vec![AppearanceItem::Stroke(st)]), + Point::new(70.0, 15.0), + )); } out }