From 3c16db264292fab2ba376a8a30b7dc9ebeab4cdf Mon Sep 17 00:00:00 2001 From: Andrei Volykhin Date: Thu, 26 Jun 2025 17:38:30 +0300 Subject: [PATCH] canvas: Add CanvasPattern 'setTranform(transform)' method (#37731) Follow the HTML canvas specification and add missing 'setTransform(transform)' method to CanvasPattern interface. https://html.spec.whatwg.org/multipage/#dom-canvaspattern-settransform Testing: Improvements in the tests - html/canvas/element/fill-and-stroke-styles/2d.pattern.transform* - html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform* Signed-off-by: Andrei Volykhin --- components/canvas/raqote_backend.rs | 37 ++++++--------- components/script/dom/canvaspattern.rs | 47 ++++++++++++++++++- .../webidls/CanvasRenderingContext2D.webidl | 3 +- components/shared/canvas/canvas.rs | 3 ++ .../2d.pattern.transform.identity.html.ini | 4 -- .../2d.pattern.transform.infinity.html.ini | 4 -- .../2d.pattern.transform.identity.html.ini | 3 -- ...d.pattern.transform.identity.worker.js.ini | 3 -- .../2d.pattern.transform.infinity.html.ini | 3 -- ...d.pattern.transform.infinity.worker.js.ini | 3 -- tests/wpt/meta/html/dom/idlharness.any.js.ini | 3 -- .../meta/html/dom/idlharness.https.html.ini | 3 -- 12 files changed, 65 insertions(+), 51 deletions(-) delete mode 100644 tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.pattern.transform.identity.html.ini delete mode 100644 tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.pattern.transform.infinity.html.ini delete mode 100644 tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.identity.html.ini delete mode 100644 tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.identity.worker.js.ini delete mode 100644 tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.infinity.html.ini delete mode 100644 tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.infinity.worker.js.ini diff --git a/components/canvas/raqote_backend.rs b/components/canvas/raqote_backend.rs index 0475b58dfdb..c86609d98e6 100644 --- a/components/canvas/raqote_backend.rs +++ b/components/canvas/raqote_backend.rs @@ -121,17 +121,6 @@ pub enum Pattern<'a> { Surface(SurfacePattern<'a>), } -impl Pattern<'_> { - fn set_transform(&mut self, transform: Transform2D) { - match self { - Pattern::Surface(pattern) => pattern.set_transform(transform), - Pattern::LinearGradient(..) | Pattern::RadialGradient(..) | Pattern::Color(..) => { - warn!("transform not supported") - }, - } - } -} - #[derive(Clone)] pub struct LinearGradientPattern { gradient: raqote::Gradient, @@ -186,7 +175,12 @@ pub struct SurfacePattern<'a> { } impl<'a> SurfacePattern<'a> { - fn new(image: raqote::Image<'a>, filter: raqote::FilterMode, repeat: Repetition) -> Self { + fn new( + image: raqote::Image<'a>, + filter: raqote::FilterMode, + repeat: Repetition, + transform: Transform2D, + ) -> Self { let extend = match repeat { Repetition::NoRepeat => raqote::ExtendMode::Pad, Repetition::RepeatX | Repetition::RepeatY | Repetition::Repeat => { @@ -198,12 +192,9 @@ impl<'a> SurfacePattern<'a> { filter, extend, repeat, - transform: Transform2D::identity(), + transform, } } - fn set_transform(&mut self, transform: Transform2D) { - self.transform = transform; - } pub fn size(&self) -> Size2D { Size2D::new(self.image.width as f32, self.image.height as f32) } @@ -427,18 +418,19 @@ impl GenericDrawTarget for raqote::DrawTarget { }, }; - let mut pattern = Pattern::Surface(SurfacePattern::new( - image, - filter.to_raqote(), - Repetition::NoRepeat, - )); let transform = raqote::Transform::translation(-dest.origin.x as f32, -dest.origin.y as f32) .then_scale( image.width as f32 / dest.size.width as f32, image.height as f32 / dest.size.height as f32, ); - pattern.set_transform(transform); + + let pattern = Pattern::Surface(SurfacePattern::new( + image, + filter.to_raqote(), + Repetition::NoRepeat, + transform, + )); let mut pb = raqote::PathBuilder::new(); pb.rect( @@ -809,6 +801,7 @@ impl ToRaqotePattern<'_> for FillOrStrokeStyle { image, raqote::FilterMode::Nearest, repeat, + style.transform, ))) }, } diff --git a/components/script/dom/canvaspattern.rs b/components/script/dom/canvaspattern.rs index 9953a4e2f00..4daa08dd7cf 100644 --- a/components/script/dom/canvaspattern.rs +++ b/components/script/dom/canvaspattern.rs @@ -4,15 +4,20 @@ use canvas_traits::canvas::{FillOrStrokeStyle, RepetitionStyle, SurfaceStyle}; use dom_struct::dom_struct; -use euclid::default::Size2D; +use euclid::default::{Size2D, Transform2D}; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasPatternMethods; +use crate::dom::bindings::codegen::Bindings::DOMMatrixBinding::DOMMatrix2DInit; +use crate::dom::bindings::error::ErrorResult; use crate::dom::bindings::reflector::{Reflector, reflect_dom_object}; use crate::dom::bindings::root::DomRoot; use crate::dom::canvasgradient::ToFillOrStrokeStyle; +use crate::dom::dommatrixreadonly::dommatrix2dinit_to_matrix; use crate::dom::globalscope::GlobalScope; use crate::script_runtime::CanGc; -// https://html.spec.whatwg.org/multipage/#canvaspattern +/// #[dom_struct] pub(crate) struct CanvasPattern { reflector_: Reflector, @@ -21,6 +26,8 @@ pub(crate) struct CanvasPattern { surface_size: Size2D, repeat_x: bool, repeat_y: bool, + #[no_trace] + transform: DomRefCell>, origin_clean: bool, } @@ -44,6 +51,7 @@ impl CanvasPattern { surface_size, repeat_x: x, repeat_y: y, + transform: DomRefCell::new(Transform2D::identity()), origin_clean, } } @@ -71,6 +79,40 @@ impl CanvasPattern { } } +impl CanvasPatternMethods for CanvasPattern { + /// + fn SetTransform(&self, transform: &DOMMatrix2DInit) -> ErrorResult { + // Step 1. Let matrix be the result of creating a DOMMatrix from the 2D + // dictionary transform. + let matrix = dommatrix2dinit_to_matrix(transform)?; + + // Step 2. 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.m41.is_finite() || + !matrix.m42.is_finite() + { + return Ok(()); + } + + // Step 3. Reset the pattern's transformation matrix to matrix. + *self.transform.borrow_mut() = Transform2D::new( + matrix.m11 as f32, + matrix.m12 as f32, + matrix.m21 as f32, + matrix.m22 as f32, + matrix.m41 as f32, + matrix.m42 as f32, + ); + + Ok(()) + } +} + impl ToFillOrStrokeStyle for &CanvasPattern { fn to_fill_or_stroke_style(self) -> FillOrStrokeStyle { FillOrStrokeStyle::Surface(SurfaceStyle::new( @@ -78,6 +120,7 @@ impl ToFillOrStrokeStyle for &CanvasPattern { self.surface_size, self.repeat_x, self.repeat_y, + *self.transform.borrow(), )) } } diff --git a/components/script_bindings/webidls/CanvasRenderingContext2D.webidl b/components/script_bindings/webidls/CanvasRenderingContext2D.webidl index ad704322662..dbafd0e995c 100644 --- a/components/script_bindings/webidls/CanvasRenderingContext2D.webidl +++ b/components/script_bindings/webidls/CanvasRenderingContext2D.webidl @@ -254,7 +254,8 @@ interface CanvasGradient { [Exposed=(Window, PaintWorklet, Worker)] interface CanvasPattern { // opaque object - //undefined setTransform(optional DOMMatrix2DInit transform = {}); + [Throws] + undefined setTransform(optional DOMMatrix2DInit transform = {}); }; // TODO: Float16Array diff --git a/components/shared/canvas/canvas.rs b/components/shared/canvas/canvas.rs index b303fdb6ed0..181ea3993b5 100644 --- a/components/shared/canvas/canvas.rs +++ b/components/shared/canvas/canvas.rs @@ -214,6 +214,7 @@ pub struct SurfaceStyle { pub surface_size: Size2D, pub repeat_x: bool, pub repeat_y: bool, + pub transform: Transform2D, } impl SurfaceStyle { @@ -222,12 +223,14 @@ impl SurfaceStyle { surface_size: Size2D, repeat_x: bool, repeat_y: bool, + transform: Transform2D, ) -> Self { Self { surface_data: ByteBuf::from(surface_data), surface_size, repeat_x, repeat_y, + transform, } } } diff --git a/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.pattern.transform.identity.html.ini b/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.pattern.transform.identity.html.ini deleted file mode 100644 index df298d2ff50..00000000000 --- a/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.pattern.transform.identity.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[2d.pattern.transform.identity.html] - [Canvas test: 2d.pattern.transform.identity] - expected: FAIL - diff --git a/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.pattern.transform.infinity.html.ini b/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.pattern.transform.infinity.html.ini deleted file mode 100644 index 95cd1994f01..00000000000 --- a/tests/wpt/meta/html/canvas/element/fill-and-stroke-styles/2d.pattern.transform.infinity.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[2d.pattern.transform.infinity.html] - [Canvas test: 2d.pattern.transform.infinity] - expected: FAIL - diff --git a/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.identity.html.ini b/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.identity.html.ini deleted file mode 100644 index e3346f3c063..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.identity.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.pattern.transform.identity.html] - [OffscreenCanvas test: 2d.pattern.transform.identity] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.identity.worker.js.ini b/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.identity.worker.js.ini deleted file mode 100644 index 09484f510c6..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.identity.worker.js.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.pattern.transform.identity.worker.html] - [2d] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.infinity.html.ini b/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.infinity.html.ini deleted file mode 100644 index f364e3ccc8e..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.infinity.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.pattern.transform.infinity.html] - [OffscreenCanvas test: 2d.pattern.transform.infinity] - expected: FAIL diff --git a/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.infinity.worker.js.ini b/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.infinity.worker.js.ini deleted file mode 100644 index 2bde0688389..00000000000 --- a/tests/wpt/meta/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.infinity.worker.js.ini +++ /dev/null @@ -1,3 +0,0 @@ -[2d.pattern.transform.infinity.worker.html] - [2d] - expected: FAIL diff --git a/tests/wpt/meta/html/dom/idlharness.any.js.ini b/tests/wpt/meta/html/dom/idlharness.any.js.ini index 81137731cc1..efaf0579c6c 100644 --- a/tests/wpt/meta/html/dom/idlharness.any.js.ini +++ b/tests/wpt/meta/html/dom/idlharness.any.js.ini @@ -1,7 +1,4 @@ [idlharness.any.worker.html] - [CanvasPattern interface: operation setTransform(optional DOMMatrix2DInit)] - expected: FAIL - [Path2D interface: operation roundRect(unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional (unrestricted double or DOMPointInit or sequence<(unrestricted double or DOMPointInit)>))] expected: FAIL diff --git a/tests/wpt/meta/html/dom/idlharness.https.html.ini b/tests/wpt/meta/html/dom/idlharness.https.html.ini index 39520f07047..08a952dac64 100644 --- a/tests/wpt/meta/html/dom/idlharness.https.html.ini +++ b/tests/wpt/meta/html/dom/idlharness.https.html.ini @@ -4343,9 +4343,6 @@ [CanvasRenderingContext2D interface: calling roundRect(unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional (unrestricted double or DOMPointInit or sequence<(unrestricted double or DOMPointInit)>)) on document.createElement("canvas").getContext("2d") with too few arguments must throw TypeError] expected: FAIL - [CanvasPattern interface: operation setTransform(optional DOMMatrix2DInit)] - expected: FAIL - [Path2D interface: operation roundRect(unrestricted double, unrestricted double, unrestricted double, unrestricted double, optional (unrestricted double or DOMPointInit or sequence<(unrestricted double or DOMPointInit)>))] expected: FAIL