blob: 9940ad595649890fcd6aa6cb65add81ae79435cb [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::{cell::RefCell, rc::Rc};
use crate::{
context::{Context, ContextInner},
path::Path,
spinel_sys::*,
Point,
};
#[derive(Clone, Debug)]
enum PathCommand {
Move(Point),
Line(Point),
Quad([Point; 2]),
QuadSmooth(Point),
Cubic([Point; 3]),
CubicSmooth([Point; 2]),
RatQuad([Point; 2], f32),
RatCubic([Point; 3], f32, f32),
}
/// Spinel `Path` builder.
///
/// Is actually a thin wrapper over the [spn_path_builder_t] stored in [`Context`].
///
/// [spn_path_builder_t]: https://fuchsia.googlesource.com/fuchsia/+/HEAD/src/graphics/lib/compute/spinel/spinel.h#34
///
/// # Examples
///
/// ```no_run
/// # use spinel_rs::{Context, PathBuilder, Point};
/// #
/// # fn catch() -> Option<()> {
/// # let context: Context = unimplemented!();
/// #
/// let tl = Point { x: 1.0, y: 1.0 };
/// let tr = Point { x: 5.0, y: 1.0 };
/// let br = Point { x: 5.0, y: 5.0 };
/// let bl = Point { x: 1.0, y: 5.0 };
///
/// let rectangle = PathBuilder::new(&context, tl)
/// .line_to(tr)
/// .line_to(br)
/// .line_to(bl)
/// .line_to(tl)
/// .build()?;
/// # None
/// # }
/// ```
#[derive(Clone, Debug)]
pub struct PathBuilder {
context: Rc<RefCell<ContextInner>>,
cmds: Vec<PathCommand>,
}
macro_rules! panic_if_not_finite {
( $point:expr ) => {
if !$point.is_finite() {
panic!("{:?} does not have finite f32 values", $point);
}
};
}
impl PathBuilder {
/// Creates a new path builder that keeps track of an `end-point` which is the last point of the
/// path used in subsequent calls.
///
/// `start_point` is the first `end-point`. [spn_path_move_to]
///
/// [spn_path_move_to]: https://fuchsia.googlesource.com/fuchsia/+/HEAD/src/graphics/lib/compute/spinel/spinel.h#73
pub fn new(context: &Context, start_point: Point) -> Self {
Self { context: Rc::clone(&context.inner), cmds: vec![PathCommand::Move(start_point)] }
}
/// Adds line from `end-point` to `point`. [spn_path_line_to]
///
/// [spn_path_line_to]: https://fuchsia.googlesource.com/fuchsia/+/HEAD/src/graphics/lib/compute/spinel/spinel.h#77
pub fn line_to(mut self, point: Point) -> Self {
panic_if_not_finite!(point);
self.cmds.push(PathCommand::Line(point));
self
}
/// Adds quadratic Bézier from `end-point` to `point[1]` with `point[0]` as a control point.
/// [spn_path_quad_to]
///
/// [spn_path_quad_to]: https://fuchsia.googlesource.com/fuchsia/+/HEAD/src/graphics/lib/compute/spinel/spinel.h#92
pub fn quad_to(mut self, points: [Point; 2]) -> Self {
for point in &points {
panic_if_not_finite!(point);
}
self.cmds.push(PathCommand::Quad(points));
self
}
/// Adds smooth quadratic Bézier from `end-point` to `point`. [spn_path_quad_smooth_to]
///
/// [spn_path_quad_smooth_to]: https://fuchsia.googlesource.com/fuchsia/+/HEAD/src/graphics/lib/compute/spinel/spinel.h#97
pub fn quad_smooth_to(mut self, point: Point) -> Self {
panic_if_not_finite!(point);
self.cmds.push(PathCommand::QuadSmooth(point));
self
}
/// Adds cubic Bézier from `end-point` to `point[2]` with `point[0]` and `point[1]` as control
/// points. [spn_path_cubic_to]
///
/// [spn_path_cubic_to]: https://fuchsia.googlesource.com/fuchsia/+/HEAD/src/graphics/lib/compute/spinel/spinel.h#81
pub fn cubic_to(mut self, points: [Point; 3]) -> Self {
for point in &points {
panic_if_not_finite!(point);
}
self.cmds.push(PathCommand::Cubic(points));
self
}
/// Adds smooth cubic Bézier from `end-point` to `point[1]` with `point[0]` as control point.
/// [spn_path_cubic_smooth_to]
///
/// [spn_path_cubic_smooth_to]: https://fuchsia.googlesource.com/fuchsia/+/HEAD/src/graphics/lib/compute/spinel/spinel.h#87
pub fn cubic_smooth_to(mut self, points: [Point; 2]) -> Self {
for point in &points {
panic_if_not_finite!(point);
}
self.cmds.push(PathCommand::CubicSmooth(points));
self
}
/// Adds rational quadratic Bézier from `end-point` to `point[1]` with `point[0]` as a control
/// point and `w0` as weight. [spn_path_rat_quad_to]
///
/// [spn_path_rat_quad_to]: https://fuchsia.googlesource.com/fuchsia/+/HEAD/src/graphics/lib/compute/spinel/spinel.h#101
pub fn rat_quad_to(mut self, points: [Point; 2], w0: f32) -> Self {
for point in &points {
panic_if_not_finite!(point);
}
if !w0.is_finite() {
panic!("{} (w0) is not finite", w0);
}
self.cmds.push(PathCommand::RatQuad(points, w0));
self
}
/// Adds rational cubic Bézier from `end-point` to `point[2]` with `point[0]` and `point[1]` as
/// control points, and `w0` and `w1` as weights. [spn_path_rat_cubic_to]
///
/// [spn_path_rat_cubic_to]: https://fuchsia.googlesource.com/fuchsia/+/HEAD/src/graphics/lib/compute/spinel/spinel.h#107
pub fn rat_cubic_to(mut self, points: [Point; 3], w0: f32, w1: f32) -> Self {
for point in &points {
panic_if_not_finite!(point);
}
if !w0.is_finite() {
panic!("{} (w0) is not finite", w0);
}
if !w1.is_finite() {
panic!("{} (w1) is not finite", w1);
}
self.cmds.push(PathCommand::RatCubic(points, w0, w1));
self
}
fn end_point(&self) -> Point {
match self.cmds.last().expect("PathBuilder should always be initialized with Move") {
PathCommand::Move(p) => *p,
PathCommand::Line(p) => *p,
PathCommand::Quad(points) => points[1],
PathCommand::QuadSmooth(p) => *p,
PathCommand::Cubic(points) => points[2],
PathCommand::CubicSmooth(points) => points[1],
PathCommand::RatQuad(points, ..) => points[1],
PathCommand::RatCubic(points, ..) => points[2],
}
}
/// Builds `Path`. Calls [spn_path_begin] and [spn_path_end] to allocate the path.
///
/// If the path is not closed, a straight line will be added to connect the end-point back to
/// the start-point.
///
/// If there is not enough memory to allocate the path, `None` is returned instead.
///
/// [spn_path_begin]: https://fuchsia.googlesource.com/fuchsia/+/HEAD/src/graphics/lib/compute/spinel/spinel.h#55
/// [spn_path_end]: https://fuchsia.googlesource.com/fuchsia/+/HEAD/src/graphics/lib/compute/spinel/spinel.h#58
pub fn build(mut self) -> Option<Path> {
macro_rules! success {
( $result:expr, $path_builder:expr $( , )? ) => {{
if let Err(SpnError::SpnErrorPathBuilderLost) = $result.res() {
$path_builder.context.borrow_mut().reset_path_builder();
return None;
}
$result.success();
}};
}
unsafe {
let spn_path_builder = self.context.borrow().spn_path_builder;
success!(spn_path_begin(spn_path_builder), self);
let start_point = match self.cmds[0] {
PathCommand::Move(p) => p,
_ => panic!("PathBuilder should always be initialized with Move"),
};
let end_point = self.end_point();
// If path is not closed, close it with a line.
if !start_point.approx_eq(end_point) {
self.cmds.push(PathCommand::Line(start_point));
}
for cmd in self.cmds {
match cmd {
PathCommand::Move(p) => {
success!(spn_path_move_to(spn_path_builder, p.x, p.y), self,)
}
PathCommand::Line(p) => {
success!(spn_path_line_to(spn_path_builder, p.x, p.y), self,)
}
PathCommand::Quad([p1, p2]) => {
success!(spn_path_quad_to(spn_path_builder, p1.x, p1.y, p2.x, p2.y), self,)
}
PathCommand::QuadSmooth(p) => {
success!(spn_path_quad_smooth_to(spn_path_builder, p.x, p.y), self,)
}
PathCommand::Cubic([p1, p2, p3]) => success!(
spn_path_cubic_to(spn_path_builder, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y),
self,
),
PathCommand::CubicSmooth([p2, p3]) => success!(
spn_path_cubic_smooth_to(spn_path_builder, p2.x, p2.y, p3.x, p3.y),
self,
),
PathCommand::RatQuad([p1, p2], w0) => success!(
spn_path_rat_quad_to(spn_path_builder, p1.x, p1.y, p2.x, p2.y, w0),
self,
),
PathCommand::RatCubic([p1, p2, p3], w0, w1) => success!(
spn_path_rat_cubic_to(
spn_path_builder,
p1.x,
p1.y,
p2.x,
p2.y,
p3.x,
p3.y,
w0,
w1,
),
self,
),
}
}
let mut spn_path = Default::default();
success!(spn_path_end(spn_path_builder, &mut spn_path as *mut _), self);
Some(Path::new(&self.context, spn_path))
}
}
}
#[cfg(test)]
mod tests {
use std::f32;
use super::*;
const FINITE: Point = Point { x: 0.0, y: 1.0 };
const X_NAN: Point = Point { x: f32::NAN, y: 1.0 };
const Y_INF: Point = Point { x: 0.0, y: f32::INFINITY };
fn new_path_builder() -> PathBuilder {
let context = Context::new();
PathBuilder::new(&context, Point::default())
}
#[test]
fn point_is_finite() {
assert!(FINITE.is_finite());
assert!(!X_NAN.is_finite());
assert!(!Y_INF.is_finite());
}
#[test]
fn line_to_finite() {
let path_builder = new_path_builder();
path_builder.line_to(FINITE);
}
#[test]
#[should_panic]
fn line_to_non_finite() {
let path_builder = new_path_builder();
path_builder.line_to(X_NAN);
}
#[test]
fn quad_to_finite() {
let path_builder = new_path_builder();
path_builder.quad_to([FINITE, FINITE]);
}
#[test]
#[should_panic]
fn quad_to_non_finite() {
let path_builder = new_path_builder();
path_builder.quad_to([X_NAN, FINITE]);
}
#[test]
fn quad_smooth_to_finite() {
let path_builder = new_path_builder();
path_builder.quad_smooth_to(FINITE);
}
#[test]
#[should_panic]
fn quad_smooth_to_non_finite() {
let path_builder = new_path_builder();
path_builder.quad_smooth_to(X_NAN);
}
#[test]
fn cubic_to_finite() {
let path_builder = new_path_builder();
path_builder.cubic_to([FINITE, FINITE, FINITE]);
}
#[test]
#[should_panic]
fn cubic_to_non_finite() {
let path_builder = new_path_builder();
path_builder.cubic_to([X_NAN, FINITE, FINITE]);
}
#[test]
fn cubic_smooth_to_finite() {
let path_builder = new_path_builder();
path_builder.cubic_smooth_to([FINITE, FINITE]);
}
#[test]
#[should_panic]
fn cubic_smooth_to_non_finite() {
let path_builder = new_path_builder();
path_builder.cubic_smooth_to([X_NAN, FINITE]);
}
#[test]
fn rat_quad_to_finite() {
let path_builder = new_path_builder();
path_builder.rat_quad_to([FINITE, FINITE], 0.0);
}
#[test]
#[should_panic]
fn rat_quad_to_non_finite_points() {
let path_builder = new_path_builder();
path_builder.rat_quad_to([X_NAN, FINITE], 0.0);
}
#[test]
#[should_panic]
fn rat_quad_to_non_finite_w0() {
let path_builder = new_path_builder();
path_builder.rat_quad_to([FINITE, FINITE], f32::NAN);
}
#[test]
fn rat_cubic_to_finite() {
let path_builder = new_path_builder();
path_builder.rat_cubic_to([FINITE, FINITE, FINITE], 0.0, 0.0);
}
#[test]
#[should_panic]
fn rat_cubic_to_non_finite_points() {
let path_builder = new_path_builder();
path_builder.rat_cubic_to([X_NAN, FINITE, FINITE], 0.0, 0.0);
}
#[test]
#[should_panic]
fn rat_cubic_to_non_finite_w0() {
let path_builder = new_path_builder();
path_builder.rat_cubic_to([FINITE, FINITE, FINITE], f32::NAN, 0.0);
}
#[test]
#[should_panic]
fn rat_cubic_to_non_finite_w1() {
let path_builder = new_path_builder();
path_builder.rat_cubic_to([FINITE, FINITE, FINITE], 0.0, f32::NAN);
}
}