diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 831336e4db..57e3ecc9a8 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -15,6 +15,7 @@ set(SOURCES Bindings/Wrappable.cpp Crypto/Crypto.cpp Crypto/SubtleCrypto.cpp + CSS/Angle.cpp CSS/CSSConditionRule.cpp CSS/CSSGroupingRule.cpp CSS/CSSImportRule.cpp diff --git a/Userland/Libraries/LibWeb/CSS/Angle.cpp b/Userland/Libraries/LibWeb/CSS/Angle.cpp new file mode 100644 index 0000000000..1d7ce95630 --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/Angle.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2022, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "Angle.h" +#include +#include + +namespace Web::CSS { + +Angle::Angle(int value, Type type) + : m_type(type) + , m_value(value) +{ +} + +Angle::Angle(float value, Type type) + : m_type(type) + , m_value(value) +{ +} + +Angle Angle::make_calculated(NonnullRefPtr calculated_style_value) +{ + Angle angle { 0, Type::Calculated }; + angle.m_calculated_style = move(calculated_style_value); + return angle; +} + +Angle Angle::make_degrees(float value) +{ + return { value, Type::Deg }; +} + +Angle Angle::percentage_of(Percentage const& percentage) const +{ + VERIFY(!is_calculated()); + + return Angle { percentage.as_fraction() * m_value, m_type }; +} + +String Angle::to_string() const +{ + if (is_calculated()) + return m_calculated_style->to_string(); + return String::formatted("{}{}", m_value, unit_name()); +} + +float Angle::to_degrees() const +{ + switch (m_type) { + case Type::Calculated: + return m_calculated_style->resolve_angle()->to_degrees(); + case Type::Deg: + return m_value; + case Type::Grad: + return m_value * (360.0f / 400.0f); + case Type::Rad: + return m_value * (360.0f / 2 * AK::Pi); + case Type::Turn: + return m_value * 360.0f; + } + VERIFY_NOT_REACHED(); +} + +StringView Angle::unit_name() const +{ + switch (m_type) { + case Type::Calculated: + return "calculated"sv; + case Type::Deg: + return "deg"sv; + case Type::Grad: + return "grad"sv; + case Type::Rad: + return "rad"sv; + case Type::Turn: + return "turn"sv; + } + VERIFY_NOT_REACHED(); +} + +Optional Angle::unit_from_name(StringView name) +{ + if (name.equals_ignoring_case("deg"sv)) { + return Type::Deg; + } else if (name.equals_ignoring_case("grad"sv)) { + return Type::Grad; + } else if (name.equals_ignoring_case("rad"sv)) { + return Type::Rad; + } else if (name.equals_ignoring_case("turn"sv)) { + return Type::Turn; + } + return {}; +} + +} diff --git a/Userland/Libraries/LibWeb/CSS/Angle.h b/Userland/Libraries/LibWeb/CSS/Angle.h new file mode 100644 index 0000000000..9795ac2a62 --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/Angle.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::CSS { + +class Angle { +public: + enum class Type { + Calculated, + Deg, + Grad, + Rad, + Turn, + }; + + static Optional unit_from_name(StringView); + + Angle(int value, Type type); + Angle(float value, Type type); + static Angle make_calculated(NonnullRefPtr); + static Angle make_degrees(float); + Angle percentage_of(Percentage const&) const; + + bool is_calculated() const { return m_type == Type::Calculated; } + + String to_string() const; + float to_degrees() const; + + bool operator==(Angle const& other) const + { + if (is_calculated()) + return m_calculated_style == other.m_calculated_style; + return m_type == other.m_type && m_value == other.m_value; + } + + bool operator!=(Angle const& other) const + { + return !(*this == other); + } + +private: + StringView unit_name() const; + + Type m_type; + float m_value { 0 }; + RefPtr m_calculated_style; +}; + +} diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index cc88ead5b0..4628a97779 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -4298,6 +4298,8 @@ Optional Parser::parse_calc_value(TokenStream const& calculated, Layout::Node const& layout_node, Angle const& reference_value) const +{ + return calculated->resolve_angle_percentage(reference_value)->resolved(layout_node, reference_value); +} + Length LengthPercentage::resolve_calculated(NonnullRefPtr const& calculated, Layout::Node const& layout_node, Length const& reference_value) const { return calculated->resolve_length_percentage(layout_node, reference_value)->resolved(layout_node, reference_value); diff --git a/Userland/Libraries/LibWeb/CSS/Percentage.h b/Userland/Libraries/LibWeb/CSS/Percentage.h index 2945d306c6..2bdda93138 100644 --- a/Userland/Libraries/LibWeb/CSS/Percentage.h +++ b/Userland/Libraries/LibWeb/CSS/Percentage.h @@ -8,6 +8,7 @@ #include #include +#include #include namespace Web::CSS { @@ -80,7 +81,7 @@ public: return m_value.template get(); } - virtual T resolve_calculated(NonnullRefPtr const&, Layout::Node const&, [[maybe_unused]] T const& reference_value) const + virtual T resolve_calculated(NonnullRefPtr const&, [[maybe_unused]] Layout::Node const&, [[maybe_unused]] T const& reference_value) const { VERIFY_NOT_REACHED(); } @@ -151,6 +152,14 @@ bool operator==(Percentage const& percentage, PercentageOr const& percentage_ return percentage == percentage_or; } +class AnglePercentage : public PercentageOr { +public: + using PercentageOr::PercentageOr; + + bool is_angle() const { return is_t(); } + Angle const& angle() const { return get_t(); } + virtual Angle resolve_calculated(NonnullRefPtr const&, Layout::Node const&, Angle const& reference_value) const override; +}; class LengthPercentage : public PercentageOr { public: using PercentageOr::PercentageOr; @@ -171,6 +180,13 @@ struct AK::Formatter : Formatter { }; template<> +struct AK::Formatter : Formatter { + ErrorOr format(FormatBuilder& builder, Web::CSS::AnglePercentage const& angle_percentage) + { + return Formatter::format(builder, angle_percentage.to_string()); + } +}; + struct AK::Formatter : Formatter { ErrorOr format(FormatBuilder& builder, Web::CSS::LengthPercentage const& length_percentage) { diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp index 074b2be9ba..914fd669ac 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp @@ -27,6 +27,12 @@ StyleValue::~StyleValue() { } +AngleStyleValue const& StyleValue::as_angle() const +{ + VERIFY(is_angle()); + return static_cast(*this); +} + BackgroundStyleValue const& StyleValue::as_background() const { VERIFY(is_background()); @@ -309,6 +315,24 @@ void CalculatedStyleValue::CalculationResult::add_or_subtract_internal(SumOperat }; } }, + [&](Angle const& angle) { + auto this_degrees = angle.to_degrees(); + if (other.m_value.has()) { + auto other_degrees = other.m_value.get().to_degrees(); + if (op == SumOperation::Add) + m_value = Angle::make_degrees(this_degrees + other_degrees); + else + m_value = Angle::make_degrees(this_degrees - other_degrees); + } else { + VERIFY(percentage_basis.has()); + + auto other_degrees = percentage_basis.get().percentage_of(other.m_value.get()).to_degrees(); + if (op == SumOperation::Add) + m_value = Angle::make_degrees(this_degrees + other_degrees); + else + m_value = Angle::make_degrees(this_degrees - other_degrees); + } + }, [&](Length const& length) { auto this_px = length.to_px(*layout_node); if (other.m_value.has()) { @@ -369,6 +393,9 @@ void CalculatedStyleValue::CalculationResult::multiply_by(CalculationResult cons *this = new_value; } }, + [&](Angle const& angle) { + m_value = Angle::make_degrees(angle.to_degrees() * other.m_value.get().value); + }, [&](Length const& length) { VERIFY(layout_node); m_value = Length::make_px(length.to_px(*layout_node) * other.m_value.get().value); @@ -393,6 +420,9 @@ void CalculatedStyleValue::CalculationResult::divide_by(CalculationResult const& .value = number.value / denominator }; }, + [&](Angle const& angle) { + m_value = Angle::make_degrees(angle.to_degrees() / denominator); + }, [&](Length const& length) { VERIFY(layout_node); m_value = Length::make_px(length.to_px(*layout_node) / denominator); @@ -418,9 +448,8 @@ String CalculatedStyleValue::CalcValue::to_string() const { return value.visit( [](Number const& number) { return String::number(number.value); }, - [](Length const& length) { return length.to_string(); }, - [](Percentage const& percentage) { return percentage.to_string(); }, - [](NonnullOwnPtr const& sum) { return String::formatted("({})", sum->to_string()); }); + [](NonnullOwnPtr const& sum) { return String::formatted("({})", sum->to_string()); }, + [](auto const& v) { return v.to_string(); }); } String CalculatedStyleValue::CalcSum::to_string() const @@ -482,20 +511,38 @@ String CalculatedStyleValue::CalcNumberSumPartWithOperator::to_string() const return String::formatted(" {} {}", op == SumOperation::Add ? "+"sv : "-"sv, value->to_string()); } +Optional CalculatedStyleValue::resolve_angle() const +{ + auto result = m_expression->resolve(nullptr, {}); + + if (result.value().has()) + return result.value().get(); + return {}; +} + +Optional CalculatedStyleValue::resolve_angle_percentage(Angle const& percentage_basis) const +{ + auto result = m_expression->resolve(nullptr, percentage_basis); + + return result.value().visit( + [&](Angle const& angle) -> Optional { + return angle; + }, + [&](Percentage const& percentage) -> Optional { + return percentage; + }, + [&](auto const&) -> Optional { + return {}; + }); +} + Optional CalculatedStyleValue::resolve_length(Layout::Node const& layout_node) const { auto result = m_expression->resolve(&layout_node, {}); - return result.value().visit( - [&](Number) -> Optional { - return {}; - }, - [&](Length const& length) -> Optional { - return length; - }, - [&](Percentage const&) -> Optional { - return {}; - }); + if (result.value().has()) + return result.value().get(); + return {}; } Optional CalculatedStyleValue::resolve_length_percentage(Layout::Node const& layout_node, Length const& percentage_basis) const @@ -503,14 +550,14 @@ Optional CalculatedStyleValue::resolve_length_percentage(Layou auto result = m_expression->resolve(&layout_node, percentage_basis); return result.value().visit( - [&](Number) -> Optional { - return {}; - }, [&](Length const& length) -> Optional { return length; }, [&](Percentage const& percentage) -> Optional { return percentage; + }, + [&](auto const&) -> Optional { + return {}; }); } @@ -697,6 +744,7 @@ Optional CalculatedStyleValue::CalcValue::re [](Number const& number) -> Optional { return { number.is_integer ? ResolvedType::Integer : ResolvedType::Number }; }, + [](Angle const&) -> Optional { return { ResolvedType::Angle }; }, [](Length const&) -> Optional { return { ResolvedType::Length }; }, [](Percentage const&) -> Optional { return { ResolvedType::Percentage }; }, [](NonnullOwnPtr const& sum) { return sum->resolved_type(); }); @@ -725,17 +773,11 @@ CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalcNumberValue::r CalculatedStyleValue::CalculationResult CalculatedStyleValue::CalcValue::resolve(Layout::Node const* layout_node, PercentageBasis const& percentage_basis) const { return value.visit( - [&](Number const& number) -> CalculatedStyleValue::CalculationResult { - return CalculatedStyleValue::CalculationResult { number }; - }, - [&](Length const& length) -> CalculatedStyleValue::CalculationResult { - return CalculatedStyleValue::CalculationResult { length }; - }, - [&](Percentage const& percentage) -> CalculatedStyleValue::CalculationResult { - return CalculatedStyleValue::CalculationResult { percentage }; - }, [&](NonnullOwnPtr const& sum) -> CalculatedStyleValue::CalculationResult { return sum->resolve(layout_node, percentage_basis); + }, + [&](auto const& v) -> CalculatedStyleValue::CalculationResult { + return CalculatedStyleValue::CalculationResult { v }; }); } diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValue.h index fdc8f0b65f..a917626794 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.h +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -283,6 +284,7 @@ public: virtual ~StyleValue(); enum class Type { + Angle, Background, BackgroundRepeat, BackgroundSize, @@ -316,6 +318,7 @@ public: Type type() const { return m_type; } + bool is_angle() const { return type() == Type::Angle; } bool is_background() const { return type() == Type::Background; } bool is_background_repeat() const { return type() == Type::BackgroundRepeat; } bool is_background_size() const { return type() == Type::BackgroundSize; } @@ -346,6 +349,7 @@ public: bool is_builtin() const { return is_inherit() || is_initial() || is_unset(); } + AngleStyleValue const& as_angle() const; BackgroundStyleValue const& as_background() const; BackgroundRepeatStyleValue const& as_background_repeat() const; BackgroundSizeStyleValue const& as_background_size() const; @@ -374,6 +378,7 @@ public: UnsetStyleValue const& as_unset() const; StyleValueList const& as_value_list() const; + AngleStyleValue& as_angle() { return const_cast(const_cast(*this).as_angle()); } BackgroundStyleValue& as_background() { return const_cast(const_cast(*this).as_background()); } BackgroundRepeatStyleValue& as_background_repeat() { return const_cast(const_cast(*this).as_background_repeat()); } BackgroundSizeStyleValue& as_background_size() { return const_cast(const_cast(*this).as_background_size()); } @@ -435,6 +440,35 @@ private: Type m_type { Type::Invalid }; }; +class AngleStyleValue : public StyleValue { +public: + static NonnullRefPtr create(Angle angle) + { + return adopt_ref(*new AngleStyleValue(move(angle))); + } + virtual ~AngleStyleValue() override { } + + Angle const& angle() const { return m_angle; } + + virtual String to_string() const override { return m_angle.to_string(); } + + virtual bool equals(StyleValue const& other) const override + { + if (type() != other.type()) + return false; + return m_angle == static_cast(other).m_angle; + } + +private: + explicit AngleStyleValue(Angle angle) + : StyleValue(Type::Angle) + , m_angle(move(angle)) + { + } + + Angle m_angle; +}; + class BackgroundStyleValue final : public StyleValue { public: static NonnullRefPtr create( @@ -696,11 +730,11 @@ public: float value; }; - using PercentageBasis = Variant; + using PercentageBasis = Variant; class CalculationResult { public: - CalculationResult(Variant value) + CalculationResult(Variant value) : m_value(move(value)) { } @@ -709,11 +743,11 @@ public: void multiply_by(CalculationResult const& other, Layout::Node const*); void divide_by(CalculationResult const& other, Layout::Node const*); - Variant const& value() const { return m_value; } + Variant const& value() const { return m_value; } private: void add_or_subtract_internal(SumOperation op, CalculationResult const& other, Layout::Node const*, PercentageBasis const& percentage_basis); - Variant m_value; + Variant m_value; }; struct CalcSum; @@ -733,7 +767,7 @@ public: }; struct CalcValue { - Variant> value; + Variant> value; String to_string() const; Optional resolved_type() const; CalculationResult resolve(Layout::Node const*, PercentageBasis const& percentage_basis) const; @@ -836,6 +870,9 @@ public: String to_string() const override; ResolvedType resolved_type() const { return m_resolved_type; } NonnullOwnPtr const& expression() const { return m_expression; } + + Optional resolve_angle() const; + Optional resolve_angle_percentage(Angle const& percentage_basis) const; Optional resolve_length(Layout::Node const& layout_node) const; Optional resolve_length_percentage(Layout::Node const&, Length const& percentage_basis) const; Optional resolve_percentage() const; diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 99a6dc760e..5e3dd94ca1 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -19,6 +19,9 @@ class SubtleCrypto; } namespace Web::CSS { +class Angle; +class AnglePercentage; +class AngleStyleValue; class BackgroundRepeatStyleValue; class BackgroundSizeStyleValue; class BackgroundStyleValue;