use super::{MAX_PATH_VERBS, Path2D, PathVerb, invalid};
use crate::{Point, ValidationError, Vector2};
use std::sync::Arc;
impl Path2D {
pub fn move_to(&mut self, point: Point) -> &mut Self {
self.author(PathVerb::MoveTo(point))
}
pub fn try_move_to(&mut self, point: Point) -> Result<(), ValidationError> {
self.push(PathVerb::MoveTo(point))
}
pub fn line_to(&mut self, point: Point) -> &mut Self {
self.author(PathVerb::LineTo(point))
}
pub fn try_line_to(&mut self, point: Point) -> Result<(), ValidationError> {
self.push(PathVerb::LineTo(point))
}
pub fn quadratic_to(&mut self, control: Point, end: Point) -> &mut Self {
self.author(PathVerb::QuadraticTo(control, end))
}
pub fn try_quadratic_to(&mut self, control: Point, end: Point) -> Result<(), ValidationError> {
self.push(PathVerb::QuadraticTo(control, end))
}
pub fn cubic_to(&mut self, first: Point, second: Point, end: Point) -> &mut Self {
self.author(PathVerb::CubicTo(first, second, end))
}
pub fn try_cubic_to(
&mut self,
first: Point,
second: Point,
end: Point,
) -> Result<(), ValidationError> {
self.push(PathVerb::CubicTo(first, second, end))
}
pub fn arc_to(
&mut self,
center: Point,
radii: Vector2,
start_angle: f64,
sweep_angle: f64,
) -> &mut Self {
if self.data.error.is_none()
&& let Err(error) = self.try_arc_to(center, radii, start_angle, sweep_angle)
{
Arc::make_mut(&mut self.data).error = Some(error);
}
self
}
pub fn try_arc_to(
&mut self,
center: Point,
radii: Vector2,
start_angle: f64,
sweep_angle: f64,
) -> Result<(), ValidationError> {
self.elliptical_arc(center, radii, start_angle, sweep_angle, 0.0)
}
pub(super) fn elliptical_arc(
&mut self,
center: Point,
radii: Vector2,
start_angle: f64,
sweep_angle: f64,
rotation: f64,
) -> Result<(), ValidationError> {
if ![
center.x,
center.y,
radii.x,
radii.y,
start_angle,
sweep_angle,
rotation,
]
.iter()
.all(|value| value.is_finite())
|| radii.x < 0.0
|| radii.y < 0.0
{
return Err(invalid(
"arc",
"Arc parameters must be finite with nonnegative radii",
));
}
let count = libm::ceil(sweep_angle.abs() / std::f64::consts::FRAC_PI_8);
if count > MAX_PATH_VERBS.saturating_sub(self.verbs().len() + 1) as f64 {
return Err(ValidationError::limit(
"path2d",
"verbs",
"Arc exceeds the verb allowance",
));
}
let start = start_angle.rem_euclid(std::f64::consts::TAU);
let rotate = |vector: Vector2| vector.rotate(rotation);
let direction = |angle: f64| {
let angle = angle.rem_euclid(std::f64::consts::TAU);
Vector2::from_angle(if angle > std::f64::consts::PI {
angle - std::f64::consts::TAU
} else {
angle
})
};
let position = |angle: f64| {
let vector = direction(angle);
center + rotate(Vector2::new(radii.x * vector.x, radii.y * vector.y))
};
let tangent = |angle: f64| {
let vector = direction(angle);
rotate(Vector2::new(-radii.x * vector.y, radii.y * vector.x))
};
let mut result = self.clone();
let first = position(start);
match result.data.current {
None => result.try_move_to(first)?,
Some(current) if current != first => result.try_line_to(first)?,
_ => {}
}
if count > 0.0 {
let step = sweep_angle / count;
let factor = 4.0 / 3.0 * libm::tan(step / 4.0);
for index in 0..count as usize {
let angle = start + step * index as f64;
let next = start + step * (index + 1) as f64;
result.try_cubic_to(
position(angle) + tangent(angle) * factor,
position(next) - tangent(next) * factor,
position(next),
)?;
}
}
*self = result;
Ok(())
}
pub fn close(&mut self) -> &mut Self {
self.author(PathVerb::Close)
}
pub fn try_close(&mut self) -> Result<(), ValidationError> {
self.push(PathVerb::Close)
}
pub fn closed(mut self) -> Self {
self.close();
self
}
fn author(&mut self, verb: PathVerb) -> &mut Self {
if let Err(error) = self.push(verb)
&& self.data.error.is_none()
{
Arc::make_mut(&mut self.data).error = Some(error);
}
self
}
}