Przeglądaj źródła

LibWeb: Add stop-color as a CSS property

(This is to set the color of a stop for an SVG gradient)
MacDue 2 lat temu
rodzic
commit
297d8eebcd

+ 4 - 0
Userland/Libraries/LibWeb/CSS/ComputedValues.h

@@ -41,6 +41,7 @@ public:
     static CSS::TextTransform text_transform() { return CSS::TextTransform::None; }
     static CSS::Display display() { return CSS::Display { CSS::Display::Outside::Inline, CSS::Display::Inside::Flow }; }
     static Color color() { return Color::Black; }
+    static Color stop_color() { return Color::Black; }
     static CSS::BackdropFilter backdrop_filter() { return BackdropFilter::make_none(); }
     static Color background_color() { return Color::Transparent; }
     static CSS::ListStyleType list_style_type() { return CSS::ListStyleType::Disc; }
@@ -259,6 +260,7 @@ public:
     Optional<Color> const& fill() const { return m_inherited.fill; }
     Optional<Color> const& stroke() const { return m_inherited.stroke; }
     Optional<LengthPercentage> const& stroke_width() const { return m_inherited.stroke_width; }
+    Color stop_color() const { return m_noninherited.stop_color; }
 
     Vector<CSS::Transformation> const& transformations() const { return m_noninherited.transformations; }
     CSS::TransformOrigin const& transform_origin() const { return m_noninherited.transform_origin; }
@@ -359,6 +361,7 @@ protected:
         CSS::Size row_gap { InitialValues::row_gap() };
         CSS::BorderCollapse border_collapse { InitialValues::border_collapse() };
         Vector<Vector<String>> grid_template_areas { InitialValues::grid_template_areas() };
+        Gfx::Color stop_color { InitialValues::stop_color() };
     } m_noninherited;
 };
 
@@ -446,6 +449,7 @@ public:
     void set_fill(Color value) { m_inherited.fill = value; }
     void set_stroke(Color value) { m_inherited.stroke = value; }
     void set_stroke_width(LengthPercentage value) { m_inherited.stroke_width = value; }
+    void set_stop_color(Color value) { m_noninherited.stop_color = value; }
 };
 
 }

+ 8 - 0
Userland/Libraries/LibWeb/CSS/Properties.json

@@ -1411,6 +1411,14 @@
       "none"
     ]
   },
+  "stop-color": {
+    "affects-layout": false,
+    "inherited": false,
+    "initial": "black",
+    "valid-types": [
+      "color"
+    ]
+  },
   "stroke-width": {
     "affects-layout": false,
     "inherited": true,

+ 11 - 0
Userland/Libraries/LibWeb/CSS/StyleProperties.cpp

@@ -818,4 +818,15 @@ String StyleProperties::grid_area() const
     return value->as_string().to_string().release_value_but_fixme_should_propagate_errors();
 }
 
+Color StyleProperties::stop_color() const
+{
+    auto value = property(CSS::PropertyID::StopColor);
+    if (value->has_color()) {
+        // FIXME: This is used by the SVGStopElement, which does not participate in layout,
+        // so can't pass a layout node (so can't resolve some colors, e.g. palette ones or currentColor)
+        return value->to_color({});
+    }
+    return Color::Black;
+}
+
 }

+ 2 - 0
Userland/Libraries/LibWeb/CSS/StyleProperties.h

@@ -98,6 +98,8 @@ public:
     Vector<CSS::Transformation> transformations() const;
     CSS::TransformOrigin transform_origin() const;
 
