/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::RefCell; use canvas_traits::canvas::PathSegment; use dom_struct::dom_struct; use js::rust::HandleObject; use script_bindings::str::DOMString; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::Path2DMethods; use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto}; use crate::dom::bindings::root::DomRoot; use crate::dom::globalscope::GlobalScope; use crate::script_runtime::CanGc; use crate::svgpath::PathParser; #[dom_struct] pub(crate) struct Path2D { reflector_: Reflector, #[no_trace] path: RefCell>, } impl Path2D { pub(crate) fn new() -> Path2D { Self { reflector_: Reflector::new(), path: RefCell::new(vec![]), } } pub(crate) fn new_with_path(other: &Path2D) -> Path2D { Self { reflector_: Reflector::new(), path: other.path.clone(), } } pub(crate) fn new_with_str(path: &str) -> Path2D { let mut path_segments = Vec::new(); for segment in PathParser::new(path) { if let Ok(segment) = segment { path_segments.push(segment); } else { break; } } Self { reflector_: Reflector::new(), path: RefCell::new(path_segments), } } pub(crate) fn push(&self, seg: PathSegment) { self.path.borrow_mut().push(seg); } pub(crate) fn segments(&self) -> Vec { self.path.borrow().clone() } } impl Path2DMethods for Path2D { /// fn AddPath(&self, other: &Path2D) { // Step 7. Add all the subpaths in c to a. let mut dest = self.path.borrow_mut(); dest.extend(other.path.borrow().iter().copied()); } /// fn ClosePath(&self) { self.push(PathSegment::ClosePath); } /// fn MoveTo(&self, x: f64, y: f64) { // Step 1. If either of the arguments are infinite or NaN, then return. if !(x.is_finite() && y.is_finite()) { return; } // Step 2. Create a new subpath with the specified point as its first (and only) point. self.push(PathSegment::MoveTo { x: x as f32, y: y as f32, }); } /// fn LineTo(&self, x: f64, y: f64) { // Step 1. If either of the arguments are infinite or NaN, then return. if !(x.is_finite() && y.is_finite()) { return; } self.push(PathSegment::LineTo { x: x as f32, y: y as f32, }); } /// fn QuadraticCurveTo(&self, cpx: f64, cpy: f64, x: f64, y: f64) { // Step 1. If any of the arguments are infinite or NaN, then return. if !(cpx.is_finite() && cpy.is_finite() && x.is_finite() && y.is_finite()) { return; } self.push(PathSegment::Quadratic { cpx: cpx as f32, cpy: cpy as f32, x: x as f32, y: y as f32, }); } /// fn BezierCurveTo(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) { // Step 1. If any of the arguments are infinite or NaN, then return. if !(cp1x.is_finite() && cp1y.is_finite() && cp2x.is_finite() && cp2y.is_finite() && x.is_finite() && y.is_finite()) { return; } self.push(PathSegment::Bezier { cp1x: cp1x as f32, cp1y: cp1y as f32, cp2x: cp2x as f32, cp2y: cp2y as f32, x: x as f32, y: y as f32, }); } /// fn ArcTo(&self, x1: f64, y1: f64, x2: f64, y2: f64, r: f64) -> Fallible<()> { // Step 1. If any of the arguments are infinite or NaN, then return. if !(x1.is_finite() && y1.is_finite() && x2.is_finite() && y2.is_finite() && r.is_finite()) { return Ok(()); } // Step 3. If radius is negative, then throw an "IndexSizeError" DOMException. if r < 0.0 { return Err(Error::IndexSize); } self.push(PathSegment::ArcTo { cp1x: x1 as f32, cp1y: y1 as f32, cp2x: x2 as f32, cp2y: y2 as f32, radius: r as f32, }); Ok(()) } /// fn Rect(&self, x: f64, y: f64, w: f64, h: f64) { // Step 1. If any of the arguments are infinite or NaN, then return. if !(x.is_finite() && y.is_finite() && w.is_finite() && h.is_finite()) { return; } // Step 2. Create a new subpath containing just the four points // (x, y), (x+w, y), (x+w, y+h), (x, y+h), in that order, // with those four points connected by straight lines. self.push(PathSegment::MoveTo { x: x as f32, y: y as f32, }); self.push(PathSegment::LineTo { x: (x + w) as f32, y: y as f32, }); self.push(PathSegment::LineTo { x: (x + w) as f32, y: (y + h) as f32, }); self.push(PathSegment::LineTo { x: x as f32, y: (y + h) as f32, }); // Step 3. Mark the subpath as closed. self.push(PathSegment::ClosePath); // Step 4. Create a new subpath with the point (x, y) as the only point in the subpath. self.push(PathSegment::MoveTo { x: x as f32, y: y as f32, }); } /// fn Arc( &self, x: f64, y: f64, r: f64, start: f64, end: f64, anticlockwise: bool, ) -> Fallible<()> { // Step 1. If any of the arguments are infinite or NaN, then return. if !(x.is_finite() && y.is_finite() && r.is_finite() && start.is_finite() && end.is_finite()) { return Ok(()); } // Step 2. If either radiusX or radiusY are negative, then throw an "IndexSizeError" DOMException. if r < 0.0 { return Err(Error::IndexSize); } self.push(PathSegment::Ellipse { x: x as f32, y: y as f32, radius_x: r as f32, radius_y: r as f32, rotation: 0., start_angle: start as f32, end_angle: end as f32, anticlockwise, }); Ok(()) } /// fn Ellipse( &self, x: f64, y: f64, rx: f64, ry: f64, rotation: f64, start: f64, end: f64, anticlockwise: bool, ) -> Fallible<()> { // Step 1. If any of the arguments are infinite or NaN, then return. if !(x.is_finite() && y.is_finite() && rx.is_finite() && ry.is_finite() && rotation.is_finite() && start.is_finite() && end.is_finite()) { return Ok(()); } // Step 2. If either radiusX or radiusY are negative, then throw an "IndexSizeError" DOMException. if rx < 0.0 || ry < 0.0 { return Err(Error::IndexSize); } self.push(PathSegment::Ellipse { x: x as f32, y: y as f32, radius_x: rx as f32, radius_y: ry as f32, rotation: rotation as f32, start_angle: start as f32, end_angle: end as f32, anticlockwise, }); Ok(()) } /// fn Constructor( global: &GlobalScope, proto: Option, can_gc: CanGc, ) -> DomRoot { reflect_dom_object_with_proto(Box::new(Self::new()), global, proto, can_gc) } /// fn Constructor_( global: &GlobalScope, proto: Option, can_gc: CanGc, other: &Path2D, ) -> DomRoot { reflect_dom_object_with_proto(Box::new(Self::new_with_path(other)), global, proto, can_gc) } /// fn Constructor__( global: &GlobalScope, proto: Option, can_gc: CanGc, path_string: DOMString, ) -> DomRoot { reflect_dom_object_with_proto( Box::new(Self::new_with_str(path_string.str())), global, proto, can_gc, ) } }