Skip to main content

script/dom/canvas/2d/
path2d.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::RefCell;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use js::rust::HandleObject;
10use script_bindings::codegen::GenericBindings::DOMMatrixBinding::DOMMatrix2DInit;
11use script_bindings::error::ErrorResult;
12use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
13use script_bindings::str::DOMString;
14use servo_canvas_traits::canvas::Path;
15
16use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::Path2DMethods;
17use crate::dom::bindings::codegen::UnionTypes::UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence;
18use crate::dom::bindings::error::{Error, Fallible};
19use crate::dom::bindings::root::DomRoot;
20use crate::dom::dommatrixreadonly::dommatrix2dinit_to_matrix;
21use crate::dom::globalscope::GlobalScope;
22
23#[dom_struct]
24pub(crate) struct Path2D {
25    reflector_: Reflector,
26    #[ignore_malloc_size_of = "Defined in kurbo."]
27    #[no_trace]
28    path: RefCell<Path>,
29}
30
31impl Path2D {
32    pub(crate) fn new() -> Path2D {
33        Self {
34            reflector_: Reflector::new(),
35            path: RefCell::new(Path::new()),
36        }
37    }
38    pub(crate) fn new_with_path(other: &Path2D) -> Path2D {
39        Self {
40            reflector_: Reflector::new(),
41            path: other.path.clone(),
42        }
43    }
44
45    pub(crate) fn new_with_str(path: &str) -> Path2D {
46        Self {
47            reflector_: Reflector::new(),
48            path: RefCell::new(Path::from_svg(path)),
49        }
50    }
51
52    pub(crate) fn segments(&self) -> Path {
53        self.path.borrow().clone()
54    }
55
56    pub(crate) fn is_path_empty(&self) -> bool {
57        self.path.borrow().0.is_empty()
58    }
59
60    fn add_path(&self, other: &Path2D, transform: &DOMMatrix2DInit) -> ErrorResult {
61        // Step 1. If the Path2D object path has no subpaths, then return.
62        if other.is_path_empty() {
63            return Ok(());
64        }
65
66        // Step 2 Let matrix be the result of creating a DOMMatrix from the 2D dictionary transform.
67        let matrix = dommatrix2dinit_to_matrix(transform)?;
68
69        // Step 3. If one or more of matrix's m11 element, m12 element, m21
70        // element, m22 element, m41 element, or m42 element are infinite or
71        // NaN, then return.
72        if !matrix.m11.is_finite() ||
73            !matrix.m12.is_finite() ||
74            !matrix.m21.is_finite() ||
75            !matrix.m22.is_finite() ||
76            !matrix.m31.is_finite() ||
77            !matrix.m32.is_finite()
78        {
79            return Ok(());
80        }
81
82        // Step 4. Create a copy of all the subpaths in path. Let c be this copy.
83        let mut c = other.segments();
84
85        // Step 5. Transform all the coordinates and lines in c by the transform matrix `matrix`.
86        c.transform(matrix);
87
88        let mut path = self.path.borrow_mut();
89
90        // Step 6. Let (x, y) be the last point in the last subpath of c
91        let last_point = path.last_point();
92
93        // Step 7. Add all the subpaths in c to a.
94        path.0.extend(c.0);
95
96        // Step 8. Create a new subpath in `a` with (x, y) as the only point in the subpath.
97        if let Some(last_point) = last_point {
98            path.move_to(last_point.x, last_point.y);
99        }
100
101        Ok(())
102    }
103}
104
105impl Path2DMethods<crate::DomTypeHolder> for Path2D {
106    /// <https://html.spec.whatwg.org/multipage/#dom-path2d-addpath>
107    fn AddPath(&self, other: &Path2D, transform: &DOMMatrix2DInit) -> ErrorResult {
108        self.add_path(other, transform)
109    }
110
111    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-closepath>
112    fn ClosePath(&self) {
113        self.path.borrow_mut().close_path();
114    }
115
116    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-moveto>
117    fn MoveTo(&self, x: f64, y: f64) {
118        self.path.borrow_mut().move_to(x, y);
119    }
120
121    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-lineto>
122    fn LineTo(&self, x: f64, y: f64) {
123        self.path.borrow_mut().line_to(x, y);
124    }
125
126    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-quadraticcurveto>
127    fn QuadraticCurveTo(&self, cpx: f64, cpy: f64, x: f64, y: f64) {
128        self.path.borrow_mut().quadratic_curve_to(cpx, cpy, x, y);
129    }
130
131    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-beziercurveto>
132    fn BezierCurveTo(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) {
133        self.path
134            .borrow_mut()
135            .bezier_curve_to(cp1x, cp1y, cp2x, cp2y, x, y);
136    }
137
138    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-arcto>
139    fn ArcTo(&self, x1: f64, y1: f64, x2: f64, y2: f64, radius: f64) -> Fallible<()> {
140        self.path
141            .borrow_mut()
142            .arc_to(x1, y1, x2, y2, radius)
143            .map_err(|_| Error::IndexSize(None))
144    }
145
146    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-rect>
147    fn Rect(&self, x: f64, y: f64, w: f64, h: f64) {
148        self.path.borrow_mut().rect(x, y, w, h);
149    }
150
151    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-roundrect>
152    fn RoundRect(
153        &self,
154        x: f64,
155        y: f64,
156        w: f64,
157        h: f64,
158        radii: UnrestrictedDoubleOrDOMPointInitOrUnrestrictedDoubleOrDOMPointInitSequence,
159    ) -> Fallible<()> {
160        let radii = super::canvas_state::round_rect_radii(&radii);
161        self.path
162            .borrow_mut()
163            .round_rect(x, y, w, h, &radii)
164            .map_err(super::canvas_state::round_rect_error)
165    }
166
167    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-arc>
168    fn Arc(
169        &self,
170        x: f64,
171        y: f64,
172        radius: f64,
173        start_angle: f64,
174        end_angle: f64,
175        counterclockwise: bool,
176    ) -> Fallible<()> {
177        self.path
178            .borrow_mut()
179            .arc(x, y, radius, start_angle, end_angle, counterclockwise)
180            .map_err(|_| Error::IndexSize(None))
181    }
182
183    /// <https://html.spec.whatwg.org/multipage/#dom-context-2d-ellipse>
184    fn Ellipse(
185        &self,
186        x: f64,
187        y: f64,
188        radius_x: f64,
189        radius_y: f64,
190        rotation_angle: f64,
191        start_angle: f64,
192        end_angle: f64,
193        counterclockwise: bool,
194    ) -> Fallible<()> {
195        self.path
196            .borrow_mut()
197            .ellipse(
198                x,
199                y,
200                radius_x,
201                radius_y,
202                rotation_angle,
203                start_angle,
204                end_angle,
205                counterclockwise,
206            )
207            .map_err(|_| Error::IndexSize(None))
208    }
209
210    /// <https://html.spec.whatwg.org/multipage/#dom-path2d-dev>
211    fn Constructor(
212        cx: &mut JSContext,
213        global: &GlobalScope,
214        proto: Option<HandleObject>,
215    ) -> DomRoot<Path2D> {
216        reflect_dom_object_with_proto(cx, Box::new(Self::new()), global, proto)
217    }
218
219    /// <https://html.spec.whatwg.org/multipage/#dom-path2d-dev>
220    fn Constructor_(
221        cx: &mut JSContext,
222        global: &GlobalScope,
223        proto: Option<HandleObject>,
224        other: &Path2D,
225    ) -> DomRoot<Path2D> {
226        reflect_dom_object_with_proto(cx, Box::new(Self::new_with_path(other)), global, proto)
227    }
228
229    /// <https://html.spec.whatwg.org/multipage/#dom-path2d-dev>
230    fn Constructor__(
231        cx: &mut JSContext,
232        global: &GlobalScope,
233        proto: Option<HandleObject>,
234        path_string: DOMString,
235    ) -> DomRoot<Path2D> {
236        reflect_dom_object_with_proto(
237            cx,
238            Box::new(Self::new_with_str(&path_string.str())),
239            global,
240            proto,
241        )
242    }
243}