#![allow(unused_mut)]
#![allow(unused_imports)]
#![allow(unused_variables)]
#![allow(non_snake_case)]
#![allow(dead_code)]
use neon::prelude::*;
use skia_safe::{
Matrix, Path, PathBuilder, PathDirection, PathEffect, PathFillType, PathOp, Point, RRect, Rect,
StrokeRec,
path::{self, AddPathMode, Verb},
trim_path_effect,
};
use std::{cell::RefCell, f32::consts::PI};
use crate::utils::*;
pub type BoxedPath2D = JsBox<RefCell<Path2D>>;
impl Finalize for Path2D {}
pub struct Path2D {
pub builder: PathBuilder,
}
impl Default for Path2D {
fn default() -> Self {
Self {
builder: PathBuilder::new(),
}
}
}
impl From<PathBuilder> for Path2D {
fn from(builder: PathBuilder) -> Self {
Self { builder }
}
}
impl Path2D {
pub fn path(&self) -> Path {
self.builder.snapshot()
}
pub fn scoot(&mut self, x: f32, y: f32) {
if self.builder.snapshot().is_empty() {
self.builder.move_to((x, y));
}
}
pub fn add_ellipse(
&mut self,
origin: impl Into<Point>,
radii: impl Into<Point>,
rotation: f32,
start_angle: f32,
end_angle: f32,
ccw: bool,
) {
let Point { x, y } = origin.into();
let Point {
x: x_radius,
y: y_radius,
} = radii.into();
let tau = 2.0 * PI;
let mut new_start_angle = start_angle % tau;
if new_start_angle < 0.0 {
new_start_angle += tau;
}
let delta = new_start_angle - start_angle;
let start_angle = new_start_angle;
let mut end_angle = end_angle + delta;
if !ccw && start_angle > end_angle {
end_angle = start_angle + (tau - (start_angle - end_angle) % tau);
} else if ccw && start_angle < end_angle {
end_angle = start_angle - (tau - (end_angle - start_angle) % tau);
}
let oval = Rect::new(x - x_radius, y - y_radius, x + x_radius, y + y_radius);
let mut rotated = Matrix::new_identity();
rotated
.pre_translate((x, y))
.pre_rotate(to_degrees(rotation), None)
.pre_translate((-x, -y));
let current_path = self.builder.snapshot();
let inverse = rotated.invert().unwrap_or_else(Matrix::new_identity);
let transformed = current_path.make_transform(&inverse);
self.builder = PathBuilder::new_path(&transformed);
{
let sweep_deg = (to_degrees(end_angle - start_angle) * 10000.0).round() / 10000.0;
let start_deg = (to_degrees(start_angle) * 10000.0).round() / 10000.0;
if sweep_deg >= 360.0 - f32::EPSILON {
self.builder.arc_to(oval, start_deg, 180.0, false);
self.builder.arc_to(oval, start_deg + 180.0, 180.0, false);
} else if sweep_deg <= -360.0 + f32::EPSILON {
self.builder.arc_to(oval, start_deg, -180.0, false);
self.builder.arc_to(oval, start_deg - 180.0, -180.0, false);
} else {
self.builder.arc_to(oval, start_deg, sweep_deg, false);
}
}
let current_path = self.builder.snapshot();
let transformed = current_path.make_transform(&rotated);
self.builder = PathBuilder::new_path(&transformed);
}
}
pub fn new(mut cx: FunctionContext) -> JsResult<BoxedPath2D> {
Ok(cx.boxed(RefCell::new(Path2D::default())))
}
pub fn from_path(mut cx: FunctionContext) -> JsResult<BoxedPath2D> {
let other_path = path2d_arg(&mut cx, 1)?;
let path = other_path.borrow().path();
let builder = PathBuilder::new_path(&path);
Ok(cx.boxed(RefCell::new(Path2D { builder })))
}
pub fn from_svg(mut cx: FunctionContext) -> JsResult<BoxedPath2D> {
let svg_string = string_arg(&mut cx, 1, "svgPath")?;
let path = Path::from_svg(svg_string).unwrap_or_default();
let builder = PathBuilder::new_path(&path);
Ok(cx.boxed(RefCell::new(Path2D { builder })))
}
pub fn addPath(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let this = cx.argument::<BoxedPath2D>(0)?;
let other = path2d_arg(&mut cx, 1)?;
let matrix = opt_matrix_arg(&mut cx, 2).unwrap_or_else(Matrix::new_identity);
let src = other.borrow().path();
this.borrow_mut()
.builder
.add_path_with_transform(&src, &matrix, AddPathMode::Append);
Ok(cx.undefined())
}
pub fn closePath(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let this = cx.argument::<BoxedPath2D>(0)?;
let mut this = this.borrow_mut();
this.builder.close();
Ok(cx.undefined())
}
pub fn moveTo(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let this = cx.argument::<BoxedPath2D>(0)?;
let mut this = this.borrow_mut();
let nums = float_args_or_bail(&mut cx, &["x", "y"])?;
if let [x, y] = nums.as_slice() {
this.builder.move_to((*x, *y));
}
Ok(cx.undefined())
}
pub fn lineTo(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let this = cx.argument::<BoxedPath2D>(0)?;
let mut this = this.borrow_mut();
let nums = float_args_or_bail(&mut cx, &["x", "y"])?;
if let [x, y] = nums.as_slice() {
this.scoot(*x, *y);
this.builder.line_to((*x, *y));
}
Ok(cx.undefined())
}
pub fn bezierCurveTo(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let this = cx.argument::<BoxedPath2D>(0)?;
let mut this = this.borrow_mut();
let nums = float_args_or_bail(&mut cx, &["cp1x", "cp1y", "cp2x", "cp2y", "x", "y"])?;
if let [cp1x, cp1y, cp2x, cp2y, x, y] = nums.as_slice() {
this.scoot(*cp1x, *cp1y);
this.builder
.cubic_to((*cp1x, *cp1y), (*cp2x, *cp2y), (*x, *y));
}
Ok(cx.undefined())
}
pub fn quadraticCurveTo(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let this = cx.argument::<BoxedPath2D>(0)?;
let mut this = this.borrow_mut();
let nums = float_args_or_bail(&mut cx, &["cpx", "cpy", "x", "y"])?;
if let [cpx, cpy, x, y] = nums.as_slice() {
this.scoot(*cpx, *cpy);
this.builder.quad_to((*cpx, *cpy), (*x, *y));
}
Ok(cx.undefined())
}
pub fn conicCurveTo(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let this = cx.argument::<BoxedPath2D>(0)?;
let mut this = this.borrow_mut();
let nums = float_args_or_bail(&mut cx, &["cpx", "cpy", "x", "y", "weight"])?;
if let [p1x, p1y, p2x, p2y, weight] = nums.as_slice() {
this.scoot(*p1x, *p1y);
this.builder.conic_to((*p1x, *p1y), (*p2x, *p2y), *weight);
}
Ok(cx.undefined())
}
pub fn arc(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let this = cx.argument::<BoxedPath2D>(0)?;
let mut this = this.borrow_mut();
let nums = float_args_or_bail(&mut cx, &["x", "y", "radius", "startAngle", "endAngle"])?;
let ccw = bool_arg_or(&mut cx, 6, false);
if let [x, y, radius, start_angle, end_angle] = nums.as_slice() {
this.add_ellipse(
(*x, *y),
(*radius, *radius),
0.0,
*start_angle,
*end_angle,
ccw,
);
}
Ok(cx.undefined())
}
pub fn arcTo(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let this = cx.argument::<BoxedPath2D>(0)?;
let mut this = this.borrow_mut();
let nums = float_args_or_bail(&mut cx, &["x1", "y1", "x2", "y2", "radius"])?;
if let [x1, y1, x2, y2, radius] = nums.as_slice() {
this.scoot(*x1, *y1);
this.builder.arc_to_tangent((*x1, *y1), (*x2, *y2), *radius);
}
Ok(cx.undefined())
}
pub fn ellipse(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let this = cx.argument::<BoxedPath2D>(0)?;
let mut this = this.borrow_mut();
let nums = float_args_or_bail(
&mut cx,
&[
"x",
"y",
"xRadius",
"yRadius",
"rotation",
"startAngle",
"endAngle",
],
)?;
let ccw = bool_arg_or(&mut cx, 8, false);
if let [x, y, x_radius, y_radius, rotation, start_angle, end_angle] = nums.as_slice() {
if *x_radius < 0.0 || *y_radius < 0.0 {
return cx.throw_range_error("Radius value must be positive");
}
this.add_ellipse(
(*x, *y),
(*x_radius, *y_radius),
*rotation,
*start_angle,
*end_angle,
ccw,
);
}
Ok(cx.undefined())
}
pub fn rect(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let this = cx.argument::<BoxedPath2D>(0)?;
let mut this = this.borrow_mut();
let nums = float_args_or_bail(&mut cx, &["x", "y", "width", "height"])?;
if let [x, y, w, h] = nums.as_slice() {
let rect = Rect::from_xywh(*x, *y, *w, *h);
let direction = if w.signum() == h.signum() {
PathDirection::CW
} else {
PathDirection::CCW
};
this.builder.add_rect(rect, direction, 0);
}
Ok(cx.undefined())
}
pub fn roundRect(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let this = cx.argument::<BoxedPath2D>(0)?;
let mut this = this.borrow_mut();
let nums = float_args(
&mut cx,
&[
"x", "y", "width", "height", "r1x", "r1y", "r2x", "r2y", "r3x", "r3y", "r4x", "r4y",
],
)?;
if let [x, y, w, h] = &nums[..4] {
let rect = Rect::from_xywh(*x, *y, *w, *h);
let radii: Vec<Point> = nums[4..]
.chunks(2)
.map(|xy| Point::new(xy[0], xy[1]))
.collect();
let rrect = RRect::new_rect_radii(rect, &[radii[0], radii[1], radii[2], radii[3]]);
let direction = if w.signum() == h.signum() {
PathDirection::CW
} else {
PathDirection::CCW
};
this.builder.add_rrect(rrect, direction, None);
}
Ok(cx.undefined())
}
pub fn op(mut cx: FunctionContext) -> JsResult<BoxedPath2D> {
let this = cx.argument::<BoxedPath2D>(0)?;
let other_path = path2d_arg(&mut cx, 1)?;
let op_name = string_arg(&mut cx, 2, "pathOp")?;
if let Some(path_op) = to_path_op(&op_name) {
let this = this.borrow();
let other = other_path.borrow();
let this_path = this.path();
let other_path = other.path();
match this_path.op(&other_path, path_op) {
Some(path) => {
let builder = PathBuilder::new_path(&path);
Ok(cx.boxed(RefCell::new(Path2D { builder })))
}
None => cx.throw_error("path operation failed"),
}
} else {
cx.throw_error("pathOp must be Difference, Intersect, Union, XOR, or Complement")
}
}
pub fn interpolate(mut cx: FunctionContext) -> JsResult<BoxedPath2D> {
let this = cx.argument::<BoxedPath2D>(0)?;
let other = path2d_arg(&mut cx, 1)?;
let weight = float_arg(&mut cx, 2, "weight")?;
let this = this.borrow();
let other = other.borrow();
let this_path = this.path();
let other_path = other.path();
if let Some(path) = other_path.interpolate(&this_path, weight) {
let builder = PathBuilder::new_path(&path);
Ok(cx.boxed(RefCell::new(Path2D { builder })))
} else {
cx.throw_type_error("Can only interpolate between two Path2D objects with the same number of points and control points")
}
}
pub fn simplify(mut cx: FunctionContext) -> JsResult<BoxedPath2D> {
let this = cx.argument::<BoxedPath2D>(0)?;
let rule = fill_rule_arg_or(&mut cx, 1, "nonzero")?;
let this = this.borrow();
let mut path = this.path();
path.set_fill_type(rule);
let result_path = match path.simplify() {
Some(simpler) => simpler,
None => path,
};
let builder = PathBuilder::new_path(&result_path);
Ok(cx.boxed(RefCell::new(Path2D { builder })))
}
pub fn unwind(mut cx: FunctionContext) -> JsResult<BoxedPath2D> {
let this = cx.argument::<BoxedPath2D>(0)?;
let this = this.borrow();
let mut path = this.path();
path.set_fill_type(PathFillType::EvenOdd);
let result_path = match path.as_winding() {
Some(rewound) => rewound,
None => path,
};
let builder = PathBuilder::new_path(&result_path);
Ok(cx.boxed(RefCell::new(Path2D { builder })))
}
pub fn offset(mut cx: FunctionContext) -> JsResult<BoxedPath2D> {
let this = cx.argument::<BoxedPath2D>(0)?;
let dx = float_arg(&mut cx, 1, "dx")?;
let dy = float_arg(&mut cx, 2, "dy")?;
let this = this.borrow();
let path = this.path().with_offset((dx, dy));
let builder = PathBuilder::new_path(&path);
Ok(cx.boxed(RefCell::new(Path2D { builder })))
}
pub fn transform(mut cx: FunctionContext) -> JsResult<BoxedPath2D> {
let this = cx.argument::<BoxedPath2D>(0)?;
let matrix = matrix_arg(&mut cx, 1)?;
let this = this.borrow();
let path = this.path().make_transform(&matrix);
let builder = PathBuilder::new_path(&path);
Ok(cx.boxed(RefCell::new(Path2D { builder })))
}
pub fn round(mut cx: FunctionContext) -> JsResult<BoxedPath2D> {
let this = cx.argument::<BoxedPath2D>(0)?;
let radius = float_arg(&mut cx, 1, "radius")?;
let this = this.borrow();
let path = this.path();
let bounds = path.bounds();
let stroke_rec = StrokeRec::new_hairline();
if let Some(rounder) = PathEffect::corner_path(radius)
&& let Some((builder, _)) = rounder.filter_path(&path, &stroke_rec, bounds)
{
return Ok(cx.boxed(RefCell::new(Path2D { builder })));
}
let builder = PathBuilder::new_path(&path);
Ok(cx.boxed(RefCell::new(Path2D { builder })))
}
pub fn trim(mut cx: FunctionContext) -> JsResult<BoxedPath2D> {
let this = cx.argument::<BoxedPath2D>(0)?;
let begin = float_arg_or_bail(&mut cx, 1, "begin")?;
let end = float_arg_or_bail(&mut cx, 2, "end")?;
let invert = bool_arg_or(&mut cx, 3, false);
let this = this.borrow();
let path = this.path();
let bounds = path.bounds();
let stroke_rec = StrokeRec::new_hairline();
let mode = if invert {
trim_path_effect::Mode::Inverted
} else {
trim_path_effect::Mode::Normal
};
if let Some(trimmer) = PathEffect::trim(begin, end, mode)
&& let Some((builder, _)) = trimmer.filter_path(&path, &stroke_rec, bounds)
{
return Ok(cx.boxed(RefCell::new(Path2D { builder })));
}
let builder = PathBuilder::new_path(&path);
Ok(cx.boxed(RefCell::new(Path2D { builder })))
}
pub fn jitter(mut cx: FunctionContext) -> JsResult<BoxedPath2D> {
let this = cx.argument::<BoxedPath2D>(0)?;
let seg_len = float_arg_or_bail(&mut cx, 1, "segmentLength")?;
let std_dev = float_arg_or_bail(&mut cx, 2, "variance")?;
let seed = float_arg_or(&mut cx, 3, 0.0) as u32;
let this = this.borrow();
let path = this.path();
let bounds = path.bounds();
let stroke_rec = StrokeRec::new_hairline();
if let Some(trimmer) = PathEffect::discrete(seg_len, std_dev, Some(seed))
&& let Some((builder, _)) = trimmer.filter_path(&path, &stroke_rec, bounds)
{
return Ok(cx.boxed(RefCell::new(Path2D { builder })));
}
let builder = PathBuilder::new_path(&path);
Ok(cx.boxed(RefCell::new(Path2D { builder })))
}
pub fn bounds(mut cx: FunctionContext) -> JsResult<JsObject> {
let this = cx.argument::<BoxedPath2D>(0)?;
let this = this.borrow();
let b = this.path().compute_tight_bounds();
let js_object: Handle<JsObject> = cx.empty_object();
let left = cx.number(b.left);
let top = cx.number(b.top);
let right = cx.number(b.right);
let bottom = cx.number(b.bottom);
let width = cx.number(b.width());
let height = cx.number(b.height());
js_object.set(&mut cx, "left", left)?;
js_object.set(&mut cx, "top", top)?;
js_object.set(&mut cx, "right", right)?;
js_object.set(&mut cx, "bottom", bottom)?;
js_object.set(&mut cx, "width", width)?;
js_object.set(&mut cx, "height", height)?;
Ok(js_object)
}
pub fn contains(mut cx: FunctionContext) -> JsResult<JsBoolean> {
let this = cx.argument::<BoxedPath2D>(0)?;
let x = float_arg(&mut cx, 1, "x")?;
let y = float_arg(&mut cx, 2, "y")?;
let this = this.borrow();
Ok(cx.boolean(this.path().contains((x, y))))
}
fn from_verb(verb: Verb) -> Option<String> {
let cmd = match verb {
Verb::Move => "moveTo",
Verb::Line => "lineTo",
Verb::Quad => "quadraticCurveTo",
Verb::Cubic => "bezierCurveTo",
Verb::Conic => "conicCurveTo",
Verb::Close => "closePath",
_ => return None,
};
Some(cmd.to_string())
}
pub fn edges(mut cx: FunctionContext) -> JsResult<JsArray> {
let this = cx.argument::<BoxedPath2D>(0)?;
let this = this.borrow();
let path = this.path();
let mut weights = path::Iter::new(&path, false);
let iter = path::Iter::new(&path, false);
let mut edges = vec![];
for (verb, points) in iter {
weights.next();
if let Some(edge) = from_verb(verb) {
let cmd = cx.string(edge);
let segment = JsArray::new(&mut cx, 1 + points.len());
segment.set(&mut cx, 0, cmd)?;
let at_point = if points.len() > 1 { 1 } else { 0 };
for (i, pt) in points.iter().skip(at_point).enumerate() {
let x = cx.number(pt.x);
let y = cx.number(pt.y);
segment.set(&mut cx, 1 + 2 * i as u32, x)?;
segment.set(&mut cx, 2 + 2 * i as u32, y)?;
}
if verb == Verb::Conic {
let weight = weights.conic_weight().unwrap();
let weight = cx.number(weight);
segment.set(&mut cx, 5, weight)?;
}
edges.push(segment);
}
}
let verbs = JsArray::new(&mut cx, edges.len());
for (i, segment) in edges.iter().enumerate() {
verbs.set(&mut cx, i as u32, *segment)?;
}
Ok(verbs)
}
pub fn get_d(mut cx: FunctionContext) -> JsResult<JsString> {
let this = cx.argument::<BoxedPath2D>(0)?;
let this = this.borrow();
Ok(cx.string(this.path().to_svg()))
}
pub fn set_d(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let this = cx.argument::<BoxedPath2D>(0)?;
let svg_string = string_arg(&mut cx, 1, "svgPath")?;
let mut this = this.borrow_mut();
if let Some(path) = Path::from_svg(svg_string) {
this.builder.reset();
this.builder.add_path(&path, None);
Ok(cx.undefined())
} else {
cx.throw_type_error("Expected a valid SVG path string")
}
}