浏览代码

LibWeb: Implement CSS Angle class

This corresponds to `<angle>` in the grammar.
Sam Atkins 3 年之前
父节点
当前提交
355d1936f2

+ 1 - 0
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

+ 99 - 0
Userland/Libraries/LibWeb/CSS/Angle.cpp

@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "Angle.h"
+#include <AK/Math.h>
+#include <LibWeb/CSS/StyleValue.h>
+
+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<CalculatedStyleValue> 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<float>);
+    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::Type> 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 {};
+}
+
+}

+ 57 - 0
Userland/Libraries/LibWeb/CSS/Angle.h

@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/RefPtr.h>
+#include <LibWeb/Forward.h>
+
+namespace Web::CSS {
+
+class Angle {
+public:
+    enum class Type {
+        Calculated,
+        Deg,
+        Grad,
+        Rad,
+        Turn,
+    };
+
+    static Optional<Type> unit_from_name(StringView);
+
+    Angle(int value, Type type);
+    Angle(float value, Type type);
+    static Angle make_calculated(NonnullRefPtr<CalculatedStyleValue>);
+    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<CalculatedStyleValue> m_calculated_style;
+};
+
+}

+ 2 - 0
Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp

@@ -4298,6 +4298,8 @@ Optional<CalculatedStyleValue::CalcValue> Parser::parse_calc_value(TokenStream<S
             return {};
         auto& dimension = maybe_dimension.value();
 
+        if (dimension.is_angle())
+            return CalculatedStyleValue::CalcValue { dimension.angle() };
         if (dimension.is_length())
             return CalculatedStyleValue::CalcValue { dimension.length() };
         if (dimension.is_percentage())

+ 5 - 0
Userland/Libraries/LibWeb/CSS/Percentage.cpp

@@ -9,6 +9,11 @@
 
 namespace Web::CSS {
 
+Angle AnglePercentage::resolve_calculated(NonnullRefPtr<CalculatedStyleValue> 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<CalculatedStyleValue> 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);

+ 17 - 1
Userland/Libraries/LibWeb/CSS/Percentage.h

@@ -8,6 +8,7 @@
 
 #include <AK/String.h>
 #include <AK/Variant.h>
+#include <LibWeb/CSS/Angle.h>
 #include <LibWeb/CSS/Length.h>
 
 namespace Web::CSS {
@@ -80,7 +81,7 @@ public:
         return m_value.template get<Percentage>();
     }
 
-    virtual T resolve_calculated(NonnullRefPtr<CalculatedStyleValue> const&, Layout::Node const&, [[maybe_unused]] T const& reference_value) const
+    virtual T resolve_calculated(NonnullRefPtr<CalculatedStyleValue> 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<T> const& percentage_
     return percentage == percentage_or;
 }
 
+class AnglePercentage : public PercentageOr<Angle> {
+public:
+    using PercentageOr<Angle>::PercentageOr;
+
+    bool is_angle() const { return is_t(); }
+    Angle const& angle() const { return get_t(); }
+    virtual Angle resolve_calculated(NonnullRefPtr<CalculatedStyleValue> const&, Layout::Node const&, Angle const& reference_value) const override;
+};
 class LengthPercentage : public PercentageOr<Length> {
 public:
     using PercentageOr<Length>::PercentageOr;
@@ -171,6 +180,13 @@ struct AK::Formatter<Web::CSS::Percentage> : Formatter<StringView> {
 };
 
 template<>
+struct AK::Formatter<Web::CSS::AnglePercentage> : Formatter<StringView> {
+    ErrorOr<void> format(FormatBuilder& builder, Web::CSS::AnglePercentage const& angle_percentage)
+    {
+        return Formatter<StringView>::format(builder, angle_percentage.to_string());
+    }
+};
+
 struct AK::Formatter<Web::CSS::LengthPercentage> : Formatter<StringView> {
     ErrorOr<void> format(FormatBuilder& builder, Web::CSS::LengthPercentage const& length_percentage)
     {

+ 64 - 22
Userland/Libraries/LibWeb/CSS/StyleValue.cpp

@@ -27,6 +27,12 @@ StyleValue::~StyleValue()
 {
 }
 
+AngleStyleValue const& StyleValue::as_angle() const
+{
+    VERIFY(is_angle());
+    return static_cast<AngleStyleValue const&>(*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<Angle>()) {
+                auto other_degrees = other.m_value.get<Angle>().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<Angle>());
+
+                auto other_degrees = percentage_basis.get<Angle>().percentage_of(other.m_value.get<Percentage>()).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<Length>()) {
@@ -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<Number>().value);
+        },
         [&](Length const& length) {
             VERIFY(layout_node);
             m_value = Length::make_px(length.to_px(*layout_node) * other.m_value.get<Number>().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<CalcSum> const& sum) { return String::formatted("({})", sum->to_string()); });
+        [](NonnullOwnPtr<CalcSum> const& sum) { return String::formatted("({})", sum->to_string()); },
+        [](auto const& v) { return v.to_string(); });
 }
 
 String CalculatedStyleValue::CalcSum::to_string() const
@@ -482,35 +511,53 @@ String CalculatedStyleValue::CalcNumberSumPartWithOperator::to_string() const
     return String::formatted(" {} {}", op == SumOperation::Add ? "+"sv : "-"sv, value->to_string());
 }
 
-Optional<Length> CalculatedStyleValue::resolve_length(Layout::Node const& layout_node) const
+Optional<Angle> CalculatedStyleValue::resolve_angle() const
 {
-    auto result = m_expression->resolve(&layout_node, {});
+    auto result = m_expression->resolve(nullptr, {});
+
+    if (result.value().has<Angle>())
+        return result.value().get<Angle>();
+    return {};
+}
+
+Optional<AnglePercentage> CalculatedStyleValue::resolve_angle_percentage(Angle const& percentage_basis) const
+{
+    auto result = m_expression->resolve(nullptr, percentage_basis);
 
     return result.value().visit(
-        [&](Number) -> Optional<Length> {
-            return {};
+        [&](Angle const& angle) -> Optional<AnglePercentage> {
+            return angle;
         },
-        [&](Length const& length) -> Optional<Length> {
-            return length;
+        [&](Percentage const& percentage) -> Optional<AnglePercentage> {
+            return percentage;
         },
-        [&](Percentage const&) -> Optional<Length> {
+        [&](auto const&) -> Optional<AnglePercentage> {
             return {};
         });
 }
 
+Optional<Length> CalculatedStyleValue::resolve_length(Layout::Node const& layout_node) const
+{
+    auto result = m_expression->resolve(&layout_node, {});
+
+    if (result.value().has<Length>())
+        return result.value().get<Length>();
+    return {};
+}
+
 Optional<LengthPercentage> CalculatedStyleValue::resolve_length_percentage(Layout::Node const& layout_node, Length const& percentage_basis) const
 {
     auto result = m_expression->resolve(&layout_node, percentage_basis);
 
     return result.value().visit(
-        [&](Number) -> Optional<LengthPercentage> {
-            return {};
-        },
         [&](Length const& length) -> Optional<LengthPercentage> {
             return length;
         },
         [&](Percentage const& percentage) -> Optional<LengthPercentage> {
             return percentage;
+        },
+        [&](auto const&) -> Optional<LengthPercentage> {
+            return {};
         });
 }
 
@@ -697,6 +744,7 @@ Optional<CalculatedStyleValue::ResolvedType> CalculatedStyleValue::CalcValue::re
         [](Number const& number) -> Optional<CalculatedStyleValue::ResolvedType> {
             return { number.is_integer ? ResolvedType::Integer : ResolvedType::Number };
         },
+        [](Angle const&) -> Optional<CalculatedStyleValue::ResolvedType> { return { ResolvedType::Angle }; },
         [](Length const&) -> Optional<CalculatedStyleValue::ResolvedType> { return { ResolvedType::Length }; },
         [](Percentage const&) -> Optional<CalculatedStyleValue::ResolvedType> { return { ResolvedType::Percentage }; },
         [](NonnullOwnPtr<CalcSum> 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<CalcSum> const& sum) -> CalculatedStyleValue::CalculationResult {
             return sum->resolve(layout_node, percentage_basis);
+        },
+        [&](auto const& v) -> CalculatedStyleValue::CalculationResult {
+            return CalculatedStyleValue::CalculationResult { v };
         });
 }
 

+ 42 - 5
Userland/Libraries/LibWeb/CSS/StyleValue.h

@@ -22,6 +22,7 @@
 #include <AK/WeakPtr.h>
 #include <LibGfx/Bitmap.h>
 #include <LibGfx/Color.h>
+#include <LibWeb/CSS/Angle.h>
 #include <LibWeb/CSS/Display.h>
 #include <LibWeb/CSS/Length.h>
 #include <LibWeb/CSS/Parser/StyleComponentValueRule.h>
@@ -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<AngleStyleValue&>(const_cast<StyleValue const&>(*this).as_angle()); }
     BackgroundStyleValue& as_background() { return const_cast<BackgroundStyleValue&>(const_cast<StyleValue const&>(*this).as_background()); }
     BackgroundRepeatStyleValue& as_background_repeat() { return const_cast<BackgroundRepeatStyleValue&>(const_cast<StyleValue const&>(*this).as_background_repeat()); }
     BackgroundSizeStyleValue& as_background_size() { return const_cast<BackgroundSizeStyleValue&>(const_cast<StyleValue const&>(*this).as_background_size()); }
@@ -435,6 +440,35 @@ private:
     Type m_type { Type::Invalid };
 };
 
