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