perf(canvas): avoid cloning simple path points

This commit is contained in:
Kayshen-X 2026-06-14 23:49:39 +08:00
parent 05fedb1ec7
commit e99e45c2c6
2 changed files with 50 additions and 11 deletions

View file

@ -164,23 +164,38 @@ fn flatten_segment(
}
}
/// Flatten a Path scene node into a doc-space polyline — cubic
/// segments whose endpoints carry handles are tessellated; a
/// handle-free path falls back to the straight `points` polyline.
/// A closed path appends the last-anchor → first-anchor segment.
pub(crate) fn flatten_path(node: &SceneNode) -> Vec<Point2D> {
pub(crate) enum PathPoints<'a> {
Borrowed(&'a [Point2D]),
Owned(Vec<Point2D>),
}
impl<'a> PathPoints<'a> {
pub(crate) fn as_slice(&self) -> &[Point2D] {
match self {
Self::Borrowed(points) => points,
Self::Owned(points) => points.as_slice(),
}
}
}
/// Flatten a Path scene node into doc-space points, borrowing the
/// original point slice for the common handle-free open-path case.
pub(crate) fn flatten_path_points(node: &SceneNode) -> PathPoints<'_> {
let anchors = &node.path_anchors;
let has_handle = anchors
.iter()
.any(|a| a.handle_in.is_some() || a.handle_out.is_some());
if anchors.len() < 2 || !has_handle {
if !node.path_closed {
return PathPoints::Borrowed(&node.points);
}
let mut out = node.points.clone();
// Closed handle-free path — link the polyline back to its
// start so the closing edge is drawn.
if node.path_closed && out.len() > 2 {
out.push(out[0]);
}
return out;
return PathPoints::Owned(out);
}
let mut out = Vec::with_capacity(anchors.len() * 16 + 16);
out.push(anchors[0].pos);
@ -190,7 +205,18 @@ pub(crate) fn flatten_path(node: &SceneNode) -> Vec<Point2D> {
if node.path_closed {
flatten_segment(&anchors[anchors.len() - 1], &anchors[0], &mut out);
}
out
PathPoints::Owned(out)
}
/// Flatten a Path scene node into a doc-space polyline — cubic
/// segments whose endpoints carry handles are tessellated; a
/// handle-free path falls back to the straight `points` polyline.
/// A closed path appends the last-anchor → first-anchor segment.
pub(crate) fn flatten_path(node: &SceneNode) -> Vec<Point2D> {
match flatten_path_points(node) {
PathPoints::Borrowed(points) => points.to_vec(),
PathPoints::Owned(points) => points,
}
}
/// Push a children-clip for a `clipContent` container (root frames
@ -516,11 +542,12 @@ fn paint_node_inner(
// Bezier-aware: when the path carries anchors with control
// handles, flatten each cubic segment; otherwise fall back
// to the straight `points` polyline.
let polyline = flatten_path(node);
let polyline = flatten_path_points(node);
let points = polyline.as_slice();
// A closed path with a fill paints its enclosed area.
let filled = node.path_closed && node.fill.is_some();
if filled {
let world: Vec<Point2D> = polyline.iter().map(|p| to_world(*p)).collect();
let world: Vec<Point2D> = points.iter().map(|p| to_world(*p)).collect();
cx.backend.fill_polygon(&world, node.fill.unwrap());
}
// Stroke: an explicit stroke always paints; with no
@ -536,7 +563,7 @@ fn paint_node_inner(
None => None,
};
if let Some((color, width)) = stroke {
for pair in polyline.windows(2) {
for pair in points.windows(2) {
cx.backend
.stroke_line(to_world(pair[0]), to_world(pair[1]), color, width);
}

View file

@ -491,7 +491,7 @@ mod text_tests {
mod path_tests {
use crate::layout_scene::{NodeKind, SceneAnchor, SceneNode, ScenePointType};
use crate::widgets::canvas_viewport_paint::flatten_path;
use crate::widgets::canvas_viewport_paint::{flatten_path, flatten_path_points, PathPoints};
use crate::{Point2D, Rect};
fn anchor(x: f32, y: f32, hout: Option<Point2D>) -> SceneAnchor {
@ -511,6 +511,18 @@ mod path_tests {
assert_eq!(flatten_path(&n), n.points);
}
#[test]
fn handle_free_open_path_borrows_points_without_allocating() {
let mut n = SceneNode::leaf("p", NodeKind::Path);
n.points = vec![Point2D::new(0.0, 0.0), Point2D::new(10.0, 0.0)];
n.path_anchors = vec![anchor(0.0, 0.0, None), anchor(10.0, 0.0, None)];
let points = flatten_path_points(&n);
assert!(matches!(points, PathPoints::Borrowed(_)));
assert_eq!(points.as_slice(), n.points.as_slice());
}
#[test]
fn curved_segment_tessellates_into_many_points() {
let mut n = SceneNode::leaf("p", NodeKind::Path);