// Copyright 2006 The Android Open Source Project // Copyright 2020 Yevhenii Reizner // // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // NOTE: this is not SkPathBuilder, but rather a reimplementation of SkPath. use alloc::vec; use alloc::vec::Vec; use crate::{Path, Point, Rect}; use crate::path::PathVerb; use crate::path_geometry; use crate::scalar::{Scalar, SCALAR_ROOT_2_OVER_2}; #[derive(Copy, Clone, PartialEq, Debug)] pub(crate) enum PathDirection { /// Clockwise direction for adding closed contours. CW, /// Counter-clockwise direction for adding closed contours. CCW, } /// A path builder. #[derive(Clone, Default, Debug)] pub struct PathBuilder { pub(crate) verbs: Vec, pub(crate) points: Vec, pub(crate) last_move_to_index: usize, pub(crate) move_to_required: bool, } impl PathBuilder { /// Creates a new builder. pub fn new() -> Self { PathBuilder { verbs: Vec::new(), points: Vec::new(), last_move_to_index: 0, move_to_required: true, } } /// Creates a new builder with a specified capacity. /// /// Number of points depends on a verb type: /// /// - Move - 1 /// - Line - 1 /// - Quad - 2 /// - Cubic - 3 /// - Close - 0 pub fn with_capacity(verbs_capacity: usize, points_capacity: usize) -> Self { PathBuilder { verbs: Vec::with_capacity(verbs_capacity), points: Vec::with_capacity(points_capacity), last_move_to_index: 0, move_to_required: true, } } /// Creates a new `Path` from `Rect`. /// /// Never fails since `Rect` is always valid. /// /// Segments are created clockwise: TopLeft -> TopRight -> BottomRight -> BottomLeft /// /// The contour is closed. pub fn from_rect(rect: Rect) -> Path { let verbs = vec![ PathVerb::Move, PathVerb::Line, PathVerb::Line, PathVerb::Line, PathVerb::Close, ]; let points = vec![ Point::from_xy(rect.left(), rect.top()), Point::from_xy(rect.right(), rect.top()), Point::from_xy(rect.right(), rect.bottom()), Point::from_xy(rect.left(), rect.bottom()), ]; Path { bounds: rect, verbs, points, } } /// Creates a new `Path` from a circle. /// /// See [`PathBuilder::push_circle`] for details. pub fn from_circle(cx: f32, cy: f32, radius: f32) -> Option { let mut b = PathBuilder::new(); b.push_circle(cx, cy, radius); b.finish() } /// Creates a new `Path` from an oval. /// /// See [`PathBuilder::push_oval`] for details. pub fn from_oval(oval: Rect) -> Option { let mut b = PathBuilder::new(); b.push_oval(oval); b.finish() } pub(crate) fn reserve(&mut self, additional_verbs: usize, additional_points: usize) { self.verbs.reserve(additional_verbs); self.points.reserve(additional_points); } /// Returns the current number of segments in the builder. pub fn len(&self) -> usize { self.verbs.len() } /// Checks if the builder has any segments added. pub fn is_empty(&self) -> bool { self.verbs.is_empty() } /// Adds beginning of a contour. /// /// Multiple continuous MoveTo segments are not allowed. /// If the previous segment was also MoveTo, it will be overwritten with the current one. pub fn move_to(&mut self, x: f32, y: f32) { if let Some(PathVerb::Move) = self.verbs.last() { let last_idx = self.points.len() - 1; self.points[last_idx] = Point::from_xy(x, y); } else { self.last_move_to_index = self.points.len(); self.move_to_required = false; self.verbs.push(PathVerb::Move); self.points.push(Point::from_xy(x, y)); } } fn inject_move_to_if_needed(&mut self) { if self.move_to_required { match self.points.get(self.last_move_to_index).cloned() { Some(p) => self.move_to(p.x, p.y), None => self.move_to(0.0, 0.0), } } } /// Adds a line from the last point. /// /// - If `Path` is empty - adds Move(0, 0) first. /// - If `Path` ends with Close - adds Move(last_x, last_y) first. pub fn line_to(&mut self, x: f32, y: f32) { self.inject_move_to_if_needed(); self.verbs.push(PathVerb::Line); self.points.push(Point::from_xy(x, y)); } /// Adds a quad curve from the last point to `x`, `y`. /// /// - If `Path` is empty - adds Move(0, 0) first. /// - If `Path` ends with Close - adds Move(last_x, last_y) first. pub fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) { self.inject_move_to_if_needed(); self.verbs.push(PathVerb::Quad); self.points.push(Point::from_xy(x1, y1)); self.points.push(Point::from_xy(x, y)); } pub(crate) fn quad_to_pt(&mut self, p1: Point, p: Point) { self.quad_to(p1.x, p1.y, p.x, p.y); } // We do not support conic segments, but Skia still relies on them from time to time. // This method will simply convert the input data into quad segments. pub(crate) fn conic_to(&mut self, x1: f32, y1: f32, x: f32, y: f32, weight: f32) { // check for <= 0 or NaN with this test if !(weight > 0.0) { self.line_to(x, y); } else if !weight.is_finite() { self.line_to(x1, y1); self.line_to(x, y); } else if weight == 1.0 { self.quad_to(x1, y1, x, y); } else { self.inject_move_to_if_needed(); let last = self.last_point().unwrap(); let quadder = path_geometry::AutoConicToQuads::compute( last, Point::from_xy(x1, y1), Point::from_xy(x, y), weight, ); if let Some(quadder) = quadder { // Points are ordered as: 0 - 1 2 - 3 4 - 5 6 - .. // `count` is a number of pairs +1 let mut offset = 1; for _ in 0..quadder.len { let pt1 = quadder.points[offset + 0]; let pt2 = quadder.points[offset + 1]; self.quad_to(pt1.x, pt1.y, pt2.x, pt2.y); offset += 2; } } } } pub(crate) fn conic_points_to(&mut self, pt1: Point, pt2: Point, weight: f32) { self.conic_to(pt1.x, pt1.y, pt2.x, pt2.y, weight); } /// Adds a cubic curve from the last point to `x`, `y`. /// /// - If `Path` is empty - adds Move(0, 0) first. /// - If `Path` ends with Close - adds Move(last_x, last_y) first. pub fn cubic_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) { self.inject_move_to_if_needed(); self.verbs.push(PathVerb::Cubic); self.points.push(Point::from_xy(x1, y1)); self.points.push(Point::from_xy(x2, y2)); self.points.push(Point::from_xy(x, y)); } pub(crate) fn cubic_to_pt(&mut self, p1: Point, p2: Point, p: Point) { self.cubic_to(p1.x, p1.y, p2.x, p2.y, p.x, p.y); } /// Closes the current contour. /// /// A closed contour connects the first and the last Point /// with a line, forming a continuous loop. /// /// Does nothing when `Path` is empty or already closed. /// /// Open and closed contour will be filled the same way. /// Stroking an open contour will add LineCap at contour's start and end. /// Stroking an closed contour will add LineJoin at contour's start and end. pub fn close(&mut self) { // don't add a close if it's the first verb or a repeat if !self.verbs.is_empty() { if self.verbs.last().cloned() != Some(PathVerb::Close) { self.verbs.push(PathVerb::Close); } } self.move_to_required = true; } /// Returns the last point if any. pub fn last_point(&self) -> Option { self.points.last().cloned() } pub(crate) fn set_last_point(&mut self, pt: Point) { match self.points.last_mut() { Some(last) => *last = pt, None => self.move_to(pt.x, pt.y), } } pub(crate) fn is_zero_length_since_point(&self, start_pt_index: usize) -> bool { let count = self.points.len() - start_pt_index; if count < 2 { return true; } let first = self.points[start_pt_index]; for i in 1..count { if first != self.points[start_pt_index + i] { return false; } } true } /// Adds a rectangle contour. /// /// The contour is closed and has a clock-wise direction. /// /// Does nothing when: /// - any value is not finite or really large pub fn push_rect(&mut self, x: f32, y: f32, w: f32, h: f32) { if let Some(rect) = Rect::from_xywh(x, y, w, h) { self.move_to(rect.left(), rect.top()); self.line_to(rect.right(), rect.top()); self.line_to(rect.right(), rect.bottom()); self.line_to(rect.left(), rect.bottom()); self.close(); } } /// Adds an oval contour bounded by the provided rectangle. /// /// The contour is closed and has a clock-wise direction. pub fn push_oval(&mut self, oval: Rect) { let cx = oval.left().half() + oval.right().half(); let cy = oval.top().half() + oval.bottom().half(); let oval_points = [ Point::from_xy(cx, oval.bottom()), Point::from_xy(oval.left(), cy), Point::from_xy(cx, oval.top()), Point::from_xy(oval.right(), cy), ]; let rect_points = [ Point::from_xy(oval.right(), oval.bottom()), Point::from_xy(oval.left(), oval.bottom()), Point::from_xy(oval.left(), oval.top()), Point::from_xy(oval.right(), oval.top()), ]; let weight = SCALAR_ROOT_2_OVER_2; self.move_to(oval_points[3].x, oval_points[3].y); for (p1, p2) in rect_points.iter().zip(oval_points.iter()) { self.conic_points_to(*p1, *p2, weight); } self.close(); } /// Adds a circle contour. /// /// The contour is closed and has a clock-wise direction. /// /// Does nothing when: /// - `radius` <= 0 /// - any value is not finite or really large pub fn push_circle(&mut self, x: f32, y: f32, r: f32) { if let Some(r) = Rect::from_xywh(x - r, y - r, r + r, r + r) { self.push_oval(r); } } pub(crate) fn push_path(&mut self, other: &PathBuilder) { if other.is_empty() { return; } if self.last_move_to_index != 0 { self.last_move_to_index = self.points.len() + other.last_move_to_index; } self.verbs.extend_from_slice(&other.verbs); self.points.extend_from_slice(&other.points); } /// Appends, in a reverse order, the first contour of path ignoring path's last point. pub(crate) fn reverse_path_to(&mut self, other: &PathBuilder) { if other.is_empty() { return; } debug_assert_eq!(other.verbs[0], PathVerb::Move); let mut points_offset = other.points.len() - 1; for verb in other.verbs.iter().rev() { match verb { PathVerb::Move => { // if the path has multiple contours, stop after reversing the last break; } PathVerb::Line => { // We're moving one point back manually, to prevent points_offset overflow. let pt = other.points[points_offset - 1]; points_offset -= 1; self.line_to(pt.x, pt.y); } PathVerb::Quad => { let pt1 = other.points[points_offset - 1]; let pt2 = other.points[points_offset - 2]; points_offset -= 2; self.quad_to(pt1.x, pt1.y, pt2.x, pt2.y); } PathVerb::Cubic => { let pt1 = other.points[points_offset - 1]; let pt2 = other.points[points_offset - 2]; let pt3 = other.points[points_offset - 3]; points_offset -= 3; self.cubic_to(pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y); } PathVerb::Close => {} } } } /// Reset the builder. /// /// Memory is not deallocated. pub fn clear(&mut self) { self.verbs.clear(); self.points.clear(); self.last_move_to_index = 0; self.move_to_required = true; } /// Finishes the builder and returns a `Path`. /// /// Returns `None` when `Path` is empty or has invalid bounds. pub fn finish(self) -> Option { if self.is_empty() { return None; } // Just a move to? Bail. if self.verbs.len() == 1 { return None; } let bounds = Rect::from_points(&self.points)?; Some(Path { bounds, verbs: self.verbs, points: self.points, }) } }