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.svg;
16  
17  import org.htmlunit.corejs.javascript.VarScope;
18  import org.htmlunit.javascript.HtmlUnitScriptable;
19  import org.htmlunit.javascript.JavaScriptEngine;
20  import org.htmlunit.javascript.configuration.JsxClass;
21  import org.htmlunit.javascript.configuration.JsxConstructor;
22  import org.htmlunit.javascript.configuration.JsxFunction;
23  import org.htmlunit.javascript.configuration.JsxGetter;
24  import org.htmlunit.javascript.configuration.JsxSetter;
25  import org.htmlunit.javascript.host.dom.DOMException;
26  
27  /**
28   * JavaScript host object for {@code SVGMatrix}.
29   *
30   * @author Marc Guillemot
31   * @author Ronald Brill
32   *
33   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/SVGMatrix">MDN Documentation</a>
34   */
35  @JsxClass
36  public class SVGMatrix extends HtmlUnitScriptable {
37  
38      private double shearX_;
39      private double shearY_;
40      private double scaleX_;
41      private double scaleY_;
42      private double translateX_;
43      private double translateY_;
44  
45      /**
46       * Creates an instance.
47       */
48      public SVGMatrix() {
49          super();
50  
51          shearX_ = 0.0;
52          shearY_ = 0.0;
53          scaleX_ = 1.0;
54          scaleY_ = 1.0;
55          translateX_ = 0.0;
56          translateY_ = 0.0;
57      }
58  
59      /**
60       * Creates an instance of this object.
61       */
62      @JsxConstructor
63      public void jsConstructor() {
64          // nothing to do
65      }
66  
67      /**
68       * Instantiates and configures the scope and prototype.
69       *
70       * @param scope the parent scope
71       */
72      public SVGMatrix(final VarScope scope) {
73          this();
74          setParentScope(scope);
75          setPrototype(getPrototype(getClass()));
76      }
77  
78      /**
79       * Returns the {@code a} entry of the matrix.
80       *
81       * @return the field
82       */
83      @JsxGetter
84      public double getA() {
85          return scaleX_;
86      }
87  
88      /**
89       * Returns the {@code b} entry of the matrix.
90       *
91       * @return the field
92       */
93      @JsxGetter
94      public double getB() {
95          return shearY_;
96      }
97  
98      /**
99       * Returns the {@code c} entry of the matrix.
100      *
101      * @return the field
102      */
103     @JsxGetter
104     public double getC() {
105         return shearX_;
106     }
107 
108     /**
109      * Returns the {@code d} entry of the matrix.
110      *
111      * @return the field
112      */
113     @JsxGetter
114     public double getD() {
115         return scaleY_;
116     }
117 
118     /**
119      * Returns the {@code e} entry of the matrix.
120      *
121      * @return the field
122      */
123     @JsxGetter
124     public double getE() {
125         return translateX_;
126     }
127 
128     /**
129      * Returns the {@code f} entry of the matrix.
130      *
131      * @return the field
132      */
133     @JsxGetter
134     public double getF() {
135         return translateY_;
136     }
137 
138     /**
139      * Sets the {@code a} entry of the matrix.
140      *
141      * @param newValue the new value for the field
142      */
143     @JsxSetter
144     public void setA(final double newValue) {
145         scaleX_ = newValue;
146     }
147 
148     /**
149      * Sets the {@code b} entry of the matrix.
150      *
151      * @param newValue the new value for the field
152      */
153     @JsxSetter
154     public void setB(final double newValue) {
155         shearY_ = newValue;
156     }
157 
158     /**
159      * Sets the {@code c} entry of the matrix.
160      *
161      * @param newValue the new value for the field
162      */
163     @JsxSetter
164     public void setC(final double newValue) {
165         shearX_ = newValue;
166     }
167 
168     /**
169      * Sets the {@code d} entry of the matrix.
170      *
171      * @param newValue the new value for the field
172      */
173     @JsxSetter
174     public void setD(final double newValue) {
175         scaleY_ = newValue;
176     }
177 
178     /**
179      * Sets the {@code e} entry of the matrix.
180      *
181      * @param newValue the new value for the field
182      */
183     @JsxSetter
184     public void setE(final double newValue) {
185         translateX_ = newValue;
186     }
187 
188     /**
189      * Sets the {@code f} entry of the matrix.
190      *
191      * @param newValue the new value for the field
192      */
193     @JsxSetter
194     public void setF(final double newValue) {
195         translateY_ = newValue;
196     }
197 
198     /**
199      * Returns a new matrix that is the result of flipping this matrix along the x-axis.
200      *
201      * @return the resulting matrix
202      */
203     @JsxFunction
204     public SVGMatrix flipX() {
205         final SVGMatrix result = new SVGMatrix(getParentScope());
206         result.shearX_ = shearX_;
207         result.shearY_ = -shearY_;
208         result.scaleX_ = -scaleX_;
209         result.scaleY_ = scaleY_;
210         result.translateX_ = translateX_;
211         result.translateY_ = translateY_;
212 
213         return result;
214     }
215 
216     /**
217      * Returns a new matrix that is the result of flipping this matrix along the y-axis.
218      *
219      * @return the resulting matrix
220      */
221     @JsxFunction
222     public SVGMatrix flipY() {
223         final SVGMatrix result = new SVGMatrix(getParentScope());
224         result.shearX_ = -shearX_;
225         result.shearY_ = shearY_;
226         result.scaleX_ = scaleX_;
227         result.scaleY_ = -scaleY_;
228         result.translateX_ = translateX_;
229         result.translateY_ = translateY_;
230 
231         return result;
232     }
233 
234     /**
235      * Returns the inverse of this matrix.
236      *
237      * @return the resulting matrix
238      */
239     @JsxFunction
240     public SVGMatrix inverse() {
241         final double determinant = scaleX_ * scaleY_ - shearX_ * shearY_;
242 
243         if (Math.abs(determinant) < 1E-10) {
244             throw JavaScriptEngine.asJavaScriptException(
245                     getWindow(),
246                     "Failed to execute 'inverse' on 'SVGMatrix': The matrix is not invertible.",
247                     DOMException.INVALID_STATE_ERR);
248         }
249 
250         final SVGMatrix result = new SVGMatrix(getParentScope());
251         result.shearX_ = -shearX_ / determinant;
252         result.shearY_ = -shearY_ / determinant;
253         result.scaleX_ = scaleY_ / determinant;
254         result.scaleY_ = scaleX_ / determinant;
255         result.translateX_ = (shearX_ * translateY_ - scaleY_ * translateX_) / determinant;
256         result.translateY_ = (shearY_ * translateX_ - scaleX_ * translateY_) / determinant;
257 
258         return result;
259     }
260 
261     /**
262      * Returns a new matrix that is the result of multiplying this matrix by the given matrix.
263      *
264      * @param by the matrix to multiply by
265      * @return the resulting matrix
266      */
267     @JsxFunction
268     public SVGMatrix multiply(final SVGMatrix by) {
269         final SVGMatrix result = new SVGMatrix(getParentScope());
270 
271         result.shearX_ = by.shearX_ * scaleX_ + by.scaleY_ * shearX_;
272         result.shearY_ = by.scaleX_ * shearY_ + by.shearY_ * scaleY_;
273         result.scaleX_ = by.scaleX_ * scaleX_ + by.shearY_ * shearX_;
274         result.scaleY_ = by.shearX_ * shearY_ + by.scaleY_ * scaleY_;
275         result.translateX_ = by.translateX_ * scaleX_ + by.translateY_ * shearX_ + translateX_;
276         result.translateY_ = by.translateX_ * shearY_ + by.translateY_ * scaleY_ + translateY_;
277 
278         return result;
279     }
280 
281     /**
282      * Returns a new matrix that is the result of rotating this matrix by the given angle.
283      *
284      * @param angle the rotation angle in degrees
285      * @return the resulting matrix
286      */
287     @JsxFunction
288     public SVGMatrix rotate(final double angle) {
289         final double theta = Math.toRadians(angle);
290         final double sin = Math.sin(theta);
291         final double cos = Math.cos(theta);
292 
293         final SVGMatrix result = new SVGMatrix(getParentScope());
294 
295         result.shearX_ = -sin * scaleX_ + cos * shearX_;
296         result.shearY_ = cos * shearY_ + sin * scaleY_;
297         result.scaleX_ = cos * scaleX_ + sin * shearX_;
298         result.scaleY_ = -sin * shearY_ + cos * scaleY_;
299         result.translateX_ = translateX_;
300         result.translateY_ = translateY_;
301 
302         return result;
303     }
304 
305     /**
306      * Returns a new matrix that is the result of rotating this matrix by the angle
307      * defined by the given vector.
308      *
309      * @param x the x-coordinate of the vector
310      * @param y the y-coordinate of the vector
311      * @return the resulting matrix
312      */
313     @JsxFunction
314     public SVGMatrix rotateFromVector(final double x, final double y) {
315         if (x == 0 || y == 0) {
316             throw JavaScriptEngine.asJavaScriptException(
317                     getWindow(),
318                     "Failed to execute 'rotateFromVector' on 'SVGMatrix': Arguments cannot be zero.",
319                     DOMException.INVALID_ACCESS_ERR);
320         }
321 
322         final double theta = Math.atan2(y, x);
323         final double sin = Math.sin(theta);
324         final double cos = Math.cos(theta);
325 
326         final SVGMatrix result = new SVGMatrix(getParentScope());
327 
328         result.shearX_ = -sin * scaleX_ + cos * shearX_;
329         result.shearY_ = cos * shearY_ + sin * scaleY_;
330         result.scaleX_ = cos * scaleX_ + sin * shearX_;
331         result.scaleY_ = -sin * shearY_ + cos * scaleY_;
332         result.translateX_ = translateX_;
333         result.translateY_ = translateY_;
334 
335         return result;
336     }
337 
338     /**
339      * Returns a new matrix that is the result of scaling this matrix uniformly by the given factor.
340      *
341      * @param factor the scale factor
342      * @return the resulting matrix
343      */
344     @JsxFunction
345     public SVGMatrix scale(final double factor) {
346         return scaleNonUniform(factor, factor);
347     }
348 
349     /**
350      * Returns a new matrix that is the result of scaling this matrix non-uniformly
351      * by the given factors along each axis.
352      *
353      * @param factorX the scale factor along the x-axis
354      * @param factorY the scale factor along the y-axis
355      * @return the resulting matrix
356      */
357     @JsxFunction
358     public SVGMatrix scaleNonUniform(final double factorX, final double factorY) {
359         final SVGMatrix result = new SVGMatrix(getParentScope());
360 
361         result.shearX_ = factorY * shearX_;
362         result.shearY_ = factorX * shearY_;
363         result.scaleX_ = factorX * scaleX_;
364         result.scaleY_ = factorY * scaleY_;
365         result.translateX_ = translateX_;
366         result.translateY_ = translateY_;
367 
368         return result;
369     }
370 
371     /**
372      * Returns a new matrix that is the result of skewing this matrix along the x-axis
373      * by the given angle.
374      *
375      * @param angle the skew angle in degrees
376      * @return the resulting matrix
377      */
378     @JsxFunction
379     public SVGMatrix skewX(final double angle) {
380         final double shear = Math.tan(Math.toRadians(angle));
381 
382         final SVGMatrix result = new SVGMatrix(getParentScope());
383 
384         result.shearX_ = shear * scaleX_ + shearX_;
385         result.shearY_ = shearY_;
386         result.scaleX_ = scaleX_;
387         result.scaleY_ = shear * shearY_ + scaleY_;
388         result.translateX_ = translateX_;
389         result.translateY_ = translateY_;
390 
391         return result;
392     }
393 
394     /**
395      * Returns a new matrix that is the result of skewing this matrix along the y-axis
396      * by the given angle.
397      *
398      * @param angle the skew angle in degrees
399      * @return the resulting matrix
400      */
401     @JsxFunction
402     public SVGMatrix skewY(final double angle) {
403         final double shear = Math.tan(Math.toRadians(angle));
404 
405         final SVGMatrix result = new SVGMatrix(getParentScope());
406 
407         result.shearX_ = shearX_;
408         result.shearY_ = shearY_ + shear * scaleY_;
409         result.scaleX_ = scaleX_ + shear * shearX_;
410         result.scaleY_ = scaleY_;
411         result.translateX_ = translateX_;
412         result.translateY_ = translateY_;
413 
414         return result;
415     }
416 
417     /**
418      * Returns a new matrix that is the result of translating this matrix by the given distances.
419      *
420      * @param x the distance to translate along the x-axis
421      * @param y the distance to translate along the y-axis
422      * @return the resulting matrix
423      */
424     @JsxFunction
425     public SVGMatrix translate(final double x, final double y) {
426         final SVGMatrix result = new SVGMatrix(getParentScope());
427 
428         result.shearX_ = shearX_;
429         result.shearY_ = shearY_;
430         result.scaleX_ = scaleX_;
431         result.scaleY_ = scaleY_;
432         result.translateX_ = x * scaleX_ + y * shearX_ + translateX_;
433         result.translateY_ = x * shearY_ + y * scaleY_ + translateY_;
434 
435         return result;
436     }
437 }