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