diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 38f22237d1..161e8e2d7e 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -4,6 +4,7 @@ * Copyright (c) 2021-2023, Sam Atkins * Copyright (c) 2021, Tobias Christiansen * Copyright (c) 2022, MacDue + * Copyright (c) 2024, Shannon Booth * * SPDX-License-Identifier: BSD-2-Clause */ @@ -2818,84 +2819,78 @@ RefPtr Parser::parse_position_value(TokenStream ] && - // [ top | bottom ] - // | - // [ left | right ] && - // [ [ top | bottom ] ] - // ] + // [ center | [ left | right ] ? ] && + // [ center | [ top | bottom ] ? ] auto alternative_5_for_background_position = [&]() -> RefPtr { auto transaction = tokens.begin_transaction(); - tokens.skip_whitespace(); - Optional horizontal_edge; - Optional horizontal_offset; - Optional vertical_edge; - Optional vertical_offset; - auto matches = [&]() { - return horizontal_edge.has_value() && vertical_edge.has_value() - && horizontal_offset.has_value() != vertical_offset.has_value(); + struct PositionAndMaybeLength { + PositionEdge position; + Optional length; }; - auto parse_horizontal = [&] { - // [ left | right ] ? - auto transaction = tokens.begin_transaction(); + // [ ? ] + auto parse_position_and_maybe_length = [&]() -> Optional { tokens.skip_whitespace(); - auto edge = parse_position_edge(tokens.next_token()); - if (!edge.has_value() || !is_horizontal(*edge, false)) - return false; - horizontal_edge = move(edge); + + auto maybe_position = parse_position_edge(tokens.next_token()); + if (!maybe_position.has_value()) + return {}; tokens.skip_whitespace(); - auto length_percentage = parse_length_percentage(tokens.peek_token()); - if (length_percentage.has_value()) { - (void)tokens.next_token(); // offset - horizontal_offset = move(length_percentage); + + auto maybe_length = parse_length_percentage(tokens.peek_token()); + if (maybe_length.has_value()) { + // 'center' cannot be followed by a + if (maybe_position.value() == PositionEdge::Center && maybe_length.has_value()) + return {}; + tokens.next_token(); } - transaction.commit(); - return true; + + return PositionAndMaybeLength { + .position = maybe_position.release_value(), + .length = move(maybe_length), + }; }; - auto parse_vertical = [&] { - // [ top | bottom ] ? - auto transaction = tokens.begin_transaction(); - tokens.skip_whitespace(); - auto edge = parse_position_edge(tokens.next_token()); - if (!edge.has_value() || !is_vertical(*edge, false)) - return false; - vertical_edge = move(edge); + auto maybe_group1 = parse_position_and_maybe_length(); + if (!maybe_group1.has_value()) + return nullptr; - tokens.skip_whitespace(); - auto length_percentage = parse_length_percentage(tokens.peek_token()); - if (length_percentage.has_value()) { - (void)tokens.next_token(); // offset - vertical_offset = move(length_percentage); - } - transaction.commit(); - return true; + auto maybe_group2 = parse_position_and_maybe_length(); + if (!maybe_group2.has_value()) + return nullptr; + + auto group1 = maybe_group1.release_value(); + auto group2 = maybe_group2.release_value(); + + // 2-value or 4-value if both s are present or missing. + if (group1.length.has_value() == group2.length.has_value()) + return nullptr; + + // If 'left' or 'right' is given, that position is X and the other is Y. + // Conversely - + // If 'top' or 'bottom' is given, that position is Y and the other is X. + if (is_vertical(group1.position, false) || is_horizontal(group2.position, false)) + swap(group1, group2); + + // [ center | [ left | right ] ] + if (!is_horizontal(group1.position, true)) + return nullptr; + + // [ center | [ top | bottom ] ] + if (!is_vertical(group2.position, true)) + return nullptr; + + auto to_style_value = [&](PositionAndMaybeLength const& group, bool is_horizontal) -> NonnullRefPtr { + if (group.position == PositionEdge::Center) + return EdgeStyleValue::create(is_horizontal ? PositionEdge::Left : PositionEdge::Top, Percentage { 50 }); + + return EdgeStyleValue::create(group.position, group.length.value_or(Length::make_px(0))); }; - if (parse_horizontal() && parse_vertical() && matches()) { - transaction.commit(); - return PositionStyleValue::create( - EdgeStyleValue::create(*horizontal_edge, horizontal_offset.value_or(Length::make_px(0))), - EdgeStyleValue::create(*vertical_edge, vertical_offset.value_or(Length::make_px(0)))); - } - - horizontal_edge.clear(); - horizontal_offset.clear(); - vertical_edge.clear(); - vertical_offset.clear(); - - if (parse_vertical() && parse_horizontal() && matches()) { - transaction.commit(); - return PositionStyleValue::create( - EdgeStyleValue::create(*horizontal_edge, horizontal_offset.value_or(Length::make_px(0))), - EdgeStyleValue::create(*vertical_edge, vertical_offset.value_or(Length::make_px(0)))); - } - - return nullptr; + transaction.commit(); + return PositionStyleValue::create(to_style_value(group1, true), to_style_value(group2, false)); }; // Note: The alternatives must be attempted in this order since shorter alternatives can match a prefix of longer ones.