diff --git a/Base/res/html/misc/inline-node.html b/Base/res/html/misc/inline-node.html index d2f247cc45..0d5e0a62f3 100644 --- a/Base/res/html/misc/inline-node.html +++ b/Base/res/html/misc/inline-node.html @@ -42,9 +42,14 @@ } .outline3 { outline: 2px solid green; + outline-offset: 5px; border-radius: 10px; border: 2px solid black; } + .outline4 { + outline: 1px solid red; + outline-offset: -20px; + } @@ -57,7 +62,7 @@
This text should only have a strip of red on the left
- This text has an outline and this text has an outline with a border radius, and this also has a border. + This text has an outline and this text has an outline with a border radius, and this also has a border. This outline is a strikethrough.
diff --git a/Base/res/html/misc/outline.html b/Base/res/html/misc/outline.html index f1ba9ef76a..02634bf38a 100644 --- a/Base/res/html/misc/outline.html +++ b/Base/res/html/misc/outline.html @@ -6,6 +6,7 @@ p { padding: 5px; border: 2px solid black; + margin: 1em; } .outline-default { outline: auto; @@ -21,6 +22,14 @@ color: saddlebrown; outline: 5px dotted currentcolor; } + .outline-offset { + outline: 5px solid blue; + outline-offset: 0.5em; + } + .outline-inset { + outline: 1px solid red; + outline-offset: -10em; + } @@ -29,5 +38,7 @@

I have an outline!

I have an outline and a radius!

My outline is dotted and brown!

+

My outline is blue and away from my border!

+

My outline is a very thin red line in the middle of this box!

