Bumping manifests a=b2g-bump
[gecko.git] / layout / style / nsStyleTransformMatrix.cpp
blobfc1d03d8929b856e10e4025a1fef9e6567933603
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/. */
6 /*
7 * A class used for intermediate representations of the -moz-transform property.
8 */
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)
35 return 0.0f;
36 else
37 return aVal;
40 float
41 ProcessTranslatePart(const nsCSSValue& aValue,
42 nsStyleContext* aContext,
43 nsPresContext* aPresContext,
44 bool& aCanStoreInRuleTree,
45 nscoord aSize)
47 nscoord offset = 0;
48 float percent = 0.0f;
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,
67 aCanStoreInRuleTree);
68 percent = result.mPercent;
69 offset = result.mLength;
70 } else {
71 offset = nsRuleNode::CalcLength(aValue, aContext, aPresContext,
72 aCanStoreInRuleTree);
75 return (percent * NSAppUnitsToFloatPixels(aSize, nsPresContext::AppUnitsPerCSSPixel())) +
76 NSAppUnitsToFloatPixels(offset, nsPresContext::AppUnitsPerCSSPixel());
79 /**
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. */
86 static void
87 ProcessMatrix(gfx3DMatrix& aMatrix,
88 const nsCSSValue::Array* aData,
89 nsStyleContext* aContext,
90 nsPresContext* aPresContext,
91 bool& aCanStoreInRuleTree,
92 nsRect& aBounds)
94 NS_PRECONDITION(aData->Count() == 7, "Invalid array!");
96 gfxMatrix result;
98 /* Take the first four elements out of the array as floats and store
99 * them.
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,
111 aBounds.Width());
112 result._32 = ProcessTranslatePart(aData->Item(6),
113 aContext, aPresContext, aCanStoreInRuleTree,
114 aBounds.Height());
116 aMatrix.PreMultiply(result);
119 static void
120 ProcessMatrix3D(gfx3DMatrix& aMatrix,
121 const nsCSSValue::Array* aData,
122 nsStyleContext* aContext,
123 nsPresContext* aPresContext,
124 bool& aCanStoreInRuleTree,
125 nsRect& aBounds)
127 NS_PRECONDITION(aData->Count() == 17, "Invalid array!");
129 gfx3DMatrix temp;
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,
147 aBounds.Width());
148 temp._42 = ProcessTranslatePart(aData->Item(14),
149 aContext, aPresContext, aCanStoreInRuleTree,
150 aBounds.Height());
151 temp._43 = ProcessTranslatePart(aData->Item(15),
152 aContext, aPresContext, aCanStoreInRuleTree,
153 aBounds.Height());
155 aMatrix.PreMultiply(temp);
158 /* Helper function to process two matrices that we need to interpolate between */
159 void
160 ProcessInterpolateMatrix(gfx3DMatrix& aMatrix,
161 const nsCSSValue::Array* aData,
162 nsStyleContext* aContext,
163 nsPresContext* aPresContext,
164 bool& aCanStoreInRuleTree,
165 nsRect& aBounds)
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,
173 aCanStoreInRuleTree,
174 aBounds, nsPresContext::AppUnitsPerCSSPixel());
176 if (aData->Item(2).GetUnit() == eCSSUnit_List) {
177 matrix2 = ReadTransforms(aData->Item(2).GetListValue(),
178 aContext, aPresContext,
179 aCanStoreInRuleTree,
180 aBounds, nsPresContext::AppUnitsPerCSSPixel());
182 double progress = aData->Item(3).GetPercentValue();
184 aMatrix =
185 StyleAnimationValue::InterpolateTransformMatrix(matrix1, matrix2, progress)
186 * aMatrix;
189 /* Helper function to process a translatex function. */
190 static void
191 ProcessTranslateX(gfx3DMatrix& aMatrix,
192 const nsCSSValue::Array* aData,
193 nsStyleContext* aContext,
194 nsPresContext* aPresContext,
195 bool& aCanStoreInRuleTree,
196 nsRect& aBounds)
198 NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
200 Point3D temp;
202 temp.x = ProcessTranslatePart(aData->Item(1),
203 aContext, aPresContext, aCanStoreInRuleTree,
204 aBounds.Width());
205 aMatrix.Translate(temp);
208 /* Helper function to process a translatey function. */
209 static void
210 ProcessTranslateY(gfx3DMatrix& aMatrix,
211 const nsCSSValue::Array* aData,
212 nsStyleContext* aContext,
213 nsPresContext* aPresContext,
214 bool& aCanStoreInRuleTree,
215 nsRect& aBounds)
217 NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
219 Point3D temp;
221 temp.y = ProcessTranslatePart(aData->Item(1),
222 aContext, aPresContext, aCanStoreInRuleTree,
223 aBounds.Height());
224 aMatrix.Translate(temp);
227 static void
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!");
236 Point3D temp;
238 temp.z = ProcessTranslatePart(aData->Item(1), aContext,
239 aPresContext, aCanStoreInRuleTree, 0);
240 aMatrix.Translate(temp);
243 /* Helper function to process a translate function. */
244 static void
245 ProcessTranslate(gfx3DMatrix& aMatrix,
246 const nsCSSValue::Array* aData,
247 nsStyleContext* aContext,
248 nsPresContext* aPresContext,
249 bool& aCanStoreInRuleTree,
250 nsRect& aBounds)
252 NS_PRECONDITION(aData->Count() == 2 || aData->Count() == 3, "Invalid array!");
254 Point3D temp;
256 temp.x = ProcessTranslatePart(aData->Item(1),
257 aContext, aPresContext, aCanStoreInRuleTree,
258 aBounds.Width());
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,
264 aBounds.Height());
266 aMatrix.Translate(temp);
269 static void
270 ProcessTranslate3D(gfx3DMatrix& aMatrix,
271 const nsCSSValue::Array* aData,
272 nsStyleContext* aContext,
273 nsPresContext* aPresContext,
274 bool& aCanStoreInRuleTree,
275 nsRect& aBounds)
277 NS_PRECONDITION(aData->Count() == 4, "Invalid array!");
279 Point3D temp;
281 temp.x = ProcessTranslatePart(aData->Item(1),
282 aContext, aPresContext, aCanStoreInRuleTree,
283 aBounds.Width());
285 temp.y = ProcessTranslatePart(aData->Item(2),
286 aContext, aPresContext, aCanStoreInRuleTree,
287 aBounds.Height());
289 temp.z = ProcessTranslatePart(aData->Item(3),
290 aContext, aPresContext, aCanStoreInRuleTree,
293 aMatrix.Translate(temp);
296 /* Helper function to set up a scale matrix. */
297 static void
298 ProcessScaleHelper(gfx3DMatrix& aMatrix,
299 float aXScale,
300 float aYScale,
301 float aZScale)
303 aMatrix.Scale(aXScale, aYScale, aZScale);
306 /* Process a scalex function. */
307 static void
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. */
315 static void
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);
322 static void
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());
329 static void
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. */
340 static void
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 :
349 aData->Item(2));
351 ProcessScaleHelper(aMatrix,
352 scaleX.GetFloatValue(),
353 scaleY.GetFloatValue(),
354 1.0f);
357 /* Helper function that, given a set of angles, constructs the appropriate
358 * skew matrix.
360 static void
361 ProcessSkewHelper(gfx3DMatrix& aMatrix, double aXAngle, double aYAngle)
363 aMatrix.SkewXY(aXAngle, aYAngle);
366 /* Function that converts a skewx transform into a matrix. */
367 static void
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. */
375 static void
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. */
383 static void
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. */
396 static void
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);
404 static void
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);
412 static void
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);
420 static void
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 |
429 * | 0 0 0 1 |
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.
435 * See bug 704468.
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()) {
446 return;
448 vector.Normalize();
450 gfx3DMatrix temp;
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;
456 temp._14 = 0.0f;
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;
460 temp._24 = 0.0f;
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);
464 temp._34 = 0.0f;
465 temp._41 = 0.0f;
466 temp._42 = 0.0f;
467 temp._43 = 0.0f;
468 temp._44 = 1.0f;
470 aMatrix = temp * aMatrix;
473 static void
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.
493 static void
494 MatrixForTransformFunction(gfx3DMatrix& aMatrix,
495 const nsCSSValue::Array * aData,
496 nsStyleContext* aContext,
497 nsPresContext* aPresContext,
498 bool& aCanStoreInRuleTree,
499 nsRect& aBounds)
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);
512 break;
513 case eCSSKeyword_translatey:
514 ProcessTranslateY(aMatrix, aData, aContext, aPresContext,
515 aCanStoreInRuleTree, aBounds);
516 break;
517 case eCSSKeyword_translatez:
518 ProcessTranslateZ(aMatrix, aData, aContext, aPresContext,
519 aCanStoreInRuleTree);
520 break;
521 case eCSSKeyword_translate:
522 ProcessTranslate(aMatrix, aData, aContext, aPresContext,
523 aCanStoreInRuleTree, aBounds);
524 break;
525 case eCSSKeyword_translate3d:
526 ProcessTranslate3D(aMatrix, aData, aContext, aPresContext,
527 aCanStoreInRuleTree, aBounds);
528 break;
529 case eCSSKeyword_scalex:
530 ProcessScaleX(aMatrix, aData);
531 break;
532 case eCSSKeyword_scaley:
533 ProcessScaleY(aMatrix, aData);
534 break;
535 case eCSSKeyword_scalez:
536 ProcessScaleZ(aMatrix, aData);
537 break;
538 case eCSSKeyword_scale:
539 ProcessScale(aMatrix, aData);
540 break;
541 case eCSSKeyword_scale3d:
542 ProcessScale3D(aMatrix, aData);
543 break;
544 case eCSSKeyword_skewx:
545 ProcessSkewX(aMatrix, aData);
546 break;
547 case eCSSKeyword_skewy:
548 ProcessSkewY(aMatrix, aData);
549 break;
550 case eCSSKeyword_skew:
551 ProcessSkew(aMatrix, aData);
552 break;
553 case eCSSKeyword_rotatex:
554 ProcessRotateX(aMatrix, aData);
555 break;
556 case eCSSKeyword_rotatey:
557 ProcessRotateY(aMatrix, aData);
558 break;
559 case eCSSKeyword_rotatez:
560 case eCSSKeyword_rotate:
561 ProcessRotateZ(aMatrix, aData);
562 break;
563 case eCSSKeyword_rotate3d:
564 ProcessRotate3D(aMatrix, aData);
565 break;
566 case eCSSKeyword_matrix:
567 ProcessMatrix(aMatrix, aData, aContext, aPresContext,
568 aCanStoreInRuleTree, aBounds);
569 break;
570 case eCSSKeyword_matrix3d:
571 ProcessMatrix3D(aMatrix, aData, aContext, aPresContext,
572 aCanStoreInRuleTree, aBounds);
573 break;
574 case eCSSKeyword_interpolatematrix:
575 ProcessInterpolateMatrix(aMatrix, aData, aContext, aPresContext,
576 aCanStoreInRuleTree, aBounds);
577 break;
578 case eCSSKeyword_perspective:
579 ProcessPerspective(aMatrix, aData, aContext, aPresContext,
580 aCanStoreInRuleTree);
581 break;
582 default:
583 NS_NOTREACHED("Unknown transform function!");
588 * Return the transform function, as an nsCSSKeyword, for the given
589 * nsCSSValue::Array from a transform list.
591 nsCSSKeyword
592 TransformFunctionOf(const nsCSSValue::Array* aData)
594 MOZ_ASSERT(aData->Item(0).GetUnit() == eCSSUnit_Enumerated);
595 return aData->Item(0).GetKeywordValue();
598 gfx3DMatrix
599 ReadTransforms(const nsCSSValueList* aList,
600 nsStyleContext* aContext,
601 nsPresContext* aPresContext,
602 bool &aCanStoreInRuleTree,
603 nsRect& aBounds,
604 float aAppUnitsPerMatrixUnit)
606 gfx3DMatrix result;
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);
624 return result;
627 } // namespace nsStyleTransformMatrix