| 
									
										
										
										
											2023-03-24 17:59:33 +00:00
										 |  |  | /*
 | 
					
						
							| 
									
										
										
										
											2025-01-15 14:58:23 +00:00
										 |  |  |  * Copyright (c) 2018-2024, Andreas Kling <andreas@ladybird.org> | 
					
						
							| 
									
										
										
										
											2023-03-24 17:59:33 +00:00
										 |  |  |  * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org> | 
					
						
							| 
									
										
										
										
											2025-01-15 14:58:23 +00:00
										 |  |  |  * Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org> | 
					
						
							| 
									
										
										
										
											2023-03-24 17:59:33 +00:00
										 |  |  |  * Copyright (c) 2022-2023, MacDue <macdue@dueutil.tech> | 
					
						
							| 
									
										
										
										
											2025-01-15 17:21:22 +00:00
										 |  |  |  * Copyright (c) 2024, Steffen T. Larssen <dudedbz@gmail.com> | 
					
						
							| 
									
										
										
										
											2023-03-24 17:59:33 +00:00
										 |  |  |  * | 
					
						
							|  |  |  |  * SPDX-License-Identifier: BSD-2-Clause | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "TransformationStyleValue.h"
 | 
					
						
							|  |  |  | #include <AK/StringBuilder.h>
 | 
					
						
							| 
									
										
										
										
											2025-08-18 16:07:12 +01:00
										 |  |  | #include <LibWeb/CSS/Serialize.h>
 | 
					
						
							| 
									
										
										
										
											2025-01-15 16:03:06 +00:00
										 |  |  | #include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
 | 
					
						
							|  |  |  | #include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
 | 
					
						
							|  |  |  | #include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
 | 
					
						
							| 
									
										
										
										
											2024-07-02 21:47:51 +02:00
										 |  |  | #include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
 | 
					
						
							| 
									
										
										
										
											2025-01-15 16:03:06 +00:00
										 |  |  | #include <LibWeb/CSS/Transformation.h>
 | 
					
						
							| 
									
										
										
										
											2023-03-24 17:59:33 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | namespace Web::CSS { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-15 16:03:06 +00:00
										 |  |  | Transformation TransformationStyleValue::to_transformation() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     auto function_metadata = transform_function_metadata(m_properties.transform_function); | 
					
						
							|  |  |  |     Vector<TransformValue> values; | 
					
						
							|  |  |  |     size_t argument_index = 0; | 
					
						
							|  |  |  |     for (auto& transformation_value : m_properties.values) { | 
					
						
							| 
									
										
										
										
											2025-03-25 14:04:17 +00:00
										 |  |  |         auto const function_type = function_metadata.parameters[argument_index].type; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-15 16:03:06 +00:00
										 |  |  |         if (transformation_value->is_calculated()) { | 
					
						
							|  |  |  |             auto& calculated = transformation_value->as_calculated(); | 
					
						
							| 
									
										
										
										
											2025-03-25 14:04:17 +00:00
										 |  |  |             if (function_type == TransformFunctionParameterType::NumberPercentage | 
					
						
							|  |  |  |                 && (calculated.resolves_to_number() || calculated.resolves_to_percentage())) { | 
					
						
							|  |  |  |                 values.append(NumberPercentage { calculated }); | 
					
						
							|  |  |  |             } else if (calculated.resolves_to_length_percentage()) { | 
					
						
							| 
									
										
										
										
											2025-01-15 16:03:06 +00:00
										 |  |  |                 values.append(LengthPercentage { calculated }); | 
					
						
							|  |  |  |             } else if (calculated.resolves_to_number()) { | 
					
						
							| 
									
										
										
										
											2025-01-21 16:15:58 +00:00
										 |  |  |                 values.append(NumberPercentage { calculated }); | 
					
						
							| 
									
										
										
										
											2025-01-15 16:03:06 +00:00
										 |  |  |             } else if (calculated.resolves_to_angle()) { | 
					
						
							| 
									
										
										
										
											2025-01-21 16:15:58 +00:00
										 |  |  |                 values.append(AngleOrCalculated { calculated }); | 
					
						
							| 
									
										
										
										
											2025-01-15 16:03:06 +00:00
										 |  |  |             } else { | 
					
						
							|  |  |  |                 dbgln("FIXME: Unsupported calc value in transform! {}", calculated.to_string(SerializationMode::Normal)); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } else if (transformation_value->is_length()) { | 
					
						
							|  |  |  |             values.append({ transformation_value->as_length().length() }); | 
					
						
							|  |  |  |         } else if (transformation_value->is_percentage()) { | 
					
						
							| 
									
										
										
										
											2025-03-25 14:04:17 +00:00
										 |  |  |             if (function_type == TransformFunctionParameterType::NumberPercentage) { | 
					
						
							| 
									
										
										
										
											2025-01-15 16:03:06 +00:00
										 |  |  |                 values.append(NumberPercentage { transformation_value->as_percentage().percentage() }); | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 values.append(LengthPercentage { transformation_value->as_percentage().percentage() }); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } else if (transformation_value->is_number()) { | 
					
						
							|  |  |  |             values.append({ Number(Number::Type::Number, transformation_value->as_number().number()) }); | 
					
						
							|  |  |  |         } else if (transformation_value->is_angle()) { | 
					
						
							|  |  |  |             values.append({ transformation_value->as_angle().angle() }); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             dbgln("FIXME: Unsupported value in transform! {}", transformation_value->to_string(SerializationMode::Normal)); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         argument_index++; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return Transformation { m_properties.transform_function, move(values) }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-07 00:59:49 +01:00
										 |  |  | String TransformationStyleValue::to_string(SerializationMode mode) const | 
					
						
							| 
									
										
										
										
											2023-03-24 17:59:33 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2025-01-15 14:58:23 +00:00
										 |  |  |     // https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization
 | 
					
						
							| 
									
										
										
										
											2025-01-15 17:21:22 +00:00
										 |  |  |     if (m_properties.property == PropertyID::Rotate) { | 
					
						
							| 
									
										
										
										
											2025-08-08 10:11:51 +01:00
										 |  |  |         auto resolve_to_number = [](ValueComparingNonnullRefPtr<StyleValue const> const& value) -> Optional<double> { | 
					
						
							| 
									
										
										
										
											2025-01-15 17:21:22 +00:00
										 |  |  |             if (value->is_number()) | 
					
						
							|  |  |  |                 return value->as_number().number(); | 
					
						
							|  |  |  |             if (value->is_calculated() && value->as_calculated().resolves_to_number()) | 
					
						
							| 
									
										
										
										
											2025-07-02 19:12:33 +12:00
										 |  |  |                 return value->as_calculated().resolve_number_deprecated({}); | 
					
						
							| 
									
										
										
										
											2025-01-15 17:21:22 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |             VERIFY_NOT_REACHED(); | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // NOTE: Serialize simple rotations directly.
 | 
					
						
							|  |  |  |         switch (m_properties.transform_function) { | 
					
						
							|  |  |  |             // If the axis is parallel with the x or y axes, it must serialize as the appropriate keyword.
 | 
					
						
							|  |  |  |         case TransformFunction::RotateX: | 
					
						
							|  |  |  |             return MUST(String::formatted("x {}", m_properties.values[0]->to_string(mode))); | 
					
						
							|  |  |  |         case TransformFunction::RotateY: | 
					
						
							|  |  |  |             return MUST(String::formatted("y {}", m_properties.values[0]->to_string(mode))); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // If a rotation about the z axis (that is, in 2D) is specified, the property must serialize as just an <angle>.
 | 
					
						
							|  |  |  |         case TransformFunction::Rotate: | 
					
						
							|  |  |  |         case TransformFunction::RotateZ: | 
					
						
							|  |  |  |             return m_properties.values[0]->to_string(mode); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         default: | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         auto& rotation_x = m_properties.values[0]; | 
					
						
							|  |  |  |         auto& rotation_y = m_properties.values[1]; | 
					
						
							|  |  |  |         auto& rotation_z = m_properties.values[2]; | 
					
						
							|  |  |  |         auto& angle = m_properties.values[3]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         auto x_value = resolve_to_number(rotation_x).value_or(0); | 
					
						
							|  |  |  |         auto y_value = resolve_to_number(rotation_y).value_or(0); | 
					
						
							|  |  |  |         auto z_value = resolve_to_number(rotation_z).value_or(0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // If the axis is parallel with the x or y axes, it must serialize as the appropriate keyword.
 | 
					
						
							|  |  |  |         if (x_value > 0.0 && y_value == 0 && z_value == 0) | 
					
						
							|  |  |  |             return MUST(String::formatted("x {}", angle->to_string(mode))); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (x_value == 0 && y_value > 0.0 && z_value == 0) | 
					
						
							|  |  |  |             return MUST(String::formatted("y {}", angle->to_string(mode))); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // If a rotation about the z axis (that is, in 2D) is specified, the property must serialize as just an <angle>.
 | 
					
						
							|  |  |  |         if (x_value == 0 && y_value == 0 && z_value > 0.0) | 
					
						
							|  |  |  |             return angle->to_string(mode); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // It must serialize as the keyword none if and only if none was originally specified.
 | 
					
						
							|  |  |  |         // NOTE: This is handled by returning a keyword from the parser.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // If any other rotation is specified, the property must serialize with an axis specified.
 | 
					
						
							|  |  |  |         return MUST(String::formatted("{} {} {} {}", rotation_x->to_string(mode), rotation_y->to_string(mode), rotation_z->to_string(mode), angle->to_string(mode))); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-01-15 14:58:23 +00:00
										 |  |  |     if (m_properties.property == PropertyID::Scale) { | 
					
						
							| 
									
										
										
										
											2025-08-08 10:11:51 +01:00
										 |  |  |         auto resolve_to_string = [mode](StyleValue const& value) -> String { | 
					
						
							| 
									
										
										
										
											2025-08-01 16:43:05 +12:00
										 |  |  |             Optional<double> raw_value; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (value.is_number()) | 
					
						
							|  |  |  |                 raw_value = value.as_number().number(); | 
					
						
							|  |  |  |             if (value.is_percentage()) | 
					
						
							|  |  |  |                 raw_value = value.as_percentage().percentage().as_fraction(); | 
					
						
							|  |  |  |             if (value.is_calculated()) { | 
					
						
							|  |  |  |                 if (value.as_calculated().resolves_to_number()) { | 
					
						
							|  |  |  |                     if (auto resolved = value.as_calculated().resolve_number({}); resolved.has_value()) | 
					
						
							|  |  |  |                         raw_value = *resolved; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 if (value.as_calculated().resolves_to_percentage()) { | 
					
						
							|  |  |  |                     if (auto resolved = value.as_calculated().resolve_percentage({}); resolved.has_value()) | 
					
						
							|  |  |  |                         raw_value = resolved->as_fraction(); | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2025-01-15 14:58:23 +00:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-08-01 16:43:05 +12:00
										 |  |  | 
 | 
					
						
							|  |  |  |             if (!raw_value.has_value()) | 
					
						
							|  |  |  |                 return value.to_string(mode); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-18 16:07:12 +01:00
										 |  |  |             return serialize_a_number(*raw_value); | 
					
						
							| 
									
										
										
										
											2025-01-15 14:58:23 +00:00
										 |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         auto x_value = resolve_to_string(m_properties.values[0]); | 
					
						
							|  |  |  |         auto y_value = resolve_to_string(m_properties.values[1]); | 
					
						
							| 
									
										
										
										
											2025-04-25 13:03:02 +02:00
										 |  |  |         Optional<String> z_value; | 
					
						
							| 
									
										
										
										
											2025-04-29 16:58:41 +01:00
										 |  |  |         if (m_properties.values.size() == 3 && (!m_properties.values[2]->is_number() || m_properties.values[2]->as_number().number() != 1)) | 
					
						
							| 
									
										
										
										
											2025-04-25 13:03:02 +02:00
										 |  |  |             z_value = resolve_to_string(m_properties.values[2]); | 
					
						
							| 
									
										
										
										
											2025-01-15 14:58:23 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         StringBuilder builder; | 
					
						
							|  |  |  |         builder.append(x_value); | 
					
						
							| 
									
										
										
										
											2025-08-01 16:43:05 +12:00
										 |  |  |         if (x_value != y_value || (z_value.has_value() && *z_value != "1"sv)) { | 
					
						
							| 
									
										
										
										
											2025-01-15 14:58:23 +00:00
										 |  |  |             builder.append(" "sv); | 
					
						
							|  |  |  |             builder.append(y_value); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-08-01 16:43:05 +12:00
										 |  |  |         if (z_value.has_value() && *z_value != "1"sv) { | 
					
						
							| 
									
										
										
										
											2025-04-25 13:03:02 +02:00
										 |  |  |             builder.append(" "sv); | 
					
						
							|  |  |  |             builder.append(z_value.value()); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-01-15 14:58:23 +00:00
										 |  |  |         return builder.to_string_without_validation(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-01-15 16:48:44 +00:00
										 |  |  |     if (m_properties.property == PropertyID::Translate) { | 
					
						
							| 
									
										
										
										
											2025-08-08 10:11:51 +01:00
										 |  |  |         auto resolve_to_string = [mode](StyleValue const& value) -> Optional<String> { | 
					
						
							| 
									
										
										
										
											2025-07-31 23:22:32 +12:00
										 |  |  |             auto string_value = value.to_string(mode); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (string_value == "0px"_string) | 
					
						
							|  |  |  |                 return {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return string_value; | 
					
						
							| 
									
										
										
										
											2025-01-15 16:48:44 +00:00
										 |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         auto x_value = resolve_to_string(m_properties.values[0]); | 
					
						
							|  |  |  |         auto y_value = resolve_to_string(m_properties.values[1]); | 
					
						
							| 
									
										
										
										
											2025-04-30 09:03:18 +01:00
										 |  |  |         Optional<String> z_value; | 
					
						
							|  |  |  |         if (m_properties.values.size() == 3 && (!m_properties.values[2]->is_length() || m_properties.values[2]->as_length().length() != Length::make_px(0))) | 
					
						
							|  |  |  |             z_value = resolve_to_string(m_properties.values[2]); | 
					
						
							| 
									
										
										
										
											2025-01-15 16:48:44 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         StringBuilder builder; | 
					
						
							|  |  |  |         builder.append(x_value.value_or("0px"_string)); | 
					
						
							| 
									
										
										
										
											2025-04-30 09:03:18 +01:00
										 |  |  |         if (y_value.has_value() || z_value.has_value()) { | 
					
						
							| 
									
										
										
										
											2025-01-15 16:48:44 +00:00
										 |  |  |             builder.append(" "sv); | 
					
						
							| 
									
										
										
										
											2025-04-30 09:03:18 +01:00
										 |  |  |             builder.append(y_value.value_or("0px"_string)); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (z_value.has_value()) { | 
					
						
							|  |  |  |             builder.append(" "sv); | 
					
						
							|  |  |  |             builder.append(z_value.value()); | 
					
						
							| 
									
										
										
										
											2025-01-15 16:48:44 +00:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return builder.to_string_without_validation(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-01-15 14:58:23 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-24 17:59:33 +00:00
										 |  |  |     StringBuilder builder; | 
					
						
							| 
									
										
										
										
											2023-08-22 14:08:15 +01:00
										 |  |  |     builder.append(CSS::to_string(m_properties.transform_function)); | 
					
						
							|  |  |  |     builder.append('('); | 
					
						
							| 
									
										
										
										
											2023-03-24 17:59:33 +00:00
										 |  |  |     for (size_t i = 0; i < m_properties.values.size(); ++i) { | 
					
						
							| 
									
										
										
										
											2024-07-02 21:47:51 +02:00
										 |  |  |         auto const& value = m_properties.values[i]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // https://www.w3.org/TR/css-transforms-2/#individual-transforms
 | 
					
						
							|  |  |  |         // A <percentage> is equivalent to a <number>, for example scale: 100% is equivalent to scale: 1.
 | 
					
						
							|  |  |  |         // Numbers are used during serialization of specified and computed values.
 | 
					
						
							|  |  |  |         if ((m_properties.transform_function == CSS::TransformFunction::Scale | 
					
						
							|  |  |  |                 || m_properties.transform_function == CSS::TransformFunction::Scale3d | 
					
						
							|  |  |  |                 || m_properties.transform_function == CSS::TransformFunction::ScaleX | 
					
						
							|  |  |  |                 || m_properties.transform_function == CSS::TransformFunction::ScaleY | 
					
						
							|  |  |  |                 || m_properties.transform_function == CSS::TransformFunction::ScaleZ) | 
					
						
							|  |  |  |             && value->is_percentage()) { | 
					
						
							| 
									
										
										
										
											2024-10-14 10:05:01 +02:00
										 |  |  |             builder.append(String::number(value->as_percentage().percentage().as_fraction())); | 
					
						
							| 
									
										
										
										
											2024-07-02 21:47:51 +02:00
										 |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2024-12-07 00:59:49 +01:00
										 |  |  |             builder.append(value->to_string(mode)); | 
					
						
							| 
									
										
										
										
											2024-07-02 21:47:51 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-24 17:59:33 +00:00
										 |  |  |         if (i != m_properties.values.size() - 1) | 
					
						
							| 
									
										
										
										
											2023-08-22 14:08:15 +01:00
										 |  |  |             builder.append(", "sv); | 
					
						
							| 
									
										
										
										
											2023-03-24 17:59:33 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-08-22 14:08:15 +01:00
										 |  |  |     builder.append(')'); | 
					
						
							| 
									
										
										
										
											2023-03-24 17:59:33 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-22 14:08:15 +01:00
										 |  |  |     return MUST(builder.to_string()); | 
					
						
							| 
									
										
										
										
											2023-03-24 17:59:33 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool TransformationStyleValue::Properties::operator==(Properties const& other) const | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2025-01-15 14:58:23 +00:00
										 |  |  |     return property == other.property | 
					
						
							|  |  |  |         && transform_function == other.transform_function | 
					
						
							|  |  |  |         && values.span() == other.values.span(); | 
					
						
							| 
									
										
										
										
											2023-03-24 17:59:33 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |