View Javadoc
1   /*
2    * Copyright (c) 2002-2026 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.host.dom;
16  
17  import org.htmlunit.corejs.javascript.Context;
18  import org.htmlunit.corejs.javascript.Function;
19  import org.htmlunit.corejs.javascript.FunctionObject;
20  import org.htmlunit.corejs.javascript.NativeArray;
21  import org.htmlunit.corejs.javascript.Scriptable;
22  import org.htmlunit.corejs.javascript.VarScope;
23  import org.htmlunit.corejs.javascript.typedarrays.NativeFloat32Array;
24  import org.htmlunit.corejs.javascript.typedarrays.NativeFloat64Array;
25  import org.htmlunit.javascript.HtmlUnitScriptable;
26  import org.htmlunit.javascript.JavaScriptEngine;
27  import org.htmlunit.javascript.configuration.JsxClass;
28  import org.htmlunit.javascript.configuration.JsxConstructor;
29  import org.htmlunit.javascript.configuration.JsxFunction;
30  import org.htmlunit.javascript.configuration.JsxGetter;
31  
32  /**
33   * A JavaScript object for {@code DOMMatrixReadOnly}.
34   *
35   * @author Ahmed Ashour
36   * @author Ronald Brill
37   *
38   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMMatrixReadOnly">MDN Documentation</a>
39   */
40  @JsxClass
41  public class DOMMatrixReadOnly extends HtmlUnitScriptable {
42  
43      // private static final Log LOG = LogFactory.getLog(DOMMatrixReadOnly.class);
44  
45      private double m11_;
46      private double m12_;
47      private double m13_;
48      private double m14_;
49  
50      private double m21_;
51      private double m22_;
52      private double m23_;
53      private double m24_;
54  
55      private double m31_;
56      private double m32_;
57      private double m33_;
58      private double m34_;
59  
60      private double m41_;
61      private double m42_;
62      private double m43_;
63      private double m44_;
64  
65      private boolean is2D_;
66  
67      /**
68       * Ctor.
69       */
70      public DOMMatrixReadOnly() {
71          m11_ = 1;
72          m12_ = 0;
73          m13_ = 0;
74          m14_ = 0;
75  
76          m21_ = 0;
77          m22_ = 1;
78          m23_ = 0;
79          m24_ = 0;
80  
81          m31_ = 0;
82          m32_ = 0;
83          m33_ = 1;
84          m34_ = 0;
85  
86          m41_ = 0;
87          m42_ = 0;
88          m43_ = 0;
89          m44_ = 1;
90  
91          is2D_ = true;
92      }
93  
94      /**
95       * JavaScript constructor.
96       * @param cx the current context
97       * @param scope the scope
98       * @param args the arguments to the DOMMatrixReadOnly constructor
99       * @param ctorObj the function object
100      * @param inNewExpr {@code true} if invoked with the {@code new} operator
101      * @return the Java object that JavaScript can access
102      */
103     @JsxConstructor
104     public static DOMMatrixReadOnly jsConstructor(final Context cx, final VarScope scope,
105             final Object[] args, final Function ctorObj, final boolean inNewExpr) {
106 
107         final DOMMatrixReadOnly matrix = new DOMMatrixReadOnly();
108         matrix.init(args, scope, ctorObj);
109         return matrix;
110     }
111 
112     protected void init(final Object[] args, final VarScope scope, final Function ctorObj) {
113         setParentScope(scope);
114         setPrototype(((FunctionObject) ctorObj).getClassPrototype());
115 
116         if (args.length == 0 || JavaScriptEngine.isUndefined(args[0])) {
117             return;
118         }
119 
120         if (args[0] instanceof NativeArray arrayArgs) {
121             if (arrayArgs.getLength() == 6) {
122                 m11_ = JavaScriptEngine.toNumber(arrayArgs.get(0));
123                 m12_ = JavaScriptEngine.toNumber(arrayArgs.get(1));
124 
125                 m21_ = JavaScriptEngine.toNumber(arrayArgs.get(2));
126                 m22_ = JavaScriptEngine.toNumber(arrayArgs.get(3));
127 
128                 m41_ = JavaScriptEngine.toNumber(arrayArgs.get(4));
129                 m42_ = JavaScriptEngine.toNumber(arrayArgs.get(5));
130 
131                 is2D_ = true;
132                 return;
133             }
134 
135             if (arrayArgs.getLength() == 16) {
136                 m11_ = JavaScriptEngine.toNumber(arrayArgs.get(0));
137                 m12_ = JavaScriptEngine.toNumber(arrayArgs.get(1));
138                 m13_ = JavaScriptEngine.toNumber(arrayArgs.get(2));
139                 m14_ = JavaScriptEngine.toNumber(arrayArgs.get(3));
140 
141                 m21_ = JavaScriptEngine.toNumber(arrayArgs.get(4));
142                 m22_ = JavaScriptEngine.toNumber(arrayArgs.get(5));
143                 m23_ = JavaScriptEngine.toNumber(arrayArgs.get(6));
144                 m24_ = JavaScriptEngine.toNumber(arrayArgs.get(7));
145 
146                 m31_ = JavaScriptEngine.toNumber(arrayArgs.get(8));
147                 m32_ = JavaScriptEngine.toNumber(arrayArgs.get(9));
148                 m33_ = JavaScriptEngine.toNumber(arrayArgs.get(10));
149                 m34_ = JavaScriptEngine.toNumber(arrayArgs.get(11));
150 
151                 m41_ = JavaScriptEngine.toNumber(arrayArgs.get(12));
152                 m42_ = JavaScriptEngine.toNumber(arrayArgs.get(13));
153                 m43_ = JavaScriptEngine.toNumber(arrayArgs.get(14));
154                 m44_ = JavaScriptEngine.toNumber(arrayArgs.get(15));
155 
156                 is2D_ = false;
157                 return;
158             }
159 
160             throw JavaScriptEngine.typeError("DOMMatrixReadOnly constructor: Matrix init sequence must have "
161                     + "a length of 6 or 16 (actual value: " + arrayArgs.getLength() + ")");
162         }
163 
164         throw JavaScriptEngine.asJavaScriptException(
165                 getWindow(),
166                 "An invalid or illegal string was specified",
167                 DOMException.SYNTAX_ERR);
168     }
169 
170     /**
171      * Returns the {@code m11} property.
172      * @return the {@code m11} property
173      */
174     @JsxGetter
175     public double getM11() {
176         return m11_;
177     }
178 
179     /**
180      * Sets the {@code m11} property.
181      * @param m11 the new value
182      */
183     public void setM11(final double m11) {
184         m11_ = m11;
185     }
186 
187     /**
188      * Returns the {@code a} property.
189      * @return the {@code a} property
190      */
191     @JsxGetter
192     public double getA() {
193         return m11_;
194     }
195 
196     /**
197      * Returns the {@code m12} property.
198      * @return the {@code m12} property
199      */
200     @JsxGetter
201     public double getM12() {
202         return m12_;
203     }
204 
205     /**
206      * Sets the {@code m12} property.
207      * @param m12 the new value
208      */
209     public void setM12(final double m12) {
210         m12_ = m12;
211     }
212 
213     /**
214      * Returns the {@code b} property.
215      * @return the {@code b} property
216      */
217     @JsxGetter
218     public double getB() {
219         return m12_;
220     }
221 
222     /**
223      * Returns the {@code m13} property.
224      * @return the {@code m13} property
225      */
226     @JsxGetter
227     public double getM13() {
228         return m13_;
229     }
230 
231     /**
232      * Sets the {@code m13} property.
233      * @param m13 the new value
234      */
235     public void setM13(final double m13) {
236         m13_ = m13;
237     }
238 
239     /**
240      * Returns the {@code m14} property.
241      * @return the {@code m14} property
242      */
243     @JsxGetter
244     public double getM14() {
245         return m14_;
246     }
247 
248     /**
249      * Sets the {@code m14} property.
250      * @param m14 the new value
251      */
252     public void setM14(final double m14) {
253         m14_ = m14;
254     }
255 
256     /**
257      * Returns the {@code m21} property.
258      * @return the {@code m21} property
259      */
260     @JsxGetter
261     public double getM21() {
262         return m21_;
263     }
264 
265     /**
266      * Sets the {@code m21} property.
267      * @param m21 the new value
268      */
269     public void setM21(final double m21) {
270         m21_ = m21;
271     }
272 
273     /**
274      * Returns the {@code c} property.
275      * @return the {@code c} property
276      */
277     @JsxGetter
278     public double getC() {
279         return m21_;
280     }
281 
282     /**
283      * Returns the {@code m22} property.
284      * @return the {@code m22} property
285      */
286     @JsxGetter
287     public double getM22() {
288         return m22_;
289     }
290 
291     /**
292      * Sets the {@code m22} property.
293      * @param m22 the new value
294      */
295     public void setM22(final double m22) {
296         m22_ = m22;
297     }
298 
299     /**
300      * Returns the {@code d} property.
301      * @return the {@code d} property
302      */
303     @JsxGetter
304     public double getD() {
305         return m22_;
306     }
307 
308     /**
309      * Returns the {@code m23} property.
310      * @return the {@code m23} property
311      */
312     @JsxGetter
313     public double getM23() {
314         return m23_;
315     }
316 
317     /**
318      * Sets the {@code m23} property.
319      * @param m23 the new value
320      */
321     public void setM23(final double m23) {
322         m23_ = m23;
323     }
324 
325     /**
326      * Returns the {@code m24} property.
327      * @return the {@code m24} property
328      */
329     @JsxGetter
330     public double getM24() {
331         return m24_;
332     }
333 
334     /**
335      * Sets the {@code m24} property.
336      * @param m24 the new value
337      */
338     public void setM24(final double m24) {
339         m24_ = m24;
340     }
341 
342     /**
343      * Returns the {@code m31} property.
344      * @return the {@code m31} property
345      */
346     @JsxGetter
347     public double getM31() {
348         return m31_;
349     }
350 
351     /**
352      * Sets the {@code m31} property.
353      * @param m31 the new value
354      */
355     public void setM31(final double m31) {
356         m31_ = m31;
357     }
358 
359     /**
360      * Returns the {@code m32} property.
361      * @return the {@code m32} property
362      */
363     @JsxGetter
364     public double getM32() {
365         return m32_;
366     }
367 
368     /**
369      * Sets the {@code m32} property.
370      * @param m32 the new value
371      */
372     public void setM32(final double m32) {
373         m32_ = m32;
374     }
375 
376     /**
377      * Returns the {@code m33} property.
378      * @return the {@code m33} property
379      */
380     @JsxGetter
381     public double getM33() {
382         return m33_;
383     }
384 
385     /**
386      * Sets the {@code m33} property.
387      * @param m33 the new value
388      */
389     public void setM33(final double m33) {
390         m33_ = m33;
391     }
392 
393     /**
394      * Returns the {@code m34} property.
395      * @return the {@code m34} property
396      */
397     @JsxGetter
398     public double getM34() {
399         return m34_;
400     }
401 
402     /**
403      * Sets the {@code m34} property.
404      * @param m34 the new value
405      */
406     public void setM34(final double m34) {
407         m34_ = m34;
408     }
409 
410     /**
411      * Returns the {@code m41} property.
412      * @return the {@code m41} property
413      */
414     @JsxGetter
415     public double getM41() {
416         return m41_;
417     }
418 
419     /**
420      * Sets the {@code m41} property.
421      * @param m41 the new value
422      */
423     public void setM41(final double m41) {
424         m41_ = m41;
425     }
426 
427     /**
428      * Returns the {@code e} property.
429      * @return the {@code e} property
430      */
431     @JsxGetter
432     public double getE() {
433         return m41_;
434     }
435 
436     /**
437      * Returns the {@code m42} property.
438      * @return the {@code m42} property
439      */
440     @JsxGetter
441     public double getM42() {
442         return m42_;
443     }
444 
445     /**
446      * Sets the {@code m42} property.
447      * @param m42 the new value
448      */
449     public void setM42(final double m42) {
450         m42_ = m42;
451     }
452 
453     /**
454      * Returns the {@code f} property.
455      * @return the {@code f} property
456      */
457     @JsxGetter
458     public double getF() {
459         return m42_;
460     }
461 
462     /**
463      * Returns the {@code m43} property.
464      * @return the {@code m43} property
465      */
466     @JsxGetter
467     public double getM43() {
468         return m43_;
469     }
470 
471     /**
472      * Sets the {@code m43} property.
473      * @param m43 the new value
474      */
475     public void setM43(final double m43) {
476         m43_ = m43;
477     }
478 
479     /**
480      * Returns the {@code m44} property.
481      * @return the {@code m44} property
482      */
483     @JsxGetter
484     public double getM44() {
485         return m44_;
486     }
487 
488     /**
489      * Sets the {@code m44} property.
490      * @param m44 the new value
491      */
492     public void setM44(final double m44) {
493         m44_ = m44;
494     }
495 
496     /**
497      * Returns the {@code is2D} property.
498      * @return the {@code is2D} property
499      */
500     @JsxGetter
501     public boolean isIs2D() {
502         return is2D_;
503     }
504 
505     /**
506      * Sets the {@code is2D} property.
507      * @param is2D the new value
508      */
509     public void setIs2D(final boolean is2D) {
510         is2D_ = is2D;
511     }
512 
513     /**
514      * Returns whether this matrix is the identity matrix.
515      * @return true if m12 element, m13 element, m14 element, m21 element, m23 element, m24 element,
516      *     m31 element, m32 element, m34 element, m41 element, m42 element, m43 element are 0 or -0
517      *     and m11 element, m22 element, m33 element, m44 element are 1. Otherwise it returns false.
518      */
519     @JsxGetter
520     public boolean getIsIdentity() {
521         return m11_ == 1 && m22_ == 1 && m33_ == 1 && m44_ == 1
522                 && m12_ == 0 && m13_ == 0 && m14_ == 0
523                 && m21_ == 0 && m23_ == 0 && m24_ == 0
524                 && m31_ == 0 && m32_ == 0 && m34_ == 0
525                 && m41_ == 0 && m42_ == 0 && m43_ == 0;
526     }
527 
528     /**
529      * Returns a new matrix being the result of the original matrix flipped about the x-axis.
530      * @return a new matrix being the result of the original matrix flipped about the x-axis.
531      *     This is equivalent to multiplying the matrix by DOMMatrix(-1, 0, 0, 1, 0, 0).
532      *     The original matrix is not modified.
533      */
534     @JsxFunction
535     public DOMMatrix flipX() {
536         final DOMMatrix matrix = new DOMMatrix();
537         matrix.setParentScope(getParentScope());
538         matrix.setPrototype(getWindow().getPrototype(DOMMatrix.class));
539 
540         matrix.setM11(-m11_);
541         matrix.setM12(-m12_);
542         matrix.setM13(-m13_);
543         matrix.setM14(-m14_);
544 
545         matrix.setM21(m21_);
546         matrix.setM22(m22_);
547         matrix.setM23(m23_);
548         matrix.setM24(m24_);
549 
550         matrix.setM31(m31_);
551         matrix.setM32(m32_);
552         matrix.setM33(m33_);
553         matrix.setM34(m34_);
554 
555         matrix.setM41(m41_);
556         matrix.setM42(m42_);
557         matrix.setM43(m43_);
558         matrix.setM44(m44_);
559 
560         matrix.setIs2D(is2D_);
561         return matrix;
562     }
563 
564     /**
565      * Returns a new matrix being the result of the original matrix flipped about the y-axis.
566      * @return a new matrix being the result of the original matrix flipped about the y-axis.
567      *     This is equivalent to multiplying the matrix by DOMMatrix(1, 0, 0, -1, 0, 0).
568      *     The original matrix is not modified.
569      */
570     @JsxFunction
571     public DOMMatrix flipY() {
572         final DOMMatrix matrix = new DOMMatrix();
573         matrix.setParentScope(getParentScope());
574         matrix.setPrototype(getWindow().getPrototype(DOMMatrix.class));
575 
576         matrix.setM11(m11_);
577         matrix.setM12(m12_);
578         matrix.setM13(m13_);
579         matrix.setM14(m14_);
580 
581         matrix.setM21(-m21_);
582         matrix.setM22(-m22_);
583         matrix.setM23(-m23_);
584         matrix.setM24(-m24_);
585 
586         matrix.setM31(m31_);
587         matrix.setM32(m32_);
588         matrix.setM33(m33_);
589         matrix.setM34(m34_);
590 
591         matrix.setM41(m41_);
592         matrix.setM42(m42_);
593         matrix.setM43(m43_);
594         matrix.setM44(m44_);
595 
596         matrix.setIs2D(is2D_);
597         return matrix;
598     }
599 
600     /**
601      * Returns a new matrix which is the inverse of the original matrix.
602      * @return new matrix which is the inverse of the original matrix.
603      *     If the matrix cannot be inverted, the new matrix's components are all set to NaN
604      *     and its is2D property is set to false. The original matrix is not changed.
605      */
606     @JsxFunction
607     public DOMMatrix inverse() {
608         final DOMMatrix matrix = new DOMMatrix();
609         matrix.setParentScope(getParentScope());
610         matrix.setPrototype(getWindow().getPrototype(DOMMatrix.class));
611 
612         matrix.setM11(m11_);
613         matrix.setM12(m12_);
614         matrix.setM13(m13_);
615         matrix.setM14(m14_);
616 
617         matrix.setM21(m21_);
618         matrix.setM22(m22_);
619         matrix.setM23(m23_);
620         matrix.setM24(m24_);
621 
622         matrix.setM31(m31_);
623         matrix.setM32(m32_);
624         matrix.setM33(m33_);
625         matrix.setM34(m34_);
626 
627         matrix.setM41(m41_);
628         matrix.setM42(m42_);
629         matrix.setM43(m43_);
630         matrix.setM44(m44_);
631 
632         matrix.setIs2D(is2D_);
633         return matrix.invertSelf();
634     }
635 
636     /**
637      * Returns a new matrix which is the dot product of the matrix and the given matrix.
638      * @param other the matrix to multiply with this matrix
639      * @return a new matrix which is the dot product of the matrix and the otherMatrix parameter.
640      *     If otherMatrix is omitted, the matrix is multiplied by a matrix in which every element
641      *     is 0 except the bottom-right corner and the element immediately above
642      *     and to its left: m33 and m34. These have the default value of 1.
643      *     The original matrix is not modified.
644      */
645     @JsxFunction
646     public DOMMatrix multiply(final Object other) {
647         final DOMMatrix result = new DOMMatrix();
648         result.setParentScope(getParentScope());
649         result.setPrototype(getWindow().getPrototype(DOMMatrix.class));
650 
651         // Handle null/undefined by treating as identity matrix
652         if (other == null || JavaScriptEngine.isUndefined(other)) {
653             return result;
654         }
655 
656         if (!(other instanceof DOMMatrixReadOnly otherMatrix)) {
657             throw JavaScriptEngine.typeError("Failed to execute 'multiply' on 'DOMMatrixReadOnly': "
658                     + "parameter 1 is not of type 'DOMMatrixReadOnly'.");
659         }
660 
661         // Matrix multiplication: result = this * otherMatrix
662         // Standard matrix multiplication formula: C[i][j] = sum(A[i][k] * B[k][j])
663         result.setIs2D(is2D_ && otherMatrix.is2D_);
664 
665         result.setM11(m11_ * otherMatrix.m11_
666                 + m21_ * otherMatrix.m12_
667                 + m31_ * otherMatrix.m13_
668                 + m41_ * otherMatrix.m14_);
669         result.setM12(m12_ * otherMatrix.m11_
670                 + m22_ * otherMatrix.m12_
671                 + m32_ * otherMatrix.m13_
672                 + m42_ * otherMatrix.m14_);
673         if (!result.isIs2D()) {
674             result.setM13(m13_ * otherMatrix.m11_
675                     + m23_ * otherMatrix.m12_
676                     + m33_ * otherMatrix.m13_
677                     + m43_ * otherMatrix.m14_);
678             result.setM14(m14_ * otherMatrix.m11_
679                     + m24_ * otherMatrix.m12_
680                     + m34_ * otherMatrix.m13_
681                     + m44_ * otherMatrix.m14_);
682         }
683 
684         result.setM21(m11_ * otherMatrix.m21_
685                 + m21_ * otherMatrix.m22_
686                 + m31_ * otherMatrix.m23_
687                 + m41_ * otherMatrix.m24_);
688         result.setM22(m12_ * otherMatrix.m21_
689                 + m22_ * otherMatrix.m22_
690                 + m32_ * otherMatrix.m23_
691                 + m42_ * otherMatrix.m24_);
692         if (!result.isIs2D()) {
693             result.setM23(m13_ * otherMatrix.m21_
694                     + m23_ * otherMatrix.m22_
695                     + m33_ * otherMatrix.m23_
696                     + m43_ * otherMatrix.m24_);
697             result.setM24(m14_ * otherMatrix.m21_
698                     + m24_ * otherMatrix.m22_
699                     + m34_ * otherMatrix.m23_
700                     + m44_ * otherMatrix.m24_);
701         }
702 
703         if (!result.isIs2D()) {
704             result.setM31(m11_ * otherMatrix.m31_
705                     + m21_ * otherMatrix.m32_
706                     + m31_ * otherMatrix.m33_
707                     + m41_ * otherMatrix.m34_);
708             result.setM32(m12_ * otherMatrix.m31_
709                     + m22_ * otherMatrix.m32_
710                     + m32_ * otherMatrix.m33_
711                     + m42_ * otherMatrix.m34_);
712             result.setM33(m13_ * otherMatrix.m31_
713                     + m23_ * otherMatrix.m32_
714                     + m33_ * otherMatrix.m33_
715                     + m43_ * otherMatrix.m34_);
716             result.setM34(m14_ * otherMatrix.m31_
717                     + m24_ * otherMatrix.m32_
718                     + m34_ * otherMatrix.m33_
719                     + m44_ * otherMatrix.m34_);
720         }
721 
722         result.setM41(m11_ * otherMatrix.m41_
723                 + m21_ * otherMatrix.m42_
724                 + m31_ * otherMatrix.m43_
725                 + m41_ * otherMatrix.m44_);
726         result.setM42(m12_ * otherMatrix.m41_
727                 + m22_ * otherMatrix.m42_
728                 + m32_ * otherMatrix.m43_
729                 + m42_ * otherMatrix.m44_);
730         if (!result.isIs2D()) {
731             result.setM43(m13_ * otherMatrix.m41_
732                     + m23_ * otherMatrix.m42_
733                     + m33_ * otherMatrix.m43_
734                     + m43_ * otherMatrix.m44_);
735             result.setM44(m14_ * otherMatrix.m41_
736                     + m24_ * otherMatrix.m42_
737                     + m34_ * otherMatrix.m43_
738                     + m44_ * otherMatrix.m44_);
739         }
740 
741         return result;
742     }
743 
744     /**
745      * Returns a new matrix rotated about the z-axis by the specified angle.
746      * @param rotZ the rotation angle in degrees. If omitted, defaults to 0.
747      * @return a new matrix which is the result of the original matrix rotated by the specified angle.
748      *     The rotation is applied around the origin (0, 0) in the 2D plane.
749      *     The original matrix is not modified.
750      */
751     @JsxFunction
752     public DOMMatrixReadOnly rotate(final Object rotZ) {
753         final DOMMatrix result = new DOMMatrix();
754         result.setParentScope(getParentScope());
755         result.setPrototype(getWindow().getPrototype(DOMMatrix.class));
756 
757         // Handle undefined/null/missing parameter - default to 0
758         double angleInDegrees = 0;
759         if (rotZ != null && !JavaScriptEngine.isUndefined(rotZ)) {
760             angleInDegrees = JavaScriptEngine.toNumber(rotZ);
761         }
762 
763         // Convert degrees to radians
764         final double angleInRadians = Math.toRadians(angleInDegrees);
765         final double cos = Math.cos(angleInRadians);
766         final double sin = Math.sin(angleInRadians);
767 
768         // Create rotation matrix:
769         // [cos  -sin  0  0]
770         // [sin   cos  0  0]
771         // [ 0     0   1  0]
772         // [ 0     0   0  1]
773 
774         // For 2D matrices, only apply to the 2x2 part
775         if (is2D_) {
776             // Matrix multiplication: result = this * rotation
777             result.setM11(m11_ * cos + m21_ * sin);
778             result.setM12(m12_ * cos + m22_ * sin);
779 
780             result.setM21(m11_ * (-sin) + m21_ * cos);
781             result.setM22(m12_ * (-sin) + m22_ * cos);
782 
783             result.setM41(m41_);
784             result.setM42(m42_);
785 
786             result.setIs2D(true);
787         }
788         else {
789             // For 3D matrices, apply rotation to all relevant components
790             result.setM11(m11_ * cos + m21_ * sin);
791             result.setM12(m12_ * cos + m22_ * sin);
792             result.setM13(m13_ * cos + m23_ * sin);
793             result.setM14(m14_ * cos + m24_ * sin);
794 
795             result.setM21(m11_ * (-sin) + m21_ * cos);
796             result.setM22(m12_ * (-sin) + m22_ * cos);
797             result.setM23(m13_ * (-sin) + m23_ * cos);
798             result.setM24(m14_ * (-sin) + m24_ * cos);
799 
800             result.setM31(m31_);
801             result.setM32(m32_);
802             result.setM33(m33_);
803             result.setM34(m34_);
804 
805             result.setM41(m41_);
806             result.setM42(m42_);
807             result.setM43(m43_);
808             result.setM44(m44_);
809 
810             result.setIs2D(false);
811         }
812 
813         return result;
814     }
815 
816     /**
817      * Rotates the matrix by a given angle around the specified axis.
818      *
819      * @param xObj the x component of the axis
820      * @param yObj the y component of the axis
821      * @param zObj the z component of the axis
822      * @param alphaObj the rotation angle in degrees
823      * @return a new matrix which is the result of the original matrix rotated by the specified axis and angle.
824      */
825     @JsxFunction
826     public DOMMatrixReadOnly rotateAxisAngle(
827                 final Object xObj, final Object yObj, final Object zObj, final Object alphaObj) {
828         // Default values
829         double x = 0;
830         double y = 0;
831         double z = 1;
832         double alpha = 0;
833         if (xObj != null && !JavaScriptEngine.isUndefined(xObj)) {
834             x = JavaScriptEngine.toNumber(xObj);
835         }
836         if (yObj != null && !JavaScriptEngine.isUndefined(yObj)) {
837             y = JavaScriptEngine.toNumber(yObj);
838         }
839         if (zObj != null && !JavaScriptEngine.isUndefined(zObj)) {
840             z = JavaScriptEngine.toNumber(zObj);
841         }
842         if (alphaObj != null && !JavaScriptEngine.isUndefined(alphaObj)) {
843             alpha = JavaScriptEngine.toNumber(alphaObj);
844         }
845 
846         // If axis is (0,0,0), throw TypeError per spec
847         if (x == 0 && y == 0 && z == 0) {
848             final DOMMatrix result = new DOMMatrix();
849             result.setParentScope(getParentScope());
850             result.setPrototype(getWindow().getPrototype(DOMMatrix.class));
851             return result;
852         }
853 
854         // Normalize the axis
855         final double length = Math.sqrt(x * x + y * y + z * z);
856         x /= length;
857         y /= length;
858         z /= length;
859 
860         // Convert angle to radians
861         final double angle2 = Math.toRadians(alpha) / 2;
862 
863         // Compute rotation matrix
864         final double sc = Math.sin(angle2) * Math.cos(angle2);
865         final double sq = Math.pow(Math.sin(angle2), 2);
866 
867         final double x2 = x * x;
868         final double y2 = y * y;
869         final double z2 = z * z;
870 
871         final DOMMatrix rot = new DOMMatrix();
872 
873         rot.setM11(1 - 2 * (y2 + z2) * sq);
874         rot.setM12(2 * (x * y * sq + z * sc));
875         rot.setM13(2 * (x * z * sq - y * sc));
876         rot.setM14(0);
877 
878         rot.setM21(2 * (x * y * sq - z * sc));
879         rot.setM22(1 - 2 * (x2 + z2) * sq);
880         rot.setM23(2 * (y * z * sq + x * sc));
881         rot.setM24(0);
882 
883         rot.setM31(2 * (x * z * sq + y * sc));
884         rot.setM32(2 * (y * z * sq - x * sc));
885         rot.setM33(1 - 2 * (x2 + y2) * sq);
886         rot.setM34(0);
887 
888         rot.setM41(0);
889         rot.setM42(0);
890         rot.setM43(0);
891         rot.setM44(1);
892 
893         rot.setIs2D(false);
894 
895         // Multiply this * rot
896         final DOMMatrix multiplied = multiply(rot);
897         multiplied.setIs2D(is2D_ && x == 0 && y == 0);
898         return multiplied;
899     }
900 
901     /**
902      * Returns a new matrix skewed along the x-axis by the specified angle.
903      * @param alphaObj the angle, in degrees, by which to skew the matrix along the x-axis
904      * @return returns a new DOMMatrix created by applying the specified skew transformation
905      *     to the source matrix along its x-axis. The original matrix is not modified.
906      */
907     @JsxFunction
908     public DOMMatrixReadOnly skewX(final Object alphaObj) {
909         // Default values
910         double alpha = 0;
911         if (alphaObj != null && !JavaScriptEngine.isUndefined(alphaObj)) {
912             alpha = JavaScriptEngine.toNumber(alphaObj);
913         }
914 
915         // Convert angle to radians
916         final double angle = Math.toRadians(alpha);
917 
918         // Compute rotation matrix
919         final DOMMatrix rot = new DOMMatrix();
920 
921         rot.setM21(Math.tan(angle));
922 
923         // Multiply this * rot
924         final DOMMatrix multiplied = multiply(rot);
925         return multiplied;
926     }
927 
928     /**
929      * Returns a new matrix skewed along the y-axis by the specified angle.
930      * @param alphaObj the angle, in degrees, by which to skew the matrix along the y-axis
931      * @return returns a new DOMMatrix created by applying the specified skew transformation
932      *     to the source matrix along its x-axis. The original matrix is not modified.
933      */
934     @JsxFunction
935     public DOMMatrixReadOnly skewY(final Object alphaObj) {
936         // Default values
937         double alpha = 0;
938         if (alphaObj != null && !JavaScriptEngine.isUndefined(alphaObj)) {
939             alpha = JavaScriptEngine.toNumber(alphaObj);
940         }
941 
942         // Convert angle to radians
943         final double angle = Math.toRadians(alpha);
944 
945         // Compute rotation matrix
946         final DOMMatrixReadOnly rot = new DOMMatrixReadOnly();
947 
948         rot.m12_ = Math.tan(angle);
949 
950         // Multiply this * rot
951         final DOMMatrixReadOnly multiplied = multiply(rot);
952         return multiplied;
953     }
954 
955     /**
956      * Creates a new matrix being the result of the original matrix with a translation applied.
957      *
958      * @param xObj a number representing the abscissa (x-coordinate) of the translating vector
959      * @param yObj a number representing the ordinate (y-coordinate) of the translating vector
960      * @param zObj A number representing the z component of the translating vector. If not supplied,
961      *        this defaults to 0. If this is anything other than 0, the resulting matrix will be 3D
962      * @return a DOMMatrix containing a new matrix being the result of the matrix being translated
963      *         by the given vector. The original matrix is not modified.
964      *         If a translation is applied about the z-axis, the resulting matrix will be a 4x4 3D matrix.
965      */
966     @JsxFunction
967     public DOMMatrixReadOnly translate(final Object xObj, final Object yObj, final Object zObj) {
968         // Default values
969         double x = 0;
970         double y = 0;
971         double z = 0;
972         if (xObj != null && !JavaScriptEngine.isUndefined(xObj)) {
973             x = JavaScriptEngine.toNumber(xObj);
974         }
975         if (yObj != null && !JavaScriptEngine.isUndefined(yObj)) {
976             y = JavaScriptEngine.toNumber(yObj);
977         }
978         if (zObj != null && !JavaScriptEngine.isUndefined(zObj)) {
979             z = JavaScriptEngine.toNumber(zObj);
980         }
981 
982         final DOMMatrixReadOnly translate = new DOMMatrixReadOnly();
983 
984         translate.m41_ = x;
985         translate.m42_ = y;
986         translate.m43_ = z;
987 
988         translate.is2D_ = false;
989 
990         // Multiply this * rot
991         final DOMMatrix multiplied = multiply(translate);
992         multiplied.setIs2D(is2D_ && (zObj == null || JavaScriptEngine.isUndefined(zObj) || z == 0));
993         return multiplied;
994     }
995 
996     /**
997      * Returns a new Float32Array containing all 16 elements of the matrix.
998      * @return a new Float32Array containing all 16 elements
999      *     (m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44)
1000      *     which comprise the matrix. The elements are stored into the array
1001      *     as single-precision floating-point numbers in column-major (colexographical access, or "colex") order.
1002      *     (In other words, down the first column from top to bottom, then the second column, and so forth.)
1003      */
1004     @JsxFunction
1005     public NativeFloat32Array toFloat32Array() {
1006         final NativeFloat32Array result =
1007                 (NativeFloat32Array) JavaScriptEngine.newObject(getParentScope(), "Float32Array", new Object[] {16});
1008 
1009         result.setArrayElement(0, m11_);
1010         result.setArrayElement(1, m12_);
1011         result.setArrayElement(2, m13_);
1012         result.setArrayElement(3, m14_);
1013 
1014         result.setArrayElement(4, m21_);
1015         result.setArrayElement(5, m22_);
1016         result.setArrayElement(6, m23_);
1017         result.setArrayElement(7, m24_);
1018 
1019         result.setArrayElement(8, m31_);
1020         result.setArrayElement(9, m32_);
1021         result.setArrayElement(10, m33_);
1022         result.setArrayElement(11, m34_);
1023 
1024         result.setArrayElement(12, m41_);
1025         result.setArrayElement(13, m42_);
1026         result.setArrayElement(14, m43_);
1027         result.setArrayElement(15, m44_);
1028 
1029         return result;
1030     }
1031 
1032     /**
1033      * Returns a new Float64Array containing all 16 elements of the matrix.
1034      * @return a new Float64Array containing all 16 elements
1035      *     (m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44)
1036      *     which comprise the matrix. The elements are stored into the array
1037      *     as single-precision floating-point numbers in column-major (colexographical access, or "colex") order.
1038      *     (In other words, down the first column from top to bottom, then the second column, and so forth.)
1039      */
1040     @JsxFunction
1041     public NativeFloat64Array toFloat64Array() {
1042         final NativeFloat64Array result =
1043                 (NativeFloat64Array) JavaScriptEngine.newObject(getParentScope(), "Float64Array", new Object[] {16});
1044 
1045         result.setArrayElement(0, m11_);
1046         result.setArrayElement(1, m12_);
1047         result.setArrayElement(2, m13_);
1048         result.setArrayElement(3, m14_);
1049 
1050         result.setArrayElement(4, m21_);
1051         result.setArrayElement(5, m22_);
1052         result.setArrayElement(6, m23_);
1053         result.setArrayElement(7, m24_);
1054 
1055         result.setArrayElement(8, m31_);
1056         result.setArrayElement(9, m32_);
1057         result.setArrayElement(10, m33_);
1058         result.setArrayElement(11, m34_);
1059 
1060         result.setArrayElement(12, m41_);
1061         result.setArrayElement(13, m42_);
1062         result.setArrayElement(14, m43_);
1063         result.setArrayElement(15, m44_);
1064 
1065         return result;
1066     }
1067 
1068     /**
1069      * Returns the values of the matrix as a CSS transform function string.
1070      * @return the values of the list separated by commas,
1071      *     within matrix() or matrix3d() function syntax.
1072      */
1073     @JsxFunction(functionName = "toString")
1074     public String js_toString() {
1075         final StringBuilder result = new StringBuilder();
1076 
1077         result.append(is2D_ ? "matrix(" : "matrix3d(");
1078         appendDouble(result, m11_).append(", ");
1079         appendDouble(result, m12_).append(", ");
1080         if (!is2D_) {
1081             appendDouble(result, m13_).append(", ");
1082             appendDouble(result, m14_).append(", ");
1083         }
1084 
1085         appendDouble(result, m21_).append(", ");
1086         appendDouble(result, m22_).append(", ");
1087         if (!is2D_) {
1088             appendDouble(result, m23_).append(", ");
1089             appendDouble(result, m24_).append(", ");
1090 
1091             appendDouble(result, m31_).append(", ");
1092             appendDouble(result, m32_).append(", ");
1093             appendDouble(result, m33_).append(", ");
1094             appendDouble(result, m34_).append(", ");
1095         }
1096 
1097         appendDouble(result, m41_).append(", ");
1098         appendDouble(result, m42_);
1099         if (!is2D_) {
1100             result.append(", ");
1101             appendDouble(result, m43_).append(", ");
1102             appendDouble(result, m44_);
1103         }
1104 
1105         result.append(')');
1106 
1107         return result.toString();
1108     }
1109 
1110     private static StringBuilder appendDouble(final StringBuilder builder, final double d) {
1111         if (Double.isNaN(d) || Double.isInfinite(d)) {
1112             return builder.append(d);
1113         }
1114 
1115         if (d % 1 == 0) {
1116             return builder.append((int) d);
1117         }
1118 
1119         return builder.append(d);
1120     }
1121 
1122     /**
1123      * Returns a JSON representation of the DOMMatrixReadOnly object.
1124      * @return a JSON representation of the DOMMatrixReadOnly object
1125      */
1126     @JsxFunction
1127     public Scriptable toJSON() {
1128         final Scriptable json = JavaScriptEngine.newObject(getParentScope());
1129         json.put("a", json, m11_);
1130         json.put("b", json, m12_);
1131         json.put("c", json, m21_);
1132         json.put("d", json, m22_);
1133         json.put("e", json, m41_);
1134         json.put("f", json, m42_);
1135 
1136         json.put("m11", json, m11_);
1137         json.put("m12", json, m12_);
1138         json.put("m13", json, m13_);
1139         json.put("m14", json, m14_);
1140 
1141         json.put("m21", json, m21_);
1142         json.put("m22", json, m22_);
1143         json.put("m23", json, m23_);
1144         json.put("m24", json, m24_);
1145 
1146         json.put("m31", json, m31_);
1147         json.put("m32", json, m32_);
1148         json.put("m33", json, m33_);
1149         json.put("m34", json, m34_);
1150 
1151         json.put("m41", json, m41_);
1152         json.put("m42", json, m42_);
1153         json.put("m43", json, m43_);
1154         json.put("m44", json, m44_);
1155 
1156         json.put("is2D", json, is2D_);
1157         json.put("isIdentity", json, getIsIdentity());
1158 
1159         return json;
1160     }
1161 }