summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb/CSS/Parser
diff options
context:
space:
mode:
authorSam Atkins <atkinssj@serenityos.org>2021-10-31 16:02:29 +0000
committerAndreas Kling <kling@serenityos.org>2021-11-10 14:38:49 +0100
commit988a8ed3d86b6b0ff878385987609a892ee320dd (patch)
treeb9e9470338403a7005e4a90bcce7450f1f5a5451 /Userland/Libraries/LibWeb/CSS/Parser
parent5594a492f0ff676f24906290b50d174af496f641 (diff)
downloadserenity-988a8ed3d86b6b0ff878385987609a892ee320dd.zip
LibWeb: Parse CSS `background-position` property
This is done a bit differently from other properties: using a TokenStream instead of just a Vector of ComponentValues. The reason for this is, we can then use call the same function when parsing the `background` shorthand. Otherwise, we would have to know in advance how many values to pass down, which basically would involve duplicating the `background-position` parsing code inside `background`. The StyleValue is PositionStyleValue, since it represents a `<position>`: https://www.w3.org/TR/css-values-4/#typedef-position Unfortunately, background-position's parsing is a bit different from `<position>`'s, (background-position allows 3-value syntax and `<position>` doesn't) so we'll need to come back and write a different parsing function for that later.
Diffstat (limited to 'Userland/Libraries/LibWeb/CSS/Parser')
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp173
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/Parser.h2
2 files changed, 175 insertions, 0 deletions
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<StyleValue> Parser::parse_background_image_value(ParsingContext const& co
return nullptr;
}
+RefPtr<StyleValue> Parser::parse_single_background_position_value(ParsingContext const& context, TokenStream<StyleComponentValueRule>& tokens)
+{
+ // NOTE: This *looks* like it parses a <position>, 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 <position> later.
+
+ auto start_position = tokens.position();
+ auto error = [&]() {
+ tokens.rewind_to_position(start_position);
+ return nullptr;
+ };
+
+ auto to_edge = [](ValueID identifier) -> Optional<PositionEdge> {
+ 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<EdgeOffset> horizontal;
+ Optional<EdgeOffset> 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 `<edge> <length>`:
+ // 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<StyleValue> Parser::parse_background_position_value(ParsingContext const& context, Vector<StyleComponentValueRule> 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<StyleValue> Parser::parse_background_repeat_value(ParsingContext const& context, Vector<StyleComponentValueRule> const& component_values)
{
auto is_directional_repeat = [](StyleValue const& value) -> bool {
@@ -3245,6 +3414,10 @@ Result<NonnullRefPtr<StyleValue>, 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<StyleValue> parse_image_value(ParsingContext const&, StyleComponentValueRule const&);
static RefPtr<StyleValue> parse_background_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);
static RefPtr<StyleValue> parse_background_image_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);
+ static RefPtr<StyleValue> parse_single_background_position_value(ParsingContext const&, TokenStream<StyleComponentValueRule>&);
+ static RefPtr<StyleValue> parse_background_position_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);
static RefPtr<StyleValue> parse_background_repeat_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);
static RefPtr<StyleValue> parse_border_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);
static RefPtr<StyleValue> parse_border_radius_value(ParsingContext const&, Vector<StyleComponentValueRule> const&);