nsStyleTransformMatrix.cpp 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062
  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. * A class used for intermediate representations of the -moz-transform property.
  7. */
  8. #include "nsStyleTransformMatrix.h"
  9. #include "nsCSSValue.h"
  10. #include "nsLayoutUtils.h"
  11. #include "nsPresContext.h"
  12. #include "nsRuleNode.h"
  13. #include "nsSVGUtils.h"
  14. #include "nsCSSKeywords.h"
  15. #include "mozilla/StyleAnimationValue.h"
  16. #include "gfxMatrix.h"
  17. #include "gfxQuaternion.h"
  18. using namespace mozilla;
  19. using namespace mozilla::gfx;
  20. namespace nsStyleTransformMatrix {
  21. /* Note on floating point precision: The transform matrix is an array
  22. * of single precision 'float's, and so are most of the input values
  23. * we get from the style system, but intermediate calculations
  24. * involving angles need to be done in 'double'.
  25. */
  26. // Define UNIFIED_CONTINUATIONS here and in nsDisplayList.cpp
  27. // to have the transform property try
  28. // to transform content with continuations as one unified block instead of
  29. // several smaller ones. This is currently disabled because it doesn't work
  30. // correctly, since when the frames are initially being reflowed, their
  31. // continuations all compute their bounding rects independently of each other
  32. // and consequently get the wrong value.
  33. //#define UNIFIED_CONTINUATIONS
  34. void
  35. TransformReferenceBox::EnsureDimensionsAreCached()
  36. {
  37. if (mIsCached) {
  38. return;
  39. }
  40. MOZ_ASSERT(mFrame);
  41. mIsCached = true;
  42. if (mFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
  43. if (!nsLayoutUtils::SVGTransformBoxEnabled()) {
  44. mX = -mFrame->GetPosition().x;
  45. mY = -mFrame->GetPosition().y;
  46. Size contextSize = nsSVGUtils::GetContextSize(mFrame);
  47. mWidth = nsPresContext::CSSPixelsToAppUnits(contextSize.width);
  48. mHeight = nsPresContext::CSSPixelsToAppUnits(contextSize.height);
  49. } else
  50. if (mFrame->StyleDisplay()->mTransformBox ==
  51. NS_STYLE_TRANSFORM_BOX_FILL_BOX) {
  52. // Percentages in transforms resolve against the SVG bbox, and the
  53. // transform is relative to the top-left of the SVG bbox.
  54. gfxRect bbox = nsSVGUtils::GetBBox(const_cast<nsIFrame*>(mFrame));
  55. nsRect bboxInAppUnits =
  56. nsLayoutUtils::RoundGfxRectToAppRect(bbox,
  57. mFrame->PresContext()->AppUnitsPerCSSPixel());
  58. // The mRect of an SVG nsIFrame is its user space bounds *including*
  59. // stroke and markers, whereas bboxInAppUnits is its user space bounds
  60. // including fill only. We need to note the offset of the reference box
  61. // from the frame's mRect in mX/mY.
  62. mX = bboxInAppUnits.x - mFrame->GetPosition().x;
  63. mY = bboxInAppUnits.y - mFrame->GetPosition().y;
  64. mWidth = bboxInAppUnits.width;
  65. mHeight = bboxInAppUnits.height;
  66. } else {
  67. // The value 'border-box' is treated as 'view-box' for SVG content.
  68. MOZ_ASSERT(mFrame->StyleDisplay()->mTransformBox ==
  69. NS_STYLE_TRANSFORM_BOX_VIEW_BOX ||
  70. mFrame->StyleDisplay()->mTransformBox ==
  71. NS_STYLE_TRANSFORM_BOX_BORDER_BOX,
  72. "Unexpected value for 'transform-box'");
  73. // Percentages in transforms resolve against the width/height of the
  74. // nearest viewport (or its viewBox if one is applied), and the
  75. // transform is relative to {0,0} in current user space.
  76. mX = -mFrame->GetPosition().x;
  77. mY = -mFrame->GetPosition().y;
  78. Size contextSize = nsSVGUtils::GetContextSize(mFrame);
  79. mWidth = nsPresContext::CSSPixelsToAppUnits(contextSize.width);
  80. mHeight = nsPresContext::CSSPixelsToAppUnits(contextSize.height);
  81. }
  82. return;
  83. }
  84. // If UNIFIED_CONTINUATIONS is not defined, this is simply the frame's
  85. // bounding rectangle, translated to the origin. Otherwise, it is the
  86. // smallest rectangle containing a frame and all of its continuations. For
  87. // example, if there is a <span> element with several continuations split
  88. // over several lines, this function will return the rectangle containing all
  89. // of those continuations.
  90. nsRect rect;
  91. #ifndef UNIFIED_CONTINUATIONS
  92. rect = mFrame->GetRect();
  93. #else
  94. // Iterate the continuation list, unioning together the bounding rects:
  95. for (const nsIFrame *currFrame = mFrame->FirstContinuation();
  96. currFrame != nullptr;
  97. currFrame = currFrame->GetNextContinuation())
  98. {
  99. // Get the frame rect in local coordinates, then translate back to the
  100. // original coordinates:
  101. rect.UnionRect(result, nsRect(currFrame->GetOffsetTo(mFrame),
  102. currFrame->GetSize()));
  103. }
  104. #endif
  105. mX = 0;
  106. mY = 0;
  107. mWidth = rect.Width();
  108. mHeight = rect.Height();
  109. }
  110. void
  111. TransformReferenceBox::Init(const nsSize& aDimensions)
  112. {
  113. MOZ_ASSERT(!mFrame && !mIsCached);
  114. mX = 0;
  115. mY = 0;
  116. mWidth = aDimensions.width;
  117. mHeight = aDimensions.height;
  118. mIsCached = true;
  119. }
  120. float
  121. ProcessTranslatePart(const nsCSSValue& aValue,
  122. nsStyleContext* aContext,
  123. nsPresContext* aPresContext,
  124. RuleNodeCacheConditions& aConditions,
  125. TransformReferenceBox* aRefBox,
  126. TransformReferenceBox::DimensionGetter aDimensionGetter)
  127. {
  128. nscoord offset = 0;
  129. float percent = 0.0f;
  130. if (aValue.GetUnit() == eCSSUnit_Percent) {
  131. percent = aValue.GetPercentValue();
  132. } else if (aValue.GetUnit() == eCSSUnit_Pixel ||
  133. aValue.GetUnit() == eCSSUnit_Number) {
  134. // Handle this here (even though nsRuleNode::CalcLength handles it
  135. // fine) so that callers are allowed to pass a null style context
  136. // and pres context to SetToTransformFunction if they know (as
  137. // StyleAnimationValue does) that all lengths within the transform
  138. // function have already been computed to pixels and percents.
  139. //
  140. // Raw numbers are treated as being pixels.
  141. //
  142. // Don't convert to aValue to AppUnits here to avoid precision issues.
  143. return aValue.GetFloatValue();
  144. } else if (aValue.IsCalcUnit()) {
  145. nsRuleNode::ComputedCalc result =
  146. nsRuleNode::SpecifiedCalcToComputedCalc(aValue, aContext, aPresContext,
  147. aConditions);
  148. percent = result.mPercent;
  149. offset = result.mLength;
  150. } else {
  151. offset = nsRuleNode::CalcLength(aValue, aContext, aPresContext,
  152. aConditions);
  153. }
  154. float translation = NSAppUnitsToFloatPixels(offset,
  155. nsPresContext::AppUnitsPerCSSPixel());
  156. // We want to avoid calling aDimensionGetter if there's no percentage to be
  157. // resolved (for performance reasons - see TransformReferenceBox).
  158. if (percent != 0.0f && aRefBox && !aRefBox->IsEmpty()) {
  159. translation += percent *
  160. NSAppUnitsToFloatPixels((aRefBox->*aDimensionGetter)(),
  161. nsPresContext::AppUnitsPerCSSPixel());
  162. }
  163. return translation;
  164. }
  165. /**
  166. * Helper functions to process all the transformation function types.
  167. *
  168. * These take a matrix parameter to accumulate the current matrix.
  169. */
  170. /* Helper function to process a matrix entry. */
  171. static void
  172. ProcessMatrix(Matrix4x4& aMatrix,
  173. const nsCSSValue::Array* aData,
  174. nsStyleContext* aContext,
  175. nsPresContext* aPresContext,
  176. RuleNodeCacheConditions& aConditions,
  177. TransformReferenceBox& aRefBox)
  178. {
  179. NS_PRECONDITION(aData->Count() == 7, "Invalid array!");
  180. gfxMatrix result;
  181. /* Take the first four elements out of the array as floats and store
  182. * them.
  183. */
  184. result._11 = aData->Item(1).GetFloatValue();
  185. result._12 = aData->Item(2).GetFloatValue();
  186. result._21 = aData->Item(3).GetFloatValue();
  187. result._22 = aData->Item(4).GetFloatValue();
  188. /* The last two elements have their length parts stored in aDelta
  189. * and their percent parts stored in aX[0] and aY[1].
  190. */
  191. result._31 = ProcessTranslatePart(aData->Item(5),
  192. aContext, aPresContext, aConditions,
  193. &aRefBox, &TransformReferenceBox::Width);
  194. result._32 = ProcessTranslatePart(aData->Item(6),
  195. aContext, aPresContext, aConditions,
  196. &aRefBox, &TransformReferenceBox::Height);
  197. aMatrix = result * aMatrix;
  198. }
  199. static void
  200. ProcessMatrix3D(Matrix4x4& aMatrix,
  201. const nsCSSValue::Array* aData,
  202. nsStyleContext* aContext,
  203. nsPresContext* aPresContext,
  204. RuleNodeCacheConditions& aConditions,
  205. TransformReferenceBox& aRefBox)
  206. {
  207. NS_PRECONDITION(aData->Count() == 17, "Invalid array!");
  208. Matrix4x4 temp;
  209. temp._11 = aData->Item(1).GetFloatValue();
  210. temp._12 = aData->Item(2).GetFloatValue();
  211. temp._13 = aData->Item(3).GetFloatValue();
  212. temp._14 = aData->Item(4).GetFloatValue();
  213. temp._21 = aData->Item(5).GetFloatValue();
  214. temp._22 = aData->Item(6).GetFloatValue();
  215. temp._23 = aData->Item(7).GetFloatValue();
  216. temp._24 = aData->Item(8).GetFloatValue();
  217. temp._31 = aData->Item(9).GetFloatValue();
  218. temp._32 = aData->Item(10).GetFloatValue();
  219. temp._33 = aData->Item(11).GetFloatValue();
  220. temp._34 = aData->Item(12).GetFloatValue();
  221. temp._44 = aData->Item(16).GetFloatValue();
  222. temp._41 = ProcessTranslatePart(aData->Item(13),
  223. aContext, aPresContext, aConditions,
  224. &aRefBox, &TransformReferenceBox::Width);
  225. temp._42 = ProcessTranslatePart(aData->Item(14),
  226. aContext, aPresContext, aConditions,
  227. &aRefBox, &TransformReferenceBox::Height);
  228. temp._43 = ProcessTranslatePart(aData->Item(15),
  229. aContext, aPresContext, aConditions,
  230. nullptr);
  231. aMatrix = temp * aMatrix;
  232. }
  233. /* Helper function to process two matrices that we need to interpolate between */
  234. void
  235. ProcessInterpolateMatrix(Matrix4x4& aMatrix,
  236. const nsCSSValue::Array* aData,
  237. nsStyleContext* aContext,
  238. nsPresContext* aPresContext,
  239. RuleNodeCacheConditions& aConditions,
  240. TransformReferenceBox& aRefBox,
  241. bool* aContains3dTransform)
  242. {
  243. NS_PRECONDITION(aData->Count() == 4, "Invalid array!");
  244. Matrix4x4 matrix1, matrix2;
  245. if (aData->Item(1).GetUnit() == eCSSUnit_List) {
  246. matrix1 = nsStyleTransformMatrix::ReadTransforms(aData->Item(1).GetListValue(),
  247. aContext, aPresContext,
  248. aConditions,
  249. aRefBox, nsPresContext::AppUnitsPerCSSPixel(),
  250. aContains3dTransform);
  251. }
  252. if (aData->Item(2).GetUnit() == eCSSUnit_List) {
  253. matrix2 = ReadTransforms(aData->Item(2).GetListValue(),
  254. aContext, aPresContext,
  255. aConditions,
  256. aRefBox, nsPresContext::AppUnitsPerCSSPixel(),
  257. aContains3dTransform);
  258. }
  259. double progress = aData->Item(3).GetPercentValue();
  260. aMatrix =
  261. StyleAnimationValue::InterpolateTransformMatrix(matrix1, matrix2, progress)
  262. * aMatrix;
  263. }
  264. /* Helper function to process a translatex function. */
  265. static void
  266. ProcessTranslateX(Matrix4x4& aMatrix,
  267. const nsCSSValue::Array* aData,
  268. nsStyleContext* aContext,
  269. nsPresContext* aPresContext,
  270. RuleNodeCacheConditions& aConditions,
  271. TransformReferenceBox& aRefBox)
  272. {
  273. NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
  274. Point3D temp;
  275. temp.x = ProcessTranslatePart(aData->Item(1),
  276. aContext, aPresContext, aConditions,
  277. &aRefBox, &TransformReferenceBox::Width);
  278. aMatrix.PreTranslate(temp);
  279. }
  280. /* Helper function to process a translatey function. */
  281. static void
  282. ProcessTranslateY(Matrix4x4& aMatrix,
  283. const nsCSSValue::Array* aData,
  284. nsStyleContext* aContext,
  285. nsPresContext* aPresContext,
  286. RuleNodeCacheConditions& aConditions,
  287. TransformReferenceBox& aRefBox)
  288. {
  289. NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
  290. Point3D temp;
  291. temp.y = ProcessTranslatePart(aData->Item(1),
  292. aContext, aPresContext, aConditions,
  293. &aRefBox, &TransformReferenceBox::Height);
  294. aMatrix.PreTranslate(temp);
  295. }
  296. static void
  297. ProcessTranslateZ(Matrix4x4& aMatrix,
  298. const nsCSSValue::Array* aData,
  299. nsStyleContext* aContext,
  300. nsPresContext* aPresContext,
  301. RuleNodeCacheConditions& aConditions)
  302. {
  303. NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
  304. Point3D temp;
  305. temp.z = ProcessTranslatePart(aData->Item(1), aContext,
  306. aPresContext, aConditions,
  307. nullptr);
  308. aMatrix.PreTranslate(temp);
  309. }
  310. /* Helper function to process a translate function. */
  311. static void
  312. ProcessTranslate(Matrix4x4& aMatrix,
  313. const nsCSSValue::Array* aData,
  314. nsStyleContext* aContext,
  315. nsPresContext* aPresContext,
  316. RuleNodeCacheConditions& aConditions,
  317. TransformReferenceBox& aRefBox)
  318. {
  319. NS_PRECONDITION(aData->Count() == 2 || aData->Count() == 3, "Invalid array!");
  320. Point3D temp;
  321. temp.x = ProcessTranslatePart(aData->Item(1),
  322. aContext, aPresContext, aConditions,
  323. &aRefBox, &TransformReferenceBox::Width);
  324. /* If we read in a Y component, set it appropriately */
  325. if (aData->Count() == 3) {
  326. temp.y = ProcessTranslatePart(aData->Item(2),
  327. aContext, aPresContext, aConditions,
  328. &aRefBox, &TransformReferenceBox::Height);
  329. }
  330. aMatrix.PreTranslate(temp);
  331. }
  332. static void
  333. ProcessTranslate3D(Matrix4x4& aMatrix,
  334. const nsCSSValue::Array* aData,
  335. nsStyleContext* aContext,
  336. nsPresContext* aPresContext,
  337. RuleNodeCacheConditions& aConditions,
  338. TransformReferenceBox& aRefBox)
  339. {
  340. NS_PRECONDITION(aData->Count() == 4, "Invalid array!");
  341. Point3D temp;
  342. temp.x = ProcessTranslatePart(aData->Item(1),
  343. aContext, aPresContext, aConditions,
  344. &aRefBox, &TransformReferenceBox::Width);
  345. temp.y = ProcessTranslatePart(aData->Item(2),
  346. aContext, aPresContext, aConditions,
  347. &aRefBox, &TransformReferenceBox::Height);
  348. temp.z = ProcessTranslatePart(aData->Item(3),
  349. aContext, aPresContext, aConditions,
  350. nullptr);
  351. aMatrix.PreTranslate(temp);
  352. }
  353. /* Helper function to set up a scale matrix. */
  354. static void
  355. ProcessScaleHelper(Matrix4x4& aMatrix,
  356. float aXScale,
  357. float aYScale,
  358. float aZScale)
  359. {
  360. aMatrix.PreScale(aXScale, aYScale, aZScale);
  361. }
  362. /* Process a scalex function. */
  363. static void
  364. ProcessScaleX(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
  365. {
  366. NS_PRECONDITION(aData->Count() == 2, "Bad array!");
  367. ProcessScaleHelper(aMatrix, aData->Item(1).GetFloatValue(), 1.0f, 1.0f);
  368. }
  369. /* Process a scaley function. */
  370. static void
  371. ProcessScaleY(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
  372. {
  373. NS_PRECONDITION(aData->Count() == 2, "Bad array!");
  374. ProcessScaleHelper(aMatrix, 1.0f, aData->Item(1).GetFloatValue(), 1.0f);
  375. }
  376. static void
  377. ProcessScaleZ(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
  378. {
  379. NS_PRECONDITION(aData->Count() == 2, "Bad array!");
  380. ProcessScaleHelper(aMatrix, 1.0f, 1.0f, aData->Item(1).GetFloatValue());
  381. }
  382. static void
  383. ProcessScale3D(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
  384. {
  385. NS_PRECONDITION(aData->Count() == 4, "Bad array!");
  386. ProcessScaleHelper(aMatrix,
  387. aData->Item(1).GetFloatValue(),
  388. aData->Item(2).GetFloatValue(),
  389. aData->Item(3).GetFloatValue());
  390. }
  391. /* Process a scale function. */
  392. static void
  393. ProcessScale(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
  394. {
  395. NS_PRECONDITION(aData->Count() == 2 || aData->Count() == 3, "Bad array!");
  396. /* We either have one element or two. If we have one, it's for both X and Y.
  397. * Otherwise it's one for each.
  398. */
  399. const nsCSSValue& scaleX = aData->Item(1);
  400. const nsCSSValue& scaleY = (aData->Count() == 2 ? scaleX :
  401. aData->Item(2));
  402. ProcessScaleHelper(aMatrix,
  403. scaleX.GetFloatValue(),
  404. scaleY.GetFloatValue(),
  405. 1.0f);
  406. }
  407. /* Helper function that, given a set of angles, constructs the appropriate
  408. * skew matrix.
  409. */
  410. static void
  411. ProcessSkewHelper(Matrix4x4& aMatrix, double aXAngle, double aYAngle)
  412. {
  413. aMatrix.SkewXY(aXAngle, aYAngle);
  414. }
  415. /* Function that converts a skewx transform into a matrix. */
  416. static void
  417. ProcessSkewX(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
  418. {
  419. NS_ASSERTION(aData->Count() == 2, "Bad array!");
  420. ProcessSkewHelper(aMatrix, aData->Item(1).GetAngleValueInRadians(), 0.0);
  421. }
  422. /* Function that converts a skewy transform into a matrix. */
  423. static void
  424. ProcessSkewY(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
  425. {
  426. NS_ASSERTION(aData->Count() == 2, "Bad array!");
  427. ProcessSkewHelper(aMatrix, 0.0, aData->Item(1).GetAngleValueInRadians());
  428. }
  429. /* Function that converts a skew transform into a matrix. */
  430. static void
  431. ProcessSkew(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
  432. {
  433. NS_ASSERTION(aData->Count() == 2 || aData->Count() == 3, "Bad array!");
  434. double xSkew = aData->Item(1).GetAngleValueInRadians();
  435. double ySkew = (aData->Count() == 2
  436. ? 0.0 : aData->Item(2).GetAngleValueInRadians());
  437. ProcessSkewHelper(aMatrix, xSkew, ySkew);
  438. }
  439. /* Function that converts a rotate transform into a matrix. */
  440. static void
  441. ProcessRotateZ(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
  442. {
  443. NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
  444. double theta = aData->Item(1).GetAngleValueInRadians();
  445. aMatrix.RotateZ(theta);
  446. }
  447. static void
  448. ProcessRotateX(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
  449. {
  450. NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
  451. double theta = aData->Item(1).GetAngleValueInRadians();
  452. aMatrix.RotateX(theta);
  453. }
  454. static void
  455. ProcessRotateY(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
  456. {
  457. NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
  458. double theta = aData->Item(1).GetAngleValueInRadians();
  459. aMatrix.RotateY(theta);
  460. }
  461. static void
  462. ProcessRotate3D(Matrix4x4& aMatrix, const nsCSSValue::Array* aData)
  463. {
  464. NS_PRECONDITION(aData->Count() == 5, "Invalid array!");
  465. double theta = aData->Item(4).GetAngleValueInRadians();
  466. float x = aData->Item(1).GetFloatValue();
  467. float y = aData->Item(2).GetFloatValue();
  468. float z = aData->Item(3).GetFloatValue();
  469. Matrix4x4 temp;
  470. temp.SetRotateAxisAngle(x, y, z, theta);
  471. aMatrix = temp * aMatrix;
  472. }
  473. static void
  474. ProcessPerspective(Matrix4x4& aMatrix,
  475. const nsCSSValue::Array* aData,
  476. nsStyleContext *aContext,
  477. nsPresContext *aPresContext,
  478. RuleNodeCacheConditions& aConditions)
  479. {
  480. NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
  481. float depth = ProcessTranslatePart(aData->Item(1), aContext,
  482. aPresContext, aConditions, nullptr);
  483. ApplyPerspectiveToMatrix(aMatrix, depth);
  484. }
  485. /**
  486. * SetToTransformFunction is essentially a giant switch statement that fans
  487. * out to many smaller helper functions.
  488. */
  489. static void
  490. MatrixForTransformFunction(Matrix4x4& aMatrix,
  491. const nsCSSValue::Array * aData,
  492. nsStyleContext* aContext,
  493. nsPresContext* aPresContext,
  494. RuleNodeCacheConditions& aConditions,
  495. TransformReferenceBox& aRefBox,
  496. bool* aContains3dTransform)
  497. {
  498. MOZ_ASSERT(aContains3dTransform);
  499. NS_PRECONDITION(aData, "Why did you want to get data from a null array?");
  500. // It's OK if aContext and aPresContext are null if the caller already
  501. // knows that all length units have been converted to pixels (as
  502. // StyleAnimationValue does).
  503. /* Get the keyword for the transform. */
  504. switch (TransformFunctionOf(aData)) {
  505. case eCSSKeyword_translatex:
  506. ProcessTranslateX(aMatrix, aData, aContext, aPresContext,
  507. aConditions, aRefBox);
  508. break;
  509. case eCSSKeyword_translatey:
  510. ProcessTranslateY(aMatrix, aData, aContext, aPresContext,
  511. aConditions, aRefBox);
  512. break;
  513. case eCSSKeyword_translatez:
  514. *aContains3dTransform = true;
  515. ProcessTranslateZ(aMatrix, aData, aContext, aPresContext,
  516. aConditions);
  517. break;
  518. case eCSSKeyword_translate:
  519. ProcessTranslate(aMatrix, aData, aContext, aPresContext,
  520. aConditions, aRefBox);
  521. break;
  522. case eCSSKeyword_translate3d:
  523. *aContains3dTransform = true;
  524. ProcessTranslate3D(aMatrix, aData, aContext, aPresContext,
  525. aConditions, aRefBox);
  526. break;
  527. case eCSSKeyword_scalex:
  528. ProcessScaleX(aMatrix, aData);
  529. break;
  530. case eCSSKeyword_scaley:
  531. ProcessScaleY(aMatrix, aData);
  532. break;
  533. case eCSSKeyword_scalez:
  534. *aContains3dTransform = true;
  535. ProcessScaleZ(aMatrix, aData);
  536. break;
  537. case eCSSKeyword_scale:
  538. ProcessScale(aMatrix, aData);
  539. break;
  540. case eCSSKeyword_scale3d:
  541. *aContains3dTransform = true;
  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. *aContains3dTransform = true;
  555. ProcessRotateX(aMatrix, aData);
  556. break;
  557. case eCSSKeyword_rotatey:
  558. *aContains3dTransform = true;
  559. ProcessRotateY(aMatrix, aData);
  560. break;
  561. case eCSSKeyword_rotatez:
  562. *aContains3dTransform = true;
  563. MOZ_FALLTHROUGH;
  564. case eCSSKeyword_rotate:
  565. ProcessRotateZ(aMatrix, aData);
  566. break;
  567. case eCSSKeyword_rotate3d:
  568. *aContains3dTransform = true;
  569. ProcessRotate3D(aMatrix, aData);
  570. break;
  571. case eCSSKeyword_matrix:
  572. ProcessMatrix(aMatrix, aData, aContext, aPresContext,
  573. aConditions, aRefBox);
  574. break;
  575. case eCSSKeyword_matrix3d:
  576. *aContains3dTransform = true;
  577. ProcessMatrix3D(aMatrix, aData, aContext, aPresContext,
  578. aConditions, aRefBox);
  579. break;
  580. case eCSSKeyword_interpolatematrix:
  581. ProcessInterpolateMatrix(aMatrix, aData, aContext, aPresContext,
  582. aConditions, aRefBox,
  583. aContains3dTransform);
  584. break;
  585. case eCSSKeyword_perspective:
  586. *aContains3dTransform = true;
  587. ProcessPerspective(aMatrix, aData, aContext, aPresContext,
  588. aConditions);
  589. break;
  590. default:
  591. NS_NOTREACHED("Unknown transform function!");
  592. }
  593. }
  594. /**
  595. * Return the transform function, as an nsCSSKeyword, for the given
  596. * nsCSSValue::Array from a transform list.
  597. */
  598. nsCSSKeyword
  599. TransformFunctionOf(const nsCSSValue::Array* aData)
  600. {
  601. MOZ_ASSERT(aData->Item(0).GetUnit() == eCSSUnit_Enumerated);
  602. return aData->Item(0).GetKeywordValue();
  603. }
  604. void
  605. SetIdentityMatrix(nsCSSValue::Array* aMatrix)
  606. {
  607. MOZ_ASSERT(aMatrix, "aMatrix should be non-null");
  608. nsCSSKeyword tfunc = TransformFunctionOf(aMatrix);
  609. MOZ_ASSERT(tfunc == eCSSKeyword_matrix ||
  610. tfunc == eCSSKeyword_matrix3d,
  611. "Only accept matrix and matrix3d");
  612. if (tfunc == eCSSKeyword_matrix) {
  613. MOZ_ASSERT(aMatrix->Count() == 7, "Invalid matrix");
  614. Matrix m;
  615. for (size_t i = 0; i < 6; ++i) {
  616. aMatrix->Item(i + 1).SetFloatValue(m.components[i], eCSSUnit_Number);
  617. }
  618. return;
  619. }
  620. MOZ_ASSERT(aMatrix->Count() == 17, "Invalid matrix3d");
  621. Matrix4x4 m;
  622. for (size_t i = 0; i < 16; ++i) {
  623. aMatrix->Item(i + 1).SetFloatValue(m.components[i], eCSSUnit_Number);
  624. }
  625. }
  626. Matrix4x4
  627. ReadTransforms(const nsCSSValueList* aList,
  628. nsStyleContext* aContext,
  629. nsPresContext* aPresContext,
  630. RuleNodeCacheConditions& aConditions,
  631. TransformReferenceBox& aRefBox,
  632. float aAppUnitsPerMatrixUnit,
  633. bool* aContains3dTransform)
  634. {
  635. Matrix4x4 result;
  636. for (const nsCSSValueList* curr = aList; curr != nullptr; curr = curr->mNext) {
  637. const nsCSSValue &currElem = curr->mValue;
  638. if (currElem.GetUnit() != eCSSUnit_Function) {
  639. NS_ASSERTION(currElem.GetUnit() == eCSSUnit_None &&
  640. !aList->mNext,
  641. "stream should either be a list of functions or a "
  642. "lone None");
  643. continue;
  644. }
  645. NS_ASSERTION(currElem.GetArrayValue()->Count() >= 1,
  646. "Incoming function is too short!");
  647. /* Read in a single transform matrix. */
  648. MatrixForTransformFunction(result, currElem.GetArrayValue(), aContext,
  649. aPresContext, aConditions, aRefBox,
  650. aContains3dTransform);
  651. }
  652. float scale = float(nsPresContext::AppUnitsPerCSSPixel()) / aAppUnitsPerMatrixUnit;
  653. result.PreScale(1/scale, 1/scale, 1/scale);
  654. result.PostScale(scale, scale, scale);
  655. return result;
  656. }
  657. /*
  658. * The relevant section of the transitions specification:
  659. * http://dev.w3.org/csswg/css3-transitions/#animation-of-property-types-
  660. * defers all of the details to the 2-D and 3-D transforms specifications.
  661. * For the 2-D transforms specification (all that's relevant for us, right
  662. * now), the relevant section is:
  663. * http://dev.w3.org/csswg/css3-2d-transforms/#animation
  664. * This, in turn, refers to the unmatrix program in Graphics Gems,
  665. * available from http://tog.acm.org/resources/GraphicsGems/ , and in
  666. * particular as the file GraphicsGems/gemsii/unmatrix.c
  667. * in http://tog.acm.org/resources/GraphicsGems/AllGems.tar.gz
  668. *
  669. * The unmatrix reference is for general 3-D transform matrices (any of the
  670. * 16 components can have any value).
  671. *
  672. * For CSS 2-D transforms, we have a 2-D matrix with the bottom row constant:
  673. *
  674. * [ A C E ]
  675. * [ B D F ]
  676. * [ 0 0 1 ]
  677. *
  678. * For that case, I believe the algorithm in unmatrix reduces to:
  679. *
  680. * (1) If A * D - B * C == 0, the matrix is singular. Fail.
  681. *
  682. * (2) Set translation components (Tx and Ty) to the translation parts of
  683. * the matrix (E and F) and then ignore them for the rest of the time.
  684. * (For us, E and F each actually consist of three constants: a
  685. * length, a multiplier for the width, and a multiplier for the
  686. * height. This actually requires its own decomposition, but I'll
  687. * keep that separate.)
  688. *
  689. * (3) Let the X scale (Sx) be sqrt(A^2 + B^2). Then divide both A and B
  690. * by it.
  691. *
  692. * (4) Let the XY shear (K) be A * C + B * D. From C, subtract A times
  693. * the XY shear. From D, subtract B times the XY shear.
  694. *
  695. * (5) Let the Y scale (Sy) be sqrt(C^2 + D^2). Divide C, D, and the XY
  696. * shear (K) by it.
  697. *
  698. * (6) At this point, A * D - B * C is either 1 or -1. If it is -1,
  699. * negate the XY shear (K), the X scale (Sx), and A, B, C, and D.
  700. * (Alternatively, we could negate the XY shear (K) and the Y scale
  701. * (Sy).)
  702. *
  703. * (7) Let the rotation be R = atan2(B, A).
  704. *
  705. * Then the resulting decomposed transformation is:
  706. *
  707. * translate(Tx, Ty) rotate(R) skewX(atan(K)) scale(Sx, Sy)
  708. *
  709. * An interesting result of this is that all of the simple transform
  710. * functions (i.e., all functions other than matrix()), in isolation,
  711. * decompose back to themselves except for:
  712. * 'skewY(φ)', which is 'matrix(1, tan(φ), 0, 1, 0, 0)', which decomposes
  713. * to 'rotate(φ) skewX(φ) scale(sec(φ), cos(φ))' since (ignoring the
  714. * alternate sign possibilities that would get fixed in step 6):
  715. * In step 3, the X scale factor is sqrt(1+tan²(φ)) = sqrt(sec²(φ)) = sec(φ).
  716. * Thus, after step 3, A = 1/sec(φ) = cos(φ) and B = tan(φ) / sec(φ) = sin(φ).
  717. * In step 4, the XY shear is sin(φ).
  718. * Thus, after step 4, C = -cos(φ)sin(φ) and D = 1 - sin²(φ) = cos²(φ).
  719. * Thus, in step 5, the Y scale is sqrt(cos²(φ)(sin²(φ) + cos²(φ)) = cos(φ).
  720. * Thus, after step 5, C = -sin(φ), D = cos(φ), and the XY shear is tan(φ).
  721. * Thus, in step 6, A * D - B * C = cos²(φ) + sin²(φ) = 1.
  722. * In step 7, the rotation is thus φ.
  723. *
  724. * skew(θ, φ), which is matrix(1, tan(φ), tan(θ), 1, 0, 0), which decomposes
  725. * to 'rotate(φ) skewX(θ + φ) scale(sec(φ), cos(φ))' since (ignoring
  726. * the alternate sign possibilities that would get fixed in step 6):
  727. * In step 3, the X scale factor is sqrt(1+tan²(φ)) = sqrt(sec²(φ)) = sec(φ).
  728. * Thus, after step 3, A = 1/sec(φ) = cos(φ) and B = tan(φ) / sec(φ) = sin(φ).
  729. * In step 4, the XY shear is cos(φ)tan(θ) + sin(φ).
  730. * Thus, after step 4,
  731. * C = tan(θ) - cos(φ)(cos(φ)tan(θ) + sin(φ)) = tan(θ)sin²(φ) - cos(φ)sin(φ)
  732. * D = 1 - sin(φ)(cos(φ)tan(θ) + sin(φ)) = cos²(φ) - sin(φ)cos(φ)tan(θ)
  733. * Thus, in step 5, the Y scale is sqrt(C² + D²) =
  734. * sqrt(tan²(θ)(sin⁴(φ) + sin²(φ)cos²(φ)) -
  735. * 2 tan(θ)(sin³(φ)cos(φ) + sin(φ)cos³(φ)) +
  736. * (sin²(φ)cos²(φ) + cos⁴(φ))) =
  737. * sqrt(tan²(θ)sin²(φ) - 2 tan(θ)sin(φ)cos(φ) + cos²(φ)) =
  738. * cos(φ) - tan(θ)sin(φ) (taking the negative of the obvious solution so
  739. * we avoid flipping in step 6).
  740. * After step 5, C = -sin(φ) and D = cos(φ), and the XY shear is
  741. * (cos(φ)tan(θ) + sin(φ)) / (cos(φ) - tan(θ)sin(φ)) =
  742. * (dividing both numerator and denominator by cos(φ))
  743. * (tan(θ) + tan(φ)) / (1 - tan(θ)tan(φ)) = tan(θ + φ).
  744. * (See http://en.wikipedia.org/wiki/List_of_trigonometric_identities .)
  745. * Thus, in step 6, A * D - B * C = cos²(φ) + sin²(φ) = 1.
  746. * In step 7, the rotation is thus φ.
  747. *
  748. * To check this result, we can multiply things back together:
  749. *
  750. * [ cos(φ) -sin(φ) ] [ 1 tan(θ + φ) ] [ sec(φ) 0 ]
  751. * [ sin(φ) cos(φ) ] [ 0 1 ] [ 0 cos(φ) ]
  752. *
  753. * [ cos(φ) cos(φ)tan(θ + φ) - sin(φ) ] [ sec(φ) 0 ]
  754. * [ sin(φ) sin(φ)tan(θ + φ) + cos(φ) ] [ 0 cos(φ) ]
  755. *
  756. * but since tan(θ + φ) = (tan(θ) + tan(φ)) / (1 - tan(θ)tan(φ)),
  757. * cos(φ)tan(θ + φ) - sin(φ)
  758. * = cos(φ)(tan(θ) + tan(φ)) - sin(φ) + sin(φ)tan(θ)tan(φ)
  759. * = cos(φ)tan(θ) + sin(φ) - sin(φ) + sin(φ)tan(θ)tan(φ)
  760. * = cos(φ)tan(θ) + sin(φ)tan(θ)tan(φ)
  761. * = tan(θ) (cos(φ) + sin(φ)tan(φ))
  762. * = tan(θ) sec(φ) (cos²(φ) + sin²(φ))
  763. * = tan(θ) sec(φ)
  764. * and
  765. * sin(φ)tan(θ + φ) + cos(φ)
  766. * = sin(φ)(tan(θ) + tan(φ)) + cos(φ) - cos(φ)tan(θ)tan(φ)
  767. * = tan(θ) (sin(φ) - sin(φ)) + sin(φ)tan(φ) + cos(φ)
  768. * = sec(φ) (sin²(φ) + cos²(φ))
  769. * = sec(φ)
  770. * so the above is:
  771. * [ cos(φ) tan(θ) sec(φ) ] [ sec(φ) 0 ]
  772. * [ sin(φ) sec(φ) ] [ 0 cos(φ) ]
  773. *
  774. * [ 1 tan(θ) ]
  775. * [ tan(φ) 1 ]
  776. */
  777. /*
  778. * Decompose2DMatrix implements the above decomposition algorithm.
  779. */
  780. bool
  781. Decompose2DMatrix(const Matrix& aMatrix,
  782. Point3D& aScale,
  783. ShearArray& aShear,
  784. gfxQuaternion& aRotate,
  785. Point3D& aTranslate)
  786. {
  787. float A = aMatrix._11,
  788. B = aMatrix._12,
  789. C = aMatrix._21,
  790. D = aMatrix._22;
  791. if (A * D == B * C) {
  792. // singular matrix
  793. return false;
  794. }
  795. float scaleX = sqrt(A * A + B * B);
  796. A /= scaleX;
  797. B /= scaleX;
  798. float XYshear = A * C + B * D;
  799. C -= A * XYshear;
  800. D -= B * XYshear;
  801. float scaleY = sqrt(C * C + D * D);
  802. C /= scaleY;
  803. D /= scaleY;
  804. XYshear /= scaleY;
  805. // A*D - B*C should now be 1 or -1
  806. NS_ASSERTION(0.99 < Abs(A*D - B*C) && Abs(A*D - B*C) < 1.01,
  807. "determinant should now be 1 or -1");
  808. if (A * D < B * C) {
  809. A = -A;
  810. B = -B;
  811. C = -C;
  812. D = -D;
  813. XYshear = -XYshear;
  814. scaleX = -scaleX;
  815. }
  816. float rotate = atan2f(B, A);
  817. aRotate = gfxQuaternion(0, 0, sin(rotate/2), cos(rotate/2));
  818. aShear[ShearType::XYSHEAR] = XYshear;
  819. aScale.x = scaleX;
  820. aScale.y = scaleY;
  821. aTranslate.x = aMatrix._31;
  822. aTranslate.y = aMatrix._32;
  823. return true;
  824. }
  825. /**
  826. * Implementation of the unmatrix algorithm, specified by:
  827. *
  828. * http://dev.w3.org/csswg/css3-2d-transforms/#unmatrix
  829. *
  830. * This, in turn, refers to the unmatrix program in Graphics Gems,
  831. * available from http://tog.acm.org/resources/GraphicsGems/ , and in
  832. * particular as the file GraphicsGems/gemsii/unmatrix.c
  833. * in http://tog.acm.org/resources/GraphicsGems/AllGems.tar.gz
  834. */
  835. bool
  836. Decompose3DMatrix(const Matrix4x4& aMatrix,
  837. Point3D& aScale,
  838. ShearArray& aShear,
  839. gfxQuaternion& aRotate,
  840. Point3D& aTranslate,
  841. Point4D& aPerspective)
  842. {
  843. Matrix4x4 local = aMatrix;
  844. if (local[3][3] == 0) {
  845. return false;
  846. }
  847. /* Normalize the matrix */
  848. local.Normalize();
  849. /**
  850. * perspective is used to solve for perspective, but it also provides
  851. * an easy way to test for singularity of the upper 3x3 component.
  852. */
  853. Matrix4x4 perspective = local;
  854. Point4D empty(0, 0, 0, 1);
  855. perspective.SetTransposedVector(3, empty);
  856. if (perspective.Determinant() == 0.0) {
  857. return false;
  858. }
  859. /* First, isolate perspective. */
  860. if (local[0][3] != 0 || local[1][3] != 0 ||
  861. local[2][3] != 0) {
  862. /* aPerspective is the right hand side of the equation. */
  863. aPerspective = local.TransposedVector(3);
  864. /**
  865. * Solve the equation by inverting perspective and multiplying
  866. * aPerspective by the inverse.
  867. */
  868. perspective.Invert();
  869. aPerspective = perspective.TransposeTransform4D(aPerspective);
  870. /* Clear the perspective partition */
  871. local.SetTransposedVector(3, empty);
  872. } else {
  873. aPerspective = Point4D(0, 0, 0, 1);
  874. }
  875. /* Next take care of translation */
  876. for (int i = 0; i < 3; i++) {
  877. aTranslate[i] = local[3][i];
  878. local[3][i] = 0;
  879. }
  880. /* Now get scale and shear. */
  881. /* Compute X scale factor and normalize first row. */
  882. aScale.x = local[0].Length();
  883. local[0] /= aScale.x;
  884. /* Compute XY shear factor and make 2nd local orthogonal to 1st. */
  885. aShear[ShearType::XYSHEAR] = local[0].DotProduct(local[1]);
  886. local[1] -= local[0] * aShear[ShearType::XYSHEAR];
  887. /* Now, compute Y scale and normalize 2nd local. */
  888. aScale.y = local[1].Length();
  889. local[1] /= aScale.y;
  890. aShear[ShearType::XYSHEAR] /= aScale.y;
  891. /* Compute XZ and YZ shears, make 3rd local orthogonal */
  892. aShear[ShearType::XZSHEAR] = local[0].DotProduct(local[2]);
  893. local[2] -= local[0] * aShear[ShearType::XZSHEAR];
  894. aShear[ShearType::YZSHEAR] = local[1].DotProduct(local[2]);
  895. local[2] -= local[1] * aShear[ShearType::YZSHEAR];
  896. /* Next, get Z scale and normalize 3rd local. */
  897. aScale.z = local[2].Length();
  898. local[2] /= aScale.z;
  899. aShear[ShearType::XZSHEAR] /= aScale.z;
  900. aShear[ShearType::YZSHEAR] /= aScale.z;
  901. /**
  902. * At this point, the matrix (in locals) is orthonormal.
  903. * Check for a coordinate system flip. If the determinant
  904. * is -1, then negate the matrix and the scaling factors.
  905. */
  906. if (local[0].DotProduct(local[1].CrossProduct(local[2])) < 0) {
  907. aScale *= -1;
  908. for (int i = 0; i < 3; i++) {
  909. local[i] *= -1;
  910. }
  911. }
  912. /* Now, get the rotations out */
  913. aRotate = gfxQuaternion(local);
  914. return true;
  915. }
  916. Matrix
  917. CSSValueArrayTo2DMatrix(nsCSSValue::Array* aArray)
  918. {
  919. MOZ_ASSERT(aArray &&
  920. TransformFunctionOf(aArray) == eCSSKeyword_matrix &&
  921. aArray->Count() == 7);
  922. Matrix m(aArray->Item(1).GetFloatValue(),
  923. aArray->Item(2).GetFloatValue(),
  924. aArray->Item(3).GetFloatValue(),
  925. aArray->Item(4).GetFloatValue(),
  926. aArray->Item(5).GetFloatValue(),
  927. aArray->Item(6).GetFloatValue());
  928. return m;
  929. }
  930. Matrix4x4
  931. CSSValueArrayTo3DMatrix(nsCSSValue::Array* aArray)
  932. {
  933. MOZ_ASSERT(aArray &&
  934. TransformFunctionOf(aArray) == eCSSKeyword_matrix3d &&
  935. aArray->Count() == 17);
  936. gfx::Float array[16];
  937. for (size_t i = 0; i < 16; ++i) {
  938. array[i] = aArray->Item(i+1).GetFloatValue();
  939. }
  940. Matrix4x4 m(array);
  941. return m;
  942. }
  943. } // namespace nsStyleTransformMatrix