use alloc::vec;
use alloc::vec::Vec;
use crate::{Point, Rect, Path};
use crate::path_geometry;
use crate::path::PathVerb;
use crate::scalar::{Scalar, SCALAR_ROOT_2_OVER_2};
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum PathDirection {
CW,
CCW,
}
#[derive(Clone, Default, Debug)]
pub struct PathBuilder {
pub(crate) verbs: Vec<PathVerb>,
pub(crate) points: Vec<Point>,
pub(crate) last_move_to_index: usize,
pub(crate) move_to_required: bool,
}
impl PathBuilder {
pub fn new() -> Self {
PathBuilder {
verbs: Vec::new(),
points: Vec::new(),
last_move_to_index: 0,
move_to_required: true,
}
}
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,
}
}
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,
}
}
pub fn from_circle(cx: f32, cy: f32, radius: f32) -> Option<Path> {
let mut b = PathBuilder::new();
b.push_circle(cx, cy, radius);
b.finish()
}
pub(crate) fn reserve(&mut self, additional_verbs: usize, additional_points: usize) {
self.verbs.reserve(additional_verbs);
self.points.reserve(additional_points);
}
pub fn len(&self) -> usize {
self.verbs.len()
}
pub fn is_empty(&self) -> bool {
self.verbs.is_empty()
}
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),
}
}
}
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));
}
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);
}
pub(crate) fn conic_to(&mut self, x1: f32, y1: f32, x: f32, y: f32, weight: f32) {
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 {
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);
}
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);
}
pub fn close(&mut self) {
if !self.verbs.is_empty() {
if self.verbs.last().cloned() != Some(PathVerb::Close) {
self.verbs.push(PathVerb::Close);
}
}
self.move_to_required = true;
}
pub(crate) fn last_point(&self) -> Option<Point> {
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
}
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();
}
}
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();
}
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);
}
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 => {
break;
}
PathVerb::Line => {
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 => {}
}
}
}
pub fn clear(&mut self) {
self.verbs.clear();
self.points.clear();
self.last_move_to_index = 0;
self.move_to_required = true;
}
pub fn finish(self) -> Option<Path> {
if self.is_empty() {
return None;
}
if self.verbs.len() == 1 {
return None;
}
let bounds = Rect::from_points(&self.points)?;
Some(Path {
bounds,
verbs: self.verbs,
points: self.points,
})
}
}