mirror of
https://github.com/penpot/penpot.git
synced 2026-06-17 04:42:03 +00:00
🐛 Fix stroke to path extra points
This commit is contained in:
parent
b03537fa68
commit
fc17d168a7
@ -156,6 +156,7 @@ impl Path {
|
||||
let mut current_point = 0;
|
||||
let mut current_conic = 0;
|
||||
let mut last_point = skia::Point::new(0.0, 0.0);
|
||||
let mut subpath_start = skia::Point::new(0.0, 0.0);
|
||||
|
||||
for verb in verbs {
|
||||
match verb {
|
||||
@ -163,12 +164,15 @@ impl Path {
|
||||
let p = points[current_point];
|
||||
segments.push(Segment::MoveTo((p.x, p.y)));
|
||||
last_point = p;
|
||||
subpath_start = p;
|
||||
current_point += 1;
|
||||
}
|
||||
skia::PathVerb::Line => {
|
||||
let p = points[current_point];
|
||||
segments.push(Segment::LineTo((p.x, p.y)));
|
||||
last_point = p;
|
||||
if p != last_point {
|
||||
segments.push(Segment::LineTo((p.x, p.y)));
|
||||
last_point = p;
|
||||
}
|
||||
current_point += 1;
|
||||
}
|
||||
skia::PathVerb::Quad => {
|
||||
@ -239,11 +243,20 @@ impl Path {
|
||||
current_point += 3;
|
||||
}
|
||||
skia::PathVerb::Close => {
|
||||
if let Some(Segment::LineTo(p)) = segments.last() {
|
||||
if (p.0 - subpath_start.x).abs() < 1e-5
|
||||
&& (p.1 - subpath_start.y).abs() < 1e-5
|
||||
{
|
||||
segments.pop();
|
||||
}
|
||||
}
|
||||
segments.push(Segment::Close);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
simplify_collinear_lines(&mut segments);
|
||||
|
||||
let mut result = Path::new(segments);
|
||||
result.skia_path.set_fill_type(fill_type);
|
||||
result
|
||||
@ -341,3 +354,48 @@ impl Path {
|
||||
math::Bounds::from_rect(self.skia_path.bounds())
|
||||
}
|
||||
}
|
||||
|
||||
fn segment_endpoint(seg: &Segment) -> Option<(f32, f32)> {
|
||||
match seg {
|
||||
Segment::MoveTo(p) | Segment::LineTo(p) => Some(*p),
|
||||
Segment::CurveTo((_, _, p)) => Some(*p),
|
||||
Segment::Close => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes intermediate LineTo segments that lie on a straight line
|
||||
/// between their previous and next endpoints. Only merges when the segments
|
||||
/// go in the same direction (dot >= 0), avoiding modification of backtracking paths.
|
||||
fn simplify_collinear_lines(segments: &mut Vec<Segment>) {
|
||||
let mut i = 2;
|
||||
while i < segments.len() {
|
||||
let p0 = segment_endpoint(&segments[i - 2]);
|
||||
let p1 = match &segments[i - 1] {
|
||||
Segment::LineTo(p) => *p,
|
||||
_ => {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let p2 = match &segments[i] {
|
||||
Segment::LineTo(p) => *p,
|
||||
_ => {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if let Some(p0) = p0 {
|
||||
let dx1 = p1.0 - p0.0;
|
||||
let dy1 = p1.1 - p0.1;
|
||||
let dx2 = p2.0 - p1.0;
|
||||
let dy2 = p2.1 - p1.1;
|
||||
let cross = dx1 * dy2 - dy1 * dx2;
|
||||
let dot = dx1 * dx2 + dy1 * dy2;
|
||||
if cross.abs() < 1e-1 && dot >= 0.0 {
|
||||
segments.remove(i - 1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,27 +58,30 @@ pub fn stroke_to_path(
|
||||
// For inner/outer strokes, use boolean ops to clip
|
||||
// the 2×-width stroke outline to the correct region.
|
||||
// Set EvenOdd to preserve the annular ring's inner hole,
|
||||
// then as_winding() on the result fixes contour winding
|
||||
// for Penpot's NonZero fill rule.
|
||||
// then switch to Winding for Penpot's NonZero fill rule.
|
||||
// Use set_fill_type instead of as_winding() because as_winding()
|
||||
// decomposes self-intersecting geometry, which removes points
|
||||
// at intersections of straight lines in closed paths.
|
||||
// Center strokes skip the conversion: fill_path_with_paint
|
||||
// already produces correctly-wound contours.
|
||||
let final_path = match render_kind {
|
||||
StrokeKind::Inner => {
|
||||
stroke_outline.set_fill_type(skia::PathFillType::EvenOdd);
|
||||
let inner = stroke_outline
|
||||
stroke_outline.set_fill_type(skia::PathFillType::Winding);
|
||||
let mut inner = stroke_outline
|
||||
.op(&transformed_shape_path, skia::PathOp::Intersect)
|
||||
.unwrap_or(stroke_outline);
|
||||
inner.as_winding().unwrap_or(inner)
|
||||
inner.set_fill_type(skia::PathFillType::Winding);
|
||||
inner
|
||||
}
|
||||
StrokeKind::Outer => {
|
||||
stroke_outline.set_fill_type(skia::PathFillType::EvenOdd);
|
||||
let outer = stroke_outline
|
||||
stroke_outline.set_fill_type(skia::PathFillType::Winding);
|
||||
let mut outer = stroke_outline
|
||||
.op(&transformed_shape_path, skia::PathOp::Difference)
|
||||
.unwrap_or(stroke_outline);
|
||||
outer.as_winding().unwrap_or(outer)
|
||||
}
|
||||
StrokeKind::Center => {
|
||||
stroke_outline.set_fill_type(skia::PathFillType::EvenOdd);
|
||||
stroke_outline.as_winding().unwrap_or(stroke_outline)
|
||||
outer.set_fill_type(skia::PathFillType::Winding);
|
||||
outer
|
||||
}
|
||||
StrokeKind::Center => stroke_outline,
|
||||
};
|
||||
|
||||
// If there was a path_transform, invert it back to local coords
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user