From 86d7f4c793a9ea863769540001dcfedfb0bf15aa Mon Sep 17 00:00:00 2001 From: arthmis Date: Sat, 9 Aug 2025 01:06:20 -0400 Subject: [PATCH] add implementation for Path2D addPath method (#37838) Add implementation for Path2D addPath method Spec: https://html.spec.whatwg.org/multipage/canvas.html#dom-path2d-addpath Testing: WPT test - `tests/wpt/tests/css/geometry/DOMMatrix2DInit-validate-fixup.html` Fixes: #37695 --------- Signed-off-by: Lloyd Massiah Signed-off-by: arthmis --- Cargo.lock | 1 + components/script/Cargo.toml | 1 + components/script/dom/path2d.rs | 57 +++++++++++++- .../webidls/CanvasRenderingContext2D.webidl | 3 +- .../DOMMatrix2DInit-validate-fixup.html.ini | 78 ------------------- 5 files changed, 57 insertions(+), 83 deletions(-) delete mode 100644 tests/wpt/meta/css/geometry/DOMMatrix2DInit-validate-fixup.html.ini diff --git a/Cargo.lock b/Cargo.lock index 1558aceeb1b..ca22416c851 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7154,6 +7154,7 @@ dependencies = [ "itertools 0.14.0", "jstraceable_derive", "keyboard-types", + "kurbo", "layout_api", "libc", "log", diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index 197e611a3f6..51e81f6c085 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -81,6 +81,7 @@ itertools = { workspace = true } js = { workspace = true } jstraceable_derive = { path = "../jstraceable_derive" } keyboard-types = { workspace = true } +kurbo = { workspace = true } layout_api = { workspace = true } libc = { workspace = true } log = { workspace = true } diff --git a/components/script/dom/path2d.rs b/components/script/dom/path2d.rs index 982f4b35883..8e6953ad7ee 100644 --- a/components/script/dom/path2d.rs +++ b/components/script/dom/path2d.rs @@ -7,12 +7,15 @@ use std::cell::RefCell; use canvas_traits::canvas::Path; use dom_struct::dom_struct; use js::rust::HandleObject; +use script_bindings::codegen::GenericBindings::DOMMatrixBinding::DOMMatrix2DInit; +use script_bindings::error::ErrorResult; 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::dommatrixreadonly::dommatrix2dinit_to_matrix; use crate::dom::globalscope::GlobalScope; use crate::script_runtime::CanGc; @@ -48,14 +51,60 @@ impl Path2D { pub(crate) fn segments(&self) -> Path { self.path.borrow().clone() } + + pub(crate) fn is_path_empty(&self) -> bool { + self.path.borrow().0.is_empty() + } + + fn add_path(&self, other: &Path2D, transform: &DOMMatrix2DInit) -> ErrorResult { + // Step 1. If the Path2D object path has no subpaths, then return. + if other.is_path_empty() { + return Ok(()); + } + + // Step 2 Let matrix be the result of creating a DOMMatrix from the 2D dictionary transform. + let matrix = dommatrix2dinit_to_matrix(transform)?; + + // Step 3. If one or more of matrix's m11 element, m12 element, m21 + // element, m22 element, m41 element, or m42 element are infinite or + // NaN, then return. + if !matrix.m11.is_finite() || + !matrix.m12.is_finite() || + !matrix.m21.is_finite() || + !matrix.m22.is_finite() || + !matrix.m31.is_finite() || + !matrix.m32.is_finite() + { + return Ok(()); + } + + // Step 4. Create a copy of all the subpaths in path. Let c be this copy. + let mut c = other.segments(); + + // Step 5. Transform all the coordinates and lines in c by the transform matrix `matrix`. + c.transform(matrix); + + let mut path = self.path.borrow_mut(); + + // Step 6. Let (x, y) be the last point in the last subpath of c + let last_point = path.last_point(); + + // Step 7. Add all the subpaths in c to a. + path.0.extend(c.0); + + // Step 8. Create a new subpath in `a` with (x, y) as the only point in the subpath. + if let Some(last_point) = last_point { + path.move_to(last_point.x, last_point.y); + } + + Ok(()) + } } impl Path2DMethods for Path2D { /// - fn AddPath(&self, other: &Path2D) { - let other = other.segments(); - // Step 7. Add all the subpaths in c to a. - self.path.borrow_mut().0.extend(other.0); + fn AddPath(&self, other: &Path2D, transform: &DOMMatrix2DInit) -> ErrorResult { + self.add_path(other, transform) } /// diff --git a/components/script_bindings/webidls/CanvasRenderingContext2D.webidl b/components/script_bindings/webidls/CanvasRenderingContext2D.webidl index dbafd0e995c..af399842bd4 100644 --- a/components/script_bindings/webidls/CanvasRenderingContext2D.webidl +++ b/components/script_bindings/webidls/CanvasRenderingContext2D.webidl @@ -287,6 +287,7 @@ interface Path2D { constructor(); constructor(Path2D other); constructor(DOMString pathString); - undefined addPath(Path2D path/*, SVGMatrix? transformation*/); + [Throws] + undefined addPath(Path2D path, optional DOMMatrix2DInit transform = {}); }; Path2D includes CanvasPath; diff --git a/tests/wpt/meta/css/geometry/DOMMatrix2DInit-validate-fixup.html.ini b/tests/wpt/meta/css/geometry/DOMMatrix2DInit-validate-fixup.html.ini deleted file mode 100644 index f1fa8d6a32c..00000000000 --- a/tests/wpt/meta/css/geometry/DOMMatrix2DInit-validate-fixup.html.ini +++ /dev/null @@ -1,78 +0,0 @@ -[DOMMatrix2DInit-validate-fixup.html] - [addPath({a: 1, m11: 2}) (invalid)] - expected: FAIL - - [addPath({b: 0, m12: -1}) (invalid)] - expected: FAIL - - [addPath({c: Infinity, m21: -Infinity}) (invalid)] - expected: FAIL - - [addPath({d: 0, m22: NaN}) (invalid)] - expected: FAIL - - [addPath({e: 1, m41: 1.00000001}) (invalid)] - expected: FAIL - - [addPath({f: 0, m42: 5e-324}) (invalid)] - expected: FAIL - - [addPath({d: Infinity, m22: Infinity})] - expected: FAIL - - [addPath({e: -Infinity, m41: -Infinity})] - expected: FAIL - - [addPath({f: NaN, m42: NaN})] - expected: FAIL - - [addPath({f: NaN, m42: NaN, is2D: true})] - expected: FAIL - - [addPath({a: 2})] - expected: FAIL - - [addPath({b: 2})] - expected: FAIL - - [addPath({c: 2})] - expected: FAIL - - [addPath({d: 2})] - expected: FAIL - - [addPath({e: 2})] - expected: FAIL - - [addPath({f: 2})] - expected: FAIL - - [addPath({a: -0, b: -0, c: -0, d: -0, e: -0, f: -0})] - expected: FAIL - - [addPath({a: -0, b: -0, c: -0, d: -0, e: -0, f: -0, is2D: true})] - expected: FAIL - - [addPath({m11: 2})] - expected: FAIL - - [addPath({m12: 2})] - expected: FAIL - - [addPath({m21: 2})] - expected: FAIL - - [addPath({m22: 2})] - expected: FAIL - - [addPath({m41: 2})] - expected: FAIL - - [addPath({m42: 2})] - expected: FAIL - - [addPath({m11: -0, m12: -0, m21: -0, m22: -0, m41: -0, m42: -0})] - expected: FAIL - - [addPath({m11: -0, m12: -0, m21: -0, m22: -0, m41: -0, m42: -0, is2D: true})] - expected: FAIL