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.
This commit is contained in:
parent
5594a492f0
commit
988a8ed3d8
Notes:
sideshowbarker
2024-07-18 01:21:05 +09:00
Author: https://github.com/AtkinsSJ Commit: https://github.com/SerenityOS/serenity/commit/988a8ed3d86 Pull-request: https://github.com/SerenityOS/serenity/pull/10844 Reviewed-by: https://github.com/awesomekling
6 changed files with 260 additions and 2 deletions
|
@ -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();
|
||||
|
|
|
@ -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&);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -139,6 +139,12 @@ OverflowStyleValue const& StyleValue::as_overflow() const
|
|||
return static_cast<OverflowStyleValue const&>(*this);
|
||||
}
|
||||
|
||||
PositionStyleValue const& StyleValue::as_position() const
|
||||
{
|
||||
VERIFY(is_position());
|
||||
return static_cast<PositionStyleValue const&>(*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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<BackgroundRepeatStyleValue&>(const_cast<StyleValue const&>(*this).as_background_repeat()); }
|
||||
BackgroundStyleValue& as_background() { return const_cast<BackgroundStyleValue&>(const_cast<StyleValue const&>(*this).as_background()); }
|
||||
BackgroundRepeatStyleValue& as_background_repeat() { return const_cast<BackgroundRepeatStyleValue&>(const_cast<StyleValue const&>(*this).as_background_repeat()); }
|
||||
BorderRadiusStyleValue& as_border_radius() { return const_cast<BorderRadiusStyleValue&>(const_cast<StyleValue const&>(*this).as_border_radius()); }
|
||||
BorderStyleValue& as_border() { return const_cast<BorderStyleValue&>(const_cast<StyleValue const&>(*this).as_border()); }
|
||||
BoxShadowStyleValue& as_box_shadow() { return const_cast<BoxShadowStyleValue&>(const_cast<StyleValue const&>(*this).as_box_shadow()); }
|
||||
|
@ -322,6 +332,7 @@ public:
|
|||
ListStyleStyleValue& as_list_style() { return const_cast<ListStyleStyleValue&>(const_cast<StyleValue const&>(*this).as_list_style()); }
|
||||
NumericStyleValue& as_numeric() { return const_cast<NumericStyleValue&>(const_cast<StyleValue const&>(*this).as_numeric()); }
|
||||
OverflowStyleValue& as_overflow() { return const_cast<OverflowStyleValue&>(const_cast<StyleValue const&>(*this).as_overflow()); }
|
||||
PositionStyleValue& as_position() { return const_cast<PositionStyleValue&>(const_cast<StyleValue const&>(*this).as_position()); }
|
||||
StringStyleValue& as_string() { return const_cast<StringStyleValue&>(const_cast<StyleValue const&>(*this).as_string()); }
|
||||
TextDecorationStyleValue& as_text_decoration() { return const_cast<TextDecorationStyleValue&>(const_cast<StyleValue const&>(*this).as_text_decoration()); }
|
||||
TransformationStyleValue& as_transformation() { return const_cast<TransformationStyleValue&>(const_cast<StyleValue const&>(*this).as_transformation()); }
|
||||
|
@ -407,6 +418,37 @@ private:
|
|||
// FIXME: background-origin
|
||||
};
|
||||
|
||||
class PositionStyleValue final : public StyleValue {
|
||||
public:
|
||||
static NonnullRefPtr<PositionStyleValue> 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<BackgroundRepeatStyleValue> create(NonnullRefPtr<StyleValue> repeat_x, NonnullRefPtr<StyleValue> repeat_y)
|
||||
|
|
|
@ -53,6 +53,7 @@ class MediaQueryList;
|
|||
class MediaQueryListEvent;
|
||||
class NumericStyleValue;
|
||||
class OverflowStyleValue;
|
||||
class PositionStyleValue;
|
||||
class PropertyOwningCSSStyleDeclaration;
|
||||
class Screen;
|
||||
class Selector;
|
||||
|
|
Loading…
Add table
Reference in a new issue