| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  | /*
 | 
					
						
							| 
									
										
										
										
											2024-10-04 13:19:50 +02:00
										 |  |  |  * Copyright (c) 2020-2022, Andreas Kling <andreas@ladybird.org> | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |  * Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org> | 
					
						
							| 
									
										
										
										
											2025-05-01 12:53:54 +02:00
										 |  |  |  * Copyright (c) 2024-2025, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com> | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |  * | 
					
						
							|  |  |  |  * SPDX-License-Identifier: BSD-2-Clause | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "Transformation.h"
 | 
					
						
							|  |  |  | #include <LibWeb/Painting/PaintableBox.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace Web::CSS { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Transformation::Transformation(TransformFunction function, Vector<TransformValue>&& values) | 
					
						
							|  |  |  |     : m_function(function) | 
					
						
							|  |  |  |     , m_values(move(values)) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  | ErrorOr<Gfx::FloatMatrix4x4> Transformation::to_matrix(Optional<Painting::PaintableBox const&> paintable_box) const | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     auto count = m_values.size(); | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |     auto value = [&](size_t index, CSSPixels const& reference_length = 0) -> ErrorOr<float> { | 
					
						
							| 
									
										
										
										
											2025-01-22 15:00:13 +00:00
										 |  |  |         CalculationResolutionContext context {}; | 
					
						
							|  |  |  |         if (paintable_box.has_value()) | 
					
						
							|  |  |  |             context.length_resolution_context = Length::ResolutionContext::for_layout_node(paintable_box->layout_node()); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |         return m_values[index].visit( | 
					
						
							| 
									
										
										
										
											2025-05-01 12:53:54 +02:00
										 |  |  |             [&](CSS::AngleOrCalculated const& value) -> ErrorOr<float> { | 
					
						
							|  |  |  |                 if (!value.is_calculated()) | 
					
						
							| 
									
										
										
										
											2025-08-03 21:05:26 +12:00
										 |  |  |                     return fmod(value.value().to_radians(), 2 * AK::Pi<double>); | 
					
						
							| 
									
										
										
										
											2025-05-01 12:53:54 +02:00
										 |  |  |                 if (auto resolved = value.resolved(context); resolved.has_value()) | 
					
						
							| 
									
										
										
										
											2025-08-03 21:05:26 +12:00
										 |  |  |                     return fmod(resolved->to_radians(), 2 * AK::Pi<double>); | 
					
						
							| 
									
										
										
										
											2025-05-01 12:53:54 +02:00
										 |  |  |                 return Error::from_string_literal("Transform contains non absolute units"); | 
					
						
							|  |  |  |             }, | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |             [&](CSS::LengthPercentage const& value) -> ErrorOr<float> { | 
					
						
							| 
									
										
										
										
											2025-01-22 15:00:13 +00:00
										 |  |  |                 context.percentage_basis = Length::make_px(reference_length); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |                 if (paintable_box.has_value()) | 
					
						
							|  |  |  |                     return value.resolved(paintable_box->layout_node(), reference_length).to_px(paintable_box->layout_node()).to_float(); | 
					
						
							| 
									
										
										
										
											2024-02-17 08:09:53 +00:00
										 |  |  |                 if (value.is_length()) { | 
					
						
							|  |  |  |                     if (auto const& length = value.length(); length.is_absolute()) | 
					
						
							|  |  |  |                         return length.absolute_length_to_px().to_float(); | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2025-05-01 12:53:54 +02:00
										 |  |  |                 if (value.is_calculated() && value.calculated()->resolves_to_length()) { | 
					
						
							| 
									
										
										
										
											2025-07-17 00:27:13 +12:00
										 |  |  |                     if (auto const& resolved = value.calculated()->resolve_length(context); resolved.has_value() && resolved->is_absolute()) | 
					
						
							| 
									
										
										
										
											2025-05-01 12:53:54 +02:00
										 |  |  |                         return resolved->absolute_length_to_px().to_float(); | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |                 return Error::from_string_literal("Transform contains non absolute units"); | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |             }, | 
					
						
							| 
									
										
										
										
											2024-01-08 18:51:19 +01:00
										 |  |  |             [&](CSS::NumberPercentage const& value) -> ErrorOr<float> { | 
					
						
							| 
									
										
										
										
											2025-03-25 14:04:17 +00:00
										 |  |  |                 if (value.is_number()) | 
					
						
							|  |  |  |                     return value.number().value(); | 
					
						
							| 
									
										
										
										
											2025-05-01 12:53:54 +02:00
										 |  |  |                 if (value.is_percentage()) | 
					
						
							|  |  |  |                     return value.percentage().as_fraction(); | 
					
						
							| 
									
										
										
										
											2025-03-25 14:04:17 +00:00
										 |  |  |                 if (value.is_calculated()) { | 
					
						
							|  |  |  |                     if (value.calculated()->resolves_to_number()) | 
					
						
							| 
									
										
										
										
											2025-07-17 00:27:13 +12:00
										 |  |  |                         if (auto resolved_number = value.calculated()->resolve_number(context); resolved_number.has_value()) | 
					
						
							|  |  |  |                             return resolved_number.value(); | 
					
						
							| 
									
										
										
										
											2025-03-25 14:04:17 +00:00
										 |  |  |                     if (value.calculated()->resolves_to_percentage()) | 
					
						
							| 
									
										
										
										
											2025-07-17 00:27:13 +12:00
										 |  |  |                         if (auto resolved_percentage = value.calculated()->resolve_percentage(context); resolved_percentage.has_value()) | 
					
						
							|  |  |  |                             return resolved_percentage->as_fraction(); | 
					
						
							| 
									
										
										
										
											2025-03-25 14:04:17 +00:00
										 |  |  |                 } | 
					
						
							|  |  |  |                 return Error::from_string_literal("Transform contains non absolute units"); | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |             }); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |     CSSPixels width = 1; | 
					
						
							|  |  |  |     CSSPixels height = 1; | 
					
						
							|  |  |  |     if (paintable_box.has_value()) { | 
					
						
							| 
									
										
										
										
											2024-09-05 15:15:17 +01:00
										 |  |  |         auto reference_box = paintable_box->transform_box_rect(); | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |         width = reference_box.width(); | 
					
						
							|  |  |  |         height = reference_box.height(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     switch (m_function) { | 
					
						
							| 
									
										
										
										
											2024-01-08 18:51:48 +01:00
										 |  |  |     case CSS::TransformFunction::Perspective: | 
					
						
							|  |  |  |         // https://drafts.csswg.org/css-transforms-2/#perspective
 | 
					
						
							|  |  |  |         // Count is zero when null parameter
 | 
					
						
							|  |  |  |         if (count == 1) { | 
					
						
							|  |  |  |             // FIXME: Add support for the 'perspective-origin' CSS property.
 | 
					
						
							|  |  |  |             auto distance = TRY(value(0)); | 
					
						
							|  |  |  |             return Gfx::FloatMatrix4x4(1, 0, 0, 0, | 
					
						
							|  |  |  |                 0, 1, 0, 0, | 
					
						
							|  |  |  |                 0, 0, 1, 0, | 
					
						
							|  |  |  |                 0, 0, -1 / (distance <= 0 ? 1 : distance), 1); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-09-12 07:57:50 -04:00
										 |  |  |         return Gfx::FloatMatrix4x4(1, 0, 0, 0, | 
					
						
							|  |  |  |             0, 1, 0, 0, | 
					
						
							|  |  |  |             0, 0, 1, 0, | 
					
						
							|  |  |  |             0, 0, 0, 1); | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |     case CSS::TransformFunction::Matrix: | 
					
						
							|  |  |  |         if (count == 6) | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |             return Gfx::FloatMatrix4x4(TRY(value(0)), TRY(value(2)), 0, TRY(value(4)), | 
					
						
							|  |  |  |                 TRY(value(1)), TRY(value(3)), 0, TRY(value(5)), | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |                 0, 0, 1, 0, | 
					
						
							|  |  |  |                 0, 0, 0, 1); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case CSS::TransformFunction::Matrix3d: | 
					
						
							|  |  |  |         if (count == 16) | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |             return Gfx::FloatMatrix4x4(TRY(value(0)), TRY(value(4)), TRY(value(8)), TRY(value(12)), | 
					
						
							|  |  |  |                 TRY(value(1)), TRY(value(5)), TRY(value(9)), TRY(value(13)), | 
					
						
							|  |  |  |                 TRY(value(2)), TRY(value(6)), TRY(value(10)), TRY(value(14)), | 
					
						
							|  |  |  |                 TRY(value(3)), TRY(value(7)), TRY(value(11)), TRY(value(15))); | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |         break; | 
					
						
							|  |  |  |     case CSS::TransformFunction::Translate: | 
					
						
							|  |  |  |         if (count == 1) | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |             return Gfx::FloatMatrix4x4(1, 0, 0, TRY(value(0, width)), | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |                 0, 1, 0, 0, | 
					
						
							|  |  |  |                 0, 0, 1, 0, | 
					
						
							|  |  |  |                 0, 0, 0, 1); | 
					
						
							|  |  |  |         if (count == 2) | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |             return Gfx::FloatMatrix4x4(1, 0, 0, TRY(value(0, width)), | 
					
						
							|  |  |  |                 0, 1, 0, TRY(value(1, height)), | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |                 0, 0, 1, 0, | 
					
						
							|  |  |  |                 0, 0, 0, 1); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case CSS::TransformFunction::Translate3d: | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |         return Gfx::FloatMatrix4x4(1, 0, 0, TRY(value(0, width)), | 
					
						
							|  |  |  |             0, 1, 0, TRY(value(1, height)), | 
					
						
							|  |  |  |             0, 0, 1, TRY(value(2)), | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |             0, 0, 0, 1); | 
					
						
							|  |  |  |     case CSS::TransformFunction::TranslateX: | 
					
						
							|  |  |  |         if (count == 1) | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |             return Gfx::FloatMatrix4x4(1, 0, 0, TRY(value(0, width)), | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |                 0, 1, 0, 0, | 
					
						
							|  |  |  |                 0, 0, 1, 0, | 
					
						
							|  |  |  |                 0, 0, 0, 1); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case CSS::TransformFunction::TranslateY: | 
					
						
							|  |  |  |         if (count == 1) | 
					
						
							|  |  |  |             return Gfx::FloatMatrix4x4(1, 0, 0, 0, | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |                 0, 1, 0, TRY(value(0, height)), | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |                 0, 0, 1, 0, | 
					
						
							|  |  |  |                 0, 0, 0, 1); | 
					
						
							|  |  |  |         break; | 
					
						
							| 
									
										
										
										
											2023-11-30 21:19:09 +01:00
										 |  |  |     case CSS::TransformFunction::TranslateZ: | 
					
						
							|  |  |  |         if (count == 1) | 
					
						
							|  |  |  |             return Gfx::FloatMatrix4x4(1, 0, 0, 0, | 
					
						
							|  |  |  |                 0, 1, 0, 0, | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |                 0, 0, 1, TRY(value(0)), | 
					
						
							| 
									
										
										
										
											2023-11-30 21:19:09 +01:00
										 |  |  |                 0, 0, 0, 1); | 
					
						
							|  |  |  |         break; | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |     case CSS::TransformFunction::Scale: | 
					
						
							|  |  |  |         if (count == 1) | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |             return Gfx::FloatMatrix4x4(TRY(value(0)), 0, 0, 0, | 
					
						
							|  |  |  |                 0, TRY(value(0)), 0, 0, | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |                 0, 0, 1, 0, | 
					
						
							|  |  |  |                 0, 0, 0, 1); | 
					
						
							|  |  |  |         if (count == 2) | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |             return Gfx::FloatMatrix4x4(TRY(value(0)), 0, 0, 0, | 
					
						
							|  |  |  |                 0, TRY(value(1)), 0, 0, | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |                 0, 0, 1, 0, | 
					
						
							|  |  |  |                 0, 0, 0, 1); | 
					
						
							|  |  |  |         break; | 
					
						
							| 
									
										
										
										
											2024-01-08 18:51:48 +01:00
										 |  |  |     case CSS::TransformFunction::Scale3d: | 
					
						
							|  |  |  |         if (count == 3) | 
					
						
							|  |  |  |             return Gfx::FloatMatrix4x4(TRY(value(0)), 0, 0, 0, | 
					
						
							|  |  |  |                 0, TRY(value(1)), 0, 0, | 
					
						
							|  |  |  |                 0, 0, TRY(value(2)), 0, | 
					
						
							|  |  |  |                 0, 0, 0, 1); | 
					
						
							|  |  |  |         break; | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |     case CSS::TransformFunction::ScaleX: | 
					
						
							|  |  |  |         if (count == 1) | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |             return Gfx::FloatMatrix4x4(TRY(value(0)), 0, 0, 0, | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |                 0, 1, 0, 0, | 
					
						
							|  |  |  |                 0, 0, 1, 0, | 
					
						
							|  |  |  |                 0, 0, 0, 1); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case CSS::TransformFunction::ScaleY: | 
					
						
							|  |  |  |         if (count == 1) | 
					
						
							|  |  |  |             return Gfx::FloatMatrix4x4(1, 0, 0, 0, | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |                 0, TRY(value(0)), 0, 0, | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |                 0, 0, 1, 0, | 
					
						
							|  |  |  |                 0, 0, 0, 1); | 
					
						
							|  |  |  |         break; | 
					
						
							| 
									
										
										
										
											2024-01-08 18:51:48 +01:00
										 |  |  |     case CSS::TransformFunction::ScaleZ: | 
					
						
							|  |  |  |         if (count == 1) | 
					
						
							|  |  |  |             return Gfx::FloatMatrix4x4(1, 0, 0, 0, | 
					
						
							|  |  |  |                 0, 1, 0, 0, | 
					
						
							|  |  |  |                 0, 0, TRY(value(0)), 0, | 
					
						
							|  |  |  |                 0, 0, 0, 1); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case CSS::TransformFunction::Rotate3d: | 
					
						
							|  |  |  |         if (count == 4) | 
					
						
							|  |  |  |             return Gfx::rotation_matrix({ TRY(value(0)), TRY(value(1)), TRY(value(2)) }, TRY(value(3))); | 
					
						
							|  |  |  |         break; | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |     case CSS::TransformFunction::RotateX: | 
					
						
							|  |  |  |         if (count == 1) | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |             return Gfx::rotation_matrix({ 1.0f, 0.0f, 0.0f }, TRY(value(0))); | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |         break; | 
					
						
							|  |  |  |     case CSS::TransformFunction::RotateY: | 
					
						
							|  |  |  |         if (count == 1) | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |             return Gfx::rotation_matrix({ 0.0f, 1.0f, 0.0f }, TRY(value(0))); | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |         break; | 
					
						
							|  |  |  |     case CSS::TransformFunction::Rotate: | 
					
						
							|  |  |  |     case CSS::TransformFunction::RotateZ: | 
					
						
							|  |  |  |         if (count == 1) | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |             return Gfx::rotation_matrix({ 0.0f, 0.0f, 1.0f }, TRY(value(0))); | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |         break; | 
					
						
							|  |  |  |     case CSS::TransformFunction::Skew: | 
					
						
							|  |  |  |         if (count == 1) | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |             return Gfx::FloatMatrix4x4(1, tanf(TRY(value(0))), 0, 0, | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |                 0, 1, 0, 0, | 
					
						
							|  |  |  |                 0, 0, 1, 0, | 
					
						
							|  |  |  |                 0, 0, 0, 1); | 
					
						
							|  |  |  |         if (count == 2) | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |             return Gfx::FloatMatrix4x4(1, tanf(TRY(value(0))), 0, 0, | 
					
						
							|  |  |  |                 tanf(TRY(value(1))), 1, 0, 0, | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |                 0, 0, 1, 0, | 
					
						
							|  |  |  |                 0, 0, 0, 1); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case CSS::TransformFunction::SkewX: | 
					
						
							|  |  |  |         if (count == 1) | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |             return Gfx::FloatMatrix4x4(1, tanf(TRY(value(0))), 0, 0, | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |                 0, 1, 0, 0, | 
					
						
							|  |  |  |                 0, 0, 1, 0, | 
					
						
							|  |  |  |                 0, 0, 0, 1); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case CSS::TransformFunction::SkewY: | 
					
						
							|  |  |  |         if (count == 1) | 
					
						
							|  |  |  |             return Gfx::FloatMatrix4x4(1, 0, 0, 0, | 
					
						
							| 
									
										
										
										
											2024-01-06 18:05:21 +01:00
										 |  |  |                 tanf(TRY(value(0))), 1, 0, 0, | 
					
						
							| 
									
										
										
										
											2023-10-13 17:25:23 +01:00
										 |  |  |                 0, 0, 1, 0, | 
					
						
							|  |  |  |                 0, 0, 0, 1); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     dbgln_if(LIBWEB_CSS_DEBUG, "FIXME: Unhandled transformation function {} with {} arguments", to_string(m_function), m_values.size()); | 
					
						
							|  |  |  |     return Gfx::FloatMatrix4x4::identity(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |