diff --git a/crates/effects/src/stroke/gradient.rs b/crates/effects/src/stroke/gradient.rs index d274bdc2a..677eea308 100644 --- a/crates/effects/src/stroke/gradient.rs +++ b/crates/effects/src/stroke/gradient.rs @@ -174,10 +174,21 @@ fn strips(bp: &BezPath, rule: FillRule, st: &StrokeLayer, tol: f64, margin: f64, } let steps = if max_dt < 1.0 { (1.0 / max_dt).ceil() as usize } else { 1 }; let profile = st.profile.iter().flat_map(|p| p.points.iter().map(|q| q.0)); - let at: Vec = - marks.iter().copied().chain(profile).chain((1..steps).map(|i| i as f64 / steps as f64)).map(|t| t * total).collect(); + let heads = if closed { (0.0, 0.0) } else { heads }; + // Where an arrowhead's reach ends gets a rib of its own, so the ribs that reach + // across a head run all the way there instead of narrowing towards the next corner + // from the head's tip, which leaves the corners of its base outside (#5964). + let head_ends = [heads.0, total - heads.1].into_iter().filter(|&s| s > 0.0 && s < total); + let at: Vec = marks + .iter() + .copied() + .chain(profile) + .chain((1..steps).map(|i| i as f64 / steps as f64)) + .map(|t| t * total) + .chain(head_ends) + .collect(); let (pts, _) = split_at(&pts, &corner, closed, &at); - strip(&lay, &pts, closed, total, side, if closed { (0.0, 0.0) } else { heads }) + strip(&lay, &pts, closed, total, side, heads) } }; out.push(ribs); diff --git a/crates/effects/src/stroke/tests_gradient.rs b/crates/effects/src/stroke/tests_gradient.rs index 489182406..bf63b46b8 100644 --- a/crates/effects/src/stroke/tests_gradient.rs +++ b/crates/effects/src/stroke/tests_gradient.rs @@ -216,3 +216,24 @@ fn meshes_colour_the_stroke_as_the_slices_do() { } } } + +/// #5964: with ribs only at the path's corners (an Along gradient), an arrowhead whose tip sits on +/// the end of a long straight leg is covered all the way to where it can reach: the ribs that +/// reach across it run to the end of its reach, rather than narrowing from its tip towards the +/// next corner and leaving the corners of its base outside. +#[test] +fn slices_cover_an_arrowhead_on_a_long_straight_leg() { + let zig = poly(&[(0.0, 0.0), (60.0, 40.0), (0.0, 80.0), (70.0, 90.0), (75.0, 20.0)], false); + for profile in [None, WidthProfile::preset("lens"), WidthProfile::preset("taperStart")] { + for align in [ArrowAlign::Tip, ArrowAlign::Extend] { + for mode in [StrokeGradientMode::Along, StrokeGradientMode::Across] { + let mut st = stroke(12.0, mode); + st.start_arrow = Some(Arrowhead::Triangle); + st.end_arrow = Some(Arrowhead::Triangle); + st.arrow_align = align; + st.profile = profile.clone(); + assert_covered(&zig, &st); + } + } + } +} diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index f128c1e9d..7cc9e14dd 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,6 +1,6 @@ # Parity with Adobe Illustrator -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Width profiles: smooth widths between width points, #5714; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Stroke gradients: an arrowhead aligned to the tip is painted to the corners of its base, #5964; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) ## Feature submission evidence @@ -31,6 +31,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard | Date | Change | Summary | |---|---|---| +| 2026-11-06 | minor | Stroke gradients: an arrowhead whose tip sits on the end of a long straight leg is painted by the gradient to the corners of its base. The slices that reach across a head now run to the end of its reach instead of narrowing towards the next corner (#5964). Test: `stroke::tests_gradient::slices_cover_an_arrowhead_on_a_long_straight_leg`. Scores and estimates unchanged. | | 2026-11-06 | minor | Width profiles: the width between two width points follows a smooth curve instead of a straight line, so a stroke no longer kinks at each width point (#5714). Each side is a monotone cubic through its points: flat at a widest or narrowest point, never past either point of a span; stacked points still step. Straight runs are sampled between width points to the drawing tolerance. Scores and estimates unchanged. | | 2026-11-05 | minor | A nearby hit on a plain long stroke or outline avoids assembling its whole path. A hit near the middle of a 200,000-anchor path took about 11–13 ms before and 1.0–1.4 ms after in paired runs on a busy Windows machine; a miss inside the path's box fell from 18.7 ms to 1.6 ms, while a distant miss was unchanged. Other stroke styles and fill hits keep their existing checks. Scores and estimates unchanged; idle performance budgets remain unverified. | | 2026-11-05 | minor | Translating a type object without changing its text or formatting reuses glyph geometry and expanded character-stroke outlines between frames. The text appears at its new position with unchanged pixels. Scaling the type, editing its content or formatting, or changing installed fonts rebuilds the geometry; scores and estimates unchanged. |