diff --git a/Userland/Libraries/LibWeb/CSS/ComputedValues.h b/Userland/Libraries/LibWeb/CSS/ComputedValues.h index 25a6e1e7f6..71fa353d1c 100644 --- a/Userland/Libraries/LibWeb/CSS/ComputedValues.h +++ b/Userland/Libraries/LibWeb/CSS/ComputedValues.h @@ -108,6 +108,7 @@ public: static CSS::Time transition_delay() { return CSS::Time::make_seconds(0); } static CSS::ObjectFit object_fit() { return CSS::ObjectFit::Fill; } static Color outline_color() { return Color::Black; } + static CSS::Length outline_offset() { return CSS::Length::make_px(0); } static CSS::OutlineStyle outline_style() { return CSS::OutlineStyle::None; } static CSS::Length outline_width() { return CSS::Length::make_px(3); } }; @@ -328,6 +329,7 @@ public: CSS::Time transition_delay() const { return m_noninherited.transition_delay; } Color outline_color() const { return m_noninherited.outline_color; } + CSS::Length outline_offset() const { return m_noninherited.outline_offset; } CSS::OutlineStyle outline_style() const { return m_noninherited.outline_style; } CSS::Length outline_width() const { return m_noninherited.outline_width; } @@ -442,6 +444,7 @@ protected: float stop_opacity { InitialValues::stop_opacity() }; CSS::Time transition_delay { InitialValues::transition_delay() }; Color outline_color { InitialValues::outline_color() }; + CSS::Length outline_offset { InitialValues::outline_offset() }; CSS::OutlineStyle outline_style { InitialValues::outline_style() }; CSS::Length outline_width { InitialValues::outline_width() }; } m_noninherited; @@ -554,6 +557,7 @@ public: void set_stop_opacity(float value) { m_noninherited.stop_opacity = value; } void set_text_anchor(CSS::TextAnchor value) { m_inherited.text_anchor = value; } void set_outline_color(Color value) { m_noninherited.outline_color = value; } + void set_outline_offset(CSS::Length value) { m_noninherited.outline_offset = value; } void set_outline_style(CSS::OutlineStyle value) { m_noninherited.outline_style = value; } void set_outline_width(CSS::Length value) { m_noninherited.outline_width = value; } }; diff --git a/Userland/Libraries/LibWeb/CSS/Properties.json b/Userland/Libraries/LibWeb/CSS/Properties.json index 726c683c76..47b6712d45 100644 --- a/Userland/Libraries/LibWeb/CSS/Properties.json +++ b/Userland/Libraries/LibWeb/CSS/Properties.json @@ -1603,6 +1603,14 @@ "color" ] }, + "outline-offset": { + "affects-layout": false, + "inherited": false, + "initial": "0", + "valid-types": [ + "length [-∞,∞]" + ] + }, "outline-style": { "affects-layout": false, "inherited": false, diff --git a/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp b/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp index f8c5a55ca2..68b60905bd 100644 --- a/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp +++ b/Userland/Libraries/LibWeb/CSS/ResolvedCSSStyleDeclaration.cpp @@ -738,6 +738,8 @@ ErrorOr> ResolvedCSSStyleDeclaration::style_value_for_p } case PropertyID::OutlineColor: return ColorStyleValue::create(layout_node.computed_values().outline_color()); + case PropertyID::OutlineOffset: + return LengthStyleValue::create(layout_node.computed_values().outline_offset()); case PropertyID::OutlineStyle: return IdentifierStyleValue::create(to_value_id(layout_node.computed_values().outline_style())); case PropertyID::OutlineWidth: diff --git a/Userland/Libraries/LibWeb/Layout/Node.cpp b/Userland/Libraries/LibWeb/Layout/Node.cpp index 7fba504bec..b8f2b12382 100644 --- a/Userland/Libraries/LibWeb/Layout/Node.cpp +++ b/Userland/Libraries/LibWeb/Layout/Node.cpp @@ -709,6 +709,8 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style) if (auto outline_color = computed_style.property(CSS::PropertyID::OutlineColor); outline_color->has_color()) computed_values.set_outline_color(outline_color->to_color(*this)); + if (auto outline_offset = computed_style.property(CSS::PropertyID::OutlineOffset); outline_offset->is_length()) + computed_values.set_outline_offset(outline_offset->as_length().length()); if (auto outline_style = computed_style.outline_style(); outline_style.has_value()) computed_values.set_outline_style(outline_style.value()); if (auto outline_width = computed_style.property(CSS::PropertyID::OutlineWidth); outline_width->is_length()) diff --git a/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp b/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp index e4d9835240..d1266b6d93 100644 --- a/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/InlinePaintable.cpp @@ -86,7 +86,7 @@ void InlinePaintable::paint(PaintContext& context, PaintPhase phase) const }); } - auto paint_border_or_outline = [&](Optional outline_data = {}) { + auto paint_border_or_outline = [&](Optional outline_data = {}, CSSPixels outline_offset = 0) { auto top_left_border_radius = computed_values().border_top_left_radius(); auto top_right_border_radius = computed_values().border_top_right_radius(); auto bottom_right_border_radius = computed_values().border_bottom_right_radius(); @@ -119,8 +119,21 @@ void InlinePaintable::paint(PaintContext& context, PaintPhase phase) const auto border_radii_data = normalized_border_radii_data(layout_node(), borders_rect, top_left_border_radius, top_right_border_radius, bottom_right_border_radius, bottom_left_border_radius); if (outline_data.has_value()) { - border_radii_data.inflate(outline_data->top.width, outline_data->right.width, outline_data->bottom.width, outline_data->left.width); - borders_rect.inflate(outline_data->top.width, outline_data->right.width, outline_data->bottom.width, outline_data->left.width); + auto outline_offset_x = outline_offset; + auto outline_offset_y = outline_offset; + // "Both the height and the width of the outside of the shape drawn by the outline should not + // become smaller than twice the computed value of the outline-width property to make sure + // that an outline can be rendered even with large negative values." + // https://www.w3.org/TR/css-ui-4/#outline-offset + // So, if the horizontal outline offset is > half the borders_rect's width then we set it to that. + // (And the same for y) + if ((borders_rect.width() / 2) + outline_offset_x < 0) + outline_offset_x = -borders_rect.width() / 2; + if ((borders_rect.height() / 2) + outline_offset_y < 0) + outline_offset_y = -borders_rect.height() / 2; + + border_radii_data.inflate(outline_data->top.width + outline_offset_y, outline_data->right.width + outline_offset_x, outline_data->bottom.width + outline_offset_y, outline_data->left.width + outline_offset_x); + borders_rect.inflate(outline_data->top.width + outline_offset_y, outline_data->right.width + outline_offset_x, outline_data->bottom.width + outline_offset_y, outline_data->left.width + outline_offset_x); paint_all_borders(context, borders_rect, border_radii_data, *outline_data); } else { paint_all_borders(context, borders_rect, border_radii_data, borders_data); @@ -138,7 +151,7 @@ void InlinePaintable::paint(PaintContext& context, PaintPhase phase) const auto outline_width = computed_values().outline_width().to_px(layout_node()); auto maybe_outline_data = borders_data_for_outline(layout_node(), computed_values().outline_color(), computed_values().outline_style(), outline_width); if (maybe_outline_data.has_value()) { - paint_border_or_outline(maybe_outline_data.value()); + paint_border_or_outline(maybe_outline_data.value(), computed_values().outline_offset().to_px(layout_node())); } } diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index 460a3753a6..12cde61398 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -174,9 +174,27 @@ void PaintableBox::paint(PaintContext& context, PaintPhase phase) const auto outline_width = computed_values().outline_width().to_px(layout_node()); auto borders_data = borders_data_for_outline(layout_node(), computed_values().outline_color(), computed_values().outline_style(), outline_width); if (borders_data.has_value()) { + auto outline_offset = computed_values().outline_offset().to_px(layout_node()); auto border_radius_data = normalized_border_radii_data(ShrinkRadiiForBorders::No); - border_radius_data.inflate(outline_width, outline_width, outline_width, outline_width); - paint_all_borders(context, absolute_border_box_rect().inflated(outline_width, outline_width, outline_width, outline_width), border_radius_data, borders_data.value()); + auto borders_rect = absolute_border_box_rect(); + + auto outline_offset_x = outline_offset; + auto outline_offset_y = outline_offset; + // "Both the height and the width of the outside of the shape drawn by the outline should not + // become smaller than twice the computed value of the outline-width property to make sure + // that an outline can be rendered even with large negative values." + // https://www.w3.org/TR/css-ui-4/#outline-offset + // So, if the horizontal outline offset is > half the borders_rect's width then we set it to that. + // (And the same for y) + if ((borders_rect.width() / 2) + outline_offset_x < 0) + outline_offset_x = -borders_rect.width() / 2; + if ((borders_rect.height() / 2) + outline_offset_y < 0) + outline_offset_y = -borders_rect.height() / 2; + + border_radius_data.inflate(outline_width + outline_offset_y, outline_width + outline_offset_x, outline_width + outline_offset_y, outline_width + outline_offset_x); + borders_rect.inflate(outline_width + outline_offset_y, outline_width + outline_offset_x, outline_width + outline_offset_y, outline_width + outline_offset_x); + + paint_all_borders(context, borders_rect, border_radius_data, borders_data.value()); } }