Extract UTI mapping and allow for additions
[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 "ScriptExecutionContext.h"
34 #include "StyleProperties.h"
35 #include "TransformFunctions.h"
36 #include <JavaScriptCore/GenericTypedArrayViewInlines.h>
37 #include <JavaScriptCore/HeapInlines.h>
38 #include <JavaScriptCore/JSGenericTypedArrayViewInlines.h>
39 #include <wtf/IsoMallocInlines.h>
40 #include <wtf/text/StringBuilder.h>
41
42 namespace WebCore {
43
44 WTF_MAKE_ISO_ALLOCATED_IMPL(DOMMatrixReadOnly);
45
46 // https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-dommatrixreadonly
47 ExceptionOr<Ref<DOMMatrixReadOnly>> DOMMatrixReadOnly::create(ScriptExecutionContext& scriptExecutionContext, Optional<Variant<String, Vector<double>>>&& init)
48 {
49     if (!init)
50         return adoptRef(*new DOMMatrixReadOnly);
51
52     return WTF::switchOn(init.value(),
53         [&scriptExecutionContext](const String& init) -> ExceptionOr<Ref<DOMMatrixReadOnly>> {
54             if (!scriptExecutionContext.isDocument())
55                 return Exception { TypeError };
56
57             auto parseResult = parseStringIntoAbstractMatrix(init);
58             if (parseResult.hasException())
59                 return parseResult.releaseException();
60             
61             return adoptRef(*new DOMMatrixReadOnly(parseResult.returnValue().matrix, parseResult.returnValue().is2D ? Is2D::Yes : Is2D::No));
62         },
63         [](const Vector<double>& init) -> ExceptionOr<Ref<DOMMatrixReadOnly>> {
64             if (init.size() == 6) {
65                 return adoptRef(*new DOMMatrixReadOnly(TransformationMatrix {
66                     init[0], init[1], init[2], init[3], init[4], init[5]
67                 }, Is2D::Yes));
68             }
69             if (init.size() == 16) {
70                 return adoptRef(*new DOMMatrixReadOnly(TransformationMatrix {
71                     init[0], init[1], init[2], init[3],
72                     init[4], init[5], init[6], init[7],
73                     init[8], init[9], init[10], init[11],
74                     init[12], init[13], init[14], init[15]
75                 }, Is2D::No));
76             }
77             return Exception { TypeError };
78         }
79     );
80 }
81
82 DOMMatrixReadOnly::DOMMatrixReadOnly(const TransformationMatrix& matrix, Is2D is2D)
83     : m_matrix(matrix)
84     , m_is2D(is2D == Is2D::Yes)
85 {
86     ASSERT(!m_is2D || m_matrix.isAffine());
87 }
88
89 DOMMatrixReadOnly::DOMMatrixReadOnly(TransformationMatrix&& matrix, Is2D is2D)
90     : m_matrix(WTFMove(matrix))
91     , m_is2D(is2D == Is2D::Yes)
92 {
93     ASSERT(!m_is2D || m_matrix.isAffine());
94 }
95
96 inline Ref<DOMMatrix> DOMMatrixReadOnly::cloneAsDOMMatrix() const
97 {
98     return DOMMatrix::create(m_matrix, m_is2D ? Is2D::Yes : Is2D::No);
99 }
100
101 // https://tc39.github.io/ecma262/#sec-samevaluezero
102 static bool sameValueZero(double a, double b)
103 {
104     if (std::isnan(a) && std::isnan(b))
105         return true;
106     return a == b;
107 }
108
109 // https://drafts.fxtf.org/geometry/#matrix-validate-and-fixup
110 ExceptionOr<void> DOMMatrixReadOnly::validateAndFixup(DOMMatrix2DInit& init)
111 {
112     if (init.a && init.m11 && !sameValueZero(init.a.value(), init.m11.value()))
113         return Exception { TypeError, "init.a and init.m11 do not match"_s };
114     if (init.b && init.m12 && !sameValueZero(init.b.value(), init.m12.value()))
115         return Exception { TypeError, "init.b and init.m12 do not match"_s };
116     if (init.c && init.m21 && !sameValueZero(init.c.value(), init.m21.value()))
117         return Exception { TypeError, "init.c and init.m21 do not match"_s };
118     if (init.d && init.m22 && !sameValueZero(init.d.value(), init.m22.value()))
119         return Exception { TypeError, "init.d and init.m22 do not match"_s };
120     if (init.e && init.m41 && !sameValueZero(init.e.value(), init.m41.value()))
121         return Exception { TypeError, "init.e and init.m41 do not match"_s };
122     if (init.f && init.m42 && !sameValueZero(init.f.value(), init.m42.value()))
123         return Exception { TypeError, "init.f and init.m42 do not match"_s };
124
125     if (!init.m11)
126         init.m11 = init.a.valueOr(1);
127     if (!init.m12)
128         init.m12 = init.b.valueOr(0);
129     if (!init.m21)
130         init.m21 = init.c.valueOr(0);
131     if (!init.m22)
132         init.m22 = init.d.valueOr(1);
133     if (!init.m41)
134         init.m41 = init.e.valueOr(0);
135     if (!init.m42)
136         init.m42 = init.f.valueOr(0);
137
138     return { };
139 }
140
141 ExceptionOr<void> DOMMatrixReadOnly::validateAndFixup(DOMMatrixInit& init)
142 {
143     auto validate2D = validateAndFixup(static_cast<DOMMatrix2DInit&>(init));
144     if (validate2D.hasException())
145         return validate2D.releaseException();
146
147     if (init.is2D && init.is2D.value()) {
148         if (init.m13)
149             return Exception { TypeError, "m13 should be 0 for a 2D matrix"_s };
150         if (init.m14)
151             return Exception { TypeError, "m14 should be 0 for a 2D matrix"_s };
152         if (init.m23)
153             return Exception { TypeError, "m23 should be 0 for a 2D matrix"_s };
154         if (init.m24)
155             return Exception { TypeError, "m24 should be 0 for a 2D matrix"_s };
156         if (init.m31)
157             return Exception { TypeError, "m31 should be 0 for a 2D matrix"_s };
158         if (init.m32)
159             return Exception { TypeError, "m32 should be 0 for a 2D matrix"_s };
160         if (init.m34)
161             return Exception { TypeError, "m34 should be 0 for a 2D matrix"_s };
162         if (init.m43)
163             return Exception { TypeError, "m43 should be 0 for a 2D matrix"_s };
164         if (init.m33 != 1)
165             return Exception { TypeError, "m33 should be 1 for a 2D matrix"_s };
166         if (init.m44 != 1)
167             return Exception { TypeError, "m44 should be 1 for a 2D matrix"_s };
168     }
169
170     if (!init.is2D) {
171         if (init.m13 || init.m14 || init.m23 || init.m24 || init.m31 || init.m32 || init.m34 || init.m43 || init.m33 != 1 || init.m44 != 1)
172             init.is2D = false;
173         else
174             init.is2D = true;
175     }
176     return { };
177 }
178
179 ExceptionOr<Ref<DOMMatrixReadOnly>> DOMMatrixReadOnly::fromMatrix(DOMMatrixInit&& init)
180 {
181     return fromMatrixHelper<DOMMatrixReadOnly>(WTFMove(init));
182 }
183
184 ExceptionOr<Ref<DOMMatrixReadOnly>> DOMMatrixReadOnly::fromFloat32Array(Ref<Float32Array>&& array32)
185 {
186     if (array32->length() == 6)
187         return DOMMatrixReadOnly::create(TransformationMatrix(array32->item(0), array32->item(1), array32->item(2), array32->item(3), array32->item(4), array32->item(5)), Is2D::Yes);
188
189     if (array32->length() == 16) {
190         return DOMMatrixReadOnly::create(TransformationMatrix(
191             array32->item(0), array32->item(1), array32->item(2), array32->item(3),
192             array32->item(4), array32->item(5), array32->item(6), array32->item(7),
193             array32->item(8), array32->item(9), array32->item(10), array32->item(11),
194             array32->item(12), array32->item(13), array32->item(14), array32->item(15)
195         ), Is2D::No);
196     }
197
198     return Exception { TypeError };
199 }
200
201 ExceptionOr<Ref<DOMMatrixReadOnly>> DOMMatrixReadOnly::fromFloat64Array(Ref<Float64Array>&& array64)
202 {
203     if (array64->length() == 6)
204         return DOMMatrixReadOnly::create(TransformationMatrix(array64->item(0), array64->item(1), array64->item(2), array64->item(3), array64->item(4), array64->item(5)), Is2D::Yes);
205
206     if (array64->length() == 16) {
207         return DOMMatrixReadOnly::create(TransformationMatrix(
208             array64->item(0), array64->item(1), array64->item(2), array64->item(3),
209             array64->item(4), array64->item(5), array64->item(6), array64->item(7),
210             array64->item(8), array64->item(9), array64->item(10), array64->item(11),
211             array64->item(12), array64->item(13), array64->item(14), array64->item(15)
212         ), Is2D::No);
213     }
214
215     return Exception { TypeError };
216 }
217
218 bool DOMMatrixReadOnly::isIdentity() const
219 {
220     return m_matrix.isIdentity();
221 }
222
223 ExceptionOr<DOMMatrixReadOnly::AbstractMatrix> DOMMatrixReadOnly::parseStringIntoAbstractMatrix(const String& string)
224 {
225     if (string.isEmpty())
226         return AbstractMatrix { };
227
228     auto styleDeclaration = MutableStyleProperties::create();
229     if (CSSParser::parseValue(styleDeclaration, CSSPropertyTransform, string, true, HTMLStandardMode) == CSSParser::ParseResult::Error)
230         return Exception { SyntaxError };
231
232     // Convert to TransformOperations. This can fail if a property requires style (i.e., param uses 'ems' or 'exs')
233     auto value = styleDeclaration->getPropertyCSSValue(CSSPropertyTransform);
234
235     // Check for a "none" or empty transform. In these cases we can use the default identity matrix.
236     if (!value || (is<CSSPrimitiveValue>(*value) && downcast<CSSPrimitiveValue>(*value).valueID() == CSSValueNone))
237         return AbstractMatrix { };
238
239     TransformOperations operations;
240     if (!transformsForValue(*value, CSSToLengthConversionData(), operations))
241         return Exception { SyntaxError };
242
243     AbstractMatrix matrix;
244     for (auto& operation : operations.operations()) {
245         if (operation->apply(matrix.matrix, { 0, 0 }))
246             return Exception { SyntaxError };
247         if (operation->is3DOperation())
248             matrix.is2D = false;
249     }
250
251     return matrix;
252 }
253
254 // https://drafts.fxtf.org/geometry/#dom-dommatrix-setmatrixvalue
255 ExceptionOr<void> DOMMatrixReadOnly::setMatrixValue(const String& string)
256 {
257     auto parseResult = parseStringIntoAbstractMatrix(string);
258     if (parseResult.hasException())
259         return parseResult.releaseException();
260
261     m_is2D = parseResult.returnValue().is2D;
262     m_matrix = parseResult.returnValue().matrix;
263     return { };
264 }
265
266 Ref<DOMMatrix> DOMMatrixReadOnly::translate(double tx, double ty, double tz)
267 {
268     auto matrix = cloneAsDOMMatrix();
269     return matrix->translateSelf(tx, ty, tz);
270 }
271
272 // https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-flipx
273 Ref<DOMMatrix> DOMMatrixReadOnly::flipX()
274 {
275     auto matrix = cloneAsDOMMatrix();
276     matrix->m_matrix.flipX();
277     return matrix;
278 }
279
280 // https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-flipy
281 Ref<DOMMatrix> DOMMatrixReadOnly::flipY()
282 {
283     auto matrix = cloneAsDOMMatrix();
284     matrix->m_matrix.flipY();
285     return matrix;
286 }
287
288 ExceptionOr<Ref<DOMMatrix>> DOMMatrixReadOnly::multiply(DOMMatrixInit&& other) const
289 {
290     auto matrix = cloneAsDOMMatrix();
291     return matrix->multiplySelf(WTFMove(other));
292 }
293
294 Ref<DOMMatrix> DOMMatrixReadOnly::scale(double scaleX, Optional<double> scaleY, double scaleZ, double originX, double originY, double originZ)
295 {
296     auto matrix = cloneAsDOMMatrix();
297     return matrix->scaleSelf(scaleX, scaleY, scaleZ, originX, originY, originZ);
298 }
299
300 Ref<DOMMatrix> DOMMatrixReadOnly::scale3d(double scale, double originX, double originY, double originZ)
301 {
302     auto matrix = cloneAsDOMMatrix();
303     return matrix->scale3dSelf(scale, originX, originY, originZ);
304 }
305
306 Ref<DOMMatrix> DOMMatrixReadOnly::rotate(double rotX, Optional<double> rotY, Optional<double> rotZ)
307 {
308     auto matrix = cloneAsDOMMatrix();
309     return matrix->rotateSelf(rotX, rotY, rotZ);
310 }
311
312 Ref<DOMMatrix> DOMMatrixReadOnly::rotateFromVector(double x, double y)
313 {
314     auto matrix = cloneAsDOMMatrix();
315     return matrix->rotateFromVectorSelf(x, y);
316 }
317
318 Ref<DOMMatrix> DOMMatrixReadOnly::rotateAxisAngle(double x, double y, double z, double angle)
319 {
320     auto matrix = cloneAsDOMMatrix();
321     return matrix->rotateAxisAngleSelf(x, y, z, angle);
322 }
323
324 Ref<DOMMatrix> DOMMatrixReadOnly::skewX(double sx)
325 {
326     auto matrix = cloneAsDOMMatrix();
327     return matrix->skewXSelf(sx);
328 }
329
330 Ref<DOMMatrix> DOMMatrixReadOnly::skewY(double sy)
331 {
332     auto matrix = cloneAsDOMMatrix();
333     return matrix->skewYSelf(sy);
334 }
335
336 Ref<DOMMatrix> DOMMatrixReadOnly::inverse() const
337 {
338     auto matrix = cloneAsDOMMatrix();
339     return matrix->invertSelf();
340 }
341
342 // https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-transformpoint
343 Ref<DOMPoint> DOMMatrixReadOnly::transformPoint(DOMPointInit&& pointInit)
344 {
345     m_matrix.map4ComponentPoint(pointInit.x, pointInit.y, pointInit.z, pointInit.w);
346     return DOMPoint::create(pointInit.x, pointInit.y, pointInit.z, pointInit.w);
347 }
348
349 ExceptionOr<Ref<Float32Array>> DOMMatrixReadOnly::toFloat32Array() const
350 {
351     auto array32 = Float32Array::tryCreateUninitialized(16);
352     if (!array32)
353         return Exception { UnknownError, "Out of memory"_s };
354
355     unsigned index = 0;
356     array32->set(index++, m_matrix.m11());
357     array32->set(index++, m_matrix.m12());
358     array32->set(index++, m_matrix.m13());
359     array32->set(index++, m_matrix.m14());
360     array32->set(index++, m_matrix.m21());
361     array32->set(index++, m_matrix.m22());
362     array32->set(index++, m_matrix.m23());
363     array32->set(index++, m_matrix.m24());
364     array32->set(index++, m_matrix.m31());
365     array32->set(index++, m_matrix.m32());
366     array32->set(index++, m_matrix.m33());
367     array32->set(index++, m_matrix.m34());
368     array32->set(index++, m_matrix.m41());
369     array32->set(index++, m_matrix.m42());
370     array32->set(index++, m_matrix.m43());
371     array32->set(index, m_matrix.m44());
372     return array32.releaseNonNull();
373 }
374
375 ExceptionOr<Ref<Float64Array>> DOMMatrixReadOnly::toFloat64Array() const
376 {
377     auto array64 = Float64Array::tryCreateUninitialized(16);
378     if (!array64)
379         return Exception { UnknownError, "Out of memory"_s };
380
381     unsigned index = 0;
382     array64->set(index++, m_matrix.m11());
383     array64->set(index++, m_matrix.m12());
384     array64->set(index++, m_matrix.m13());
385     array64->set(index++, m_matrix.m14());
386     array64->set(index++, m_matrix.m21());
387     array64->set(index++, m_matrix.m22());
388     array64->set(index++, m_matrix.m23());
389     array64->set(index++, m_matrix.m24());
390     array64->set(index++, m_matrix.m31());
391     array64->set(index++, m_matrix.m32());
392     array64->set(index++, m_matrix.m33());
393     array64->set(index++, m_matrix.m34());
394     array64->set(index++, m_matrix.m41());
395     array64->set(index++, m_matrix.m42());
396     array64->set(index++, m_matrix.m43());
397     array64->set(index, m_matrix.m44());
398     return array64.releaseNonNull();
399 }
400
401 // https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-stringifier
402 ExceptionOr<String> DOMMatrixReadOnly::toString() const
403 {
404     if (!m_matrix.containsOnlyFiniteValues())
405         return Exception { InvalidStateError, "Matrix contains non-finite values"_s };
406
407     StringBuilder builder;
408     if (is2D()) {
409         builder.appendLiteral("matrix(");
410         builder.appendECMAScriptNumber(m_matrix.a());
411         builder.appendLiteral(", ");
412         builder.appendECMAScriptNumber(m_matrix.b());
413         builder.appendLiteral(", ");
414         builder.appendECMAScriptNumber(m_matrix.c());
415         builder.appendLiteral(", ");
416         builder.appendECMAScriptNumber(m_matrix.d());
417         builder.appendLiteral(", ");
418         builder.appendECMAScriptNumber(m_matrix.e());
419         builder.appendLiteral(", ");
420         builder.appendECMAScriptNumber(m_matrix.f());
421     } else {
422         builder.appendLiteral("matrix3d(");
423         builder.appendECMAScriptNumber(m_matrix.m11());
424         builder.appendLiteral(", ");
425         builder.appendECMAScriptNumber(m_matrix.m12());
426         builder.appendLiteral(", ");
427         builder.appendECMAScriptNumber(m_matrix.m13());
428         builder.appendLiteral(", ");
429         builder.appendECMAScriptNumber(m_matrix.m14());
430         builder.appendLiteral(", ");
431         builder.appendECMAScriptNumber(m_matrix.m21());
432         builder.appendLiteral(", ");
433         builder.appendECMAScriptNumber(m_matrix.m22());
434         builder.appendLiteral(", ");
435         builder.appendECMAScriptNumber(m_matrix.m23());
436         builder.appendLiteral(", ");
437         builder.appendECMAScriptNumber(m_matrix.m24());
438         builder.appendLiteral(", ");
439         builder.appendECMAScriptNumber(m_matrix.m31());
440         builder.appendLiteral(", ");
441         builder.appendECMAScriptNumber(m_matrix.m32());
442         builder.appendLiteral(", ");
443         builder.appendECMAScriptNumber(m_matrix.m33());
444         builder.appendLiteral(", ");
445         builder.appendECMAScriptNumber(m_matrix.m34());
446         builder.appendLiteral(", ");
447         builder.appendECMAScriptNumber(m_matrix.m41());
448         builder.appendLiteral(", ");
449         builder.appendECMAScriptNumber(m_matrix.m42());
450         builder.appendLiteral(", ");
451         builder.appendECMAScriptNumber(m_matrix.m43());
452         builder.appendLiteral(", ");
453         builder.appendECMAScriptNumber(m_matrix.m44());
454     }
455     builder.append(')');
456     return builder.toString();
457 }
458
459 } // namespace WebCore