Line data Source code
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/. */
5 :
6 : /*
7 : * A class used for intermediate representations of the -moz-transform property.
8 : */
9 :
10 : #include "nsStyleTransformMatrix.h"
11 : #include "nsCSSValue.h"
12 : #include "nsLayoutUtils.h"
13 : #include "nsPresContext.h"
14 : #include "nsRuleNode.h"
15 : #include "nsSVGUtils.h"
16 : #include "nsCSSKeywords.h"
17 : #include "mozilla/ServoBindings.h"
18 : #include "mozilla/StyleAnimationValue.h"
19 : #include "gfxMatrix.h"
20 : #include "gfxQuaternion.h"
21 :
22 : using namespace mozilla;
23 : using namespace mozilla::gfx;
24 :
25 : namespace nsStyleTransformMatrix {
26 :
27 : /* Note on floating point precision: The transform matrix is an array
28 : * of single precision 'float's, and so are most of the input values
29 : * we get from the style system, but intermediate calculations
30 : * involving angles need to be done in 'double'.
31 : */
32 :
33 :
34 : // Define UNIFIED_CONTINUATIONS here and in nsDisplayList.cpp
35 : // to have the transform property try
36 : // to transform content with continuations as one unified block instead of
37 : // several smaller ones. This is currently disabled because it doesn't work
38 : // correctly, since when the frames are initially being reflowed, their
39 : // continuations all compute their bounding rects independently of each other
40 : // and consequently get the wrong value.
41 : //#define UNIFIED_CONTINUATIONS
42 :
43 : void
44 592 : TransformReferenceBox::EnsureDimensionsAreCached()
45 : {
46 592 : if (mIsCached) {
47 940 : return;
48 : }
49 :
50 194 : MOZ_ASSERT(mFrame);
51 :
52 194 : mIsCached = true;
53 :
54 194 : if (mFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
55 144 : if (!nsLayoutUtils::SVGTransformBoxEnabled()) {
56 0 : mX = -mFrame->GetPosition().x;
57 0 : mY = -mFrame->GetPosition().y;
58 0 : Size contextSize = nsSVGUtils::GetContextSize(mFrame);
59 0 : mWidth = nsPresContext::CSSPixelsToAppUnits(contextSize.width);
60 0 : mHeight = nsPresContext::CSSPixelsToAppUnits(contextSize.height);
61 : } else
62 144 : if (mFrame->StyleDisplay()->mTransformBox == StyleGeometryBox::FillBox) {
63 : // Percentages in transforms resolve against the SVG bbox, and the
64 : // transform is relative to the top-left of the SVG bbox.
65 : nsRect bboxInAppUnits =
66 0 : nsLayoutUtils::ComputeGeometryBox(const_cast<nsIFrame*>(mFrame),
67 0 : StyleGeometryBox::FillBox);
68 : // The mRect of an SVG nsIFrame is its user space bounds *including*
69 : // stroke and markers, whereas bboxInAppUnits is its user space bounds
70 : // including fill only. We need to note the offset of the reference box
71 : // from the frame's mRect in mX/mY.
72 0 : mX = bboxInAppUnits.x - mFrame->GetPosition().x;
73 0 : mY = bboxInAppUnits.y - mFrame->GetPosition().y;
74 0 : mWidth = bboxInAppUnits.width;
75 0 : mHeight = bboxInAppUnits.height;
76 : } else {
77 : // The value 'border-box' is treated as 'view-box' for SVG content.
78 144 : MOZ_ASSERT(mFrame->StyleDisplay()->mTransformBox ==
79 : StyleGeometryBox::ViewBox ||
80 : mFrame->StyleDisplay()->mTransformBox ==
81 : StyleGeometryBox::BorderBox,
82 : "Unexpected value for 'transform-box'");
83 : // Percentages in transforms resolve against the width/height of the
84 : // nearest viewport (or its viewBox if one is applied), and the
85 : // transform is relative to {0,0} in current user space.
86 144 : mX = -mFrame->GetPosition().x;
87 144 : mY = -mFrame->GetPosition().y;
88 144 : Size contextSize = nsSVGUtils::GetContextSize(mFrame);
89 144 : mWidth = nsPresContext::CSSPixelsToAppUnits(contextSize.width);
90 144 : mHeight = nsPresContext::CSSPixelsToAppUnits(contextSize.height);
91 : }
92 144 : return;
93 : }
94 :
95 : // If UNIFIED_CONTINUATIONS is not defined, this is simply the frame's
96 : // bounding rectangle, translated to the origin. Otherwise, it is the
97 : // smallest rectangle containing a frame and all of its continuations. For
98 : // example, if there is a <span> element with several continuations split
99 : // over several lines, this function will return the rectangle containing all
100 : // of those continuations.
101 :
102 100 : nsRect rect;
103 :
104 : #ifndef UNIFIED_CONTINUATIONS
105 50 : rect = mFrame->GetRect();
106 : #else
107 : // Iterate the continuation list, unioning together the bounding rects:
108 : for (const nsIFrame *currFrame = mFrame->FirstContinuation();
109 : currFrame != nullptr;
110 : currFrame = currFrame->GetNextContinuation())
111 : {
112 : // Get the frame rect in local coordinates, then translate back to the
113 : // original coordinates:
114 : rect.UnionRect(result, nsRect(currFrame->GetOffsetTo(mFrame),
115 : currFrame->GetSize()));
116 : }
117 : #endif
118 :
119 50 : mX = 0;
120 50 : mY = 0;
121 50 : mWidth = rect.Width();
122 50 : mHeight = rect.Height();
123 : }
124 :
125 : void
126 0 : TransformReferenceBox::Init(const nsSize& aDimensions)
127 : {
128 0 : MOZ_ASSERT(!mFrame && !mIsCached);
129 :
130 0 : mX = 0;
131 0 : mY = 0;
132 0 : mWidth = aDimensions.width;
133 0 : mHeight = aDimensions.height;
134 0 : mIsCached = true;
135 0 : }
136 :
137 : float
138 36 : ProcessTranslatePart(const nsCSSValue& aValue,
139 : nsStyleContext* aContext,
140 : nsPresContext* aPresContext,
141 : RuleNodeCacheConditions& aConditions,
142 : TransformReferenceBox* aRefBox,
143 : TransformReferenceBox::DimensionGetter aDimensionGetter)
144 : {
145 36 : nscoord offset = 0;
146 36 : float percent = 0.0f;
147 :
148 36 : if (aValue.GetUnit() == eCSSUnit_Percent) {
149 0 : percent = aValue.GetPercentValue();
150 36 : } else if (aValue.GetUnit() == eCSSUnit_Pixel ||
151 0 : aValue.GetUnit() == eCSSUnit_Number) {
152 : // Handle this here (even though nsRuleNode::CalcLength handles it
153 : // fine) so that callers are allowed to pass a null style context
154 : // and pres context to SetToTransformFunction if they know (as
155 : // StyleAnimationValue does) that all lengths within the transform
156 : // function have already been computed to pixels and percents.
157 : //
158 : // Raw numbers are treated as being pixels.
159 : //
160 : // Don't convert to aValue to AppUnits here to avoid precision issues.
161 36 : return aValue.GetFloatValue();
162 0 : } else if (aValue.IsCalcUnit()) {
163 : nsRuleNode::ComputedCalc result =
164 : nsRuleNode::SpecifiedCalcToComputedCalc(aValue, aContext, aPresContext,
165 0 : aConditions);
166 0 : percent = result.mPercent;
167 0 : offset = result.mLength;
168 : } else {
169 : offset = nsRuleNode::CalcLength(aValue, aContext, aPresContext,
170 0 : aConditions);
171 : }
172 :
173 0 : float translation = NSAppUnitsToFloatPixels(offset,
174 0 : nsPresContext::AppUnitsPerCSSPixel());
175 : // We want to avoid calling aDimensionGetter if there's no percentage to be
176 : // resolved (for performance reasons - see TransformReferenceBox).
177 0 : if (percent != 0.0f && aRefBox && !aRefBox->IsEmpty()) {
178 0 : translation += percent *
179 0 : NSAppUnitsToFloatPixels((aRefBox->*aDimensionGetter)(),
180 0 : nsPresContext::AppUnitsPerCSSPixel());
181 : }
182 0 : return translation;
183 : }
184 :
185 : /**
186 : * Helper functions to process all the transformation function types.
187 : *
188 : * These take a matrix parameter to accumulate the current matrix.
189 : */
190 :
191 : /* Helper function to process a matrix entry. */
192 : static void
193 0 : ProcessMatrix(Matrix4x4& aMatrix,
194 : const nsCSSValue::Array* aData,
195 : nsStyleContext* aContext,
196 : nsPresContext* aPresContext,
197 : RuleNodeCacheConditions& aConditions,
198 : TransformReferenceBox& aRefBox)
199 : {
200 0 : NS_PRECONDITION(aData->Count() == 7, "Invalid array!");
201 :
202 0 : gfxMatrix result;
203 :
204 : /* Take the first four elements out of the array as floats and store
205 : * them.
206 : */
207 0 : result._11 = aData->Item(1).GetFloatValue();
208 0 : result._12 = aData->Item(2).GetFloatValue();
209 0 : result._21 = aData->Item(3).GetFloatValue();
210 0 : result._22 = aData->Item(4).GetFloatValue();
211 :
212 : /* The last two elements have their length parts stored in aDelta
213 : * and their percent parts stored in aX[0] and aY[1].
214 : */
215 0 : result._31 = ProcessTranslatePart(aData->Item(5),
216 : aContext, aPresContext, aConditions,
217 : &aRefBox, &TransformReferenceBox::Width);
218 0 : result._32 = ProcessTranslatePart(aData->Item(6),
219 : aContext, aPresContext, aConditions,
220 : &aRefBox, &TransformReferenceBox::Height);
221 :
222 0 : aMatrix = result * aMatrix;
223 0 : }
224 :
225 : static void
226 0 : ProcessMatrix3D(Matrix4x4& aMatrix,
227 : const nsCSSValue::Array* aData,
228 : nsStyleContext* aContext,
229 : nsPresContext* aPresContext,
230 : RuleNodeCacheConditions& aConditions,
231 : TransformReferenceBox& aRefBox)
232 : {
233 0 : NS_PRECONDITION(aData->Count() == 17, "Invalid array!");
234 :
235 0 : Matrix4x4 temp;
236 :
237 0 : temp._11 = aData->Item(1).GetFloatValue();
238 0 : temp._12 = aData->Item(2).GetFloatValue();
239 0 : temp._13 = aData->Item(3).GetFloatValue();
240 0 : temp._14 = aData->Item(4).GetFloatValue();
241 0 : temp._21 = aData->Item(5).GetFloatValue();
242 0 : temp._22 = aData->Item(6).GetFloatValue();
243 0 : temp._23 = aData->Item(7).GetFloatValue();
244 0 : temp._24 = aData->Item(8).GetFloatValue();
245 0 : temp._31 = aData->Item(9).GetFloatValue();
246 0 : temp._32 = aData->Item(10).GetFloatValue();
247 0 : temp._33 = aData->Item(11).GetFloatValue();
248 0 : temp._34 = aData->Item(12).GetFloatValue();
249 0 : temp._44 = aData->Item(16).GetFloatValue();
250 :
251 0 : temp._41 = ProcessTranslatePart(aData->Item(13),
252 : aContext, aPresContext, aConditions,
253 : &aRefBox, &TransformReferenceBox::Width);
254 0 : temp._42 = ProcessTranslatePart(aData->Item(14),
255 : aContext, aPresContext, aConditions,
256 : &aRefBox, &TransformReferenceBox::Height);
257 0 : temp._43 = ProcessTranslatePart(aData->Item(15),
258 : aContext, aPresContext, aConditions,
259 : nullptr);
260 :
261 0 : aMatrix = temp * aMatrix;
262 0 : }
263 :
264 : // For accumulation for transform functions, |aOne| corresponds to |aB| and
265 : // |aTwo| corresponds to |aA| for StyleAnimationValue::Accumulate().
266 : class Accumulate {
267 : public:
268 : template<typename T>
269 0 : static T operate(const T& aOne, const T& aTwo, double aCoeff)
270 : {
271 0 : return aOne + aTwo * aCoeff;
272 : }
273 :
274 0 : static Point4D operateForPerspective(const Point4D& aOne,
275 : const Point4D& aTwo,
276 : double aCoeff)
277 : {
278 0 : return (aOne - Point4D(0, 0, 0, 1)) +
279 0 : (aTwo - Point4D(0, 0, 0, 1)) * aCoeff +
280 0 : Point4D(0, 0, 0, 1);
281 : }
282 0 : static Point3D operateForScale(const Point3D& aOne,
283 : const Point3D& aTwo,
284 : double aCoeff)
285 : {
286 : // For scale, the identify element is 1, see AddTransformScale in
287 : // StyleAnimationValue.cpp.
288 0 : return (aOne - Point3D(1, 1, 1)) +
289 0 : (aTwo - Point3D(1, 1, 1)) * aCoeff +
290 0 : Point3D(1, 1, 1);
291 : }
292 :
293 0 : static Matrix4x4 operateForRotate(const gfxQuaternion& aOne,
294 : const gfxQuaternion& aTwo,
295 : double aCoeff)
296 : {
297 0 : if (aCoeff == 0.0) {
298 0 : return aOne.ToMatrix();
299 : }
300 :
301 0 : double theta = acos(mozilla::clamped(aTwo.w, -1.0, 1.0));
302 0 : double scale = (theta != 0.0) ? 1.0 / sin(theta) : 0.0;
303 0 : theta *= aCoeff;
304 0 : scale *= sin(theta);
305 :
306 0 : gfxQuaternion result = gfxQuaternion(scale * aTwo.x,
307 0 : scale * aTwo.y,
308 0 : scale * aTwo.z,
309 0 : cos(theta)) * aOne;
310 0 : return result.ToMatrix();
311 : }
312 :
313 0 : static Matrix4x4 operateByServo(const Matrix4x4& aMatrix1,
314 : const Matrix4x4& aMatrix2,
315 : double aCount)
316 : {
317 0 : Matrix4x4 result;
318 0 : Servo_MatrixTransform_Operate(MatrixTransformOperator::Accumulate,
319 : &aMatrix1.components,
320 : &aMatrix2.components,
321 : aCount,
322 0 : &result.components);
323 0 : return result;
324 : }
325 : };
326 :
327 : class Interpolate {
328 : public:
329 : template<typename T>
330 0 : static T operate(const T& aOne, const T& aTwo, double aCoeff)
331 : {
332 0 : return aOne + (aTwo - aOne) * aCoeff;
333 : }
334 :
335 0 : static Point4D operateForPerspective(const Point4D& aOne,
336 : const Point4D& aTwo,
337 : double aCoeff)
338 : {
339 0 : return aOne + (aTwo - aOne) * aCoeff;
340 : }
341 :
342 0 : static Point3D operateForScale(const Point3D& aOne,
343 : const Point3D& aTwo,
344 : double aCoeff)
345 : {
346 0 : return aOne + (aTwo - aOne) * aCoeff;
347 : }
348 :
349 0 : static Matrix4x4 operateForRotate(const gfxQuaternion& aOne,
350 : const gfxQuaternion& aTwo,
351 : double aCoeff)
352 : {
353 0 : return aOne.Slerp(aTwo, aCoeff).ToMatrix();
354 : }
355 :
356 0 : static Matrix4x4 operateByServo(const Matrix4x4& aMatrix1,
357 : const Matrix4x4& aMatrix2,
358 : double aProgress)
359 : {
360 0 : Matrix4x4 result;
361 0 : Servo_MatrixTransform_Operate(MatrixTransformOperator::Interpolate,
362 : &aMatrix1.components,
363 : &aMatrix2.components,
364 : aProgress,
365 0 : &result.components);
366 0 : return result;
367 : }
368 : };
369 :
370 : /**
371 : * Calculate 2 matrices by decomposing them with Operator.
372 : *
373 : * @param aMatrix1 First matrix, using CSS pixel units.
374 : * @param aMatrix2 Second matrix, using CSS pixel units.
375 : * @param aProgress Coefficient for the Operator.
376 : */
377 : template <typename Operator>
378 : static Matrix4x4
379 0 : OperateTransformMatrix(const Matrix4x4 &aMatrix1,
380 : const Matrix4x4 &aMatrix2,
381 : double aProgress)
382 : {
383 : // Decompose both matrices
384 :
385 : // TODO: What do we do if one of these returns false (singular matrix)
386 0 : Point3D scale1(1, 1, 1), translate1;
387 0 : Point4D perspective1(0, 0, 0, 1);
388 0 : gfxQuaternion rotate1;
389 0 : nsStyleTransformMatrix::ShearArray shear1{0.0f, 0.0f, 0.0f};
390 :
391 0 : Point3D scale2(1, 1, 1), translate2;
392 0 : Point4D perspective2(0, 0, 0, 1);
393 0 : gfxQuaternion rotate2;
394 0 : nsStyleTransformMatrix::ShearArray shear2{0.0f, 0.0f, 0.0f};
395 :
396 0 : Matrix matrix2d1, matrix2d2;
397 0 : if (aMatrix1.Is2D(&matrix2d1) && aMatrix2.Is2D(&matrix2d2)) {
398 0 : Decompose2DMatrix(matrix2d1, scale1, shear1, rotate1, translate1);
399 0 : Decompose2DMatrix(matrix2d2, scale2, shear2, rotate2, translate2);
400 : } else {
401 0 : Decompose3DMatrix(aMatrix1, scale1, shear1,
402 : rotate1, translate1, perspective1);
403 0 : Decompose3DMatrix(aMatrix2, scale2, shear2,
404 : rotate2, translate2, perspective2);
405 : }
406 :
407 0 : Matrix4x4 result;
408 :
409 : // Operate each of the pieces in response to |Operator|.
410 : Point4D perspective =
411 0 : Operator::operateForPerspective(perspective1, perspective2, aProgress);
412 0 : result.SetTransposedVector(3, perspective);
413 :
414 : Point3D translate =
415 0 : Operator::operate(translate1, translate2, aProgress);
416 0 : result.PreTranslate(translate.x, translate.y, translate.z);
417 :
418 0 : Matrix4x4 rotate = Operator::operateForRotate(rotate1, rotate2, aProgress);
419 0 : if (!rotate.IsIdentity()) {
420 0 : result = rotate * result;
421 : }
422 :
423 : // TODO: Would it be better to operate these as angles?
424 : // How do we convert back to angles?
425 : float yzshear =
426 0 : Operator::operate(shear1[ShearType::YZSHEAR],
427 : shear2[ShearType::YZSHEAR],
428 0 : aProgress);
429 0 : if (yzshear != 0.0) {
430 0 : result.SkewYZ(yzshear);
431 : }
432 :
433 : float xzshear =
434 0 : Operator::operate(shear1[ShearType::XZSHEAR],
435 : shear2[ShearType::XZSHEAR],
436 0 : aProgress);
437 0 : if (xzshear != 0.0) {
438 0 : result.SkewXZ(xzshear);
439 : }
440 :
441 : float xyshear =
442 0 : Operator::operate(shear1[ShearType::XYSHEAR],
443 : shear2[ShearType::XYSHEAR],
444 0 : aProgress);
445 0 : if (xyshear != 0.0) {
446 0 : result.SkewXY(xyshear);
447 : }
448 :
449 : Point3D scale =
450 0 : Operator::operateForScale(scale1, scale2, aProgress);
451 0 : if (scale != Point3D(1.0, 1.0, 1.0)) {
452 0 : result.PreScale(scale.x, scale.y, scale.z);
453 : }
454 :
455 0 : return result;
456 : }
457 :
458 : template <typename Operator>
459 : static Matrix4x4
460 0 : OperateTransformMatrixByServo(const Matrix4x4 &aMatrix1,
461 : const Matrix4x4 &aMatrix2,
462 : double aProgress)
463 : {
464 0 : return Operator::operateByServo(aMatrix1, aMatrix2, aProgress);
465 : }
466 :
467 : template <typename Operator>
468 : static void
469 0 : ProcessMatrixOperator(Matrix4x4& aMatrix,
470 : const nsCSSValue::Array* aData,
471 : nsStyleContext* aContext,
472 : nsPresContext* aPresContext,
473 : RuleNodeCacheConditions& aConditions,
474 : TransformReferenceBox& aRefBox,
475 : bool* aContains3dTransform)
476 : {
477 0 : NS_PRECONDITION(aData->Count() == 4, "Invalid array!");
478 :
479 0 : auto readTransform = [&](const nsCSSValue& aValue) -> Matrix4x4 {
480 0 : const nsCSSValueList* list = nullptr;
481 0 : switch (aValue.GetUnit()) {
482 : case eCSSUnit_List:
483 : // For Gecko style backend.
484 0 : list = aValue.GetListValue();
485 0 : break;
486 : case eCSSUnit_SharedList:
487 : // For Servo style backend. The transform lists of interpolatematrix
488 : // are not created on the main thread (i.e. during parallel traversal),
489 : // and nsCSSValueList_heap is not thread safe. Therefore, we use
490 : // nsCSSValueSharedList as a workaround.
491 0 : list = aValue.GetSharedListValue()->mHead;
492 0 : break;
493 : default:
494 0 : list = nullptr;
495 : }
496 :
497 0 : Matrix4x4 matrix;
498 0 : if (!list) {
499 0 : return matrix;
500 : }
501 :
502 0 : float appUnitPerCSSPixel = nsPresContext::AppUnitsPerCSSPixel();
503 0 : matrix = nsStyleTransformMatrix::ReadTransforms(list,
504 0 : aContext,
505 0 : aPresContext,
506 0 : aConditions,
507 0 : aRefBox,
508 : appUnitPerCSSPixel,
509 0 : aContains3dTransform);
510 0 : return matrix;
511 0 : };
512 :
513 0 : Matrix4x4 matrix1 = readTransform(aData->Item(1));
514 0 : Matrix4x4 matrix2 = readTransform(aData->Item(2));
515 0 : double progress = aData->Item(3).GetPercentValue();
516 :
517 0 : if (aContext && aContext->IsServo()) {
518 0 : aMatrix =
519 : OperateTransformMatrixByServo<Operator>(matrix1, matrix2, progress)
520 : * aMatrix;
521 0 : return;
522 : }
523 :
524 0 : aMatrix =
525 : OperateTransformMatrix<Operator>(matrix1, matrix2, progress) * aMatrix;
526 : }
527 :
528 : /* Helper function to process two matrices that we need to interpolate between */
529 : void
530 0 : ProcessInterpolateMatrix(Matrix4x4& aMatrix,
531 : const nsCSSValue::Array* aData,
532 : nsStyleContext* aContext,
533 : nsPresContext* aPresContext,
534 : RuleNodeCacheConditions& aConditions,
535 : TransformReferenceBox& aRefBox,
536 : bool* aContains3dTransform)
537 : {
538 : ProcessMatrixOperator<Interpolate>(aMatrix, aData, aContext, aPresContext,
539 : aConditions, aRefBox,
540 0 : aContains3dTransform);
541 0 : }
542 :
543 : void
544 0 : ProcessAccumulateMatrix(Matrix4x4& aMatrix,
545 : const nsCSSValue::Array* aData,
546 : nsStyleContext* aContext,
547 : nsPresContext* aPresContext,
548 : RuleNodeCacheConditions& aConditions,
549 : TransformReferenceBox& aRefBox,
550 : bool* aContains3dTransform)
551 : {
552 : ProcessMatrixOperator<Accumulate>(aMatrix, aData, aContext, aPresContext,
553 : aConditions, aRefBox,
554 0 : aContains3dTransform);
555 0 : }
556 :
557 : /* Helper function to process a translatex function. */
558 : static void
559 12 : ProcessTranslateX(Matrix4x4& aMatrix,
560 : const nsCSSValue::Array* aData,
561 : nsStyleContext* aContext,
562 : nsPresContext* aPresContext,
563 : RuleNodeCacheConditions& aConditions,
564 : TransformReferenceBox& aRefBox)
565 : {
566 12 : NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
567 :
568 12 : Point3D temp;
569 :
570 12 : temp.x = ProcessTranslatePart(aData->Item(1),
571 : aContext, aPresContext, aConditions,
572 : &aRefBox, &TransformReferenceBox::Width);
573 12 : aMatrix.PreTranslate(temp);
574 12 : }
575 :
576 : /* Helper function to process a translatey function. */
577 : static void
578 0 : ProcessTranslateY(Matrix4x4& aMatrix,
579 : const nsCSSValue::Array* aData,
580 : nsStyleContext* aContext,
581 : nsPresContext* aPresContext,
582 : RuleNodeCacheConditions& aConditions,
583 : TransformReferenceBox& aRefBox)
584 : {
585 0 : NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
586 :
587 0 : Point3D temp;
588 :
589 0 : temp.y = ProcessTranslatePart(aData->Item(1),
590 : aContext, aPresContext, aConditions,
591 : &aRefBox, &TransformReferenceBox::Height);
592 0 : aMatrix.PreTranslate(temp);
593 0 : }
594 :
595 : static void
596 0 : ProcessTranslateZ(Matrix4x4& aMatrix,
597 : const nsCSSValue::Array* aData,
598 : nsStyleContext* aContext,
599 : nsPresContext* aPresContext,
600 : RuleNodeCacheConditions& aConditions)
601 : {
602 0 : NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
603 :
604 0 : Point3D temp;
605 :
606 0 : temp.z = ProcessTranslatePart(aData->Item(1), aContext,
607 : aPresContext, aConditions,
608 : nullptr);
609 0 : aMatrix.PreTranslate(temp);
610 0 : }
611 :
612 : /* Helper function to process a translate function. */
613 : static void
614 24 : ProcessTranslate(Matrix4x4& aMatrix,
615 : const nsCSSValue::Array* aData,
616 : nsStyleContext* aContext,
617 : nsPresContext* aPresContext,
618 : RuleNodeCacheConditions& aConditions,
619 : TransformReferenceBox& aRefBox)
620 : {
621 24 : NS_PRECONDITION(aData->Count() == 2 || aData->Count() == 3, "Invalid array!");
622 :
623 24 : Point3D temp;
624 :
625 24 : temp.x = ProcessTranslatePart(aData->Item(1),
626 : aContext, aPresContext, aConditions,
627 : &aRefBox, &TransformReferenceBox::Width);
628 :
629 : /* If we read in a Y component, set it appropriately */
630 24 : if (aData->Count() == 3) {
631 0 : temp.y = ProcessTranslatePart(aData->Item(2),
632 : aContext, aPresContext, aConditions,
633 : &aRefBox, &TransformReferenceBox::Height);
634 : }
635 24 : aMatrix.PreTranslate(temp);
636 24 : }
637 :
638 : static void
639 0 : ProcessTranslate3D(Matrix4x4& aMatrix,
640 : const nsCSSValue::Array* aData,
641 : nsStyleContext* aContext,
642 : nsPresContext* aPresContext,
643 : RuleNodeCacheConditions& aConditions,
644 : TransformReferenceBox& aRefBox)
645 : {
646 0 : NS_PRECONDITION(aData->Count() == 4, "Invalid array!");
647 :
648 0 : Point3D temp;
649 :
650 0 : temp.x = ProcessTranslatePart(aData->Item(1),
651 : aContext, aPresContext, aConditions,
652 : &aRefBox, &TransformReferenceBox::Width);
653 :
654 0 : temp.y = ProcessTranslatePart(aData->Item(2),
655 : aContext, aPresContext, aConditions,
656 : &aRefBox, &TransformReferenceBox::Height);
657 :
658 0 : temp.z = ProcessTranslatePart(aData->Item(3),
659 : aContext, aPresContext, aConditions,
660 : nullptr);
661 :
662 0 : aMatrix.PreTranslate(temp);
663 0 : }
664 :
665 : /* Helper function to set up a scale matrix. */
666 : static void
667 14 : ProcessScaleHelper(Matrix4x4& aMatrix,
668 : float aXScale,
669 : float aYScale,
670 : float aZScale)
671 : {
672 14 : aMatrix.PreScale(aXScale, aYScale, aZScale);
673 14 : }
674 :
675 : /* Process a scalex function. */
676 : static void
677 14 : ProcessScaleX(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
678 : {
679 14 : NS_PRECONDITION(aData->Count() == 2, "Bad array!");
680 14 : ProcessScaleHelper(aMatrix, aData->Item(1).GetFloatValue(), 1.0f, 1.0f);
681 14 : }
682 :
683 : /* Process a scaley function. */
684 : static void
685 0 : ProcessScaleY(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
686 : {
687 0 : NS_PRECONDITION(aData->Count() == 2, "Bad array!");
688 0 : ProcessScaleHelper(aMatrix, 1.0f, aData->Item(1).GetFloatValue(), 1.0f);
689 0 : }
690 :
691 : static void
692 0 : ProcessScaleZ(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
693 : {
694 0 : NS_PRECONDITION(aData->Count() == 2, "Bad array!");
695 0 : ProcessScaleHelper(aMatrix, 1.0f, 1.0f, aData->Item(1).GetFloatValue());
696 0 : }
697 :
698 : static void
699 0 : ProcessScale3D(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
700 : {
701 0 : NS_PRECONDITION(aData->Count() == 4, "Bad array!");
702 0 : ProcessScaleHelper(aMatrix,
703 0 : aData->Item(1).GetFloatValue(),
704 0 : aData->Item(2).GetFloatValue(),
705 0 : aData->Item(3).GetFloatValue());
706 0 : }
707 :
708 : /* Process a scale function. */
709 : static void
710 0 : ProcessScale(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
711 : {
712 0 : NS_PRECONDITION(aData->Count() == 2 || aData->Count() == 3, "Bad array!");
713 : /* We either have one element or two. If we have one, it's for both X and Y.
714 : * Otherwise it's one for each.
715 : */
716 0 : const nsCSSValue& scaleX = aData->Item(1);
717 0 : const nsCSSValue& scaleY = (aData->Count() == 2 ? scaleX :
718 0 : aData->Item(2));
719 :
720 0 : ProcessScaleHelper(aMatrix,
721 : scaleX.GetFloatValue(),
722 : scaleY.GetFloatValue(),
723 0 : 1.0f);
724 0 : }
725 :
726 : /* Helper function that, given a set of angles, constructs the appropriate
727 : * skew matrix.
728 : */
729 : static void
730 0 : ProcessSkewHelper(Matrix4x4& aMatrix, double aXAngle, double aYAngle)
731 : {
732 0 : aMatrix.SkewXY(aXAngle, aYAngle);
733 0 : }
734 :
735 : /* Function that converts a skewx transform into a matrix. */
736 : static void
737 0 : ProcessSkewX(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
738 : {
739 0 : NS_ASSERTION(aData->Count() == 2, "Bad array!");
740 0 : ProcessSkewHelper(aMatrix, aData->Item(1).GetAngleValueInRadians(), 0.0);
741 0 : }
742 :
743 : /* Function that converts a skewy transform into a matrix. */
744 : static void
745 0 : ProcessSkewY(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
746 : {
747 0 : NS_ASSERTION(aData->Count() == 2, "Bad array!");
748 0 : ProcessSkewHelper(aMatrix, 0.0, aData->Item(1).GetAngleValueInRadians());
749 0 : }
750 :
751 : /* Function that converts a skew transform into a matrix. */
752 : static void
753 0 : ProcessSkew(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
754 : {
755 0 : NS_ASSERTION(aData->Count() == 2 || aData->Count() == 3, "Bad array!");
756 :
757 0 : double xSkew = aData->Item(1).GetAngleValueInRadians();
758 0 : double ySkew = (aData->Count() == 2
759 0 : ? 0.0 : aData->Item(2).GetAngleValueInRadians());
760 :
761 0 : ProcessSkewHelper(aMatrix, xSkew, ySkew);
762 0 : }
763 :
764 : /* Function that converts a rotate transform into a matrix. */
765 : static void
766 0 : ProcessRotateZ(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
767 : {
768 0 : NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
769 0 : double theta = aData->Item(1).GetAngleValueInRadians();
770 0 : aMatrix.RotateZ(theta);
771 0 : }
772 :
773 : static void
774 0 : ProcessRotateX(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
775 : {
776 0 : NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
777 0 : double theta = aData->Item(1).GetAngleValueInRadians();
778 0 : aMatrix.RotateX(theta);
779 0 : }
780 :
781 : static void
782 0 : ProcessRotateY(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
783 : {
784 0 : NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
785 0 : double theta = aData->Item(1).GetAngleValueInRadians();
786 0 : aMatrix.RotateY(theta);
787 0 : }
788 :
789 : static void
790 0 : ProcessRotate3D(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
791 : {
792 0 : NS_PRECONDITION(aData->Count() == 5, "Invalid array!");
793 :
794 0 : double theta = aData->Item(4).GetAngleValueInRadians();
795 0 : float x = aData->Item(1).GetFloatValue();
796 0 : float y = aData->Item(2).GetFloatValue();
797 0 : float z = aData->Item(3).GetFloatValue();
798 :
799 0 : Matrix4x4 temp;
800 0 : temp.SetRotateAxisAngle(x, y, z, theta);
801 :
802 0 : aMatrix = temp * aMatrix;
803 0 : }
804 :
805 : static void
806 0 : ProcessPerspective(Matrix4x4& aMatrix,
807 : const nsCSSValue::Array* aData,
808 : nsStyleContext *aContext,
809 : nsPresContext *aPresContext,
810 : RuleNodeCacheConditions& aConditions)
811 : {
812 0 : NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
813 :
814 0 : float depth = ProcessTranslatePart(aData->Item(1), aContext,
815 0 : aPresContext, aConditions, nullptr);
816 0 : ApplyPerspectiveToMatrix(aMatrix, depth);
817 0 : }
818 :
819 :
820 : /**
821 : * SetToTransformFunction is essentially a giant switch statement that fans
822 : * out to many smaller helper functions.
823 : */
824 : static void
825 50 : MatrixForTransformFunction(Matrix4x4& aMatrix,
826 : const nsCSSValue::Array * aData,
827 : nsStyleContext* aContext,
828 : nsPresContext* aPresContext,
829 : RuleNodeCacheConditions& aConditions,
830 : TransformReferenceBox& aRefBox,
831 : bool* aContains3dTransform)
832 : {
833 50 : MOZ_ASSERT(aContains3dTransform);
834 50 : NS_PRECONDITION(aData, "Why did you want to get data from a null array?");
835 : // It's OK if aContext and aPresContext are null if the caller already
836 : // knows that all length units have been converted to pixels (as
837 : // StyleAnimationValue does).
838 :
839 :
840 : /* Get the keyword for the transform. */
841 50 : switch (TransformFunctionOf(aData)) {
842 : case eCSSKeyword_translatex:
843 : ProcessTranslateX(aMatrix, aData, aContext, aPresContext,
844 12 : aConditions, aRefBox);
845 12 : break;
846 : case eCSSKeyword_translatey:
847 : ProcessTranslateY(aMatrix, aData, aContext, aPresContext,
848 0 : aConditions, aRefBox);
849 0 : break;
850 : case eCSSKeyword_translatez:
851 0 : *aContains3dTransform = true;
852 : ProcessTranslateZ(aMatrix, aData, aContext, aPresContext,
853 0 : aConditions);
854 0 : break;
855 : case eCSSKeyword_translate:
856 : ProcessTranslate(aMatrix, aData, aContext, aPresContext,
857 24 : aConditions, aRefBox);
858 24 : break;
859 : case eCSSKeyword_translate3d:
860 0 : *aContains3dTransform = true;
861 : ProcessTranslate3D(aMatrix, aData, aContext, aPresContext,
862 0 : aConditions, aRefBox);
863 0 : break;
864 : case eCSSKeyword_scalex:
865 14 : ProcessScaleX(aMatrix, aData);
866 14 : break;
867 : case eCSSKeyword_scaley:
868 0 : ProcessScaleY(aMatrix, aData);
869 0 : break;
870 : case eCSSKeyword_scalez:
871 0 : *aContains3dTransform = true;
872 0 : ProcessScaleZ(aMatrix, aData);
873 0 : break;
874 : case eCSSKeyword_scale:
875 0 : ProcessScale(aMatrix, aData);
876 0 : break;
877 : case eCSSKeyword_scale3d:
878 0 : *aContains3dTransform = true;
879 0 : ProcessScale3D(aMatrix, aData);
880 0 : break;
881 : case eCSSKeyword_skewx:
882 0 : ProcessSkewX(aMatrix, aData);
883 0 : break;
884 : case eCSSKeyword_skewy:
885 0 : ProcessSkewY(aMatrix, aData);
886 0 : break;
887 : case eCSSKeyword_skew:
888 0 : ProcessSkew(aMatrix, aData);
889 0 : break;
890 : case eCSSKeyword_rotatex:
891 0 : *aContains3dTransform = true;
892 0 : ProcessRotateX(aMatrix, aData);
893 0 : break;
894 : case eCSSKeyword_rotatey:
895 0 : *aContains3dTransform = true;
896 0 : ProcessRotateY(aMatrix, aData);
897 0 : break;
898 : case eCSSKeyword_rotatez:
899 0 : *aContains3dTransform = true;
900 : MOZ_FALLTHROUGH;
901 : case eCSSKeyword_rotate:
902 0 : ProcessRotateZ(aMatrix, aData);
903 0 : break;
904 : case eCSSKeyword_rotate3d:
905 0 : *aContains3dTransform = true;
906 0 : ProcessRotate3D(aMatrix, aData);
907 0 : break;
908 : case eCSSKeyword_matrix:
909 : ProcessMatrix(aMatrix, aData, aContext, aPresContext,
910 0 : aConditions, aRefBox);
911 0 : break;
912 : case eCSSKeyword_matrix3d:
913 0 : *aContains3dTransform = true;
914 : ProcessMatrix3D(aMatrix, aData, aContext, aPresContext,
915 0 : aConditions, aRefBox);
916 0 : break;
917 : case eCSSKeyword_interpolatematrix:
918 : ProcessMatrixOperator<Interpolate>(aMatrix, aData, aContext, aPresContext,
919 : aConditions, aRefBox,
920 0 : aContains3dTransform);
921 0 : break;
922 : case eCSSKeyword_accumulatematrix:
923 : ProcessMatrixOperator<Accumulate>(aMatrix, aData, aContext, aPresContext,
924 : aConditions, aRefBox,
925 0 : aContains3dTransform);
926 0 : break;
927 : case eCSSKeyword_perspective:
928 0 : *aContains3dTransform = true;
929 : ProcessPerspective(aMatrix, aData, aContext, aPresContext,
930 0 : aConditions);
931 0 : break;
932 : default:
933 0 : NS_NOTREACHED("Unknown transform function!");
934 : }
935 50 : }
936 :
937 : /**
938 : * Return the transform function, as an nsCSSKeyword, for the given
939 : * nsCSSValue::Array from a transform list.
940 : */
941 : nsCSSKeyword
942 50 : TransformFunctionOf(const nsCSSValue::Array* aData)
943 : {
944 50 : MOZ_ASSERT(aData->Item(0).GetUnit() == eCSSUnit_Enumerated);
945 50 : return aData->Item(0).GetKeywordValue();
946 : }
947 :
948 : void
949 0 : SetIdentityMatrix(nsCSSValue::Array* aMatrix)
950 : {
951 0 : MOZ_ASSERT(aMatrix, "aMatrix should be non-null");
952 :
953 0 : nsCSSKeyword tfunc = TransformFunctionOf(aMatrix);
954 0 : MOZ_ASSERT(tfunc == eCSSKeyword_matrix ||
955 : tfunc == eCSSKeyword_matrix3d,
956 : "Only accept matrix and matrix3d");
957 :
958 0 : if (tfunc == eCSSKeyword_matrix) {
959 0 : MOZ_ASSERT(aMatrix->Count() == 7, "Invalid matrix");
960 0 : Matrix m;
961 0 : for (size_t i = 0; i < 6; ++i) {
962 0 : aMatrix->Item(i + 1).SetFloatValue(m.components[i], eCSSUnit_Number);
963 : }
964 0 : return;
965 : }
966 :
967 0 : MOZ_ASSERT(aMatrix->Count() == 17, "Invalid matrix3d");
968 0 : Matrix4x4 m;
969 0 : for (size_t i = 0; i < 16; ++i) {
970 0 : aMatrix->Item(i + 1).SetFloatValue(m.components[i], eCSSUnit_Number);
971 : }
972 : }
973 :
974 : Matrix4x4
975 50 : ReadTransforms(const nsCSSValueList* aList,
976 : nsStyleContext* aContext,
977 : nsPresContext* aPresContext,
978 : RuleNodeCacheConditions& aConditions,
979 : TransformReferenceBox& aRefBox,
980 : float aAppUnitsPerMatrixUnit,
981 : bool* aContains3dTransform)
982 : {
983 50 : Matrix4x4 result;
984 :
985 100 : for (const nsCSSValueList* curr = aList; curr != nullptr; curr = curr->mNext) {
986 50 : const nsCSSValue &currElem = curr->mValue;
987 50 : if (currElem.GetUnit() != eCSSUnit_Function) {
988 0 : NS_ASSERTION(currElem.GetUnit() == eCSSUnit_None &&
989 : !aList->mNext,
990 : "stream should either be a list of functions or a "
991 : "lone None");
992 0 : continue;
993 : }
994 50 : NS_ASSERTION(currElem.GetArrayValue()->Count() >= 1,
995 : "Incoming function is too short!");
996 :
997 : /* Read in a single transform matrix. */
998 50 : MatrixForTransformFunction(result, currElem.GetArrayValue(), aContext,
999 : aPresContext, aConditions, aRefBox,
1000 50 : aContains3dTransform);
1001 : }
1002 :
1003 50 : float scale = float(nsPresContext::AppUnitsPerCSSPixel()) / aAppUnitsPerMatrixUnit;
1004 50 : result.PreScale(1/scale, 1/scale, 1/scale);
1005 50 : result.PostScale(scale, scale, scale);
1006 :
1007 50 : return result;
1008 : }
1009 :
1010 : Point
1011 0 : Convert2DPosition(nsStyleCoord const (&aValue)[2],
1012 : TransformReferenceBox& aRefBox,
1013 : int32_t aAppUnitsPerDevPixel)
1014 : {
1015 : float position[2];
1016 : nsStyleTransformMatrix::TransformReferenceBox::DimensionGetter dimensionGetter[] =
1017 : { &nsStyleTransformMatrix::TransformReferenceBox::Width,
1018 0 : &nsStyleTransformMatrix::TransformReferenceBox::Height };
1019 0 : for (uint8_t index = 0; index < 2; ++index) {
1020 0 : const nsStyleCoord& value = aValue[index];
1021 0 : if (value.GetUnit() == eStyleUnit_Calc) {
1022 0 : const nsStyleCoord::Calc *calc = value.GetCalcValue();
1023 0 : position[index] =
1024 0 : NSAppUnitsToFloatPixels((aRefBox.*dimensionGetter[index])(), aAppUnitsPerDevPixel) *
1025 0 : calc->mPercent +
1026 0 : NSAppUnitsToFloatPixels(calc->mLength, aAppUnitsPerDevPixel);
1027 0 : } else if (value.GetUnit() == eStyleUnit_Percent) {
1028 0 : position[index] =
1029 0 : NSAppUnitsToFloatPixels((aRefBox.*dimensionGetter[index])(), aAppUnitsPerDevPixel) *
1030 0 : value.GetPercentValue();
1031 : } else {
1032 0 : MOZ_ASSERT(value.GetUnit() == eStyleUnit_Coord,
1033 : "unexpected unit");
1034 0 : position[index] =
1035 0 : NSAppUnitsToFloatPixels(value.GetCoordValue(),
1036 : aAppUnitsPerDevPixel);
1037 : }
1038 : }
1039 :
1040 0 : return Point(position[0], position[1]);
1041 : }
1042 :
1043 : /*
1044 : * The relevant section of the transitions specification:
1045 : * http://dev.w3.org/csswg/css3-transitions/#animation-of-property-types-
1046 : * defers all of the details to the 2-D and 3-D transforms specifications.
1047 : * For the 2-D transforms specification (all that's relevant for us, right
1048 : * now), the relevant section is:
1049 : * http://dev.w3.org/csswg/css3-2d-transforms/#animation
1050 : * This, in turn, refers to the unmatrix program in Graphics Gems,
1051 : * available from http://tog.acm.org/resources/GraphicsGems/ , and in
1052 : * particular as the file GraphicsGems/gemsii/unmatrix.c
1053 : * in http://tog.acm.org/resources/GraphicsGems/AllGems.tar.gz
1054 : *
1055 : * The unmatrix reference is for general 3-D transform matrices (any of the
1056 : * 16 components can have any value).
1057 : *
1058 : * For CSS 2-D transforms, we have a 2-D matrix with the bottom row constant:
1059 : *
1060 : * [ A C E ]
1061 : * [ B D F ]
1062 : * [ 0 0 1 ]
1063 : *
1064 : * For that case, I believe the algorithm in unmatrix reduces to:
1065 : *
1066 : * (1) If A * D - B * C == 0, the matrix is singular. Fail.
1067 : *
1068 : * (2) Set translation components (Tx and Ty) to the translation parts of
1069 : * the matrix (E and F) and then ignore them for the rest of the time.
1070 : * (For us, E and F each actually consist of three constants: a
1071 : * length, a multiplier for the width, and a multiplier for the
1072 : * height. This actually requires its own decomposition, but I'll
1073 : * keep that separate.)
1074 : *
1075 : * (3) Let the X scale (Sx) be sqrt(A^2 + B^2). Then divide both A and B
1076 : * by it.
1077 : *
1078 : * (4) Let the XY shear (K) be A * C + B * D. From C, subtract A times
1079 : * the XY shear. From D, subtract B times the XY shear.
1080 : *
1081 : * (5) Let the Y scale (Sy) be sqrt(C^2 + D^2). Divide C, D, and the XY
1082 : * shear (K) by it.
1083 : *
1084 : * (6) At this point, A * D - B * C is either 1 or -1. If it is -1,
1085 : * negate the XY shear (K), the X scale (Sx), and A, B, C, and D.
1086 : * (Alternatively, we could negate the XY shear (K) and the Y scale
1087 : * (Sy).)
1088 : *
1089 : * (7) Let the rotation be R = atan2(B, A).
1090 : *
1091 : * Then the resulting decomposed transformation is:
1092 : *
1093 : * translate(Tx, Ty) rotate(R) skewX(atan(K)) scale(Sx, Sy)
1094 : *
1095 : * An interesting result of this is that all of the simple transform
1096 : * functions (i.e., all functions other than matrix()), in isolation,
1097 : * decompose back to themselves except for:
1098 : * 'skewY(φ)', which is 'matrix(1, tan(φ), 0, 1, 0, 0)', which decomposes
1099 : * to 'rotate(φ) skewX(φ) scale(sec(φ), cos(φ))' since (ignoring the
1100 : * alternate sign possibilities that would get fixed in step 6):
1101 : * In step 3, the X scale factor is sqrt(1+tan²(φ)) = sqrt(sec²(φ)) = sec(φ).
1102 : * Thus, after step 3, A = 1/sec(φ) = cos(φ) and B = tan(φ) / sec(φ) = sin(φ).
1103 : * In step 4, the XY shear is sin(φ).
1104 : * Thus, after step 4, C = -cos(φ)sin(φ) and D = 1 - sin²(φ) = cos²(φ).
1105 : * Thus, in step 5, the Y scale is sqrt(cos²(φ)(sin²(φ) + cos²(φ)) = cos(φ).
1106 : * Thus, after step 5, C = -sin(φ), D = cos(φ), and the XY shear is tan(φ).
1107 : * Thus, in step 6, A * D - B * C = cos²(φ) + sin²(φ) = 1.
1108 : * In step 7, the rotation is thus φ.
1109 : *
1110 : * skew(θ, φ), which is matrix(1, tan(φ), tan(θ), 1, 0, 0), which decomposes
1111 : * to 'rotate(φ) skewX(θ + φ) scale(sec(φ), cos(φ))' since (ignoring
1112 : * the alternate sign possibilities that would get fixed in step 6):
1113 : * In step 3, the X scale factor is sqrt(1+tan²(φ)) = sqrt(sec²(φ)) = sec(φ).
1114 : * Thus, after step 3, A = 1/sec(φ) = cos(φ) and B = tan(φ) / sec(φ) = sin(φ).
1115 : * In step 4, the XY shear is cos(φ)tan(θ) + sin(φ).
1116 : * Thus, after step 4,
1117 : * C = tan(θ) - cos(φ)(cos(φ)tan(θ) + sin(φ)) = tan(θ)sin²(φ) - cos(φ)sin(φ)
1118 : * D = 1 - sin(φ)(cos(φ)tan(θ) + sin(φ)) = cos²(φ) - sin(φ)cos(φ)tan(θ)
1119 : * Thus, in step 5, the Y scale is sqrt(C² + D²) =
1120 : * sqrt(tan²(θ)(sin⁴(φ) + sin²(φ)cos²(φ)) -
1121 : * 2 tan(θ)(sin³(φ)cos(φ) + sin(φ)cos³(φ)) +
1122 : * (sin²(φ)cos²(φ) + cos⁴(φ))) =
1123 : * sqrt(tan²(θ)sin²(φ) - 2 tan(θ)sin(φ)cos(φ) + cos²(φ)) =
1124 : * cos(φ) - tan(θ)sin(φ) (taking the negative of the obvious solution so
1125 : * we avoid flipping in step 6).
1126 : * After step 5, C = -sin(φ) and D = cos(φ), and the XY shear is
1127 : * (cos(φ)tan(θ) + sin(φ)) / (cos(φ) - tan(θ)sin(φ)) =
1128 : * (dividing both numerator and denominator by cos(φ))
1129 : * (tan(θ) + tan(φ)) / (1 - tan(θ)tan(φ)) = tan(θ + φ).
1130 : * (See http://en.wikipedia.org/wiki/List_of_trigonometric_identities .)
1131 : * Thus, in step 6, A * D - B * C = cos²(φ) + sin²(φ) = 1.
1132 : * In step 7, the rotation is thus φ.
1133 : *
1134 : * To check this result, we can multiply things back together:
1135 : *
1136 : * [ cos(φ) -sin(φ) ] [ 1 tan(θ + φ) ] [ sec(φ) 0 ]
1137 : * [ sin(φ) cos(φ) ] [ 0 1 ] [ 0 cos(φ) ]
1138 : *
1139 : * [ cos(φ) cos(φ)tan(θ + φ) - sin(φ) ] [ sec(φ) 0 ]
1140 : * [ sin(φ) sin(φ)tan(θ + φ) + cos(φ) ] [ 0 cos(φ) ]
1141 : *
1142 : * but since tan(θ + φ) = (tan(θ) + tan(φ)) / (1 - tan(θ)tan(φ)),
1143 : * cos(φ)tan(θ + φ) - sin(φ)
1144 : * = cos(φ)(tan(θ) + tan(φ)) - sin(φ) + sin(φ)tan(θ)tan(φ)
1145 : * = cos(φ)tan(θ) + sin(φ) - sin(φ) + sin(φ)tan(θ)tan(φ)
1146 : * = cos(φ)tan(θ) + sin(φ)tan(θ)tan(φ)
1147 : * = tan(θ) (cos(φ) + sin(φ)tan(φ))
1148 : * = tan(θ) sec(φ) (cos²(φ) + sin²(φ))
1149 : * = tan(θ) sec(φ)
1150 : * and
1151 : * sin(φ)tan(θ + φ) + cos(φ)
1152 : * = sin(φ)(tan(θ) + tan(φ)) + cos(φ) - cos(φ)tan(θ)tan(φ)
1153 : * = tan(θ) (sin(φ) - sin(φ)) + sin(φ)tan(φ) + cos(φ)
1154 : * = sec(φ) (sin²(φ) + cos²(φ))
1155 : * = sec(φ)
1156 : * so the above is:
1157 : * [ cos(φ) tan(θ) sec(φ) ] [ sec(φ) 0 ]
1158 : * [ sin(φ) sec(φ) ] [ 0 cos(φ) ]
1159 : *
1160 : * [ 1 tan(θ) ]
1161 : * [ tan(φ) 1 ]
1162 : */
1163 :
1164 : /*
1165 : * Decompose2DMatrix implements the above decomposition algorithm.
1166 : */
1167 :
1168 : bool
1169 0 : Decompose2DMatrix(const Matrix& aMatrix,
1170 : Point3D& aScale,
1171 : ShearArray& aShear,
1172 : gfxQuaternion& aRotate,
1173 : Point3D& aTranslate)
1174 : {
1175 0 : float A = aMatrix._11,
1176 0 : B = aMatrix._12,
1177 0 : C = aMatrix._21,
1178 0 : D = aMatrix._22;
1179 0 : if (A * D == B * C) {
1180 : // singular matrix
1181 0 : return false;
1182 : }
1183 :
1184 0 : float scaleX = sqrt(A * A + B * B);
1185 0 : A /= scaleX;
1186 0 : B /= scaleX;
1187 :
1188 0 : float XYshear = A * C + B * D;
1189 0 : C -= A * XYshear;
1190 0 : D -= B * XYshear;
1191 :
1192 0 : float scaleY = sqrt(C * C + D * D);
1193 0 : C /= scaleY;
1194 0 : D /= scaleY;
1195 0 : XYshear /= scaleY;
1196 :
1197 : // A*D - B*C should now be 1 or -1
1198 0 : NS_ASSERTION(0.99 < Abs(A*D - B*C) && Abs(A*D - B*C) < 1.01,
1199 : "determinant should now be 1 or -1");
1200 0 : if (A * D < B * C) {
1201 0 : A = -A;
1202 0 : B = -B;
1203 0 : C = -C;
1204 0 : D = -D;
1205 0 : XYshear = -XYshear;
1206 0 : scaleX = -scaleX;
1207 : }
1208 :
1209 0 : float rotate = atan2f(B, A);
1210 0 : aRotate = gfxQuaternion(0, 0, sin(rotate/2), cos(rotate/2));
1211 0 : aShear[ShearType::XYSHEAR] = XYshear;
1212 0 : aScale.x = scaleX;
1213 0 : aScale.y = scaleY;
1214 0 : aTranslate.x = aMatrix._31;
1215 0 : aTranslate.y = aMatrix._32;
1216 0 : return true;
1217 : }
1218 :
1219 : /**
1220 : * Implementation of the unmatrix algorithm, specified by:
1221 : *
1222 : * http://dev.w3.org/csswg/css3-2d-transforms/#unmatrix
1223 : *
1224 : * This, in turn, refers to the unmatrix program in Graphics Gems,
1225 : * available from http://tog.acm.org/resources/GraphicsGems/ , and in
1226 : * particular as the file GraphicsGems/gemsii/unmatrix.c
1227 : * in http://tog.acm.org/resources/GraphicsGems/AllGems.tar.gz
1228 : */
1229 : bool
1230 0 : Decompose3DMatrix(const Matrix4x4& aMatrix,
1231 : Point3D& aScale,
1232 : ShearArray& aShear,
1233 : gfxQuaternion& aRotate,
1234 : Point3D& aTranslate,
1235 : Point4D& aPerspective)
1236 : {
1237 0 : Matrix4x4 local = aMatrix;
1238 :
1239 0 : if (local[3][3] == 0) {
1240 0 : return false;
1241 : }
1242 : /* Normalize the matrix */
1243 0 : local.Normalize();
1244 :
1245 : /**
1246 : * perspective is used to solve for perspective, but it also provides
1247 : * an easy way to test for singularity of the upper 3x3 component.
1248 : */
1249 0 : Matrix4x4 perspective = local;
1250 0 : Point4D empty(0, 0, 0, 1);
1251 0 : perspective.SetTransposedVector(3, empty);
1252 :
1253 0 : if (perspective.Determinant() == 0.0) {
1254 0 : return false;
1255 : }
1256 :
1257 : /* First, isolate perspective. */
1258 0 : if (local[0][3] != 0 || local[1][3] != 0 ||
1259 0 : local[2][3] != 0) {
1260 : /* aPerspective is the right hand side of the equation. */
1261 0 : aPerspective = local.TransposedVector(3);
1262 :
1263 : /**
1264 : * Solve the equation by inverting perspective and multiplying
1265 : * aPerspective by the inverse.
1266 : */
1267 0 : perspective.Invert();
1268 0 : aPerspective = perspective.TransposeTransform4D(aPerspective);
1269 :
1270 : /* Clear the perspective partition */
1271 0 : local.SetTransposedVector(3, empty);
1272 : } else {
1273 0 : aPerspective = Point4D(0, 0, 0, 1);
1274 : }
1275 :
1276 : /* Next take care of translation */
1277 0 : for (int i = 0; i < 3; i++) {
1278 0 : aTranslate[i] = local[3][i];
1279 0 : local[3][i] = 0;
1280 : }
1281 :
1282 : /* Now get scale and shear. */
1283 :
1284 : /* Compute X scale factor and normalize first row. */
1285 0 : aScale.x = local[0].Length();
1286 0 : local[0] /= aScale.x;
1287 :
1288 : /* Compute XY shear factor and make 2nd local orthogonal to 1st. */
1289 0 : aShear[ShearType::XYSHEAR] = local[0].DotProduct(local[1]);
1290 0 : local[1] -= local[0] * aShear[ShearType::XYSHEAR];
1291 :
1292 : /* Now, compute Y scale and normalize 2nd local. */
1293 0 : aScale.y = local[1].Length();
1294 0 : local[1] /= aScale.y;
1295 0 : aShear[ShearType::XYSHEAR] /= aScale.y;
1296 :
1297 : /* Compute XZ and YZ shears, make 3rd local orthogonal */
1298 0 : aShear[ShearType::XZSHEAR] = local[0].DotProduct(local[2]);
1299 0 : local[2] -= local[0] * aShear[ShearType::XZSHEAR];
1300 0 : aShear[ShearType::YZSHEAR] = local[1].DotProduct(local[2]);
1301 0 : local[2] -= local[1] * aShear[ShearType::YZSHEAR];
1302 :
1303 : /* Next, get Z scale and normalize 3rd local. */
1304 0 : aScale.z = local[2].Length();
1305 0 : local[2] /= aScale.z;
1306 :
1307 0 : aShear[ShearType::XZSHEAR] /= aScale.z;
1308 0 : aShear[ShearType::YZSHEAR] /= aScale.z;
1309 :
1310 : /**
1311 : * At this point, the matrix (in locals) is orthonormal.
1312 : * Check for a coordinate system flip. If the determinant
1313 : * is -1, then negate the matrix and the scaling factors.
1314 : */
1315 0 : if (local[0].DotProduct(local[1].CrossProduct(local[2])) < 0) {
1316 0 : aScale *= -1;
1317 0 : for (int i = 0; i < 3; i++) {
1318 0 : local[i] *= -1;
1319 : }
1320 : }
1321 :
1322 : /* Now, get the rotations out */
1323 0 : aRotate = gfxQuaternion(local);
1324 :
1325 0 : return true;
1326 : }
1327 :
1328 : Matrix
1329 0 : CSSValueArrayTo2DMatrix(nsCSSValue::Array* aArray)
1330 : {
1331 0 : MOZ_ASSERT(aArray &&
1332 : TransformFunctionOf(aArray) == eCSSKeyword_matrix &&
1333 : aArray->Count() == 7);
1334 0 : Matrix m(aArray->Item(1).GetFloatValue(),
1335 0 : aArray->Item(2).GetFloatValue(),
1336 0 : aArray->Item(3).GetFloatValue(),
1337 0 : aArray->Item(4).GetFloatValue(),
1338 0 : aArray->Item(5).GetFloatValue(),
1339 0 : aArray->Item(6).GetFloatValue());
1340 0 : return m;
1341 : }
1342 :
1343 : Matrix4x4
1344 0 : CSSValueArrayTo3DMatrix(nsCSSValue::Array* aArray)
1345 : {
1346 0 : MOZ_ASSERT(aArray &&
1347 : TransformFunctionOf(aArray) == eCSSKeyword_matrix3d &&
1348 : aArray->Count() == 17);
1349 : gfx::Float array[16];
1350 0 : for (size_t i = 0; i < 16; ++i) {
1351 0 : array[i] = aArray->Item(i+1).GetFloatValue();
1352 : }
1353 0 : Matrix4x4 m(array);
1354 0 : return m;
1355 : }
1356 :
1357 : gfxSize
1358 0 : GetScaleValue(const nsCSSValueSharedList* aList,
1359 : const nsIFrame* aForFrame)
1360 : {
1361 0 : MOZ_ASSERT(aList && aList->mHead);
1362 0 : MOZ_ASSERT(aForFrame);
1363 :
1364 0 : RuleNodeCacheConditions dontCare;
1365 : bool dontCareBool;
1366 0 : TransformReferenceBox refBox(aForFrame);
1367 : Matrix4x4 transform = ReadTransforms(
1368 0 : aList->mHead,
1369 : aForFrame->StyleContext(),
1370 : aForFrame->PresContext(), dontCare, refBox,
1371 0 : aForFrame->PresContext()->AppUnitsPerDevPixel(),
1372 0 : &dontCareBool);
1373 0 : Matrix transform2d;
1374 0 : bool canDraw2D = transform.CanDraw2D(&transform2d);
1375 0 : if (!canDraw2D) {
1376 0 : return gfxSize();
1377 : }
1378 :
1379 0 : return ThebesMatrix(transform2d).ScaleFactors(true);
1380 : }
1381 :
1382 : } // namespace nsStyleTransformMatrix
|