+class AngleStyleValue : public StyleValue {
+public:
+    static NonnullRefPtr<AngleStyleValue> 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<AngleStyleValue const&>(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<BackgroundStyleValue> create(
@@ -696,11 +730,11 @@ public:
         float value;
     };
 
-    using PercentageBasis = Variant<Empty, Length>;
+    using PercentageBasis = Variant<Empty, Angle, Length>;
 
     class CalculationResult {
     public:
-        CalculationResult(Variant<Number, Length, Percentage> value)
+        CalculationResult(Variant<Number, Angle, Length, Percentage> 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<Number, Length, Percentage> const& value() const { return m_value; }
+        Variant<Number, Angle, Length, Percentage> 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<Number, Length, Percentage> m_value;
+        Variant<Number, Angle, Length, Percentage> m_value;
     };
 
     struct CalcSum;
@@ -733,7 +767,7 @@ public:
     };
 
     struct CalcValue {
-        Variant<Number, Length, Percentage, NonnullOwnPtr<CalcSum>> value;
+        Variant<Number, Angle, Length, Percentage, NonnullOwnPtr<CalcSum>> value;
         String to_string() const;
         Optional<ResolvedType> 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<CalcSum> const& expression() const { return m_expression; }
+
+    Optional<Angle> resolve_angle() const;
+    Optional<AnglePercentage> resolve_angle_percentage(Angle const& percentage_basis) const;
     Optional<Length> resolve_length(Layout::Node const& layout_node) const;
     Optional<LengthPercentage> resolve_length_percentage(Layout::Node const&, Length const& percentage_basis) const;
     Optional<Percentage> resolve_percentage() const;

+ 3 - 0
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;