/* * Copyright (c) 2018-2020, Andreas Kling * Copyright (c) 2021, Tobias Christiansen * Copyright (c) 2021-2024, Sam Atkins * Copyright (c) 2022-2023, MacDue * * SPDX-License-Identifier: BSD-2-Clause */ #include "CSSMathValue.h" #include #include namespace Web::CSS { static bool is_number(CSSMathValue::ResolvedType type) { return type == CSSMathValue::ResolvedType::Number || type == CSSMathValue::ResolvedType::Integer; } static bool is_dimension(CSSMathValue::ResolvedType type) { return type != CSSMathValue::ResolvedType::Number && type != CSSMathValue::ResolvedType::Integer && type != CSSMathValue::ResolvedType::Percentage; } static double resolve_value_radians(CSSMathValue::CalculationResult::Value value) { return value.visit( [](Number const& number) { return number.value(); }, [](Angle const& angle) { return angle.to_radians(); }, [](auto const&) { VERIFY_NOT_REACHED(); return 0.0; }); } static double resolve_value(CSSMathValue::CalculationResult::Value value, Optional context) { return value.visit( [](Number const& number) { return number.value(); }, [](Angle const& angle) { return angle.to_degrees(); }, [](Flex const& flex) { return flex.to_fr(); }, [](Frequency const& frequency) { return frequency.to_hertz(); }, [](Percentage const& percentage) { return percentage.value(); }, [](Resolution const& resolution) { return resolution.to_dots_per_pixel(); }, [](Time const& time) { return time.to_seconds(); }, [&context](Length const& length) { // Handle some common cases first, so we can resolve more without a context if (length.is_auto()) return 0.0; if (length.is_absolute()) return length.absolute_length_to_px().to_double(); // If we dont have a context, we cant resolve the length, so return NAN if (!context.has_value()) { dbgln("Failed to resolve length, likely due to calc() being used with relative units and a property not taking it into account"); return Number(Number::Type::Number, NAN).value(); } return length.to_px(*context).to_double(); }); } static Optional add_the_types(Vector> const& nodes, PropertyID property_id) { Optional left_type; for (auto const& value : nodes) { auto right_type = value->determine_type(property_id); if (!right_type.has_value()) return {}; if (left_type.has_value()) { left_type = left_type->added_to(right_type.value()); } else { left_type = right_type; } if (!left_type.has_value()) return {}; } return left_type; } static CSSMathValue::CalculationResult to_resolved_type(CSSMathValue::ResolvedType type, double value) { switch (type) { case CSSMathValue::ResolvedType::Integer: return { Number(Number::Type::Integer, value) }; case CSSMathValue::ResolvedType::Number: return { Number(Number::Type::Number, value) }; case CSSMathValue::ResolvedType::Angle: return { Angle::make_degrees(value) }; case CSSMathValue::ResolvedType::Flex: return { Flex::make_fr(value) }; case CSSMathValue::ResolvedType::Frequency: return { Frequency::make_hertz(value) }; case CSSMathValue::ResolvedType::Length: return { Length::make_px(CSSPixels::nearest_value_for(value)) }; case CSSMathValue::ResolvedType::Percentage: return { Percentage(value) }; case CSSMathValue::ResolvedType::Resolution: return { Resolution::make_dots_per_pixel(value) }; case CSSMathValue::ResolvedType::Time: return { Time::make_seconds(value) }; } VERIFY_NOT_REACHED(); } Optional CalculationNode::constant_type_from_string(StringView string) { if (string.equals_ignoring_ascii_case("e"sv)) return CalculationNode::ConstantType::E; if (string.equals_ignoring_ascii_case("pi"sv)) return CalculationNode::ConstantType::Pi; if (string.equals_ignoring_ascii_case("infinity"sv)) return CalculationNode::ConstantType::Infinity; if (string.equals_ignoring_ascii_case("-infinity"sv)) return CalculationNode::ConstantType::MinusInfinity; if (string.equals_ignoring_ascii_case("NaN"sv)) return CalculationNode::ConstantType::NaN; return {}; } CalculationNode::CalculationNode(Type type) : m_type(type) { } CalculationNode::~CalculationNode() = default; NonnullOwnPtr NumericCalculationNode::create(NumericValue value) { return adopt_own(*new (nothrow) NumericCalculationNode(move(value))); } NumericCalculationNode::NumericCalculationNode(NumericValue value) : CalculationNode(Type::Numeric) , m_value(move(value)) { } NumericCalculationNode::~NumericCalculationNode() = default; String NumericCalculationNode::to_string() const { return m_value.visit([](auto& value) { return value.to_string(); }); } Optional NumericCalculationNode::resolved_type() const { return m_value.visit( [](Number const&) { return CSSMathValue::ResolvedType::Number; }, [](Angle const&) { return CSSMathValue::ResolvedType::Angle; }, [](Flex const&) { return CSSMathValue::ResolvedType::Flex; }, [](Frequency const&) { return CSSMathValue::ResolvedType::Frequency; }, [](Length const&) { return CSSMathValue::ResolvedType::Length; }, [](Percentage const&) { return CSSMathValue::ResolvedType::Percentage; }, [](Resolution const&) { return CSSMathValue::ResolvedType::Resolution; }, [](Time const&) { return CSSMathValue::ResolvedType::Time; }); } // https://www.w3.org/TR/css-values-4/#determine-the-type-of-a-calculation Optional NumericCalculationNode::determine_type(PropertyID property_id) const { // Anything else is a terminal value, whose type is determined based on its CSS type: return m_value.visit( [](Number const&) { // -> // -> // the type is «[ ]» (empty map) return CSSNumericType {}; }, [](Length const&) { // -> // the type is «[ "length" → 1 ]» return CSSNumericType { CSSNumericType::BaseType::Length, 1 }; }, [](Angle const&) { // -> // the type is «[ "angle" → 1 ]» return CSSNumericType { CSSNumericType::BaseType::Angle, 1 }; }, [](Time const&) { // ->