1use std::cell::Cell;
6use std::{f64, ptr};
7
8use cssparser::{Parser, ParserInput};
9use dom_struct::dom_struct;
10use euclid::Angle;
11use euclid::default::{Transform2D, Transform3D};
12use js::conversions::jsstr_to_string;
13use js::jsapi::JSObject;
14use js::jsval;
15use js::rust::{CustomAutoRooterGuard, HandleObject, ToString};
16use js::typedarray::{Float32Array, Float64Array, HeapFloat32Array, HeapFloat64Array};
17use rustc_hash::FxHashMap;
18use script_bindings::cell::{DomRefCell, Ref};
19use script_bindings::cformat;
20use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
21use script_bindings::trace::RootedTraceableBox;
22use servo_base::id::{DomMatrixId, DomMatrixIndex};
23use servo_constellation_traits::DomMatrix;
24use style::stylesheets::CssRuleType;
25use style_traits::ParsingMode;
26use url::Url;
27
28use crate::css::parser_context_for_anonymous_content;
29use crate::dom::bindings::buffer_source::create_buffer_source;
30use crate::dom::bindings::codegen::Bindings::DOMMatrixBinding::{
31 DOMMatrix2DInit, DOMMatrixInit, DOMMatrixMethods,
32};
33use crate::dom::bindings::codegen::Bindings::DOMMatrixReadOnlyBinding::DOMMatrixReadOnlyMethods;
34use crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit;
35use crate::dom::bindings::codegen::UnionTypes::StringOrUnrestrictedDoubleSequence;
36use crate::dom::bindings::error;
37use crate::dom::bindings::error::Fallible;
38use crate::dom::bindings::inheritance::Castable;
39use crate::dom::bindings::reflector::DomGlobal;
40use crate::dom::bindings::root::DomRoot;
41use crate::dom::bindings::serializable::Serializable;
42use crate::dom::bindings::str::DOMString;
43use crate::dom::bindings::structuredclone::StructuredData;
44use crate::dom::dommatrix::DOMMatrix;
45use crate::dom::dompoint::DOMPoint;
46use crate::dom::globalscope::GlobalScope;
47use crate::dom::window::Window;
48use crate::script_runtime::CanGc;
49
50#[dom_struct]
51#[expect(non_snake_case)]
52pub(crate) struct DOMMatrixReadOnly {
53 reflector_: Reflector,
54 #[no_trace]
55 matrix: DomRefCell<Transform3D<f64>>,
56 is2D: Cell<bool>,
57}
58
59#[expect(non_snake_case)]
60impl DOMMatrixReadOnly {
61 pub(crate) fn new(
62 global: &GlobalScope,
63 is2D: bool,
64 matrix: Transform3D<f64>,
65 cx: &mut js::context::JSContext,
66 ) -> DomRoot<Self> {
67 Self::new_with_proto(global, None, is2D, matrix, cx)
68 }
69
70 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
71 fn new_with_proto(
72 global: &GlobalScope,
73 proto: Option<HandleObject>,
74 is2D: bool,
75 matrix: Transform3D<f64>,
76 cx: &mut js::context::JSContext,
77 ) -> DomRoot<Self> {
78 let dommatrix = Self::new_inherited(is2D, matrix);
79 reflect_dom_object_with_proto(Box::new(dommatrix), global, proto, CanGc::from_cx(cx))
80 }
81
82 pub(crate) fn new_inherited(is2D: bool, matrix: Transform3D<f64>) -> Self {
83 DOMMatrixReadOnly {
84 reflector_: Reflector::new(),
85 matrix: DomRefCell::new(matrix),
86 is2D: Cell::new(is2D),
87 }
88 }
89
90 pub(crate) fn matrix(&self) -> Ref<'_, Transform3D<f64>> {
91 self.matrix.borrow()
92 }
93
94 pub(crate) fn set_matrix(&self, value: Transform3D<f64>) {
95 self.set_m11(value.m11);
96 self.set_m12(value.m12);
97 self.set_m13(value.m13);
98 self.set_m14(value.m14);
99 self.set_m21(value.m21);
100 self.set_m22(value.m22);
101 self.set_m23(value.m23);
102 self.set_m24(value.m24);
103 self.set_m31(value.m31);
104 self.set_m32(value.m32);
105 self.set_m33(value.m33);
106 self.set_m34(value.m34);
107 self.set_m41(value.m41);
108 self.set_m42(value.m42);
109 self.set_m43(value.m43);
110 self.set_m44(value.m44);
111 }
112
113 pub(crate) fn is2D(&self) -> bool {
114 self.is2D.get()
115 }
116
117 pub(crate) fn set_is2D(&self, value: bool) {
118 self.is2D.set(value);
119 }
120
121 pub(crate) fn set_m11(&self, value: f64) {
123 self.matrix.borrow_mut().m11 = value;
124 }
125
126 pub(crate) fn set_m12(&self, value: f64) {
128 self.matrix.borrow_mut().m12 = value;
129 }
130
131 pub(crate) fn set_m13(&self, value: f64) {
133 self.matrix.borrow_mut().m13 = value;
137 if value.abs() != 0. {
138 self.is2D.set(false);
139 }
140 }
141
142 pub(crate) fn set_m14(&self, value: f64) {
144 self.matrix.borrow_mut().m14 = value;
147
148 if value.abs() != 0. {
149 self.is2D.set(false);
150 }
151 }
152
153 pub(crate) fn set_m21(&self, value: f64) {
155 self.matrix.borrow_mut().m21 = value;
156 }
157
158 pub(crate) fn set_m22(&self, value: f64) {
160 self.matrix.borrow_mut().m22 = value;
161 }
162
163 pub(crate) fn set_m23(&self, value: f64) {
165 self.matrix.borrow_mut().m23 = value;
168
169 if value.abs() != 0. {
170 self.is2D.set(false);
171 }
172 }
173
174 pub(crate) fn set_m24(&self, value: f64) {
176 self.matrix.borrow_mut().m24 = value;
179
180 if value.abs() != 0. {
181 self.is2D.set(false);
182 }
183 }
184
185 pub(crate) fn set_m31(&self, value: f64) {
187 self.matrix.borrow_mut().m31 = value;
190
191 if value.abs() != 0. {
192 self.is2D.set(false);
193 }
194 }
195
196 pub(crate) fn set_m32(&self, value: f64) {
198 self.matrix.borrow_mut().m32 = value;
201
202 if value.abs() != 0. {
203 self.is2D.set(false);
204 }
205 }
206
207 pub(crate) fn set_m33(&self, value: f64) {
209 self.matrix.borrow_mut().m33 = value;
212
213 if value != 1. {
214 self.is2D.set(false);
215 }
216 }
217
218 pub(crate) fn set_m34(&self, value: f64) {
220 self.matrix.borrow_mut().m34 = value;
223
224 if value.abs() != 0. {
225 self.is2D.set(false);
226 }
227 }
228
229 pub(crate) fn set_m41(&self, value: f64) {
231 self.matrix.borrow_mut().m41 = value;
232 }
233
234 pub(crate) fn set_m42(&self, value: f64) {
236 self.matrix.borrow_mut().m42 = value;
237 }
238
239 pub(crate) fn set_m43(&self, value: f64) {
241 self.matrix.borrow_mut().m43 = value;
244
245 if value.abs() != 0. {
246 self.is2D.set(false);
247 }
248 }
249
250 pub(crate) fn set_m44(&self, value: f64) {
252 self.matrix.borrow_mut().m44 = value;
255
256 if value != 1. {
257 self.is2D.set(false);
258 }
259 }
260
261 pub(crate) fn multiply_self(&self, other: &DOMMatrixInit) -> Fallible<()> {
263 dommatrixinit_to_matrix(other).map(|(is2D, other_matrix)| {
265 let mut matrix = self.matrix.borrow_mut();
267 *matrix = other_matrix.then(&matrix);
268 if !is2D {
270 self.is2D.set(false);
271 }
272 })
274 }
275
276 pub(crate) fn pre_multiply_self(&self, other: &DOMMatrixInit) -> Fallible<()> {
278 dommatrixinit_to_matrix(other).map(|(is2D, other_matrix)| {
280 let mut matrix = self.matrix.borrow_mut();
282 *matrix = matrix.then(&other_matrix);
283 if !is2D {
285 self.is2D.set(false);
286 }
287 })
289 }
290
291 pub(crate) fn translate_self(&self, tx: f64, ty: f64, tz: f64) {
293 let translation = Transform3D::translation(tx, ty, tz);
295 let mut matrix = self.matrix.borrow_mut();
296 *matrix = translation.then(&matrix);
297 if tz != 0.0 {
299 self.is2D.set(false);
300 }
301 }
303
304 pub(crate) fn scale_self(
306 &self,
307 scaleX: f64,
308 scaleY: Option<f64>,
309 scaleZ: f64,
310 mut originX: f64,
311 mut originY: f64,
312 mut originZ: f64,
313 ) {
314 self.translate_self(originX, originY, originZ);
316 let scaleY = scaleY.unwrap_or(scaleX);
318 {
320 let scale3D = Transform3D::scale(scaleX, scaleY, scaleZ);
321 let mut matrix = self.matrix.borrow_mut();
322 *matrix = scale3D.then(&matrix);
323 }
324 originX = -originX;
326 originY = -originY;
327 originZ = -originZ;
328 self.translate_self(originX, originY, originZ);
330 if scaleZ != 1.0 || originZ != 0.0 {
332 self.is2D.set(false);
333 }
334 }
336
337 pub(crate) fn scale_3d_self(&self, scale: f64, originX: f64, originY: f64, originZ: f64) {
339 self.translate_self(originX, originY, originZ);
341 {
343 let scale3D = Transform3D::scale(scale, scale, scale);
344 let mut matrix = self.matrix.borrow_mut();
345 *matrix = scale3D.then(&matrix);
346 }
347 self.translate_self(-originX, -originY, -originZ);
349 if scale != 1.0 {
351 self.is2D.set(false);
352 }
353 }
355
356 pub(crate) fn rotate_self(&self, mut rotX: f64, mut rotY: Option<f64>, mut rotZ: Option<f64>) {
358 if rotY.is_none() && rotZ.is_none() {
360 rotZ = Some(rotX);
361 rotX = 0.0;
362 rotY = Some(0.0);
363 }
364 let rotY = rotY.unwrap_or(0.0);
366 let rotZ = rotZ.unwrap_or(0.0);
368 if rotX != 0.0 || rotY != 0.0 {
370 self.is2D.set(false);
371 }
372 if rotZ != 0.0 {
373 let rotation = Transform3D::rotation(0.0, 0.0, 1.0, Angle::radians(rotZ.to_radians()));
375 let mut matrix = self.matrix.borrow_mut();
376 *matrix = rotation.then(&matrix);
377 }
378 if rotY != 0.0 {
379 let rotation = Transform3D::rotation(0.0, 1.0, 0.0, Angle::radians(rotY.to_radians()));
381 let mut matrix = self.matrix.borrow_mut();
382 *matrix = rotation.then(&matrix);
383 }
384 if rotX != 0.0 {
385 let rotation = Transform3D::rotation(1.0, 0.0, 0.0, Angle::radians(rotX.to_radians()));
387 let mut matrix = self.matrix.borrow_mut();
388 *matrix = rotation.then(&matrix);
389 }
390 }
392
393 pub(crate) fn rotate_from_vector_self(&self, x: f64, y: f64) {
395 if y != 0.0 || x < 0.0 {
397 let rotZ = Angle::radians(f64::atan2(y, x));
399 let rotation = Transform3D::rotation(0.0, 0.0, 1.0, rotZ);
400 let mut matrix = self.matrix.borrow_mut();
401 *matrix = rotation.then(&matrix);
402 }
403 }
405
406 pub(crate) fn rotate_axis_angle_self(&self, x: f64, y: f64, z: f64, angle: f64) {
408 let (norm_x, norm_y, norm_z) = normalize_point(x, y, z);
410 let rotation =
412 Transform3D::rotation(norm_x, norm_y, norm_z, Angle::radians(angle.to_radians()));
413 let mut matrix = self.matrix.borrow_mut();
414 *matrix = rotation.then(&matrix);
415 if x != 0.0 || y != 0.0 {
417 self.is2D.set(false);
418 }
419 }
421
422 pub(crate) fn skew_x_self(&self, sx: f64) {
424 let skew = Transform3D::skew(Angle::radians(sx.to_radians()), Angle::radians(0.0));
426 let mut matrix = self.matrix.borrow_mut();
427 *matrix = skew.then(&matrix);
428 }
430
431 pub(crate) fn skew_y_self(&self, sy: f64) {
433 let skew = Transform3D::skew(Angle::radians(0.0), Angle::radians(sy.to_radians()));
435 let mut matrix = self.matrix.borrow_mut();
436 *matrix = skew.then(&matrix);
437 }
439
440 pub(crate) fn invert_self(&self) {
442 let mut matrix = self.matrix.borrow_mut();
443 let inverted = match self.is2D() {
445 true => matrix.to_2d().inverse().map(|m| m.to_3d()),
446 false => matrix.inverse(),
447 };
448
449 *matrix = inverted.unwrap_or_else(|| -> Transform3D<f64> {
452 self.is2D.set(false);
453 Transform3D::new(
454 f64::NAN,
455 f64::NAN,
456 f64::NAN,
457 f64::NAN,
458 f64::NAN,
459 f64::NAN,
460 f64::NAN,
461 f64::NAN,
462 f64::NAN,
463 f64::NAN,
464 f64::NAN,
465 f64::NAN,
466 f64::NAN,
467 f64::NAN,
468 f64::NAN,
469 f64::NAN,
470 )
471 });
472 }
474}
475
476#[expect(non_snake_case)]
477impl DOMMatrixReadOnlyMethods<crate::DomTypeHolder> for DOMMatrixReadOnly {
478 fn Constructor(
480 cx: &mut js::context::JSContext,
481 global: &GlobalScope,
482 proto: Option<HandleObject>,
483 init: Option<StringOrUnrestrictedDoubleSequence>,
484 ) -> Fallible<DomRoot<Self>> {
485 if init.is_none() {
486 return Ok(Self::new_with_proto(
487 global,
488 proto,
489 true,
490 Transform3D::identity(),
491 cx,
492 ));
493 }
494 match init.unwrap() {
495 StringOrUnrestrictedDoubleSequence::String(ref s) => {
496 if !global.is::<Window>() {
497 return Err(error::Error::Type(
498 c"String constructor is only supported in the main thread.".to_owned(),
499 ));
500 }
501 if s.is_empty() {
502 return Ok(Self::new(global, true, Transform3D::identity(), cx));
503 }
504 transform_to_matrix(&s.str())
505 .map(|(is2D, matrix)| Self::new_with_proto(global, proto, is2D, matrix, cx))
506 },
507 StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(ref entries) => {
508 entries_to_matrix(&entries[..])
509 .map(|(is2D, matrix)| Self::new_with_proto(global, proto, is2D, matrix, cx))
510 },
511 }
512 }
513
514 fn FromMatrix(
516 cx: &mut js::context::JSContext,
517 global: &GlobalScope,
518 other: &DOMMatrixInit,
519 ) -> Fallible<DomRoot<Self>> {
520 dommatrixinit_to_matrix(other).map(|(is2D, matrix)| Self::new(global, is2D, matrix, cx))
521 }
522
523 fn FromFloat32Array(
525 cx: &mut js::context::JSContext,
526 global: &GlobalScope,
527 array: CustomAutoRooterGuard<Float32Array>,
528 ) -> Fallible<DomRoot<DOMMatrixReadOnly>> {
529 let vec: Vec<f64> = array.to_vec().iter().map(|&x| x as f64).collect();
530 DOMMatrixReadOnly::Constructor(
531 cx,
532 global,
533 None,
534 Some(StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(vec)),
535 )
536 }
537
538 fn FromFloat64Array(
540 cx: &mut js::context::JSContext,
541 global: &GlobalScope,
542 array: CustomAutoRooterGuard<Float64Array>,
543 ) -> Fallible<DomRoot<DOMMatrixReadOnly>> {
544 let vec: Vec<f64> = array.to_vec();
545 DOMMatrixReadOnly::Constructor(
546 cx,
547 global,
548 None,
549 Some(StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(vec)),
550 )
551 }
552
553 fn M11(&self) -> f64 {
555 self.matrix.borrow().m11
556 }
557
558 fn M12(&self) -> f64 {
560 self.matrix.borrow().m12
561 }
562
563 fn M13(&self) -> f64 {
565 self.matrix.borrow().m13
566 }
567
568 fn M14(&self) -> f64 {
570 self.matrix.borrow().m14
571 }
572
573 fn M21(&self) -> f64 {
575 self.matrix.borrow().m21
576 }
577
578 fn M22(&self) -> f64 {
580 self.matrix.borrow().m22
581 }
582
583 fn M23(&self) -> f64 {
585 self.matrix.borrow().m23
586 }
587
588 fn M24(&self) -> f64 {
590 self.matrix.borrow().m24
591 }
592
593 fn M31(&self) -> f64 {
595 self.matrix.borrow().m31
596 }
597
598 fn M32(&self) -> f64 {
600 self.matrix.borrow().m32
601 }
602
603 fn M33(&self) -> f64 {
605 self.matrix.borrow().m33
606 }
607
608 fn M34(&self) -> f64 {
610 self.matrix.borrow().m34
611 }
612
613 fn M41(&self) -> f64 {
615 self.matrix.borrow().m41
616 }
617
618 fn M42(&self) -> f64 {
620 self.matrix.borrow().m42
621 }
622
623 fn M43(&self) -> f64 {
625 self.matrix.borrow().m43
626 }
627
628 fn M44(&self) -> f64 {
630 self.matrix.borrow().m44
631 }
632
633 fn A(&self) -> f64 {
635 self.M11()
636 }
637
638 fn B(&self) -> f64 {
640 self.M12()
641 }
642
643 fn C(&self) -> f64 {
645 self.M21()
646 }
647
648 fn D(&self) -> f64 {
650 self.M22()
651 }
652
653 fn E(&self) -> f64 {
655 self.M41()
656 }
657
658 fn F(&self) -> f64 {
660 self.M42()
661 }
662
663 fn Is2D(&self) -> bool {
665 self.is2D.get()
666 }
667
668 fn IsIdentity(&self) -> bool {
670 let matrix = self.matrix.borrow();
671 matrix.m12 == 0.0 &&
672 matrix.m13 == 0.0 &&
673 matrix.m14 == 0.0 &&
674 matrix.m21 == 0.0 &&
675 matrix.m23 == 0.0 &&
676 matrix.m24 == 0.0 &&
677 matrix.m31 == 0.0 &&
678 matrix.m32 == 0.0 &&
679 matrix.m34 == 0.0 &&
680 matrix.m41 == 0.0 &&
681 matrix.m42 == 0.0 &&
682 matrix.m43 == 0.0 &&
683 matrix.m11 == 1.0 &&
684 matrix.m22 == 1.0 &&
685 matrix.m33 == 1.0 &&
686 matrix.m44 == 1.0
687 }
688
689 fn Translate(
691 &self,
692 cx: &mut js::context::JSContext,
693 tx: f64,
694 ty: f64,
695 tz: f64,
696 ) -> DomRoot<DOMMatrix> {
697 DOMMatrix::from_readonly(&self.global(), self, cx).TranslateSelf(tx, ty, tz)
698 }
699
700 fn Scale(
702 &self,
703 cx: &mut js::context::JSContext,
704 scaleX: f64,
705 scaleY: Option<f64>,
706 scaleZ: f64,
707 originX: f64,
708 originY: f64,
709 originZ: f64,
710 ) -> DomRoot<DOMMatrix> {
711 DOMMatrix::from_readonly(&self.global(), self, cx)
712 .ScaleSelf(scaleX, scaleY, scaleZ, originX, originY, originZ)
713 }
714
715 fn ScaleNonUniform(
717 &self,
718 cx: &mut js::context::JSContext,
719 scaleX: f64,
720 scaleY: f64,
721 ) -> DomRoot<DOMMatrix> {
722 DOMMatrix::from_readonly(&self.global(), self, cx).ScaleSelf(
723 scaleX,
724 Some(scaleY),
725 1.0,
726 0.0,
727 0.0,
728 0.0,
729 )
730 }
731
732 fn Scale3d(
734 &self,
735 cx: &mut js::context::JSContext,
736 scale: f64,
737 originX: f64,
738 originY: f64,
739 originZ: f64,
740 ) -> DomRoot<DOMMatrix> {
741 DOMMatrix::from_readonly(&self.global(), self, cx)
742 .Scale3dSelf(scale, originX, originY, originZ)
743 }
744
745 fn Rotate(
747 &self,
748 cx: &mut js::context::JSContext,
749 rotX: f64,
750 rotY: Option<f64>,
751 rotZ: Option<f64>,
752 ) -> DomRoot<DOMMatrix> {
753 DOMMatrix::from_readonly(&self.global(), self, cx).RotateSelf(rotX, rotY, rotZ)
754 }
755
756 fn RotateFromVector(
758 &self,
759 cx: &mut js::context::JSContext,
760 x: f64,
761 y: f64,
762 ) -> DomRoot<DOMMatrix> {
763 DOMMatrix::from_readonly(&self.global(), self, cx).RotateFromVectorSelf(x, y)
764 }
765
766 fn RotateAxisAngle(
768 &self,
769 cx: &mut js::context::JSContext,
770 x: f64,
771 y: f64,
772 z: f64,
773 angle: f64,
774 ) -> DomRoot<DOMMatrix> {
775 DOMMatrix::from_readonly(&self.global(), self, cx).RotateAxisAngleSelf(x, y, z, angle)
776 }
777
778 fn SkewX(&self, cx: &mut js::context::JSContext, sx: f64) -> DomRoot<DOMMatrix> {
780 DOMMatrix::from_readonly(&self.global(), self, cx).SkewXSelf(sx)
781 }
782
783 fn SkewY(&self, cx: &mut js::context::JSContext, sy: f64) -> DomRoot<DOMMatrix> {
785 DOMMatrix::from_readonly(&self.global(), self, cx).SkewYSelf(sy)
786 }
787
788 fn Multiply(
790 &self,
791 cx: &mut js::context::JSContext,
792 other: &DOMMatrixInit,
793 ) -> Fallible<DomRoot<DOMMatrix>> {
794 DOMMatrix::from_readonly(&self.global(), self, cx).MultiplySelf(other)
795 }
796
797 fn FlipX(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMMatrix> {
799 let is2D = self.is2D.get();
800 let flip = Transform3D::new(
801 -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
802 );
803 let matrix = flip.then(&self.matrix.borrow());
804 DOMMatrix::new(&self.global(), is2D, matrix, cx)
805 }
806
807 fn FlipY(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMMatrix> {
809 let is2D = self.is2D.get();
810 let flip = Transform3D::new(
811 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
812 );
813 let matrix = flip.then(&self.matrix.borrow());
814 DOMMatrix::new(&self.global(), is2D, matrix, cx)
815 }
816
817 fn Inverse(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMMatrix> {
819 DOMMatrix::from_readonly(&self.global(), self, cx).InvertSelf()
820 }
821
822 fn TransformPoint(
824 &self,
825 cx: &mut js::context::JSContext,
826 point: &DOMPointInit,
827 ) -> DomRoot<DOMPoint> {
828 let mat = self.matrix.borrow();
835 let x = point.x * mat.m11 + point.y * mat.m21 + point.z * mat.m31 + point.w * mat.m41;
836 let y = point.x * mat.m12 + point.y * mat.m22 + point.z * mat.m32 + point.w * mat.m42;
837 let z = point.x * mat.m13 + point.y * mat.m23 + point.z * mat.m33 + point.w * mat.m43;
838 let w = point.x * mat.m14 + point.y * mat.m24 + point.z * mat.m34 + point.w * mat.m44;
839
840 DOMPoint::new(cx, &self.global(), x, y, z, w)
841 }
842
843 fn ToFloat32Array(
845 &self,
846 cx: &mut js::context::JSContext,
847 ) -> RootedTraceableBox<HeapFloat32Array> {
848 let vec: Vec<f32> = self
849 .matrix
850 .borrow()
851 .to_array()
852 .iter()
853 .map(|&x| x as f32)
854 .collect();
855 rooted!(&in(cx) let mut array = ptr::null_mut::<JSObject>());
856 create_buffer_source(cx, &vec, array.handle_mut())
857 .expect("Converting matrix to float32 array should never fail")
858 }
859
860 fn ToFloat64Array(
862 &self,
863 cx: &mut js::context::JSContext,
864 ) -> RootedTraceableBox<HeapFloat64Array> {
865 rooted!(&in(cx) let mut array = ptr::null_mut::<JSObject>());
866 create_buffer_source(cx, &self.matrix.borrow().to_array(), array.handle_mut())
867 .expect("Converting matrix to float64 array should never fail")
868 }
869
870 #[expect(unsafe_code)]
872 fn Stringifier(&self, cx: &mut js::context::JSContext) -> Fallible<DOMString> {
873 let mat = self.matrix.borrow();
876 if !mat.m11.is_finite() ||
877 !mat.m12.is_finite() ||
878 !mat.m13.is_finite() ||
879 !mat.m14.is_finite() ||
880 !mat.m21.is_finite() ||
881 !mat.m22.is_finite() ||
882 !mat.m23.is_finite() ||
883 !mat.m24.is_finite() ||
884 !mat.m31.is_finite() ||
885 !mat.m32.is_finite() ||
886 !mat.m33.is_finite() ||
887 !mat.m34.is_finite() ||
888 !mat.m41.is_finite() ||
889 !mat.m42.is_finite() ||
890 !mat.m43.is_finite() ||
891 !mat.m44.is_finite()
892 {
893 return Err(error::Error::InvalidState(None));
894 }
895
896 let mut to_string = |f: f64| {
897 rooted!(&in(cx) let rooted_value = jsval::DoubleValue(f));
898 let serialization =
899 std::ptr::NonNull::new(unsafe { ToString(cx, rooted_value.handle()) })
900 .expect("Pointer cannot be null");
901 unsafe { jsstr_to_string(cx, serialization) }
902 };
903
904 let string = if self.is2D() {
907 format!(
921 "matrix({}, {}, {}, {}, {}, {})",
922 to_string(mat.m11),
923 to_string(mat.m12),
924 to_string(mat.m21),
925 to_string(mat.m22),
926 to_string(mat.m41),
927 to_string(mat.m42)
928 )
929 .into()
930 }
931 else {
933 format!(
962 "matrix3d({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {})",
963 to_string(mat.m11),
964 to_string(mat.m12),
965 to_string(mat.m13),
966 to_string(mat.m14),
967 to_string(mat.m21),
968 to_string(mat.m22),
969 to_string(mat.m23),
970 to_string(mat.m24),
971 to_string(mat.m31),
972 to_string(mat.m32),
973 to_string(mat.m33),
974 to_string(mat.m34),
975 to_string(mat.m41),
976 to_string(mat.m42),
977 to_string(mat.m43),
978 to_string(mat.m44)
979 )
980 .into()
981 };
982
983 Ok(string)
984 }
985}
986
987impl Serializable for DOMMatrixReadOnly {
988 type Index = DomMatrixIndex;
989 type Data = DomMatrix;
990
991 fn serialize(&self) -> Result<(DomMatrixId, Self::Data), ()> {
992 let serialized = if self.is2D() {
993 DomMatrix {
994 matrix: Transform3D::new(
995 self.M11(),
996 self.M12(),
997 f64::NAN,
998 f64::NAN,
999 self.M21(),
1000 self.M22(),
1001 f64::NAN,
1002 f64::NAN,
1003 f64::NAN,
1004 f64::NAN,
1005 f64::NAN,
1006 f64::NAN,
1007 self.M41(),
1008 self.M42(),
1009 f64::NAN,
1010 f64::NAN,
1011 ),
1012 is_2d: true,
1013 }
1014 } else {
1015 DomMatrix {
1016 matrix: *self.matrix(),
1017 is_2d: false,
1018 }
1019 };
1020 Ok((DomMatrixId::new(), serialized))
1021 }
1022
1023 fn deserialize(
1024 cx: &mut js::context::JSContext,
1025 owner: &GlobalScope,
1026 serialized: Self::Data,
1027 ) -> Result<DomRoot<Self>, ()>
1028 where
1029 Self: Sized,
1030 {
1031 if serialized.is_2d {
1032 Ok(Self::new(
1033 owner,
1034 true,
1035 Transform3D::new(
1036 serialized.matrix.m11,
1037 serialized.matrix.m12,
1038 0.0,
1039 0.0,
1040 serialized.matrix.m21,
1041 serialized.matrix.m22,
1042 0.0,
1043 0.0,
1044 0.0,
1045 0.0,
1046 1.0,
1047 0.0,
1048 serialized.matrix.m41,
1049 serialized.matrix.m42,
1050 0.0,
1051 1.0,
1052 ),
1053 cx,
1054 ))
1055 } else {
1056 Ok(Self::new(owner, false, serialized.matrix, cx))
1057 }
1058 }
1059
1060 fn serialized_storage<'a>(
1061 data: StructuredData<'a, '_>,
1062 ) -> &'a mut Option<FxHashMap<DomMatrixId, Self::Data>> {
1063 match data {
1064 StructuredData::Reader(reader) => &mut reader.matrices,
1065 StructuredData::Writer(writer) => &mut writer.matrices,
1066 }
1067 }
1068}
1069
1070pub(crate) fn entries_to_matrix(entries: &[f64]) -> Fallible<(bool, Transform3D<f64>)> {
1072 if let Ok(array) = entries.try_into() {
1073 Ok((true, Transform2D::from_array(array).to_3d()))
1074 } else if let Ok(array) = entries.try_into() {
1075 Ok((false, Transform3D::from_array(array)))
1076 } else {
1077 let err_msg = cformat!("Expected 6 or 16 entries, but found {}.", entries.len());
1078 Err(error::Error::Type(err_msg))
1079 }
1080}
1081
1082fn validate_and_fixup_2d(dict: &DOMMatrix2DInit) -> Fallible<Transform2D<f64>> {
1084 let same_value_zero = |x: f64, y: f64| -> bool { x.is_nan() && y.is_nan() || x == y };
1086
1087 if dict.a.is_some() &&
1090 dict.m11.is_some() &&
1091 !same_value_zero(dict.a.unwrap(), dict.m11.unwrap()) ||
1092 dict.b.is_some() &&
1093 dict.m12.is_some() &&
1094 !same_value_zero(dict.b.unwrap(), dict.m12.unwrap()) ||
1095 dict.c.is_some() &&
1096 dict.m21.is_some() &&
1097 !same_value_zero(dict.c.unwrap(), dict.m21.unwrap()) ||
1098 dict.d.is_some() &&
1099 dict.m22.is_some() &&
1100 !same_value_zero(dict.d.unwrap(), dict.m22.unwrap()) ||
1101 dict.e.is_some() &&
1102 dict.m41.is_some() &&
1103 !same_value_zero(dict.e.unwrap(), dict.m41.unwrap()) ||
1104 dict.f.is_some() &&
1105 dict.m42.is_some() &&
1106 !same_value_zero(dict.f.unwrap(), dict.m42.unwrap())
1107 {
1108 return Err(error::Error::Type(
1109 c"Property mismatch on matrix initialization.".to_owned(),
1110 ));
1111 }
1112
1113 let m11 = dict.m11.unwrap_or(dict.a.unwrap_or(1.0));
1116
1117 let m12 = dict.m12.unwrap_or(dict.b.unwrap_or(0.0));
1120
1121 let m21 = dict.m21.unwrap_or(dict.c.unwrap_or(0.0));
1124
1125 let m22 = dict.m22.unwrap_or(dict.d.unwrap_or(1.0));
1128
1129 let m41 = dict.m41.unwrap_or(dict.e.unwrap_or(0.0));
1132
1133 let m42 = dict.m42.unwrap_or(dict.f.unwrap_or(0.0));
1136
1137 Ok(Transform2D::new(m11, m12, m21, m22, m41, m42))
1138}
1139
1140fn validate_and_fixup(dict: &DOMMatrixInit) -> Fallible<(bool, Transform3D<f64>)> {
1142 let transform2d = validate_and_fixup_2d(&dict.parent)?;
1144
1145 if dict.is2D == Some(true) &&
1150 (dict.m13 != 0.0 ||
1151 dict.m14 != 0.0 ||
1152 dict.m23 != 0.0 ||
1153 dict.m24 != 0.0 ||
1154 dict.m31 != 0.0 ||
1155 dict.m32 != 0.0 ||
1156 dict.m34 != 0.0 ||
1157 dict.m43 != 0.0 ||
1158 dict.m33 != 1.0 ||
1159 dict.m44 != 1.0)
1160 {
1161 return Err(error::Error::Type(
1162 c"The is2D member is set to true but the input matrix is a 3d matrix.".to_owned(),
1163 ));
1164 }
1165
1166 let mut is_2d = dict.is2D;
1167
1168 if is_2d.is_none() &&
1173 (dict.m13 != 0.0 ||
1174 dict.m14 != 0.0 ||
1175 dict.m23 != 0.0 ||
1176 dict.m24 != 0.0 ||
1177 dict.m31 != 0.0 ||
1178 dict.m32 != 0.0 ||
1179 dict.m34 != 0.0 ||
1180 dict.m43 != 0.0 ||
1181 dict.m33 != 1.0 ||
1182 dict.m44 != 1.0)
1183 {
1184 is_2d = Some(false);
1185 }
1186
1187 let is_2d = is_2d.unwrap_or(true);
1189
1190 let mut transform = transform2d.to_3d();
1191 transform.m13 = dict.m13;
1192 transform.m14 = dict.m14;
1193 transform.m23 = dict.m23;
1194 transform.m24 = dict.m24;
1195 transform.m31 = dict.m31;
1196 transform.m32 = dict.m32;
1197 transform.m33 = dict.m33;
1198 transform.m34 = dict.m34;
1199 transform.m43 = dict.m43;
1200 transform.m44 = dict.m44;
1201
1202 Ok((is_2d, transform))
1203}
1204
1205pub(crate) fn dommatrix2dinit_to_matrix(dict: &DOMMatrix2DInit) -> Fallible<Transform2D<f64>> {
1207 validate_and_fixup_2d(dict)
1213}
1214
1215pub(crate) fn dommatrixinit_to_matrix(dict: &DOMMatrixInit) -> Fallible<(bool, Transform3D<f64>)> {
1217 validate_and_fixup(dict)
1223}
1224
1225#[inline]
1226fn normalize_point(x: f64, y: f64, z: f64) -> (f64, f64, f64) {
1227 let len = (x * x + y * y + z * z).sqrt();
1228 if len == 0.0 {
1229 (0.0, 0.0, 0.0)
1230 } else {
1231 (x / len, y / len, z / len)
1232 }
1233}
1234
1235pub(crate) fn transform_to_matrix(value: &str) -> Fallible<(bool, Transform3D<f64>)> {
1236 use style::properties::longhands::transform;
1237
1238 let mut input = ParserInput::new(value);
1239 let mut parser = Parser::new(&mut input);
1240 let url_data = Url::parse("about:blank").unwrap().into();
1241 let context =
1242 parser_context_for_anonymous_content(CssRuleType::Style, ParsingMode::DEFAULT, &url_data);
1243
1244 let transform = match parser.parse_entirely(|t| transform::parse(&context, t)) {
1245 Ok(result) => result,
1246 Err(..) => return Err(error::Error::Syntax(None)),
1247 };
1248
1249 let (m, is_3d) = match transform.to_transform_3d_matrix_f64(None) {
1250 Ok(result) => result,
1251 Err(..) => return Err(error::Error::Syntax(None)),
1252 };
1253
1254 Ok((!is_3d, m))
1255}