diff --git a/Userland/Libraries/LibWeb/CSS/ComputedValues.h b/Userland/Libraries/LibWeb/CSS/ComputedValues.h index 5296c06ee6..dd50f5a282 100644 --- a/Userland/Libraries/LibWeb/CSS/ComputedValues.h +++ b/Userland/Libraries/LibWeb/CSS/ComputedValues.h @@ -62,6 +62,9 @@ public: static float flex_shrink() { return 1.0f; } static int order() { return 0; } static float opacity() { return 1.0f; } + static float fill_opacity() { return 1.0f; } + static float stroke_opacity() { return 1.0f; } + static float stop_opacity() { return 1.0f; } static CSS::Length border_radius() { return Length::make_px(0); } static Variant vertical_align() { return CSS::VerticalAlign::Baseline; } static CSS::LengthBox inset() { return { CSS::Length::make_auto(), CSS::Length::make_auto(), CSS::Length::make_auto(), CSS::Length::make_auto() }; } @@ -284,8 +287,11 @@ public: Optional const& fill() const { return m_inherited.fill; } Optional const& stroke() const { return m_inherited.stroke; } + float fill_opacity() const { return m_inherited.fill_opacity; } + float stroke_opacity() const { return m_inherited.stroke_opacity; } Optional const& stroke_width() const { return m_inherited.stroke_width; } Color stop_color() const { return m_noninherited.stop_color; } + float stop_opacity() const { return m_noninherited.stop_opacity; } Vector const& transformations() const { return m_noninherited.transformations; } CSS::TransformOrigin const& transform_origin() const { return m_noninherited.transform_origin; } @@ -321,6 +327,8 @@ protected: Optional fill; Optional stroke; + float fill_opacity { InitialValues::fill_opacity() }; + float stroke_opacity { InitialValues::stroke_opacity() }; Optional stroke_width; } m_inherited; @@ -388,6 +396,7 @@ protected: CSS::BorderCollapse border_collapse { InitialValues::border_collapse() }; Vector> grid_template_areas { InitialValues::grid_template_areas() }; Gfx::Color stop_color { InitialValues::stop_color() }; + float stop_opacity { InitialValues::stop_opacity() }; } m_noninherited; }; @@ -475,8 +484,11 @@ public: void set_fill(SVGPaint value) { m_inherited.fill = value; } void set_stroke(SVGPaint value) { m_inherited.stroke = value; } + void set_fill_opacity(float value) { m_inherited.fill_opacity = value; } + void set_stroke_opacity(float value) { m_inherited.stroke_opacity = value; } void set_stroke_width(LengthPercentage value) { m_inherited.stroke_width = value; } void set_stop_color(Color value) { m_noninherited.stop_color = value; } + void set_stop_opacity(float value) { m_noninherited.stop_opacity = value; } }; } diff --git a/Userland/Libraries/LibWeb/CSS/Properties.json b/Userland/Libraries/LibWeb/CSS/Properties.json index 3129c4401b..06d240c415 100644 --- a/Userland/Libraries/LibWeb/CSS/Properties.json +++ b/Userland/Libraries/LibWeb/CSS/Properties.json @@ -629,6 +629,15 @@ "none" ] }, + "fill-opacity": { + "affects-layout": false, + "inherited": true, + "initial": "1", + "valid-types": [ + "number", + "percentage" + ] + }, "flex": { "inherited": false, "initial": "0 1 auto", @@ -1406,6 +1415,15 @@ "none" ] }, + "stroke-opacity": { + "affects-layout": false, + "inherited": true, + "initial": "1", + "valid-types": [ + "number", + "percentage" + ] + }, "stop-color": { "affects-layout": false, "inherited": false, @@ -1414,6 +1432,15 @@ "color" ] }, + "stop-opacity": { + "affects-layout": false, + "inherited": false, + "initial": "1", + "valid-types": [ + "number", + "percentage" + ] + }, "stroke-width": { "affects-layout": false, "inherited": true, diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp index 6cea700ded..f1409954a1 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.cpp @@ -236,36 +236,58 @@ Optional StyleProperties::z_index() const return {}; } -float StyleProperties::opacity() const +static float resolve_opacity_value(CSS::StyleValue const& value) { - auto value = property(CSS::PropertyID::Opacity); - float unclamped_opacity = 1.0f; - if (value->has_number()) { - unclamped_opacity = value->to_number(); - } else if (value->is_calculated()) { - auto& calculated = value->as_calculated(); + if (value.has_number()) { + unclamped_opacity = value.to_number(); + } else if (value.is_calculated()) { + auto& calculated = value.as_calculated(); if (calculated.resolved_type() == CalculatedStyleValue::ResolvedType::Percentage) { - auto maybe_percentage = value->as_calculated().resolve_percentage(); + auto maybe_percentage = value.as_calculated().resolve_percentage(); if (maybe_percentage.has_value()) unclamped_opacity = maybe_percentage->as_fraction(); else - dbgln("Unable to resolve calc() as opacity (percentage): {}", value->to_string()); + dbgln("Unable to resolve calc() as opacity (percentage): {}", value.to_string()); } else { - auto maybe_number = const_cast(value->as_calculated()).resolve_number(); + auto maybe_number = const_cast(value.as_calculated()).resolve_number(); if (maybe_number.has_value()) unclamped_opacity = maybe_number.value(); else - dbgln("Unable to resolve calc() as opacity (number): {}", value->to_string()); + dbgln("Unable to resolve calc() as opacity (number): {}", value.to_string()); } - } else if (value->is_percentage()) { - unclamped_opacity = value->as_percentage().percentage().as_fraction(); + } else if (value.is_percentage()) { + unclamped_opacity = value.as_percentage().percentage().as_fraction(); } return clamp(unclamped_opacity, 0.0f, 1.0f); } +float StyleProperties::opacity() const +{ + auto value = property(CSS::PropertyID::Opacity); + return resolve_opacity_value(*value); +} + +float StyleProperties::fill_opacity() const +{ + auto value = property(CSS::PropertyID::FillOpacity); + return resolve_opacity_value(*value); +} + +float StyleProperties::stroke_opacity() const +{ + auto value = property(CSS::PropertyID::StrokeOpacity); + return resolve_opacity_value(*value); +} + +float StyleProperties::stop_opacity() const +{ + auto value = property(CSS::PropertyID::StopOpacity); + return resolve_opacity_value(*value); +} + Optional StyleProperties::flex_direction() const { auto value = property(CSS::PropertyID::FlexDirection); diff --git a/Userland/Libraries/LibWeb/CSS/StyleProperties.h b/Userland/Libraries/LibWeb/CSS/StyleProperties.h index 856beb2eb0..48611441dd 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleProperties.h +++ b/Userland/Libraries/LibWeb/CSS/StyleProperties.h @@ -99,6 +99,9 @@ public: CSS::TransformOrigin transform_origin() const; Color stop_color() const; + float stop_opacity() const; + float fill_opacity() const; + float stroke_opacity() const; Gfx::Font const& computed_font() const { diff --git a/Userland/Libraries/LibWeb/Layout/Node.cpp b/Userland/Libraries/LibWeb/Layout/Node.cpp index 3f494ba0de..0aa3351637 100644 --- a/Userland/Libraries/LibWeb/Layout/Node.cpp +++ b/Userland/Libraries/LibWeb/Layout/Node.cpp @@ -668,6 +668,10 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style) else computed_values.set_stroke_width(stroke_width->to_length()); + computed_values.set_fill_opacity(computed_style.fill_opacity()); + computed_values.set_stroke_opacity(computed_style.stroke_opacity()); + computed_values.set_stop_opacity(computed_style.stop_opacity()); + computed_values.set_column_gap(computed_style.size_value(CSS::PropertyID::ColumnGap)); computed_values.set_row_gap(computed_style.size_value(CSS::PropertyID::RowGap)); diff --git a/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp index fa1fefc59c..09ac1270a5 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/SVGGeometryPaintable.cpp @@ -101,19 +101,22 @@ void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const .transform = paint_transform }; + // FIXME: Apply fill opacity to paint styles? + auto fill_opacity = geometry_element.fill_opacity().value_or(svg_context.fill_opacity()); if (auto paint_style = geometry_element.fill_paint_style(paint_context); paint_style.has_value()) { painter.fill_path( closed_path(), *paint_style, Gfx::Painter::WindingRule::EvenOdd); - } else if (auto fill_color = geometry_element.fill_color().value_or(svg_context.fill_color()); fill_color.alpha() > 0) { + } else if (auto fill_color = geometry_element.fill_color().value_or(svg_context.fill_color()).with_opacity(fill_opacity); fill_color.alpha() > 0) { painter.fill_path( closed_path(), fill_color, Gfx::Painter::WindingRule::EvenOdd); } - if (auto stroke_color = geometry_element.stroke_color().value_or(svg_context.stroke_color()); stroke_color.alpha() > 0) { + auto stroke_opacity = geometry_element.stroke_opacity().value_or(svg_context.stroke_opacity()); + if (auto stroke_color = geometry_element.stroke_color().value_or(svg_context.stroke_color()).with_opacity(stroke_opacity); stroke_color.alpha() > 0) { painter.stroke_path( path, stroke_color, diff --git a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp index cceaeb3cab..43a790e6cc 100644 --- a/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/SVGGraphicsPaintable.cpp @@ -32,12 +32,16 @@ void SVGGraphicsPaintable::before_children_paint(PaintContext& context, PaintPha auto& graphics_element = layout_box().dom_node(); - if (graphics_element.fill_color().has_value()) - context.svg_context().set_fill_color(graphics_element.fill_color().value()); - if (graphics_element.stroke_color().has_value()) - context.svg_context().set_stroke_color(graphics_element.stroke_color().value()); - if (graphics_element.stroke_width().has_value()) - context.svg_context().set_stroke_width(graphics_element.stroke_width().value()); + if (auto fill_color = graphics_element.fill_color(); fill_color.has_value()) + context.svg_context().set_fill_color(*fill_color); + if (auto stroke_color = graphics_element.stroke_color(); stroke_color.has_value()) + context.svg_context().set_stroke_color(*stroke_color); + if (auto stroke_width = graphics_element.stroke_width(); stroke_width.has_value()) + context.svg_context().set_stroke_width(*stroke_width); + if (auto fill_opacity = graphics_element.fill_opacity(); fill_opacity.has_value()) + context.svg_context().set_fill_opacity(*fill_opacity); + if (auto stroke_opacity = graphics_element.stroke_opacity(); stroke_opacity.has_value()) + context.svg_context().set_stroke_opacity(*stroke_opacity); } } diff --git a/Userland/Libraries/LibWeb/SVG/SVGContext.h b/Userland/Libraries/LibWeb/SVG/SVGContext.h index 04885a2209..27c8c902e9 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGContext.h +++ b/Userland/Libraries/LibWeb/SVG/SVGContext.h @@ -23,10 +23,14 @@ public: Gfx::Color fill_color() const { return state().fill_color; } Gfx::Color stroke_color() const { return state().stroke_color; } float stroke_width() const { return state().stroke_width; } + float fill_opacity() const { return state().fill_opacity; } + float stroke_opacity() const { return state().stroke_opacity; } void set_fill_color(Gfx::Color color) { state().fill_color = color; } void set_stroke_color(Gfx::Color color) { state().stroke_color = color; } void set_stroke_width(float width) { state().stroke_width = width; } + void set_fill_opacity(float opacity) { state().fill_opacity = opacity; } + void set_stroke_opacity(float opacity) { state().stroke_opacity = opacity; } CSSPixelPoint svg_element_position() const { return m_svg_element_bounds.top_left(); } CSSPixelSize svg_element_size() const { return m_svg_element_bounds.size(); } @@ -38,7 +42,9 @@ private: struct State { Gfx::Color fill_color { Gfx::Color::Transparent }; Gfx::Color stroke_color { Gfx::Color::Transparent }; - float stroke_width { 1.0 }; + float stroke_width { 1.0f }; + float fill_opacity { 1.0f }; + float stroke_opacity { 1.0f }; }; State const& state() const { return m_states.last(); } diff --git a/Userland/Libraries/LibWeb/SVG/SVGGradientElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGGradientElement.cpp index ad4c2ba96d..8c57b6739a 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGGradientElement.cpp +++ b/Userland/Libraries/LibWeb/SVG/SVGGradientElement.cpp @@ -74,7 +74,7 @@ void SVGGradientElement::add_color_stops(Gfx::SVGGradientPaintStyle& paint_style // stop's offset value. If a given gradient stop's offset value is not equal to or greater than all // previous offset values, then the offset value is adjusted to be equal to the largest of all previous // offset values. - paint_style.add_color_stop(stop_offset, stop.stop_color()).release_value_but_fixme_should_propagate_errors(); + paint_style.add_color_stop(stop_offset, stop.stop_color().with_opacity(stop.stop_opacity())).release_value_but_fixme_should_propagate_errors(); }); } diff --git a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp index 3fbd746421..86214f95ad 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp +++ b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.cpp @@ -33,9 +33,7 @@ JS::ThrowCompletionOr SVGGraphicsElement::initialize(JS::Realm& realm) void SVGGraphicsElement::parse_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) { SVGElement::parse_attribute(name, value); - if (name == "fill-opacity"sv) { - m_fill_opacity = AttributeParser::parse_length(value); - } else if (name == "transform"sv) { + if (name == "transform"sv) { auto transform_list = AttributeParser::parse_transform(value); if (transform_list.has_value()) m_transform = transform_from_transform_list(*transform_list); @@ -120,6 +118,12 @@ void SVGGraphicsElement::apply_presentational_hints(CSS::StyleProperties& style) } else if (name.equals_ignoring_ascii_case("stroke-width"sv)) { if (auto stroke_width_value = parse_css_value(parsing_context, value, CSS::PropertyID::StrokeWidth).release_value_but_fixme_should_propagate_errors()) style.set_property(CSS::PropertyID::StrokeWidth, stroke_width_value.release_nonnull()); + } else if (name.equals_ignoring_ascii_case("fill-opacity"sv)) { + if (auto fill_opacity_value = parse_css_value(parsing_context, value, CSS::PropertyID::FillOpacity).release_value_but_fixme_should_propagate_errors()) + style.set_property(CSS::PropertyID::FillOpacity, fill_opacity_value.release_nonnull()); + } else if (name.equals_ignoring_ascii_case("stroke-opacity"sv)) { + if (auto stroke_opacity_value = parse_css_value(parsing_context, value, CSS::PropertyID::FillOpacity).release_value_but_fixme_should_propagate_errors()) + style.set_property(CSS::PropertyID::StrokeOpacity, stroke_opacity_value.release_nonnull()); } }); } @@ -133,7 +137,7 @@ Optional SVGGraphicsElement::fill_color() const return layout_node()->computed_values().fill().map([&](auto& paint) -> Gfx::Color { if (!paint.is_color()) return Color::Black; - return paint.as_color().with_alpha(m_fill_opacity.value_or(1) * 255); + return paint.as_color(); }); } @@ -150,6 +154,20 @@ Optional SVGGraphicsElement::stroke_color() const }); } +Optional SVGGraphicsElement::fill_opacity() const +{ + if (!layout_node()) + return {}; + return layout_node()->computed_values().fill_opacity(); +} + +Optional SVGGraphicsElement::stroke_opacity() const +{ + if (!layout_node()) + return {}; + return layout_node()->computed_values().stroke_opacity(); +} + Optional SVGGraphicsElement::stroke_width() const { if (!layout_node()) diff --git a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h index 1016ee8dc1..e3192e5eda 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h +++ b/Userland/Libraries/LibWeb/SVG/SVGGraphicsElement.h @@ -30,6 +30,8 @@ public: Gfx::Painter::WindingRule fill_rule() const; Optional stroke_color() const; Optional stroke_width() const; + Optional fill_opacity() const; + Optional stroke_opacity() const; float visible_stroke_width() const { @@ -47,7 +49,6 @@ protected: virtual JS::ThrowCompletionOr initialize(JS::Realm&) override; - Optional m_fill_opacity = {}; Gfx::AffineTransform m_transform = {}; }; diff --git a/Userland/Libraries/LibWeb/SVG/SVGStopElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGStopElement.cpp index 7f7d70807c..3f8447f479 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGStopElement.cpp +++ b/Userland/Libraries/LibWeb/SVG/SVGStopElement.cpp @@ -31,11 +31,15 @@ void SVGStopElement::apply_presentational_hints(CSS::StyleProperties& style) con { CSS::Parser::ParsingContext parsing_context { document() }; for_each_attribute([&](auto& name, auto& value) { + CSS::Parser::ParsingContext parsing_context { document() }; if (name.equals_ignoring_ascii_case("stop-color"sv)) { - CSS::Parser::ParsingContext parsing_context { document() }; if (auto stop_color = parse_css_value(parsing_context, value, CSS::PropertyID::StopColor).release_value_but_fixme_should_propagate_errors()) { style.set_property(CSS::PropertyID::StopColor, stop_color.release_nonnull()); } + } else if (name.equals_ignoring_ascii_case("stop-opacity"sv)) { + if (auto stop_opacity = parse_css_value(parsing_context, value, CSS::PropertyID::StopOpacity).release_value_but_fixme_should_propagate_errors()) { + style.set_property(CSS::PropertyID::StopOpacity, stop_opacity.release_nonnull()); + } } }); } @@ -47,6 +51,13 @@ Gfx::Color SVGStopElement::stop_color() const return Color::Black; } +float SVGStopElement::stop_opacity() const +{ + if (auto css_values = computed_css_values()) + return css_values->stop_opacity(); + return 1; +} + JS::NonnullGCPtr SVGStopElement::offset() const { TODO(); diff --git a/Userland/Libraries/LibWeb/SVG/SVGStopElement.h b/Userland/Libraries/LibWeb/SVG/SVGStopElement.h index 49b2cb20d6..bfd9e24d33 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGStopElement.h +++ b/Userland/Libraries/LibWeb/SVG/SVGStopElement.h @@ -27,6 +27,7 @@ public: NumberPercentage stop_offset() const { return m_offset.value_or(NumberPercentage::create_number(0)); } Gfx::Color stop_color() const; + float stop_opacity() const; private: SVGStopElement(DOM::Document&, DOM::QualifiedName);