+    Color stop_color() const;
+
     Gfx::Font const& computed_font() const
     {
         VERIFY(m_font);

+ 2 - 0
Userland/Libraries/LibWeb/Layout/Node.cpp

@@ -650,6 +650,8 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
         computed_values.set_fill(fill->to_color(*this));
     if (auto stroke = computed_style.property(CSS::PropertyID::Stroke); stroke->has_color())
         computed_values.set_stroke(stroke->to_color(*this));
+    if (auto stop_color = computed_style.property(CSS::PropertyID::StopColor); stop_color->has_color())
+        computed_values.set_stop_color(stop_color->to_color(*this));
     auto stroke_width = computed_style.property(CSS::PropertyID::StrokeWidth);
     // FIXME: Converting to pixels isn't really correct - values should be in "user units"
     //        https://svgwg.org/svg2-draft/coords.html#TermUserUnits

+ 30 - 2
Userland/Libraries/LibWeb/SVG/AttributeParser.cpp

@@ -66,6 +66,30 @@ Optional<float> AttributeParser::parse_length(StringView input)
     return {};
 }
 
+float NumberPercentage::resolve_relative_to(float length)
+{
+    if (!m_is_percentage)
+        return m_value;
+    return m_value * length;
+}
+
+Optional<NumberPercentage> AttributeParser::parse_number_percentage(StringView input)
+{
+    AttributeParser parser { input };
+    parser.parse_whitespace();
+    if (parser.match_number()) {
+        float number = parser.parse_number();
+        bool is_percentage = parser.match('%');
+        if (is_percentage)
+            parser.consume();
+        parser.parse_whitespace();
+        if (parser.done())
+            return NumberPercentage(number, is_percentage);
+    }
+
+    return {};
+}
+
 Optional<float> AttributeParser::parse_positive_length(StringView input)
 {
     // FIXME: Where this is used, the spec usually (always?) says "A negative value is an error (see Error processing)."
@@ -482,11 +506,10 @@ Optional<Vector<Transform>> AttributeParser::parse_transform()
         m_lexer.consume_specific(',');
         consume_whitespace();
     };
-
     // FIXME: AttributeParser currently does not handle invalid parses in most cases (e.g. parse_number()) and just crashes.
     auto parse_optional_number = [&](float default_value = 0.0f) {
         consume_comma_whitespace();
-        if (m_lexer.next_is(isdigit))
+        if (match_number())
             return parse_number();
         return default_value;
     };
@@ -586,6 +609,11 @@ bool AttributeParser::match_coordinate() const
     return match_length();
 }
 
+bool AttributeParser::match_number() const
+{
+    return match_length();
+}
+
 bool AttributeParser::match_length() const
 {
     return !done() && (isdigit(ch()) || ch() == '-' || ch() == '+' || ch() == '.');

+ 29 - 0
Userland/Libraries/LibWeb/SVG/AttributeParser.h

@@ -88,12 +88,40 @@ struct PreserveAspectRatio {
     MeetOrSlice meet_or_slice { MeetOrSlice::Meet };
 };
 
+class NumberPercentage {
+public:
+    NumberPercentage(float value, bool is_percentage)
+        : m_value(is_percentage ? value / 100 : value)
+        , m_is_percentage(is_percentage)
+    {
+    }
+
+    static NumberPercentage create_percentage(float value)
+    {
+        return NumberPercentage(value, true);
+    }
+
+    static NumberPercentage create_number(float value)
+    {
+        return NumberPercentage(value, false);
+    }
+
+    float resolve_relative_to(float length);
+
+    float value() { return m_value; }
+
+private:
+    float m_value;
+    bool m_is_percentage { false };
+};
+
 class AttributeParser final {
 public:
     ~AttributeParser() = default;
 
     static Optional<float> parse_coordinate(StringView input);
     static Optional<float> parse_length(StringView input);
+    static Optional<NumberPercentage> parse_number_percentage(StringView input);
     static Optional<float> parse_positive_length(StringView input);
     static Vector<Gfx::FloatPoint> parse_points(StringView input);
     static Vector<PathInstruction> parse_path_data(StringView input);
@@ -137,6 +165,7 @@ private:
     bool match_comma_whitespace() const;
     bool match_coordinate() const;
     bool match_length() const;
+    bool match_number() const;
     bool match(char c) const { return !done() && ch() == c; }
 
     bool done() const { return m_lexer.is_eof(); }