Implement DOMMatrixReadOnly.transformPoint()
[WebKit-https.git] / Source / WebCore / css / DOMMatrixReadOnly.cpp
1 /*
2  * Copyright (C) 2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "DOMMatrixReadOnly.h"
28
29 #include "CSSParser.h"
30 #include "CSSToLengthConversionData.h"
31 #include "DOMMatrix.h"
32 #include "DOMPoint.h"
33 #include "ExceptionCode.h"
34 #include "StyleProperties.h"
35 #include "TransformFunctions.h"
36 #include <JavaScriptCore/GenericTypedArrayViewInlines.h>
37 #include <JavaScriptCore/JSGenericTypedArrayViewInlines.h>
38 #include <heap/HeapInlines.h>
39 #include <wtf/text/StringBuilder.h>
40
41 namespace WebCore {
42
43 DOMMatrixReadOnly::DOMMatrixReadOnly(const TransformationMatrix& matrix, Is2D is2D)
44     : m_matrix(matrix)
45     , m_is2D(is2D == Is2D::Yes)
46 {
47     ASSERT(!m_is2D || m_matrix.isAffine());
48 }
49
50 inline Ref<DOMMatrix> DOMMatrixReadOnly::cloneAsDOMMatrix() const
51 {
52     return DOMMatrix::create(m_matrix, m_is2D ? Is2D::Yes : Is2D::No);
53 }
54
55 // https://drafts.fxtf.org/geometry/#matrix-validate-and-fixup
56 ExceptionOr<void> DOMMatrixReadOnly::validateAndFixup(DOMMatrixInit& init)
57 {
58     if (init.a && init.m11 && init.a.value() != init.m11.value())
59         return Exception { TypeError, ASCIILiteral("init.a and init.m11 do not match") };
60     if (init.b && init.m12 && init.b.value() != init.m12.value())
61         return Exception { TypeError, ASCIILiteral("init.b and init.m12 do not match") };
62     if (init.c && init.m21 && init.c.value() != init.m21.value())
63         return Exception { TypeError, ASCIILiteral("init.c and init.m21 do not match") };
64     if (init.d && init.m22 && init.d.value() != init.m22.value())
65         return Exception { TypeError, ASCIILiteral("init.d and init.m22 do not match") };
66     if (init.e && init.m41 && init.e.value() != init.m41.value())
67         return Exception { TypeError, ASCIILiteral("init.e and init.m41 do not match") };
68     if (init.f && init.m42 && init.f.value() != init.m42.value())
69         return Exception { TypeError, ASCIILiteral("init.f and init.m42 do not match") };
70
71     if (init.is2D && init.is2D.value()) {
72         if (init.m13)
73             return Exception { TypeError, ASCIILiteral("m13 should be 0 for a 2D matrix") };
74         if (init.m14)
75             return Exception { TypeError, ASCIILiteral("m14 should be 0 for a 2D matrix") };
76         if (init.m23)
77             return Exception { TypeError, ASCIILiteral("m23 should be 0 for a 2D matrix") };
78         if (init.m24)
79             return Exception { TypeError, ASCIILiteral("m24 should be 0 for a 2D matrix") };
80         if (init.m31)
81             return Exception { TypeError, ASCIILiteral("m31 should be 0 for a 2D matrix") };
82         if (init.m32)
83             return Exception { TypeError, ASCIILiteral("m32 should be 0 for a 2D matrix") };
84         if (init.m34)
85             return Exception { TypeError, ASCIILiteral("m34 should be 0 for a 2D matrix") };
86         if (init.m43)
87             return Exception { TypeError, ASCIILiteral("m43 should be 0 for a 2D matrix") };
88         if (init.m33 != 1)
89             return Exception { TypeError, ASCIILiteral("m33 should be 1 for a 2D matrix") };
90         if (init.m44 != 1)
91             return Exception { TypeError, ASCIILiteral("m44 should be 1 for a 2D matrix") };
92     }
93
94     if (!init.m11)
95         init.m11 = init.a.value_or(1);
96     if (!init.m12)
97         init.m12 = init.b.value_or(0);
98     if (!init.m21)
99         init.m21 = init.c.value_or(0);
100     if (!init.m22)
101         init.m22 = init.d.value_or(1);
102     if (!init.m41)
103         init.m41 = init.e.value_or(0);
104     if (!init.m42)
105         init.m42 = init.f.value_or(0);
106
107     if (!init.is2D) {
108         if (init.m13 || init.m14 || init.m23 || init.m24 || init.m31 || init.m32 || init.m34 || init.m43 || init.m33 != 1 || init.m44 != 1)
109             init.is2D = false;
110         else
111             init.is2D = true;
112     }
113     return { };
114 }
115
116 ExceptionOr<Ref<DOMMatrixReadOnly>> DOMMatrixReadOnly::fromMatrix(DOMMatrixInit&& init)
117 {
118     return fromMatrixHelper<DOMMatrixReadOnly>(WTFMove(init));
119 }
120
121 ExceptionOr<Ref<DOMMatrixReadOnly>> DOMMatrixReadOnly::fromFloat32Array(Ref<Float32Array>&& array32)
122 {
123     if (array32->length() == 6)
124         return DOMMatrixReadOnly::create(TransformationMatrix(array32->item(0), array32->item(1), array32->item(2), array32->item(3), array32->item(4), array32->item(5)), Is2D::Yes);
125
126     if (array32->length() == 16) {
127         return DOMMatrixReadOnly::create(TransformationMatrix(
128             array32->item(0), array32->item(1), array32->item(2), array32->item(3),
129             array32->item(4), array32->item(5), array32->item(6), array32->item(7),
130             array32->item(8), array32->item(9), array32->item(10), array32->item(11),
131             array32->item(12), array32->item(13), array32->item(14), array32->item(15)
132         ), Is2D::No);
133     }
134
135     return Exception { TypeError };
136 }
137
138 ExceptionOr<Ref<DOMMatrixReadOnly>> DOMMatrixReadOnly::fromFloat64Array(Ref<Float64Array>&& array64)
139 {
140     if (array64->length() == 6)
141         return DOMMatrixReadOnly::create(TransformationMatrix(array64->item(0), array64->item(1), array64->item(2), array64->item(3), array64->item(4), array64->item(5)), Is2D::Yes);
142
143     if (array64->length() == 16) {
144         return DOMMatrixReadOnly::create(TransformationMatrix(
145             array64->item(0), array64->item(1), array64->item(2), array64->item(3),
146             array64->item(4), array64->item(5), array64->item(6), array64->item(7),
147             array64->item(8), array64->item(9), array64->item(10), array64->item(11),
148             array64->item(12), array64->item(13), array64->item(14), array64->item(15)
149         ), Is2D::No);
150     }
151
152     return Exception { TypeError };
153 }
154
155 bool DOMMatrixReadOnly::isIdentity() const
156 {
157     return m_matrix.isIdentity();
158 }
159
160 // https://drafts.fxtf.org/geometry/#dom-dommatrix-setmatrixvalue
161 ExceptionOr<void> DOMMatrixReadOnly::setMatrixValue(const String& string)
162 {
163     if (string.isEmpty())
164         return { };
165
166     auto styleDeclaration = MutableStyleProperties::create();
167     if (CSSParser::parseValue(styleDeclaration, CSSPropertyTransform, string, true, HTMLStandardMode) == CSSParser::ParseResult::Error)
168         return Exception { SYNTAX_ERR };
169
170     // Convert to TransformOperations. This can fail if a property requires style (i.e., param uses 'ems' or 'exs')
171     auto value = styleDeclaration->getPropertyCSSValue(CSSPropertyTransform);
172
173     // Check for a "none" or empty transform. In these cases we can use the default identity matrix.
174     if (!value || (is<CSSPrimitiveValue>(*value) && downcast<CSSPrimitiveValue>(*value).valueID() == CSSValueNone))
175         return { };
176
177     TransformOperations operations;
178     if (!transformsForValue(*value, CSSToLengthConversionData(), operations))
179         return Exception { SYNTAX_ERR };
180
181     m_is2D = true;
182     // Convert transform operations to a TransformationMatrix. This can fail if a parameter has a percentage ('%').
183     TransformationMatrix matrix;
184     for (auto& operation : operations.operations()) {
185         if (operation->apply(matrix, IntSize(0, 0)))
186             return Exception { SYNTAX_ERR };
187         if (operation->is3DOperation())
188             m_is2D = false;
189     }
190     m_matrix = matrix;
191     return { };
192 }
193
194 ExceptionOr<void> DOMMatrixReadOnly::setMatrixValue(const Vector<double>& init)
195 {
196     if (init.size() == 6) {
197         m_matrix = TransformationMatrix { init[0], init[1], init[2], init[3], init[4], init[5] };
198         return { };
199     }
200     if (init.size() == 16) {
201         m_matrix = TransformationMatrix {
202             init[0], init[1], init[2], init[3],
203             init[4], init[5], init[6], init[7],
204             init[8], init[9], init[10], init[11],
205             init[12], init[13], init[14], init[15]
206         };
207         m_is2D = false;
208         return { };
209     }
210     return Exception { TypeError };
211 }
212
213 Ref<DOMMatrix> DOMMatrixReadOnly::translate(double tx, double ty, double tz)
214 {
215     auto matrix = cloneAsDOMMatrix();
216     return matrix->translateSelf(tx, ty, tz);
217 }
218
219 // https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-flipx
220 Ref<DOMMatrix> DOMMatrixReadOnly::flipX()
221 {
222     auto matrix = cloneAsDOMMatrix();
223     matrix->m_matrix.flipX();
224     return matrix;
225 }
226
227 // https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-flipy
228 Ref<DOMMatrix> DOMMatrixReadOnly::flipY()
229 {
230     auto matrix = cloneAsDOMMatrix();
231     matrix->m_matrix.flipY();
232     return matrix;
233 }
234
235 ExceptionOr<Ref<DOMMatrix>> DOMMatrixReadOnly::multiply(DOMMatrixInit&& other) const
236 {
237     auto matrix = cloneAsDOMMatrix();
238     return matrix->multiplySelf(WTFMove(other));
239 }
240
241 Ref<DOMMatrix> DOMMatrixReadOnly::scale(double scaleX, std::optional<double> scaleY, double scaleZ, double originX, double originY, double originZ)
242 {
243     auto matrix = cloneAsDOMMatrix();
244     return matrix->scaleSelf(scaleX, scaleY, scaleZ, originX, originY, originZ);
245 }
246
247 Ref<DOMMatrix> DOMMatrixReadOnly::scale3d(double scale, double originX, double originY, double originZ)
248 {
249     auto matrix = cloneAsDOMMatrix();
250     return matrix->scale3dSelf(scale, originX, originY, originZ);
251 }
252
253 Ref<DOMMatrix> DOMMatrixReadOnly::rotate(double rotX, std::optional<double> rotY, std::optional<double> rotZ)
254 {
255     auto matrix = cloneAsDOMMatrix();
256     return matrix->rotateSelf(rotX, rotY, rotZ);
257 }
258
259 Ref<DOMMatrix> DOMMatrixReadOnly::rotateFromVector(double x, double y)
260 {
261     auto matrix = cloneAsDOMMatrix();
262     return matrix->rotateFromVectorSelf(x, y);
263 }
264
265 Ref<DOMMatrix> DOMMatrixReadOnly::rotateAxisAngle(double x, double y, double z, double angle)
266 {
267     auto matrix = cloneAsDOMMatrix();
268     return matrix->rotateAxisAngleSelf(x, y, z, angle);
269 }
270
271 Ref<DOMMatrix> DOMMatrixReadOnly::skewX(double sx)
272 {
273     auto matrix = cloneAsDOMMatrix();
274     return matrix->skewXSelf(sx);
275 }
276
277 Ref<DOMMatrix> DOMMatrixReadOnly::skewY(double sy)
278 {
279     auto matrix = cloneAsDOMMatrix();
280     return matrix->skewYSelf(sy);
281 }
282
283 Ref<DOMMatrix> DOMMatrixReadOnly::inverse() const
284 {
285     auto matrix = cloneAsDOMMatrix();
286     return matrix->invertSelf();
287 }
288
289 // https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-transformpoint
290 Ref<DOMPoint> DOMMatrixReadOnly::transformPoint(DOMPointInit&& pointInit)
291 {
292     m_matrix.map4ComponentPoint(pointInit.x, pointInit.y, pointInit.z, pointInit.w);
293     return DOMPoint::create(pointInit.x, pointInit.y, pointInit.z, pointInit.w);
294 }
295
296 ExceptionOr<Ref<Float32Array>> DOMMatrixReadOnly::toFloat32Array() const
297 {
298     auto array32 = Float32Array::createUninitialized(16);
299     if (!array32)
300         return Exception { UnknownError, ASCIILiteral("Out of memory") };
301
302     unsigned index = 0;
303     array32->set(index++, m_matrix.m11());
304     array32->set(index++, m_matrix.m12());
305     array32->set(index++, m_matrix.m13());
306     array32->set(index++, m_matrix.m14());
307     array32->set(index++, m_matrix.m21());
308     array32->set(index++, m_matrix.m22());
309     array32->set(index++, m_matrix.m23());
310     array32->set(index++, m_matrix.m24());
311     array32->set(index++, m_matrix.m31());
312     array32->set(index++, m_matrix.m32());
313     array32->set(index++, m_matrix.m33());
314     array32->set(index++, m_matrix.m34());
315     array32->set(index++, m_matrix.m41());
316     array32->set(index++, m_matrix.m42());
317     array32->set(index++, m_matrix.m43());
318     array32->set(index, m_matrix.m44());
319     return array32.releaseNonNull();
320 }
321
322 ExceptionOr<Ref<Float64Array>> DOMMatrixReadOnly::toFloat64Array() const
323 {
324     auto array64 = Float64Array::createUninitialized(16);
325     if (!array64)
326         return Exception { UnknownError, ASCIILiteral("Out of memory") };
327
328     unsigned index = 0;
329     array64->set(index++, m_matrix.m11());
330     array64->set(index++, m_matrix.m12());
331     array64->set(index++, m_matrix.m13());
332     array64->set(index++, m_matrix.m14());
333     array64->set(index++, m_matrix.m21());
334     array64->set(index++, m_matrix.m22());
335     array64->set(index++, m_matrix.m23());
336     array64->set(index++, m_matrix.m24());
337     array64->set(index++, m_matrix.m31());
338     array64->set(index++, m_matrix.m32());
339     array64->set(index++, m_matrix.m33());
340     array64->set(index++, m_matrix.m34());
341     array64->set(index++, m_matrix.m41());
342     array64->set(index++, m_matrix.m42());
343     array64->set(index++, m_matrix.m43());
344     array64->set(index, m_matrix.m44());
345     return array64.releaseNonNull();
346 }
347
348 // https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-stringifier
349 ExceptionOr<String> DOMMatrixReadOnly::toString() const
350 {
351     if (!m_matrix.containsOnlyFiniteValues())
352         return Exception { INVALID_STATE_ERR, ASCIILiteral("Matrix contains non-finite values") };
353
354     StringBuilder builder;
355     if (is2D()) {
356         builder.appendLiteral("matrix(");
357         builder.append(String::numberToStringECMAScript(m_matrix.a()));
358         builder.appendLiteral(", ");
359         builder.append(String::numberToStringECMAScript(m_matrix.b()));
360         builder.appendLiteral(", ");
361         builder.append(String::numberToStringECMAScript(m_matrix.c()));
362         builder.appendLiteral(", ");
363         builder.append(String::numberToStringECMAScript(m_matrix.d()));
364         builder.appendLiteral(", ");
365         builder.append(String::numberToStringECMAScript(m_matrix.e()));
366         builder.appendLiteral(", ");
367         builder.append(String::numberToStringECMAScript(m_matrix.f()));
368     } else {
369         builder.appendLiteral("matrix3d(");
370         builder.append(String::numberToStringECMAScript(m_matrix.m11()));
371         builder.appendLiteral(", ");
372         builder.append(String::numberToStringECMAScript(m_matrix.m12()));
373         builder.appendLiteral(", ");
374         builder.append(String::numberToStringECMAScript(m_matrix.m13()));
375         builder.appendLiteral(", ");
376         builder.append(String::numberToStringECMAScript(m_matrix.m14()));
377         builder.appendLiteral(", ");
378         builder.append(String::numberToStringECMAScript(m_matrix.m21()));
379         builder.appendLiteral(", ");
380         builder.append(String::numberToStringECMAScript(m_matrix.m22()));
381         builder.appendLiteral(", ");
382         builder.append(String::numberToStringECMAScript(m_matrix.m23()));
383         builder.appendLiteral(", ");
384         builder.append(String::numberToStringECMAScript(m_matrix.m24()));
385         builder.appendLiteral(", ");
386         builder.append(String::numberToStringECMAScript(m_matrix.m31()));
387         builder.appendLiteral(", ");
388         builder.append(String::numberToStringECMAScript(m_matrix.m32()));
389         builder.appendLiteral(", ");
390         builder.append(String::numberToStringECMAScript(m_matrix.m33()));
391         builder.appendLiteral(", ");
392         builder.append(String::numberToStringECMAScript(m_matrix.m34()));
393         builder.appendLiteral(", ");
394         builder.append(String::numberToStringECMAScript(m_matrix.m41()));
395         builder.appendLiteral(", ");
396         builder.append(String::numberToStringECMAScript(m_matrix.m42()));
397         builder.appendLiteral(", ");
398         builder.append(String::numberToStringECMAScript(m_matrix.m43()));
399         builder.appendLiteral(", ");
400         builder.append(String::numberToStringECMAScript(m_matrix.m44()));
401     }
402     builder.append(')');
403     return builder.toString();
404 }
405
406 } // namespace WebCore