426 lines
13 KiB
Rust
426 lines
13 KiB
Rust
// 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<PathVerb>,
|
|
pub(crate) points: Vec<Point>,
|
|
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<Path> {
|
|
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<Path> {
|
|
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<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
|
|
}
|
|
|
|
/// 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<Path> {
|
|
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,
|
|
})
|
|
}
|
|
}
|