From 8917378aa77dae2018f92b0fe24f5f86fbde1aa8 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Tue, 7 Nov 2023 11:50:44 +0000 Subject: [PATCH] LibWeb: Adapt `parse_position()` into `parse_position_value()` Having two ways that `` is represented is awkward and unnecessary. So, let's combine the two paths together. This first step copies and modifies the `parse_position()` code to produce a `PositionStyleValue`. Apart from returning a StyleValue, this also makes use of automatic enum parsing instead of manually comparing identifier strings. --- Userland/Libraries/LibWeb/CSS/Enums.json | 1 + .../Libraries/LibWeb/CSS/Parser/Parser.cpp | 202 ++++++++++++++++++ Userland/Libraries/LibWeb/CSS/Parser/Parser.h | 1 + .../LibWeb/CSS/StyleValues/EdgeStyleValue.h | 2 + 4 files changed, 206 insertions(+) diff --git a/Userland/Libraries/LibWeb/CSS/Enums.json b/Userland/Libraries/LibWeb/CSS/Enums.json index 1eded3827e..91bd687aff 100644 --- a/Userland/Libraries/LibWeb/CSS/Enums.json +++ b/Userland/Libraries/LibWeb/CSS/Enums.json @@ -334,6 +334,7 @@ "sticky" ], "position-edge": [ + "center", "left", "right", "top", diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 290b83bf5c..8dea7cbb1a 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -2558,6 +2558,208 @@ RefPtr Parser::parse_paint_value(TokenStream& tokens return nullptr; } +// https://www.w3.org/TR/css-values-4/#position +RefPtr Parser::parse_position_value(TokenStream& tokens) +{ + auto parse_position_edge = [](ComponentValue const& token) -> Optional { + if (!token.is(Token::Type::Ident)) + return {}; + auto ident = value_id_from_string(token.token().ident()); + if (!ident.has_value()) + return {}; + return value_id_to_position_edge(*ident); + }; + + auto parse_length_percentage = [&](ComponentValue const& token) -> Optional { + if (token.is(Token::Type::EndOfFile)) + return {}; + // FIXME: calc()! + auto dimension = parse_dimension(token); + if (!dimension.has_value() || !dimension->is_length_percentage()) + return {}; + return dimension->length_percentage(); + }; + + auto is_horizontal = [](PositionEdge edge, bool accept_center) -> bool { + switch (edge) { + case PositionEdge::Left: + case PositionEdge::Right: + return true; + case PositionEdge::Center: + return accept_center; + default: + return false; + } + }; + + auto is_vertical = [](PositionEdge edge, bool accept_center) -> bool { + switch (edge) { + case PositionEdge::Top: + case PositionEdge::Bottom: + return true; + case PositionEdge::Center: + return accept_center; + default: + return false; + } + }; + + auto make_edge_style_value = [](PositionEdge position_edge, bool is_horizontal) -> NonnullRefPtr { + if (position_edge == PositionEdge::Center) + return EdgeStyleValue::create(is_horizontal ? PositionEdge::Left : PositionEdge::Top, Percentage { 50 }); + return EdgeStyleValue::create(position_edge, Length::make_px(0)); + }; + + // = [ + // [ left | center | right ] || [ top | center | bottom ] + // | + // [ left | center | right | ] + // [ top | center | bottom | ]? + // | + // [ [ left | right ] ] && + // [ [ top | bottom ] ] + // ] + + // [ left | center | right ] || [ top | center | bottom ] + auto alternative_1 = [&]() -> RefPtr { + auto transaction = tokens.begin_transaction(); + tokens.skip_whitespace(); + auto maybe_first_edge = parse_position_edge(tokens.next_token()); + if (!maybe_first_edge.has_value()) + return nullptr; + auto first_edge = maybe_first_edge.release_value(); + + // Try and parse the two-value variant + tokens.skip_whitespace(); + auto maybe_second_edge = parse_position_edge(tokens.peek_token()); + if (maybe_second_edge.has_value()) { + auto second_edge = maybe_second_edge.release_value(); + + if (is_horizontal(first_edge, true) && is_vertical(second_edge, true)) { + (void)tokens.next_token(); // second_edge + transaction.commit(); + return PositionStyleValue::create(make_edge_style_value(first_edge, true), make_edge_style_value(second_edge, false)); + } else if (is_vertical(first_edge, true) && is_horizontal(second_edge, true)) { + (void)tokens.next_token(); // second_edge + transaction.commit(); + return PositionStyleValue::create(make_edge_style_value(second_edge, true), make_edge_style_value(first_edge, false)); + } + + // Otherwise, second value isn't valid as part of this position, so ignore it and fall back to single-edge parsing. + } + + // Single-value variant + transaction.commit(); + if (is_horizontal(first_edge, false)) + return PositionStyleValue::create(make_edge_style_value(first_edge, true), make_edge_style_value(PositionEdge::Center, false)); + if (is_vertical(first_edge, false)) + return PositionStyleValue::create(make_edge_style_value(PositionEdge::Center, true), make_edge_style_value(first_edge, false)); + VERIFY(first_edge == PositionEdge::Center); + return PositionStyleValue::create(make_edge_style_value(PositionEdge::Center, true), make_edge_style_value(PositionEdge::Center, false)); + }; + + // [ left | center | right | ] + // [ top | center | bottom | ]? + auto alternative_2 = [&]() -> RefPtr { + auto transaction = tokens.begin_transaction(); + tokens.skip_whitespace(); + RefPtr horizontal_edge; + RefPtr vertical_edge; + + auto& first_token = tokens.next_token(); + if (auto edge = parse_position_edge(first_token); edge.has_value() && is_horizontal(*edge, true)) { + horizontal_edge = make_edge_style_value(*edge, true); + } else { + auto length_percentage = parse_length_percentage(first_token); + if (!length_percentage.has_value()) + return nullptr; + horizontal_edge = EdgeStyleValue::create(PositionEdge::Left, *length_percentage); + } + + auto transaction_optional_parse = tokens.begin_transaction(); + tokens.skip_whitespace(); + if (tokens.has_next_token()) { + auto& second_token = tokens.next_token(); + if (auto edge = parse_position_edge(second_token); edge.has_value() && is_vertical(*edge, true)) { + transaction_optional_parse.commit(); + vertical_edge = make_edge_style_value(*edge, false); + } else { + auto length_percentage = parse_length_percentage(second_token); + if (length_percentage.has_value()) { + transaction_optional_parse.commit(); + vertical_edge = EdgeStyleValue::create(PositionEdge::Top, *length_percentage); + } + } + } + + transaction.commit(); + if (!vertical_edge) + vertical_edge = make_edge_style_value(PositionEdge::Center, false); + return PositionStyleValue::create(horizontal_edge.release_nonnull(), vertical_edge.release_nonnull()); + }; + + // [ [ left | right ] ] && + // [ [ top | bottom ] ] + auto alternative_3 = [&]() -> RefPtr { + auto transaction = tokens.begin_transaction(); + tokens.skip_whitespace(); + RefPtr horizontal_edge; + RefPtr vertical_edge; + + auto parse_horizontal = [&] { + // [ left | right ] ] + auto transaction = tokens.begin_transaction(); + tokens.skip_whitespace(); + auto edge = parse_position_edge(tokens.next_token()); + if (!edge.has_value() || !is_horizontal(*edge, false)) + return false; + + tokens.skip_whitespace(); + auto length_percentage = parse_length_percentage(tokens.next_token()); + if (!length_percentage.has_value()) + return false; + + horizontal_edge = EdgeStyleValue::create(*edge, *length_percentage); + transaction.commit(); + return true; + }; + + 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; + + tokens.skip_whitespace(); + auto length_percentage = parse_length_percentage(tokens.next_token()); + if (!length_percentage.has_value()) + return false; + + vertical_edge = EdgeStyleValue::create(*edge, *length_percentage); + transaction.commit(); + return true; + }; + + if ((parse_horizontal() && parse_vertical()) || (parse_vertical() && parse_horizontal())) { + transaction.commit(); + return PositionStyleValue::create(horizontal_edge.release_nonnull(), vertical_edge.release_nonnull()); + } + + return nullptr; + }; + + // Note: The alternatives must be attempted in this order since `alternative_2' can match a prefix of `alternative_3' + if (auto position = alternative_3()) + return position.release_nonnull(); + if (auto position = alternative_2()) + return position; + if (auto position = alternative_1()) + return position; + return nullptr; +} + template RefPtr Parser::parse_comma_separated_value_list(Vector const& component_values, ParseFunction parse_one_value) { diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index b05ef64de4..49f9f4ae97 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -217,6 +217,7 @@ private: RefPtr parse_string_value(ComponentValue const&); RefPtr parse_image_value(ComponentValue const&); RefPtr parse_paint_value(TokenStream&); + RefPtr parse_position_value(TokenStream&); template RefPtr parse_comma_separated_value_list(Vector const&, ParseFunction); RefPtr parse_simple_comma_separated_value_list(PropertyID, Vector const&); diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.h index ee97637bc6..531e208db4 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.h +++ b/Userland/Libraries/LibWeb/CSS/StyleValues/EdgeStyleValue.h @@ -16,10 +16,12 @@ class EdgeStyleValue final : public StyleValueWithDefaultOperators create(PositionEdge edge, LengthPercentage const& offset) { + VERIFY(edge != PositionEdge::Center); return adopt_ref(*new (nothrow) EdgeStyleValue(edge, offset)); } virtual ~EdgeStyleValue() override = default; + // NOTE: `center` is converted to `left 50%` or `top 50%` in parsing, so is never returned here. PositionEdge edge() const { return m_properties.edge; } LengthPercentage const& offset() const { return m_properties.offset; }