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