Interpolation.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  1. /*
  2. * Copyright (c) 2018-2023, Andreas Kling <andreas@ladybird.org>
  3. * Copyright (c) 2021, the SerenityOS developers.
  4. * Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
  5. * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
  6. *
  7. * SPDX-License-Identifier: BSD-2-Clause
  8. */
  9. #include "Interpolation.h"
  10. #include <LibWeb/CSS/PropertyID.h>
  11. #include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
  12. #include <LibWeb/CSS/StyleValues/CSSColorValue.h>
  13. #include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
  14. #include <LibWeb/CSS/StyleValues/FrequencyStyleValue.h>
  15. #include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
  16. #include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
  17. #include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
  18. #include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
  19. #include <LibWeb/CSS/StyleValues/RatioStyleValue.h>
  20. #include <LibWeb/CSS/StyleValues/RectStyleValue.h>
  21. #include <LibWeb/CSS/StyleValues/StyleValueList.h>
  22. #include <LibWeb/CSS/StyleValues/TimeStyleValue.h>
  23. #include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
  24. #include <LibWeb/CSS/Transformation.h>
  25. #include <LibWeb/DOM/Element.h>
  26. #include <LibWeb/Layout/Node.h>
  27. #include <LibWeb/Painting/PaintableBox.h>
  28. namespace Web::CSS {
  29. template<typename T>
  30. static T interpolate_raw(T from, T to, float delta)
  31. {
  32. if constexpr (AK::Detail::IsSame<T, double>) {
  33. return from + (to - from) * static_cast<double>(delta);
  34. } else {
  35. return static_cast<AK::Detail::RemoveCVReference<T>>(from + (to - from) * delta);
  36. }
  37. }
  38. static NonnullRefPtr<CSSStyleValue const> with_keyword_values_resolved(DOM::Element& element, PropertyID property_id, CSSStyleValue const& value)
  39. {
  40. if (!value.is_keyword())
  41. return value;
  42. switch (value.as_keyword().keyword()) {
  43. case CSS::Keyword::Initial:
  44. case CSS::Keyword::Unset:
  45. return property_initial_value(element.realm(), property_id);
  46. case CSS::Keyword::Inherit:
  47. return CSS::StyleComputer::get_inherit_value(element.realm(), property_id, &element);
  48. default:
  49. break;
  50. }
  51. return value;
  52. }
  53. ValueComparingRefPtr<CSSStyleValue const> interpolate_property(DOM::Element& element, PropertyID property_id, CSSStyleValue const& a_from, CSSStyleValue const& a_to, float delta)
  54. {
  55. auto from = with_keyword_values_resolved(element, property_id, a_from);
  56. auto to = with_keyword_values_resolved(element, property_id, a_to);
  57. auto animation_type = animation_type_from_longhand_property(property_id);
  58. switch (animation_type) {
  59. case AnimationType::ByComputedValue:
  60. return interpolate_value(element, from, to, delta);
  61. case AnimationType::None:
  62. return to;
  63. case AnimationType::Custom: {
  64. if (property_id == PropertyID::Transform) {
  65. if (auto interpolated_transform = interpolate_transform(element, from, to, delta))
  66. return *interpolated_transform;
  67. // https://drafts.csswg.org/css-transforms-1/#interpolation-of-transforms
  68. // In some cases, an animation might cause a transformation matrix to be singular or non-invertible.
  69. // For example, an animation in which scale moves from 1 to -1. At the time when the matrix is in
  70. // such a state, the transformed element is not rendered.
  71. return {};
  72. }
  73. if (property_id == PropertyID::BoxShadow)
  74. return interpolate_box_shadow(element, from, to, delta);
  75. // FIXME: Handle all custom animatable properties
  76. [[fallthrough]];
  77. }
  78. // FIXME: Handle repeatable-list animatable properties
  79. case AnimationType::RepeatableList:
  80. case AnimationType::Discrete:
  81. default:
  82. return delta >= 0.5f ? to : from;
  83. }
  84. }
  85. // https://drafts.csswg.org/css-transitions/#transitionable
  86. bool property_values_are_transitionable(PropertyID property_id, CSSStyleValue const& old_value, CSSStyleValue const& new_value)
  87. {
  88. // When comparing the before-change style and after-change style for a given property,
  89. // the property values are transitionable if they have an animation type that is neither not animatable nor discrete.
  90. auto animation_type = animation_type_from_longhand_property(property_id);
  91. if (animation_type == AnimationType::None || animation_type == AnimationType::Discrete)
  92. return false;
  93. // FIXME: Even when a property is transitionable, the two values may not be. The spec uses the example of inset/non-inset shadows.
  94. (void)old_value;
  95. (void)new_value;
  96. return true;
  97. }
  98. // A null return value means the interpolated matrix was not invertible or otherwise invalid
  99. RefPtr<CSSStyleValue const> interpolate_transform(DOM::Element& element, CSSStyleValue const& from, CSSStyleValue const& to, float delta)
  100. {
  101. // Note that the spec uses column-major notation, so all the matrix indexing is reversed.
  102. static constexpr auto make_transformation = [](TransformationStyleValue const& transformation) -> AK::Optional<Transformation> {
  103. AK::Vector<TransformValue> values;
  104. for (auto const& value : transformation.values()) {
  105. switch (value->type()) {
  106. case CSSStyleValue::Type::Angle:
  107. values.append(AngleOrCalculated { value->as_angle().angle() });
  108. break;
  109. case CSSStyleValue::Type::Math:
  110. values.append(LengthPercentage { value->as_math() });
  111. break;
  112. case CSSStyleValue::Type::Length:
  113. values.append(LengthPercentage { value->as_length().length() });
  114. break;
  115. case CSSStyleValue::Type::Percentage:
  116. values.append(LengthPercentage { value->as_percentage().percentage() });
  117. break;
  118. case CSSStyleValue::Type::Number:
  119. values.append(NumberPercentage { Number(Number::Type::Number, value->as_number().number()) });
  120. break;
  121. default:
  122. return {};
  123. }
  124. }
  125. return Transformation { transformation.transform_function(), move(values) };
  126. };
  127. static constexpr auto transformation_style_value_to_matrix = [](DOM::Element& element, TransformationStyleValue const& value) -> Optional<FloatMatrix4x4> {
  128. auto transformation = make_transformation(value);
  129. if (!transformation.has_value())
  130. return {};
  131. Optional<Painting::PaintableBox const&> paintable_box;
  132. if (auto layout_node = element.layout_node()) {
  133. if (auto paintable = layout_node->first_paintable(); paintable && is<Painting::PaintableBox>(paintable))
  134. paintable_box = *static_cast<Painting::PaintableBox*>(paintable);
  135. }
  136. if (auto matrix = transformation->to_matrix(paintable_box); !matrix.is_error())
  137. return matrix.value();
  138. return {};
  139. };
  140. static constexpr auto style_value_to_matrix = [](DOM::Element& element, CSSStyleValue const& value) -> FloatMatrix4x4 {
  141. if (value.is_transformation())
  142. return transformation_style_value_to_matrix(element, value.as_transformation()).value_or(FloatMatrix4x4::identity());
  143. // This encompasses both the allowed value "none" and any invalid values
  144. if (!value.is_value_list())
  145. return FloatMatrix4x4::identity();
  146. auto matrix = FloatMatrix4x4::identity();
  147. for (auto const& value_element : value.as_value_list().values()) {
  148. if (value_element->is_transformation()) {
  149. if (auto value_matrix = transformation_style_value_to_matrix(element, value_element->as_transformation()); value_matrix.has_value())
  150. matrix = matrix * value_matrix.value();
  151. }
  152. }
  153. return matrix;
  154. };
  155. struct DecomposedValues {
  156. FloatVector3 translation;
  157. FloatVector3 scale;
  158. FloatVector3 skew;
  159. FloatVector4 rotation;
  160. FloatVector4 perspective;
  161. };
  162. // https://drafts.csswg.org/css-transforms-2/#decomposing-a-3d-matrix
  163. static constexpr auto decompose = [](FloatMatrix4x4 matrix) -> Optional<DecomposedValues> {
  164. // https://drafts.csswg.org/css-transforms-1/#supporting-functions
  165. static constexpr auto combine = [](auto a, auto b, float ascl, float bscl) {
  166. return FloatVector3 {
  167. ascl * a[0] + bscl * b[0],
  168. ascl * a[1] + bscl * b[1],
  169. ascl * a[2] + bscl * b[2],
  170. };
  171. };
  172. // Normalize the matrix.
  173. if (matrix(3, 3) == 0.f)
  174. return {};
  175. for (int i = 0; i < 4; i++)
  176. for (int j = 0; j < 4; j++)
  177. matrix(i, j) /= matrix(3, 3);
  178. // perspectiveMatrix is used to solve for perspective, but it also provides
  179. // an easy way to test for singularity of the upper 3x3 component.
  180. auto perspective_matrix = matrix;
  181. for (int i = 0; i < 3; i++)
  182. perspective_matrix(3, i) = 0.f;
  183. perspective_matrix(3, 3) = 1.f;
  184. if (!perspective_matrix.is_invertible())
  185. return {};
  186. DecomposedValues values;
  187. // First, isolate perspective.
  188. if (matrix(3, 0) != 0.f || matrix(3, 1) != 0.f || matrix(3, 2) != 0.f) {
  189. // rightHandSide is the right hand side of the equation.
  190. // Note: It is the bottom side in a row-major matrix
  191. FloatVector4 bottom_side = {
  192. matrix(3, 0),
  193. matrix(3, 1),
  194. matrix(3, 2),
  195. matrix(3, 3),
  196. };
  197. // Solve the equation by inverting perspectiveMatrix and multiplying
  198. // rightHandSide by the inverse.
  199. auto inverse_perspective_matrix = perspective_matrix.inverse();
  200. auto transposed_inverse_perspective_matrix = inverse_perspective_matrix.transpose();
  201. values.perspective = transposed_inverse_perspective_matrix * bottom_side;
  202. } else {
  203. // No perspective.
  204. values.perspective = { 0.0, 0.0, 0.0, 1.0 };
  205. }
  206. // Next take care of translation
  207. for (int i = 0; i < 3; i++)
  208. values.translation[i] = matrix(i, 3);
  209. // Now get scale and shear. 'row' is a 3 element array of 3 component vectors
  210. FloatVector3 row[3];
  211. for (int i = 0; i < 3; i++)
  212. row[i] = { matrix(0, i), matrix(1, i), matrix(2, i) };
  213. // Compute X scale factor and normalize first row.
  214. values.scale[0] = row[0].length();
  215. row[0].normalize();
  216. // Compute XY shear factor and make 2nd row orthogonal to 1st.
  217. values.skew[0] = row[0].dot(row[1]);
  218. row[1] = combine(row[1], row[0], 1.f, -values.skew[0]);
  219. // Now, compute Y scale and normalize 2nd row.
  220. values.scale[1] = row[1].length();
  221. row[1].normalize();
  222. values.skew[0] /= values.scale[1];
  223. // Compute XZ and YZ shears, orthogonalize 3rd row
  224. values.skew[1] = row[0].dot(row[2]);
  225. row[2] = combine(row[2], row[0], 1.f, -values.skew[1]);
  226. values.skew[2] = row[1].dot(row[2]);
  227. row[2] = combine(row[2], row[1], 1.f, -values.skew[2]);
  228. // Next, get Z scale and normalize 3rd row.
  229. values.scale[2] = row[2].length();
  230. row[2].normalize();
  231. values.skew[1] /= values.scale[2];
  232. values.skew[2] /= values.scale[2];
  233. // At this point, the matrix (in rows) is orthonormal.
  234. // Check for a coordinate system flip. If the determinant
  235. // is -1, then negate the matrix and the scaling factors.
  236. auto pdum3 = row[1].cross(row[2]);
  237. if (row[0].dot(pdum3) < 0.f) {
  238. for (int i = 0; i < 3; i++) {
  239. values.scale[i] *= -1.f;
  240. row[i][0] *= -1.f;
  241. row[i][1] *= -1.f;
  242. row[i][2] *= -1.f;
  243. }
  244. }
  245. // Now, get the rotations out
  246. values.rotation[0] = 0.5f * sqrt(max(1.f + row[0][0] - row[1][1] - row[2][2], 0.f));
  247. values.rotation[1] = 0.5f * sqrt(max(1.f - row[0][0] + row[1][1] - row[2][2], 0.f));
  248. values.rotation[2] = 0.5f * sqrt(max(1.f - row[0][0] - row[1][1] + row[2][2], 0.f));
  249. values.rotation[3] = 0.5f * sqrt(max(1.f + row[0][0] + row[1][1] + row[2][2], 0.f));
  250. if (row[2][1] > row[1][2])
  251. values.rotation[0] = -values.rotation[0];
  252. if (row[0][2] > row[2][0])
  253. values.rotation[1] = -values.rotation[1];
  254. if (row[1][0] > row[0][1])
  255. values.rotation[2] = -values.rotation[2];
  256. // FIXME: This accounts for the fact that the browser coordinate system is left-handed instead of right-handed.
  257. // The reason for this is that the positive Y-axis direction points down instead of up. To fix this, we
  258. // invert the Y axis. However, it feels like the spec pseudo-code above should have taken something like
  259. // this into account, so we're probably doing something else wrong.
  260. values.rotation[2] *= -1;
  261. return values;
  262. };
  263. // https://drafts.csswg.org/css-transforms-2/#recomposing-to-a-3d-matrix
  264. static constexpr auto recompose = [](DecomposedValues const& values) -> FloatMatrix4x4 {
  265. auto matrix = FloatMatrix4x4::identity();
  266. // apply perspective
  267. for (int i = 0; i < 4; i++)
  268. matrix(3, i) = values.perspective[i];
  269. // apply translation
  270. for (int i = 0; i < 4; i++) {
  271. for (int j = 0; j < 3; j++)
  272. matrix(i, 3) += values.translation[j] * matrix(i, j);
  273. }
  274. // apply rotation
  275. auto x = values.rotation[0];
  276. auto y = values.rotation[1];
  277. auto z = values.rotation[2];
  278. auto w = values.rotation[3];
  279. // Construct a composite rotation matrix from the quaternion values
  280. // rotationMatrix is a identity 4x4 matrix initially
  281. auto rotation_matrix = FloatMatrix4x4::identity();
  282. rotation_matrix(0, 0) = 1.f - 2.f * (y * y + z * z);
  283. rotation_matrix(1, 0) = 2.f * (x * y - z * w);
  284. rotation_matrix(2, 0) = 2.f * (x * z + y * w);
  285. rotation_matrix(0, 1) = 2.f * (x * y + z * w);
  286. rotation_matrix(1, 1) = 1.f - 2.f * (x * x + z * z);
  287. rotation_matrix(2, 1) = 2.f * (y * z - x * w);
  288. rotation_matrix(0, 2) = 2.f * (x * z - y * w);
  289. rotation_matrix(1, 2) = 2.f * (y * z + x * w);
  290. rotation_matrix(2, 2) = 1.f - 2.f * (x * x + y * y);
  291. matrix = matrix * rotation_matrix;
  292. // apply skew
  293. // temp is a identity 4x4 matrix initially
  294. auto temp = FloatMatrix4x4::identity();
  295. if (values.skew[2] != 0.f) {
  296. temp(1, 2) = values.skew[2];
  297. matrix = matrix * temp;
  298. }
  299. if (values.skew[1] != 0.f) {
  300. temp(1, 2) = 0.f;
  301. temp(0, 2) = values.skew[1];
  302. matrix = matrix * temp;
  303. }
  304. if (values.skew[0] != 0.f) {
  305. temp(0, 2) = 0.f;
  306. temp(0, 1) = values.skew[0];
  307. matrix = matrix * temp;
  308. }
  309. // apply scale
  310. for (int i = 0; i < 3; i++) {
  311. for (int j = 0; j < 4; j++)
  312. matrix(j, i) *= values.scale[i];
  313. }
  314. return matrix;
  315. };
  316. // https://drafts.csswg.org/css-transforms-2/#interpolation-of-decomposed-3d-matrix-values
  317. static constexpr auto interpolate = [](DecomposedValues& from, DecomposedValues& to, float delta) -> DecomposedValues {
  318. auto product = clamp(from.rotation.dot(to.rotation), -1.0f, 1.0f);
  319. FloatVector4 interpolated_rotation;
  320. if (fabsf(product) == 1.0f) {
  321. interpolated_rotation = from.rotation;
  322. } else {
  323. auto theta = acos(product);
  324. auto w = sin(delta * theta) / sqrtf(1.0f - product * product);
  325. for (int i = 0; i < 4; i++) {
  326. from.rotation[i] *= cos(delta * theta) - product * w;
  327. to.rotation[i] *= w;
  328. interpolated_rotation[i] = from.rotation[i] + to.rotation[i];
  329. }
  330. }
  331. return {
  332. interpolate_raw(from.translation, to.translation, delta),
  333. interpolate_raw(from.scale, to.scale, delta),
  334. interpolate_raw(from.skew, to.skew, delta),
  335. interpolated_rotation,
  336. interpolate_raw(from.perspective, to.perspective, delta),
  337. };
  338. };
  339. auto from_matrix = style_value_to_matrix(element, from);
  340. auto to_matrix = style_value_to_matrix(element, to);
  341. auto from_decomposed = decompose(from_matrix);
  342. auto to_decomposed = decompose(to_matrix);
  343. if (!from_decomposed.has_value() || !to_decomposed.has_value())
  344. return {};
  345. auto interpolated_decomposed = interpolate(from_decomposed.value(), to_decomposed.value(), delta);
  346. auto interpolated = recompose(interpolated_decomposed);
  347. StyleValueVector values;
  348. values.ensure_capacity(16);
  349. for (int i = 0; i < 16; i++)
  350. values.append(NumberStyleValue::create(static_cast<double>(interpolated(i % 4, i / 4))));
  351. return StyleValueList::create({ TransformationStyleValue::create(TransformFunction::Matrix3d, move(values)) }, StyleValueList::Separator::Comma);
  352. }
  353. Color interpolate_color(Color from, Color to, float delta)
  354. {
  355. // https://drafts.csswg.org/css-color/#interpolation-space
  356. // If the host syntax does not define what color space interpolation should take place in, it defaults to Oklab.
  357. auto from_oklab = from.to_oklab();
  358. auto to_oklab = to.to_oklab();
  359. auto color = Color::from_oklab(
  360. interpolate_raw(from_oklab.L, to_oklab.L, delta),
  361. interpolate_raw(from_oklab.a, to_oklab.a, delta),
  362. interpolate_raw(from_oklab.b, to_oklab.b, delta));
  363. color.set_alpha(interpolate_raw(from.alpha(), to.alpha(), delta));
  364. return color;
  365. }
  366. NonnullRefPtr<CSSStyleValue const> interpolate_box_shadow(DOM::Element& element, CSSStyleValue const& from, CSSStyleValue const& to, float delta)
  367. {
  368. // https://drafts.csswg.org/css-backgrounds/#box-shadow
  369. // Animation type: by computed value, treating none as a zero-item list and appending blank shadows
  370. // (transparent 0 0 0 0) with a corresponding inset keyword as needed to match the longer list if
  371. // the shorter list is otherwise compatible with the longer one
  372. static constexpr auto process_list = [](CSSStyleValue const& value) {
  373. StyleValueVector shadows;
  374. if (value.is_value_list()) {
  375. for (auto const& element : value.as_value_list().values()) {
  376. if (element->is_shadow())
  377. shadows.append(element);
  378. }
  379. } else if (value.is_shadow()) {
  380. shadows.append(value);
  381. } else if (!value.is_keyword() || value.as_keyword().keyword() != Keyword::None) {
  382. VERIFY_NOT_REACHED();
  383. }
  384. return shadows;
  385. };
  386. static constexpr auto extend_list_if_necessary = [](StyleValueVector& values, StyleValueVector const& other) {
  387. values.ensure_capacity(other.size());
  388. for (size_t i = values.size(); i < other.size(); i++) {
  389. values.unchecked_append(ShadowStyleValue::create(
  390. CSSColorValue::create_from_color(Color::Transparent),
  391. LengthStyleValue::create(Length::make_px(0)),
  392. LengthStyleValue::create(Length::make_px(0)),
  393. LengthStyleValue::create(Length::make_px(0)),
  394. LengthStyleValue::create(Length::make_px(0)),
  395. other[i]->as_shadow().placement()));
  396. }
  397. };
  398. StyleValueVector from_shadows = process_list(from);
  399. StyleValueVector to_shadows = process_list(to);
  400. extend_list_if_necessary(from_shadows, to_shadows);
  401. extend_list_if_necessary(to_shadows, from_shadows);
  402. VERIFY(from_shadows.size() == to_shadows.size());
  403. StyleValueVector result_shadows;
  404. result_shadows.ensure_capacity(from_shadows.size());
  405. for (size_t i = 0; i < from_shadows.size(); i++) {
  406. auto const& from_shadow = from_shadows[i]->as_shadow();
  407. auto const& to_shadow = to_shadows[i]->as_shadow();
  408. auto result_shadow = ShadowStyleValue::create(
  409. CSSColorValue::create_from_color(interpolate_color(from_shadow.color()->to_color({}), to_shadow.color()->to_color({}), delta)),
  410. interpolate_value(element, from_shadow.offset_x(), to_shadow.offset_x(), delta),
  411. interpolate_value(element, from_shadow.offset_y(), to_shadow.offset_y(), delta),
  412. interpolate_value(element, from_shadow.blur_radius(), to_shadow.blur_radius(), delta),
  413. interpolate_value(element, from_shadow.spread_distance(), to_shadow.spread_distance(), delta),
  414. delta >= 0.5f ? to_shadow.placement() : from_shadow.placement());
  415. result_shadows.unchecked_append(result_shadow);
  416. }
  417. return StyleValueList::create(move(result_shadows), StyleValueList::Separator::Comma);
  418. }
  419. NonnullRefPtr<CSSStyleValue const> interpolate_value(DOM::Element& element, CSSStyleValue const& from, CSSStyleValue const& to, float delta)
  420. {
  421. if (from.type() != to.type()) {
  422. // Handle mixed percentage and dimension types
  423. // https://www.w3.org/TR/css-values-4/#mixed-percentages
  424. struct NumericBaseTypeAndDefault {
  425. CSSNumericType::BaseType base_type;
  426. ValueComparingNonnullRefPtr<CSSStyleValue> default_value;
  427. };
  428. static constexpr auto numeric_base_type_and_default = [](CSSStyleValue const& value) -> Optional<NumericBaseTypeAndDefault> {
  429. switch (value.type()) {
  430. case CSSStyleValue::Type::Angle: {
  431. static auto default_angle_value = AngleStyleValue::create(Angle::make_degrees(0));
  432. return NumericBaseTypeAndDefault { CSSNumericType::BaseType::Angle, default_angle_value };
  433. }
  434. case CSSStyleValue::Type::Frequency: {
  435. static auto default_frequency_value = FrequencyStyleValue::create(Frequency::make_hertz(0));
  436. return NumericBaseTypeAndDefault { CSSNumericType::BaseType::Frequency, default_frequency_value };
  437. }
  438. case CSSStyleValue::Type::Length: {
  439. static auto default_length_value = LengthStyleValue::create(Length::make_px(0));
  440. return NumericBaseTypeAndDefault { CSSNumericType::BaseType::Length, default_length_value };
  441. }
  442. case CSSStyleValue::Type::Percentage: {
  443. static auto default_percentage_value = PercentageStyleValue::create(Percentage { 0.0 });
  444. return NumericBaseTypeAndDefault { CSSNumericType::BaseType::Percent, default_percentage_value };
  445. }
  446. case CSSStyleValue::Type::Time: {
  447. static auto default_time_value = TimeStyleValue::create(Time::make_seconds(0));
  448. return NumericBaseTypeAndDefault { CSSNumericType::BaseType::Time, default_time_value };
  449. }
  450. default:
  451. return {};
  452. }
  453. };
  454. static constexpr auto to_calculation_node = [](CSSStyleValue const& value) -> NonnullOwnPtr<CalculationNode> {
  455. switch (value.type()) {
  456. case CSSStyleValue::Type::Angle:
  457. return NumericCalculationNode::create(value.as_angle().angle());
  458. case CSSStyleValue::Type::Frequency:
  459. return NumericCalculationNode::create(value.as_frequency().frequency());
  460. case CSSStyleValue::Type::Length:
  461. return NumericCalculationNode::create(value.as_length().length());
  462. case CSSStyleValue::Type::Percentage:
  463. return NumericCalculationNode::create(value.as_percentage().percentage());
  464. case CSSStyleValue::Type::Time:
  465. return NumericCalculationNode::create(value.as_time().time());
  466. default:
  467. VERIFY_NOT_REACHED();
  468. }
  469. };
  470. auto from_base_type_and_default = numeric_base_type_and_default(from);
  471. auto to_base_type_and_default = numeric_base_type_and_default(to);
  472. if (from_base_type_and_default.has_value() && to_base_type_and_default.has_value() && (from_base_type_and_default->base_type == CSSNumericType::BaseType::Percent || to_base_type_and_default->base_type == CSSNumericType::BaseType::Percent)) {
  473. // This is an interpolation from a numeric unit to a percentage, or vice versa. The trick here is to
  474. // interpolate two separate values. For example, consider an interpolation from 30px to 80%. It's quite
  475. // hard to understand how this interpolation works, but if instead we rewrite the values as "30px + 0%" and
  476. // "0px + 80%", then it is very simple to understand; we just interpolate each component separately.
  477. auto interpolated_from = interpolate_value(element, from, from_base_type_and_default->default_value, delta);
  478. auto interpolated_to = interpolate_value(element, to_base_type_and_default->default_value, to, delta);
  479. Vector<NonnullOwnPtr<CalculationNode>> values;
  480. values.ensure_capacity(2);
  481. values.unchecked_append(to_calculation_node(interpolated_from));
  482. values.unchecked_append(to_calculation_node(interpolated_to));
  483. auto calc_node = SumCalculationNode::create(move(values));
  484. return CSSMathValue::create(move(calc_node), CSSNumericType { to_base_type_and_default->base_type, 1 });
  485. }
  486. return delta >= 0.5f ? to : from;
  487. }
  488. switch (from.type()) {
  489. case CSSStyleValue::Type::Angle:
  490. return AngleStyleValue::create(Angle::make_degrees(interpolate_raw(from.as_angle().angle().to_degrees(), to.as_angle().angle().to_degrees(), delta)));
  491. case CSSStyleValue::Type::Color: {
  492. Optional<Layout::NodeWithStyle const&> layout_node;
  493. if (auto node = element.layout_node())
  494. layout_node = *node;
  495. return CSSColorValue::create_from_color(interpolate_color(from.to_color(layout_node), to.to_color(layout_node), delta));
  496. }
  497. case CSSStyleValue::Type::Integer:
  498. return IntegerStyleValue::create(interpolate_raw(from.as_integer().integer(), to.as_integer().integer(), delta));
  499. case CSSStyleValue::Type::Length: {
  500. auto& from_length = from.as_length().length();
  501. auto& to_length = to.as_length().length();
  502. return LengthStyleValue::create(Length(interpolate_raw(from_length.raw_value(), to_length.raw_value(), delta), from_length.type()));
  503. }
  504. case CSSStyleValue::Type::Number:
  505. return NumberStyleValue::create(interpolate_raw(from.as_number().number(), to.as_number().number(), delta));
  506. case CSSStyleValue::Type::Percentage:
  507. return PercentageStyleValue::create(Percentage(interpolate_raw(from.as_percentage().percentage().value(), to.as_percentage().percentage().value(), delta)));
  508. case CSSStyleValue::Type::Position: {
  509. // https://www.w3.org/TR/css-values-4/#combine-positions
  510. // FIXME: Interpolation of <position> is defined as the independent interpolation of each component (x, y) normalized as an offset from the top left corner as a <length-percentage>.
  511. auto& from_position = from.as_position();
  512. auto& to_position = to.as_position();
  513. return PositionStyleValue::create(
  514. interpolate_value(element, from_position.edge_x(), to_position.edge_x(), delta)->as_edge(),
  515. interpolate_value(element, from_position.edge_y(), to_position.edge_y(), delta)->as_edge());
  516. }
  517. case CSSStyleValue::Type::Ratio: {
  518. auto from_ratio = from.as_ratio().ratio();
  519. auto to_ratio = to.as_ratio().ratio();
  520. // The interpolation of a <ratio> is defined by converting each <ratio> to a number by dividing the first value
  521. // by the second (so a ratio of 3 / 2 would become 1.5), taking the logarithm of that result (so the 1.5 would
  522. // become approximately 0.176), then interpolating those values. The result during the interpolation is
  523. // converted back to a <ratio> by inverting the logarithm, then interpreting the result as a <ratio> with the
  524. // result as the first value and 1 as the second value.
  525. auto from_number = log(from_ratio.value());
  526. auto to_number = log(to_ratio.value());
  527. auto interp_number = interpolate_raw(from_number, to_number, delta);
  528. return RatioStyleValue::create(Ratio(pow(M_E, interp_number)));
  529. }
  530. case CSSStyleValue::Type::Rect: {
  531. auto from_rect = from.as_rect().rect();
  532. auto to_rect = to.as_rect().rect();
  533. return RectStyleValue::create({
  534. Length(interpolate_raw(from_rect.top_edge.raw_value(), to_rect.top_edge.raw_value(), delta), from_rect.top_edge.type()),
  535. Length(interpolate_raw(from_rect.right_edge.raw_value(), to_rect.right_edge.raw_value(), delta), from_rect.right_edge.type()),
  536. Length(interpolate_raw(from_rect.bottom_edge.raw_value(), to_rect.bottom_edge.raw_value(), delta), from_rect.bottom_edge.type()),
  537. Length(interpolate_raw(from_rect.left_edge.raw_value(), to_rect.left_edge.raw_value(), delta), from_rect.left_edge.type()),
  538. });
  539. }
  540. case CSSStyleValue::Type::Transformation:
  541. VERIFY_NOT_REACHED();
  542. case CSSStyleValue::Type::ValueList: {
  543. auto& from_list = from.as_value_list();
  544. auto& to_list = to.as_value_list();
  545. if (from_list.size() != to_list.size())
  546. return from;
  547. StyleValueVector interpolated_values;
  548. interpolated_values.ensure_capacity(from_list.size());
  549. for (size_t i = 0; i < from_list.size(); ++i)
  550. interpolated_values.append(interpolate_value(element, from_list.values()[i], to_list.values()[i], delta));
  551. return StyleValueList::create(move(interpolated_values), from_list.separator());
  552. }
  553. default:
  554. return from;
  555. }
  556. }
  557. }