1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 * A class used for intermediate representations of the -moz-transform property.
10 #include "nsStyleTransformMatrix.h"
11 #include "nsCSSValue.h"
12 #include "nsPresContext.h"
13 #include "nsRuleNode.h"
14 #include "nsCSSKeywords.h"
15 #include "mozilla/StyleAnimationValue.h"
16 #include "gfxMatrix.h"
18 using namespace mozilla
;
19 using namespace mozilla::gfx
;
21 namespace nsStyleTransformMatrix
{
23 /* Note on floating point precision: The transform matrix is an array
24 * of single precision 'float's, and so are most of the input values
25 * we get from the style system, but intermediate calculations
26 * involving angles need to be done in 'double'.
29 /* Force small values to zero. We do this to avoid having sin(360deg)
30 * evaluate to a tiny but nonzero value.
32 static double FlushToZero(double aVal
)
34 if (-FLT_EPSILON
< aVal
&& aVal
< FLT_EPSILON
)
41 ProcessTranslatePart(const nsCSSValue
& aValue
,
42 nsStyleContext
* aContext
,
43 nsPresContext
* aPresContext
,
44 bool& aCanStoreInRuleTree
,
50 if (aValue
.GetUnit() == eCSSUnit_Percent
) {
51 percent
= aValue
.GetPercentValue();
52 } else if (aValue
.GetUnit() == eCSSUnit_Pixel
||
53 aValue
.GetUnit() == eCSSUnit_Number
) {
54 // Handle this here (even though nsRuleNode::CalcLength handles it
55 // fine) so that callers are allowed to pass a null style context
56 // and pres context to SetToTransformFunction if they know (as
57 // StyleAnimationValue does) that all lengths within the transform
58 // function have already been computed to pixels and percents.
60 // Raw numbers are treated as being pixels.
62 // Don't convert to aValue to AppUnits here to avoid precision issues.
63 return aValue
.GetFloatValue();
64 } else if (aValue
.IsCalcUnit()) {
65 nsRuleNode::ComputedCalc result
=
66 nsRuleNode::SpecifiedCalcToComputedCalc(aValue
, aContext
, aPresContext
,
68 percent
= result
.mPercent
;
69 offset
= result
.mLength
;
71 offset
= nsRuleNode::CalcLength(aValue
, aContext
, aPresContext
,
75 return (percent
* NSAppUnitsToFloatPixels(aSize
, nsPresContext::AppUnitsPerCSSPixel())) +
76 NSAppUnitsToFloatPixels(offset
, nsPresContext::AppUnitsPerCSSPixel());
80 * Helper functions to process all the transformation function types.
82 * These take a matrix parameter to accumulate the current matrix.
85 /* Helper function to process a matrix entry. */
87 ProcessMatrix(gfx3DMatrix
& aMatrix
,
88 const nsCSSValue::Array
* aData
,
89 nsStyleContext
* aContext
,
90 nsPresContext
* aPresContext
,
91 bool& aCanStoreInRuleTree
,
94 NS_PRECONDITION(aData
->Count() == 7, "Invalid array!");
98 /* Take the first four elements out of the array as floats and store
101 result
._11
= aData
->Item(1).GetFloatValue();
102 result
._12
= aData
->Item(2).GetFloatValue();
103 result
._21
= aData
->Item(3).GetFloatValue();
104 result
._22
= aData
->Item(4).GetFloatValue();
106 /* The last two elements have their length parts stored in aDelta
107 * and their percent parts stored in aX[0] and aY[1].
109 result
._31
= ProcessTranslatePart(aData
->Item(5),
110 aContext
, aPresContext
, aCanStoreInRuleTree
,
112 result
._32
= ProcessTranslatePart(aData
->Item(6),
113 aContext
, aPresContext
, aCanStoreInRuleTree
,
116 aMatrix
.PreMultiply(result
);
120 ProcessMatrix3D(gfx3DMatrix
& aMatrix
,
121 const nsCSSValue::Array
* aData
,
122 nsStyleContext
* aContext
,
123 nsPresContext
* aPresContext
,
124 bool& aCanStoreInRuleTree
,
127 NS_PRECONDITION(aData
->Count() == 17, "Invalid array!");
131 temp
._11
= aData
->Item(1).GetFloatValue();
132 temp
._12
= aData
->Item(2).GetFloatValue();
133 temp
._13
= aData
->Item(3).GetFloatValue();
134 temp
._14
= aData
->Item(4).GetFloatValue();
135 temp
._21
= aData
->Item(5).GetFloatValue();
136 temp
._22
= aData
->Item(6).GetFloatValue();
137 temp
._23
= aData
->Item(7).GetFloatValue();
138 temp
._24
= aData
->Item(8).GetFloatValue();
139 temp
._31
= aData
->Item(9).GetFloatValue();
140 temp
._32
= aData
->Item(10).GetFloatValue();
141 temp
._33
= aData
->Item(11).GetFloatValue();
142 temp
._34
= aData
->Item(12).GetFloatValue();
143 temp
._44
= aData
->Item(16).GetFloatValue();
145 temp
._41
= ProcessTranslatePart(aData
->Item(13),
146 aContext
, aPresContext
, aCanStoreInRuleTree
,
148 temp
._42
= ProcessTranslatePart(aData
->Item(14),
149 aContext
, aPresContext
, aCanStoreInRuleTree
,
151 temp
._43
= ProcessTranslatePart(aData
->Item(15),
152 aContext
, aPresContext
, aCanStoreInRuleTree
,
155 aMatrix
.PreMultiply(temp
);
158 /* Helper function to process two matrices that we need to interpolate between */
160 ProcessInterpolateMatrix(gfx3DMatrix
& aMatrix
,
161 const nsCSSValue::Array
* aData
,
162 nsStyleContext
* aContext
,
163 nsPresContext
* aPresContext
,
164 bool& aCanStoreInRuleTree
,
167 NS_PRECONDITION(aData
->Count() == 4, "Invalid array!");
169 gfx3DMatrix matrix1
, matrix2
;
170 if (aData
->Item(1).GetUnit() == eCSSUnit_List
) {
171 matrix1
= nsStyleTransformMatrix::ReadTransforms(aData
->Item(1).GetListValue(),
172 aContext
, aPresContext
,
174 aBounds
, nsPresContext::AppUnitsPerCSSPixel());
176 if (aData
->Item(2).GetUnit() == eCSSUnit_List
) {
177 matrix2
= ReadTransforms(aData
->Item(2).GetListValue(),
178 aContext
, aPresContext
,
180 aBounds
, nsPresContext::AppUnitsPerCSSPixel());
182 double progress
= aData
->Item(3).GetPercentValue();
185 StyleAnimationValue::InterpolateTransformMatrix(matrix1
, matrix2
, progress
)
189 /* Helper function to process a translatex function. */
191 ProcessTranslateX(gfx3DMatrix
& aMatrix
,
192 const nsCSSValue::Array
* aData
,
193 nsStyleContext
* aContext
,
194 nsPresContext
* aPresContext
,
195 bool& aCanStoreInRuleTree
,
198 NS_PRECONDITION(aData
->Count() == 2, "Invalid array!");
202 temp
.x
= ProcessTranslatePart(aData
->Item(1),
203 aContext
, aPresContext
, aCanStoreInRuleTree
,
205 aMatrix
.Translate(temp
);
208 /* Helper function to process a translatey function. */
210 ProcessTranslateY(gfx3DMatrix
& aMatrix
,
211 const nsCSSValue::Array
* aData
,
212 nsStyleContext
* aContext
,
213 nsPresContext
* aPresContext
,
214 bool& aCanStoreInRuleTree
,
217 NS_PRECONDITION(aData
->Count() == 2, "Invalid array!");
221 temp
.y
= ProcessTranslatePart(aData
->Item(1),
222 aContext
, aPresContext
, aCanStoreInRuleTree
,
224 aMatrix
.Translate(temp
);
228 ProcessTranslateZ(gfx3DMatrix
& aMatrix
,
229 const nsCSSValue::Array
* aData
,
230 nsStyleContext
* aContext
,
231 nsPresContext
* aPresContext
,
232 bool& aCanStoreInRuleTree
)
234 NS_PRECONDITION(aData
->Count() == 2, "Invalid array!");
238 temp
.z
= ProcessTranslatePart(aData
->Item(1), aContext
,
239 aPresContext
, aCanStoreInRuleTree
, 0);
240 aMatrix
.Translate(temp
);
243 /* Helper function to process a translate function. */
245 ProcessTranslate(gfx3DMatrix
& aMatrix
,
246 const nsCSSValue::Array
* aData
,
247 nsStyleContext
* aContext
,
248 nsPresContext
* aPresContext
,
249 bool& aCanStoreInRuleTree
,
252 NS_PRECONDITION(aData
->Count() == 2 || aData
->Count() == 3, "Invalid array!");
256 temp
.x
= ProcessTranslatePart(aData
->Item(1),
257 aContext
, aPresContext
, aCanStoreInRuleTree
,
260 /* If we read in a Y component, set it appropriately */
261 if (aData
->Count() == 3) {
262 temp
.y
= ProcessTranslatePart(aData
->Item(2),
263 aContext
, aPresContext
, aCanStoreInRuleTree
,
266 aMatrix
.Translate(temp
);
270 ProcessTranslate3D(gfx3DMatrix
& aMatrix
,
271 const nsCSSValue::Array
* aData
,
272 nsStyleContext
* aContext
,
273 nsPresContext
* aPresContext
,
274 bool& aCanStoreInRuleTree
,
277 NS_PRECONDITION(aData
->Count() == 4, "Invalid array!");
281 temp
.x
= ProcessTranslatePart(aData
->Item(1),
282 aContext
, aPresContext
, aCanStoreInRuleTree
,
285 temp
.y
= ProcessTranslatePart(aData
->Item(2),
286 aContext
, aPresContext
, aCanStoreInRuleTree
,
289 temp
.z
= ProcessTranslatePart(aData
->Item(3),
290 aContext
, aPresContext
, aCanStoreInRuleTree
,
293 aMatrix
.Translate(temp
);
296 /* Helper function to set up a scale matrix. */
298 ProcessScaleHelper(gfx3DMatrix
& aMatrix
,
303 aMatrix
.Scale(aXScale
, aYScale
, aZScale
);
306 /* Process a scalex function. */
308 ProcessScaleX(gfx3DMatrix
& aMatrix
, const nsCSSValue::Array
* aData
)
310 NS_PRECONDITION(aData
->Count() == 2, "Bad array!");
311 ProcessScaleHelper(aMatrix
, aData
->Item(1).GetFloatValue(), 1.0f
, 1.0f
);
314 /* Process a scaley function. */
316 ProcessScaleY(gfx3DMatrix
& aMatrix
, const nsCSSValue::Array
* aData
)
318 NS_PRECONDITION(aData
->Count() == 2, "Bad array!");
319 ProcessScaleHelper(aMatrix
, 1.0f
, aData
->Item(1).GetFloatValue(), 1.0f
);
323 ProcessScaleZ(gfx3DMatrix
& aMatrix
, const nsCSSValue::Array
* aData
)
325 NS_PRECONDITION(aData
->Count() == 2, "Bad array!");
326 ProcessScaleHelper(aMatrix
, 1.0f
, 1.0f
, aData
->Item(1).GetFloatValue());
330 ProcessScale3D(gfx3DMatrix
& aMatrix
, const nsCSSValue::Array
* aData
)
332 NS_PRECONDITION(aData
->Count() == 4, "Bad array!");
333 ProcessScaleHelper(aMatrix
,
334 aData
->Item(1).GetFloatValue(),
335 aData
->Item(2).GetFloatValue(),
336 aData
->Item(3).GetFloatValue());
339 /* Process a scale function. */
341 ProcessScale(gfx3DMatrix
& aMatrix
, const nsCSSValue::Array
* aData
)
343 NS_PRECONDITION(aData
->Count() == 2 || aData
->Count() == 3, "Bad array!");
344 /* We either have one element or two. If we have one, it's for both X and Y.
345 * Otherwise it's one for each.
347 const nsCSSValue
& scaleX
= aData
->Item(1);
348 const nsCSSValue
& scaleY
= (aData
->Count() == 2 ? scaleX
:
351 ProcessScaleHelper(aMatrix
,
352 scaleX
.GetFloatValue(),
353 scaleY
.GetFloatValue(),
357 /* Helper function that, given a set of angles, constructs the appropriate
361 ProcessSkewHelper(gfx3DMatrix
& aMatrix
, double aXAngle
, double aYAngle
)
363 aMatrix
.SkewXY(aXAngle
, aYAngle
);
366 /* Function that converts a skewx transform into a matrix. */
368 ProcessSkewX(gfx3DMatrix
& aMatrix
, const nsCSSValue::Array
* aData
)
370 NS_ASSERTION(aData
->Count() == 2, "Bad array!");
371 ProcessSkewHelper(aMatrix
, aData
->Item(1).GetAngleValueInRadians(), 0.0);
374 /* Function that converts a skewy transform into a matrix. */
376 ProcessSkewY(gfx3DMatrix
& aMatrix
, const nsCSSValue::Array
* aData
)
378 NS_ASSERTION(aData
->Count() == 2, "Bad array!");
379 ProcessSkewHelper(aMatrix
, 0.0, aData
->Item(1).GetAngleValueInRadians());
382 /* Function that converts a skew transform into a matrix. */
384 ProcessSkew(gfx3DMatrix
& aMatrix
, const nsCSSValue::Array
* aData
)
386 NS_ASSERTION(aData
->Count() == 2 || aData
->Count() == 3, "Bad array!");
388 double xSkew
= aData
->Item(1).GetAngleValueInRadians();
389 double ySkew
= (aData
->Count() == 2
390 ? 0.0 : aData
->Item(2).GetAngleValueInRadians());
392 ProcessSkewHelper(aMatrix
, xSkew
, ySkew
);
395 /* Function that converts a rotate transform into a matrix. */
397 ProcessRotateZ(gfx3DMatrix
& aMatrix
, const nsCSSValue::Array
* aData
)
399 NS_PRECONDITION(aData
->Count() == 2, "Invalid array!");
400 double theta
= aData
->Item(1).GetAngleValueInRadians();
401 aMatrix
.RotateZ(theta
);
405 ProcessRotateX(gfx3DMatrix
& aMatrix
, const nsCSSValue::Array
* aData
)
407 NS_PRECONDITION(aData
->Count() == 2, "Invalid array!");
408 double theta
= aData
->Item(1).GetAngleValueInRadians();
409 aMatrix
.RotateX(theta
);
413 ProcessRotateY(gfx3DMatrix
& aMatrix
, const nsCSSValue::Array
* aData
)
415 NS_PRECONDITION(aData
->Count() == 2, "Invalid array!");
416 double theta
= aData
->Item(1).GetAngleValueInRadians();
417 aMatrix
.RotateY(theta
);
421 ProcessRotate3D(gfx3DMatrix
& aMatrix
, const nsCSSValue::Array
* aData
)
423 NS_PRECONDITION(aData
->Count() == 5, "Invalid array!");
425 /* We want our matrix to look like this:
426 * | 1 + (1-cos(angle))*(x*x-1) -z*sin(angle)+(1-cos(angle))*x*y y*sin(angle)+(1-cos(angle))*x*z 0 |
427 * | z*sin(angle)+(1-cos(angle))*x*y 1 + (1-cos(angle))*(y*y-1) -x*sin(angle)+(1-cos(angle))*y*z 0 |
428 * | -y*sin(angle)+(1-cos(angle))*x*z x*sin(angle)+(1-cos(angle))*y*z 1 + (1-cos(angle))*(z*z-1) 0 |
430 * (see http://www.w3.org/TR/css3-3d-transforms/#transform-functions)
433 /* The current spec specifies a matrix that rotates in the wrong direction. For now we just negate
434 * the angle provided to get the correct rotation direction until the spec is updated.
437 double theta
= -aData
->Item(4).GetAngleValueInRadians();
438 float cosTheta
= FlushToZero(cos(theta
));
439 float sinTheta
= FlushToZero(sin(theta
));
441 Point3D
vector(aData
->Item(1).GetFloatValue(),
442 aData
->Item(2).GetFloatValue(),
443 aData
->Item(3).GetFloatValue());
445 if (!vector
.Length()) {
452 /* Create our matrix */
453 temp
._11
= 1 + (1 - cosTheta
) * (vector
.x
* vector
.x
- 1);
454 temp
._12
= -vector
.z
* sinTheta
+ (1 - cosTheta
) * vector
.x
* vector
.y
;
455 temp
._13
= vector
.y
* sinTheta
+ (1 - cosTheta
) * vector
.x
* vector
.z
;
457 temp
._21
= vector
.z
* sinTheta
+ (1 - cosTheta
) * vector
.x
* vector
.y
;
458 temp
._22
= 1 + (1 - cosTheta
) * (vector
.y
* vector
.y
- 1);
459 temp
._23
= -vector
.x
* sinTheta
+ (1 - cosTheta
) * vector
.y
* vector
.z
;
461 temp
._31
= -vector
.y
* sinTheta
+ (1 - cosTheta
) * vector
.x
* vector
.z
;
462 temp
._32
= vector
.x
* sinTheta
+ (1 - cosTheta
) * vector
.y
* vector
.z
;
463 temp
._33
= 1 + (1 - cosTheta
) * (vector
.z
* vector
.z
- 1);
470 aMatrix
= temp
* aMatrix
;
474 ProcessPerspective(gfx3DMatrix
& aMatrix
,
475 const nsCSSValue::Array
* aData
,
476 nsStyleContext
*aContext
,
477 nsPresContext
*aPresContext
,
478 bool &aCanStoreInRuleTree
)
480 NS_PRECONDITION(aData
->Count() == 2, "Invalid array!");
482 float depth
= ProcessTranslatePart(aData
->Item(1), aContext
,
483 aPresContext
, aCanStoreInRuleTree
,
485 aMatrix
.Perspective(depth
);
490 * SetToTransformFunction is essentially a giant switch statement that fans
491 * out to many smaller helper functions.
494 MatrixForTransformFunction(gfx3DMatrix
& aMatrix
,
495 const nsCSSValue::Array
* aData
,
496 nsStyleContext
* aContext
,
497 nsPresContext
* aPresContext
,
498 bool& aCanStoreInRuleTree
,
501 NS_PRECONDITION(aData
, "Why did you want to get data from a null array?");
502 // It's OK if aContext and aPresContext are null if the caller already
503 // knows that all length units have been converted to pixels (as
504 // StyleAnimationValue does).
507 /* Get the keyword for the transform. */
508 switch (TransformFunctionOf(aData
)) {
509 case eCSSKeyword_translatex
:
510 ProcessTranslateX(aMatrix
, aData
, aContext
, aPresContext
,
511 aCanStoreInRuleTree
, aBounds
);
513 case eCSSKeyword_translatey
:
514 ProcessTranslateY(aMatrix
, aData
, aContext
, aPresContext
,
515 aCanStoreInRuleTree
, aBounds
);
517 case eCSSKeyword_translatez
:
518 ProcessTranslateZ(aMatrix
, aData
, aContext
, aPresContext
,
519 aCanStoreInRuleTree
);
521 case eCSSKeyword_translate
:
522 ProcessTranslate(aMatrix
, aData
, aContext
, aPresContext
,
523 aCanStoreInRuleTree
, aBounds
);
525 case eCSSKeyword_translate3d
:
526 ProcessTranslate3D(aMatrix
, aData
, aContext
, aPresContext
,
527 aCanStoreInRuleTree
, aBounds
);
529 case eCSSKeyword_scalex
:
530 ProcessScaleX(aMatrix
, aData
);
532 case eCSSKeyword_scaley
:
533 ProcessScaleY(aMatrix
, aData
);
535 case eCSSKeyword_scalez
:
536 ProcessScaleZ(aMatrix
, aData
);
538 case eCSSKeyword_scale
:
539 ProcessScale(aMatrix
, aData
);
541 case eCSSKeyword_scale3d
:
542 ProcessScale3D(aMatrix
, aData
);
544 case eCSSKeyword_skewx
:
545 ProcessSkewX(aMatrix
, aData
);
547 case eCSSKeyword_skewy
:
548 ProcessSkewY(aMatrix
, aData
);
550 case eCSSKeyword_skew
:
551 ProcessSkew(aMatrix
, aData
);
553 case eCSSKeyword_rotatex
:
554 ProcessRotateX(aMatrix
, aData
);
556 case eCSSKeyword_rotatey
:
557 ProcessRotateY(aMatrix
, aData
);
559 case eCSSKeyword_rotatez
:
560 case eCSSKeyword_rotate
:
561 ProcessRotateZ(aMatrix
, aData
);
563 case eCSSKeyword_rotate3d
:
564 ProcessRotate3D(aMatrix
, aData
);
566 case eCSSKeyword_matrix
:
567 ProcessMatrix(aMatrix
, aData
, aContext
, aPresContext
,
568 aCanStoreInRuleTree
, aBounds
);
570 case eCSSKeyword_matrix3d
:
571 ProcessMatrix3D(aMatrix
, aData
, aContext
, aPresContext
,
572 aCanStoreInRuleTree
, aBounds
);
574 case eCSSKeyword_interpolatematrix
:
575 ProcessInterpolateMatrix(aMatrix
, aData
, aContext
, aPresContext
,
576 aCanStoreInRuleTree
, aBounds
);
578 case eCSSKeyword_perspective
:
579 ProcessPerspective(aMatrix
, aData
, aContext
, aPresContext
,
580 aCanStoreInRuleTree
);
583 NS_NOTREACHED("Unknown transform function!");
588 * Return the transform function, as an nsCSSKeyword, for the given
589 * nsCSSValue::Array from a transform list.
592 TransformFunctionOf(const nsCSSValue::Array
* aData
)
594 MOZ_ASSERT(aData
->Item(0).GetUnit() == eCSSUnit_Enumerated
);
595 return aData
->Item(0).GetKeywordValue();
599 ReadTransforms(const nsCSSValueList
* aList
,
600 nsStyleContext
* aContext
,
601 nsPresContext
* aPresContext
,
602 bool &aCanStoreInRuleTree
,
604 float aAppUnitsPerMatrixUnit
)
608 for (const nsCSSValueList
* curr
= aList
; curr
!= nullptr; curr
= curr
->mNext
) {
609 const nsCSSValue
&currElem
= curr
->mValue
;
610 NS_ASSERTION(currElem
.GetUnit() == eCSSUnit_Function
,
611 "Stream should consist solely of functions!");
612 NS_ASSERTION(currElem
.GetArrayValue()->Count() >= 1,
613 "Incoming function is too short!");
615 /* Read in a single transform matrix. */
616 MatrixForTransformFunction(result
, currElem
.GetArrayValue(), aContext
,
617 aPresContext
, aCanStoreInRuleTree
, aBounds
);
620 float scale
= float(nsPresContext::AppUnitsPerCSSPixel()) / aAppUnitsPerMatrixUnit
;
621 result
.Scale(1/scale
, 1/scale
, 1/scale
);
622 result
.ScalePost(scale
, scale
, scale
);
627 } // namespace nsStyleTransformMatrix