diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 44f47c1700..a4828d2ccd 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -2487,6 +2487,175 @@ RefPtr Parser::parse_background_image_value(ParsingContext const& co return nullptr; } +RefPtr Parser::parse_single_background_position_value(ParsingContext const& context, TokenStream& tokens) +{ + // NOTE: This *looks* like it parses a , but it doesn't. From the spec: + // "Note: The background-position property also accepts a three-value syntax. + // This has been disallowed generically because it creates parsing ambiguities + // when combined with other length or percentage components in a property value." + // - https://www.w3.org/TR/css-values-4/#typedef-position + // So, we'll need a separate function to parse later. + + auto start_position = tokens.position(); + auto error = [&]() { + tokens.rewind_to_position(start_position); + return nullptr; + }; + + auto to_edge = [](ValueID identifier) -> Optional { + switch (identifier) { + case ValueID::Top: + return PositionEdge::Top; + case ValueID::Bottom: + return PositionEdge::Bottom; + case ValueID::Left: + return PositionEdge::Left; + case ValueID::Right: + return PositionEdge::Right; + default: + return {}; + } + }; + auto is_horizontal = [](ValueID identifier) -> bool { + switch (identifier) { + case ValueID::Left: + case ValueID::Right: + return true; + default: + return false; + } + }; + auto is_vertical = [](ValueID identifier) -> bool { + switch (identifier) { + case ValueID::Top: + case ValueID::Bottom: + return true; + default: + return false; + } + }; + + auto zero_offset = Length::make_px(0); + auto center_offset = Length { 50, Length::Type::Percentage }; + + struct EdgeOffset { + PositionEdge edge; + Length offset; + bool edge_provided; + bool offset_provided; + }; + + Optional horizontal; + Optional vertical; + bool found_center = false; + + while (tokens.has_next_token()) { + // Check if we're done + auto seen_items = (horizontal.has_value() ? 1 : 0) + (vertical.has_value() ? 1 : 0) + (found_center ? 1 : 0); + if (seen_items == 2) + break; + + auto& token = tokens.peek_token(); + auto maybe_value = parse_css_value(context, token); + if (!maybe_value || !property_accepts_value(PropertyID::BackgroundPosition, *maybe_value)) + break; + tokens.next_token(); + auto value = maybe_value.release_nonnull(); + + if (value->has_length()) { + if (!horizontal.has_value()) { + horizontal = EdgeOffset { PositionEdge::Left, value->to_length(), false, true }; + } else if (!vertical.has_value()) { + vertical = EdgeOffset { PositionEdge::Top, value->to_length(), false, true }; + } else { + return error(); + } + continue; + } + + if (value->has_identifier()) { + auto identifier = value->to_identifier(); + if (is_horizontal(identifier)) { + Length offset = zero_offset; + bool offset_provided = false; + if (tokens.has_next_token()) { + auto maybe_offset = parse_length(context, tokens.peek_token()); + if (maybe_offset.has_value()) { + offset = maybe_offset.value(); + offset_provided = true; + tokens.next_token(); + } + } + horizontal = EdgeOffset { *to_edge(identifier), offset, true, offset_provided }; + } else if (is_vertical(identifier)) { + Length offset = zero_offset; + bool offset_provided = false; + if (tokens.has_next_token()) { + auto maybe_offset = parse_length(context, tokens.peek_token()); + if (maybe_offset.has_value()) { + offset = maybe_offset.value(); + offset_provided = true; + tokens.next_token(); + } + } + vertical = EdgeOffset { *to_edge(identifier), offset, true, offset_provided }; + } else if (identifier == ValueID::Center) { + found_center = true; + } else { + return error(); + } + continue; + } + + tokens.reconsume_current_input_token(); + break; + } + + if (found_center) { + if (horizontal.has_value() && vertical.has_value()) + return error(); + if (!horizontal.has_value()) + horizontal = EdgeOffset { PositionEdge::Left, center_offset, true, false }; + if (!vertical.has_value()) + vertical = EdgeOffset { PositionEdge::Top, center_offset, true, false }; + } + + if (!horizontal.has_value() && !vertical.has_value()) + return error(); + + // Unpack ` `: + // The loop above reads this pattern as a single EdgeOffset, when actually, it should be treated + // as `x y` if the edge is horizontal, and `y` (with the second token reconsumed) otherwise. + if (!vertical.has_value() && horizontal->edge_provided && horizontal->offset_provided) { + // Split into `x y` + vertical = EdgeOffset { PositionEdge::Top, horizontal->offset, false, true }; + horizontal->offset = zero_offset; + horizontal->offset_provided = false; + } else if (!horizontal.has_value() && vertical->edge_provided && vertical->offset_provided) { + // `y`, reconsume + vertical->offset = zero_offset; + vertical->offset_provided = false; + tokens.reconsume_current_input_token(); + } + + // If only one value is specified, the second value is assumed to be center. + if (!horizontal.has_value()) + horizontal = EdgeOffset { PositionEdge::Left, center_offset, false, false }; + if (!vertical.has_value()) + vertical = EdgeOffset { PositionEdge::Top, center_offset, false, false }; + + return PositionStyleValue::create( + horizontal->edge, horizontal->offset, + vertical->edge, vertical->offset); +} + +RefPtr Parser::parse_background_position_value(ParsingContext const& context, Vector const& component_values) +{ + auto tokens = TokenStream { component_values }; + // FIXME: Handle multiple sets of comma-separated values. + return parse_single_background_position_value(context, tokens); +} + RefPtr Parser::parse_background_repeat_value(ParsingContext const& context, Vector const& component_values) { auto is_directional_repeat = [](StyleValue const& value) -> bool { @@ -3245,6 +3414,10 @@ Result, Parser::ParsingResult> Parser::parse_css_value if (auto parsed_value = parse_background_image_value(m_context, component_values)) return parsed_value.release_nonnull(); return ParsingResult::SyntaxError; + case PropertyID::BackgroundPosition: + if (auto parsed_value = parse_background_position_value(m_context, component_values)) + return parsed_value.release_nonnull(); + return ParsingResult::SyntaxError; case PropertyID::BackgroundRepeat: if (auto parsed_value = parse_background_repeat_value(m_context, component_values)) return parsed_value.release_nonnull(); diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index 2863a3c19d..4adb079b73 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -214,6 +214,8 @@ private: static RefPtr parse_image_value(ParsingContext const&, StyleComponentValueRule const&); static RefPtr parse_background_value(ParsingContext const&, Vector const&); static RefPtr parse_background_image_value(ParsingContext const&, Vector const&); + static RefPtr parse_single_background_position_value(ParsingContext const&, TokenStream&); + static RefPtr parse_background_position_value(ParsingContext const&, Vector const&); static RefPtr parse_background_repeat_value(ParsingContext const&, Vector const&); static RefPtr parse_border_value(ParsingContext const&, Vector const&); static RefPtr parse_border_radius_value(ParsingContext const&, Vector const&); diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp index f0c303b659..9bed5b7c21 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -343,6 +343,21 @@ static void set_property_expanding_shorthands(StyleProperties& style, CSS::Prope return; } + if (property_id == CSS::PropertyID::BackgroundPosition) { + if (value.is_value_list()) { + auto& background_position_list = value.as_value_list().values(); + // FIXME: Handle multiple backgrounds. + if (!background_position_list.is_empty()) { + auto& background_position = background_position_list.first(); + style.set_property(CSS::PropertyID::BackgroundPosition, background_position); + } + return; + } + + style.set_property(CSS::PropertyID::BackgroundPosition, value); + return; + } + if (property_id == CSS::PropertyID::BackgroundRepeat) { if (value.is_value_list()) { auto& background_repeat_list = value.as_value_list().values(); diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp index 3bd88e87ed..58edf51ccd 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp @@ -139,6 +139,12 @@ OverflowStyleValue const& StyleValue::as_overflow() const return static_cast(*this); } +PositionStyleValue const& StyleValue::as_position() const +{ + VERIFY(is_position()); + return static_cast(*this); +} + StringStyleValue const& StyleValue::as_string() const { VERIFY(is_string()); @@ -401,4 +407,23 @@ String ColorStyleValue::to_string() const return String::formatted("rgba({}, {}, {}, {})", m_color.red(), m_color.green(), m_color.blue(), (float)(m_color.alpha()) / 255.0f); } +String PositionStyleValue::to_string() const +{ + auto to_string = [](PositionEdge edge) { + switch (edge) { + case PositionEdge::Left: + return "left"; + case PositionEdge::Right: + return "right"; + case PositionEdge::Top: + return "top"; + case PositionEdge::Bottom: + return "bottom"; + } + VERIFY_NOT_REACHED(); + }; + + return String::formatted("{} {} {} {}", to_string(m_edge_x), m_offset_x.to_string(), to_string(m_edge_y), m_offset_y.to_string()); +} + } diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValue.h index 58dfc94903..608c128ba9 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.h +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.h @@ -167,6 +167,13 @@ enum class Position { Sticky, }; +enum class PositionEdge { + Left, + Right, + Top, + Bottom, +}; + enum class Repeat : u8 { NoRepeat, Repeat, @@ -242,6 +249,7 @@ public: ListStyle, Numeric, Overflow, + Position, String, TextDecoration, Transformation, @@ -270,6 +278,7 @@ public: bool is_list_style() const { return type() == Type::ListStyle; } bool is_numeric() const { return type() == Type::Numeric; } bool is_overflow() const { return type() == Type::Overflow; } + bool is_position() const { return type() == Type::Position; } bool is_string() const { return type() == Type::String; } bool is_text_decoration() const { return type() == Type::TextDecoration; } bool is_transformation() const { return type() == Type::Transformation; } @@ -278,8 +287,8 @@ public: bool is_builtin() const { return is_inherit() || is_initial() || is_unset(); } - BackgroundRepeatStyleValue const& as_background_repeat() const; BackgroundStyleValue const& as_background() const; + BackgroundRepeatStyleValue const& as_background_repeat() const; BorderRadiusStyleValue const& as_border_radius() const; BorderStyleValue const& as_border() const; BoxShadowStyleValue const& as_box_shadow() const; @@ -297,14 +306,15 @@ public: ListStyleStyleValue const& as_list_style() const; NumericStyleValue const& as_numeric() const; OverflowStyleValue const& as_overflow() const; + PositionStyleValue const& as_position() const; StringStyleValue const& as_string() const; TextDecorationStyleValue const& as_text_decoration() const; TransformationStyleValue const& as_transformation() const; UnsetStyleValue const& as_unset() const; StyleValueList const& as_value_list() const; - BackgroundRepeatStyleValue& as_background_repeat() { return const_cast(const_cast(*this).as_background_repeat()); } BackgroundStyleValue& as_background() { return const_cast(const_cast(*this).as_background()); } + BackgroundRepeatStyleValue& as_background_repeat() { return const_cast(const_cast(*this).as_background_repeat()); } BorderRadiusStyleValue& as_border_radius() { return const_cast(const_cast(*this).as_border_radius()); } BorderStyleValue& as_border() { return const_cast(const_cast(*this).as_border()); } BoxShadowStyleValue& as_box_shadow() { return const_cast(const_cast(*this).as_box_shadow()); } @@ -322,6 +332,7 @@ public: ListStyleStyleValue& as_list_style() { return const_cast(const_cast(*this).as_list_style()); } NumericStyleValue& as_numeric() { return const_cast(const_cast(*this).as_numeric()); } OverflowStyleValue& as_overflow() { return const_cast(const_cast(*this).as_overflow()); } + PositionStyleValue& as_position() { return const_cast(const_cast(*this).as_position()); } StringStyleValue& as_string() { return const_cast(const_cast(*this).as_string()); } TextDecorationStyleValue& as_text_decoration() { return const_cast(const_cast(*this).as_text_decoration()); } TransformationStyleValue& as_transformation() { return const_cast(const_cast(*this).as_transformation()); } @@ -407,6 +418,37 @@ private: // FIXME: background-origin }; +class PositionStyleValue final : public StyleValue { +public: + static NonnullRefPtr create(PositionEdge edge_x, Length const& offset_x, PositionEdge edge_y, Length const& offset_y) + { + return adopt_ref(*new PositionStyleValue(edge_x, offset_x, edge_y, offset_y)); + } + virtual ~PositionStyleValue() override { } + + PositionEdge edge_x() const { return m_edge_x; } + Length const& offset_x() const { return m_offset_x; } + PositionEdge edge_y() const { return m_edge_y; } + Length const& offset_y() const { return m_offset_y; } + + virtual String to_string() const override; + +private: + PositionStyleValue(PositionEdge edge_x, Length const& offset_x, PositionEdge edge_y, Length const& offset_y) + : StyleValue(Type::Position) + , m_edge_x(edge_x) + , m_offset_x(offset_x) + , m_edge_y(edge_y) + , m_offset_y(offset_y) + { + } + + PositionEdge m_edge_x; + Length m_offset_x; + PositionEdge m_edge_y; + Length m_offset_y; +}; + class BackgroundRepeatStyleValue final : public StyleValue { public: static NonnullRefPtr create(NonnullRefPtr repeat_x, NonnullRefPtr repeat_y) diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 894c9bed5c..7e48e812bd 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -53,6 +53,7 @@ class MediaQueryList; class MediaQueryListEvent; class NumericStyleValue; class OverflowStyleValue; +class PositionStyleValue; class PropertyOwningCSSStyleDeclaration; class Screen; class Selector;