LibWeb: Parse the rotate css property

(cherry picked from commit 488436fb54347e69040cfed4f523926241a136a3)
This commit is contained in:
stelar7 2024-10-16 08:50:35 +02:00 committed by Nico Weber
parent ef09e057ee
commit c39fa9a033
17 changed files with 284 additions and 0 deletions

View file

@ -38,6 +38,7 @@ source_set("StyleValues") {
"PositionStyleValue.cpp",
"RadialGradientStyleValue.cpp",
"RectStyleValue.cpp",
"RotationStyleValue.cpp",
"ShadowStyleValue.cpp",
"ShorthandStyleValue.cpp",
"StyleValueList.cpp",

View file

@ -170,6 +170,7 @@ padding-top: 0px
position: static
r: 0px
right: auto
rotate: none
row-gap: auto
rx: auto
ry: auto

View file

@ -139,6 +139,7 @@ set(SOURCES
CSS/StyleValues/PositionStyleValue.cpp
CSS/StyleValues/RadialGradientStyleValue.cpp
CSS/StyleValues/RectStyleValue.cpp
CSS/StyleValues/RotationStyleValue.cpp
CSS/StyleValues/ShadowStyleValue.cpp
CSS/StyleValues/ShorthandStyleValue.cpp
CSS/StyleValues/StyleValueList.cpp

View file

@ -47,6 +47,7 @@
#include <LibWeb/CSS/StyleValues/RatioStyleValue.h>
#include <LibWeb/CSS/StyleValues/RectStyleValue.h>
#include <LibWeb/CSS/StyleValues/ResolutionStyleValue.h>
#include <LibWeb/CSS/StyleValues/RotationStyleValue.h>
#include <LibWeb/CSS/StyleValues/ScrollbarGutterStyleValue.h>
#include <LibWeb/CSS/StyleValues/ShadowStyleValue.h>
#include <LibWeb/CSS/StyleValues/ShorthandStyleValue.h>
@ -287,6 +288,12 @@ ResolutionStyleValue const& CSSStyleValue::as_resolution() const
return static_cast<ResolutionStyleValue const&>(*this);
}
RotationStyleValue const& CSSStyleValue::as_rotation() const
{
VERIFY(is_rotation());
return static_cast<RotationStyleValue const&>(*this);
}
ScrollbarGutterStyleValue const& CSSStyleValue::as_scrollbar_gutter() const
{
VERIFY(is_scrollbar_gutter());

View file

@ -124,6 +124,7 @@ public:
Ratio,
Rect,
Resolution,
Rotation,
ScrollbarGutter,
Shadow,
Shorthand,
@ -289,6 +290,10 @@ public:
ResolutionStyleValue const& as_resolution() const;
ResolutionStyleValue& as_resolution() { return const_cast<ResolutionStyleValue&>(const_cast<CSSStyleValue const&>(*this).as_resolution()); }
bool is_rotation() const { return type() == Type::Rotation; }
RotationStyleValue const& as_rotation() const;
RotationStyleValue& as_rotation() { return const_cast<RotationStyleValue&>(const_cast<CSSStyleValue const&>(*this).as_rotation()); }
bool is_scrollbar_gutter() const { return type() == Type::ScrollbarGutter; }
ScrollbarGutterStyleValue const& as_scrollbar_gutter() const;
ScrollbarGutterStyleValue& as_scrollbar_gutter() { return const_cast<ScrollbarGutterStyleValue&>(const_cast<CSSStyleValue const&>(*this).as_scrollbar_gutter()); }

View file

@ -498,6 +498,7 @@ public:
Vector<CSS::Transformation> const& transformations() const { return m_noninherited.transformations; }
CSS::TransformBox const& transform_box() const { return m_noninherited.transform_box; }
CSS::TransformOrigin const& transform_origin() const { return m_noninherited.transform_origin; }
Optional<CSS::Transformation> const& rotate() const { return m_noninherited.rotate; }
Gfx::FontCascadeList const& font_list() const { return *m_inherited.font_list; }
CSSPixels font_size() const { return m_inherited.font_size; }
@ -670,6 +671,7 @@ protected:
CSS::ObjectFit object_fit { InitialValues::object_fit() };
CSS::ObjectPosition object_position { InitialValues::object_position() };
CSS::UnicodeBidi unicode_bidi { InitialValues::unicode_bidi() };
Optional<CSS::Transformation> rotate;
Optional<MaskReference> mask;
CSS::MaskType mask_type { InitialValues::mask_type() };
@ -781,6 +783,7 @@ public:
void set_justify_items(CSS::JustifyItems value) { m_noninherited.justify_items = value; }
void set_justify_self(CSS::JustifySelf value) { m_noninherited.justify_self = value; }
void set_box_shadow(Vector<ShadowData>&& value) { m_noninherited.box_shadow = move(value); }
void set_rotate(CSS::Transformation value) { m_noninherited.rotate = value; }
void set_transformations(Vector<CSS::Transformation> value) { m_noninherited.transformations = move(value); }
void set_transform_box(CSS::TransformBox value) { m_noninherited.transform_box = value; }
void set_transform_origin(CSS::TransformOrigin value) { m_noninherited.transform_origin = value; }

View file

@ -76,6 +76,7 @@
#include <LibWeb/CSS/StyleValues/RatioStyleValue.h>
#include <LibWeb/CSS/StyleValues/RectStyleValue.h>
#include <LibWeb/CSS/StyleValues/ResolutionStyleValue.h>
#include <LibWeb/CSS/StyleValues/RotationStyleValue.h>
#include <LibWeb/CSS/StyleValues/ScrollbarGutterStyleValue.h>
#include <LibWeb/CSS/StyleValues/ShadowStyleValue.h>
#include <LibWeb/CSS/StyleValues/ShorthandStyleValue.h>
@ -4945,6 +4946,95 @@ RefPtr<CSSStyleValue> Parser::parse_single_shadow_value(TokenStream<ComponentVal
return ShadowStyleValue::create(color.release_nonnull(), offset_x.release_nonnull(), offset_y.release_nonnull(), blur_radius.release_nonnull(), spread_distance.release_nonnull(), placement.release_value());
}
RefPtr<CSSStyleValue> Parser::parse_rotate_value(TokenStream<ComponentValue>& tokens)
{
// Value: none | <angle> | [ x | y | z | <number>{3} ] && <angle>
if (tokens.remaining_token_count() == 1) {
// "none"
if (auto none = parse_all_as_single_keyword_value(tokens, Keyword::None))
return none;
// <angle>
if (auto angle = parse_angle_value(tokens))
return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(0), NumberStyleValue::create(0), NumberStyleValue::create(1));
}
auto parse_one_of_xyz = [&]() -> Optional<ComponentValue> {
auto transaction = tokens.begin_transaction();
auto axis = tokens.consume_a_token();
if (axis.is_ident("x"sv) || axis.is_ident("y"sv) || axis.is_ident("z"sv)) {
transaction.commit();
return axis;
}
return {};
};
// [ x | y | z ] && <angle>
if (tokens.remaining_token_count() == 2) {
// Try parsing `x <angle>`
if (auto axis = parse_one_of_xyz(); axis.has_value()) {
if (auto angle = parse_angle_value(tokens); angle) {
if (axis->is_ident("x"sv))
return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(1), NumberStyleValue::create(0), NumberStyleValue::create(0));
if (axis->is_ident("y"sv))
return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(0), NumberStyleValue::create(1), NumberStyleValue::create(0));
if (axis->is_ident("z"sv))
return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(0), NumberStyleValue::create(0), NumberStyleValue::create(1));
}
}
// Try parsing `<angle> x`
if (auto angle = parse_angle_value(tokens); angle) {
if (auto axis = parse_one_of_xyz(); axis.has_value()) {
if (axis->is_ident("x"sv))
return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(1), NumberStyleValue::create(0), NumberStyleValue::create(0));
if (axis->is_ident("y"sv))
return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(0), NumberStyleValue::create(1), NumberStyleValue::create(0));
if (axis->is_ident("z"sv))
return RotationStyleValue::create(angle.release_nonnull(), NumberStyleValue::create(0), NumberStyleValue::create(0), NumberStyleValue::create(1));
}
}
}
auto parse_three_numbers = [&]() -> Optional<StyleValueVector> {
auto transaction = tokens.begin_transaction();
StyleValueVector numbers;
for (size_t i = 0; i < 3; ++i) {
if (auto number = parse_number_value(tokens); number) {
numbers.append(number.release_nonnull());
} else {
return {};
}
}
transaction.commit();
return numbers;
};
// <number>{3} && <angle>
if (tokens.remaining_token_count() == 4) {
// Try parsing <number>{3} <angle>
if (auto maybe_numbers = parse_three_numbers(); maybe_numbers.has_value()) {
if (auto angle = parse_angle_value(tokens); angle) {
auto numbers = maybe_numbers.release_value();
return RotationStyleValue::create(angle.release_nonnull(), numbers[0], numbers[1], numbers[2]);
}
}
// Try parsing <angle> <number>{3}
if (auto angle = parse_angle_value(tokens); angle) {
if (auto maybe_numbers = parse_three_numbers(); maybe_numbers.has_value()) {
auto numbers = maybe_numbers.release_value();
return RotationStyleValue::create(angle.release_nonnull(), numbers[0], numbers[1], numbers[2]);
}
}
}
return nullptr;
}
RefPtr<CSSStyleValue> Parser::parse_content_value(TokenStream<ComponentValue>& tokens)
{
// FIXME: `content` accepts several kinds of function() type, which we don't handle in property_accepts_value() yet.
@ -8104,6 +8194,10 @@ Parser::ParseErrorOr<NonnullRefPtr<CSSStyleValue>> Parser::parse_css_value(Prope
if (auto parsed_value = parse_quotes_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();
return ParseError::SyntaxError;
case PropertyID::Rotate:
if (auto parsed_value = parse_rotate_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();
return ParseError::SyntaxError;
case PropertyID::ScrollbarGutter:
if (auto parsed_value = parse_scrollbar_gutter_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();

View file

@ -334,6 +334,7 @@ private:
RefPtr<CSSStyleValue> parse_single_shadow_value(TokenStream<ComponentValue>&, AllowInsetKeyword);
RefPtr<CSSStyleValue> parse_text_decoration_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_text_decoration_line_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_rotate_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_easing_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_transform_value(TokenStream<ComponentValue>&);
RefPtr<CSSStyleValue> parse_transform_origin_value(TokenStream<ComponentValue>&);

View file

@ -2297,6 +2297,13 @@
"unitless-length"
]
},
"rotate": {
"animation-type": "custom",
"inherited": false,
"initial": "none",
"affects-layout": false,
"affects-stacking-context": true
},
"row-gap": {
"animation-type": "by-computed-value",
"inherited": false,

View file

@ -27,6 +27,7 @@
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
#include <LibWeb/CSS/StyleValues/RectStyleValue.h>
#include <LibWeb/CSS/StyleValues/RotationStyleValue.h>
#include <LibWeb/CSS/StyleValues/ScrollbarGutterStyleValue.h>
#include <LibWeb/CSS/StyleValues/ShadowStyleValue.h>
#include <LibWeb/CSS/StyleValues/StringStyleValue.h>
@ -547,6 +548,43 @@ Vector<CSS::Transformation> StyleProperties::transformations() const
return transformations_for_style_value(property(CSS::PropertyID::Transform));
}
Optional<CSS::Transformation> StyleProperties::rotate(Layout::Node const& layout_node) const
{
auto value = property(CSS::PropertyID::Rotate);
if (!value->is_rotation())
return {};
auto& rotation = value->as_rotation();
auto resolve_angle = [&layout_node](CSSStyleValue const& value) -> Optional<Angle> {
if (value.is_angle())
return value.as_angle().angle();
if (value.is_math() && value.as_math().resolves_to_angle())
return value.as_math().resolve_angle(layout_node);
return {};
};
auto resolve_number = [&](CSSStyleValue const& value) -> Optional<double> {
if (value.is_number())
return value.as_number().number();
if (value.is_math() && value.as_math().resolves_to_number())
return value.as_math().resolve_number();
return {};
};
auto x = resolve_number(rotation.rotation_x()).value_or(0);
auto y = resolve_number(rotation.rotation_y()).value_or(0);
auto z = resolve_number(rotation.rotation_z()).value_or(0);
auto angle = resolve_angle(rotation.angle()).value_or(Angle::make_degrees(0));
Vector<TransformValue> values;
values.append({ Number(Number::Type::Number, x) });
values.append({ Number(Number::Type::Number, y) });
values.append({ Number(Number::Type::Number, z) });
values.append({ angle });
return CSS::Transformation(CSS::TransformFunction::Rotate3d, move(values));
}
static Optional<LengthPercentage> length_percentage_for_style_value(CSSStyleValue const& value)
{
if (value.is_length())

View file

@ -176,6 +176,7 @@ public:
Vector<CSS::Transformation> transformations() const;
Optional<CSS::TransformBox> transform_box() const;
CSS::TransformOrigin transform_origin() const;
Optional<CSS::Transformation> rotate(Layout::Node const&) const;
Optional<CSS::MaskType> mask_type() const;
Color stop_color() const;

View file

@ -2650,6 +2650,20 @@ Optional<Angle> CSSMathValue::resolve_angle() const
return {};
}
Optional<Angle> CSSMathValue::resolve_angle(Layout::Node const& layout_node) const
{
return resolve_angle(Length::ResolutionContext::for_layout_node(layout_node));
}
Optional<Angle> CSSMathValue::resolve_angle(Length::ResolutionContext const& context) const
{
auto result = m_calculation->resolve(context, {});
if (result.value().has<Angle>())
return result.value().get<Angle>();
return {};
}
Optional<Angle> CSSMathValue::resolve_angle_percentage(Angle const& percentage_basis) const
{
auto result = m_calculation->resolve({}, percentage_basis);

View file

@ -85,6 +85,8 @@ public:
bool resolves_to_angle() const { return m_resolved_type.matches_angle(); }
bool resolves_to_angle_percentage() const { return m_resolved_type.matches_angle_percentage(); }
Optional<Angle> resolve_angle() const;
Optional<Angle> resolve_angle(Layout::Node const& layout_node) const;
Optional<Angle> resolve_angle(Length::ResolutionContext const& context) const;
Optional<Angle> resolve_angle_percentage(Angle const& percentage_basis) const;
bool resolves_to_flex() const { return m_resolved_type.matches_flex(); }

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2024, Steffen T. Larssen <dudedbz@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/String.h>
#include <LibWeb/CSS/StyleValues/CSSMathValue.h>
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
#include "RotationStyleValue.h"
namespace Web::CSS {
// https://www.w3.org/TR/2021/WD-css-transforms-2-20211109/#individual-transform-serialization
String RotationStyleValue::to_string() const
{
auto resolve_to_number = [](ValueComparingNonnullRefPtr<CSSStyleValue const> const& value) -> Optional<double> {
if (value->is_number())
return value->as_number().number();
if (value->is_math() && value->as_math().resolves_to_number())
return value->as_math().resolve_number();
VERIFY_NOT_REACHED();
};
auto x_value = resolve_to_number(m_properties.rotation_x).value_or(0);
auto y_value = resolve_to_number(m_properties.rotation_y).value_or(0);
auto z_value = resolve_to_number(m_properties.rotation_z).value_or(0);
// If the axis is parallel with the x or y axes, it must serialize as the appropriate keyword.
if (x_value > 0.0 && y_value == 0 && z_value == 0)
return MUST(String::formatted("x {}", m_properties.angle->to_string()));
if (x_value == 0 && y_value > 0.0 && z_value == 0)
return MUST(String::formatted("y {}", m_properties.angle->to_string()));
// If a rotation about the z axis (that is, in 2D) is specified, the property must serialize as just an <angle>.
if (x_value == 0 && y_value == 0 && z_value > 0.0)
return m_properties.angle->to_string();
// It must serialize as the keyword none if and only if none was originally specified.
// NOTE: This is handled by returning a keyword from the parser.
// If any other rotation is specified, the property must serialize with an axis specified.
return MUST(String::formatted("{} {} {} {}", m_properties.rotation_x->to_string(), m_properties.rotation_y->to_string(), m_properties.rotation_z->to_string(), m_properties.angle->to_string()));
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2024, Steffen T. Larssen <dudedbz@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/CSS/CSSStyleValue.h>
namespace Web::CSS {
class RotationStyleValue : public StyleValueWithDefaultOperators<RotationStyleValue> {
public:
static ValueComparingNonnullRefPtr<RotationStyleValue> create(ValueComparingNonnullRefPtr<CSSStyleValue const> angle, ValueComparingNonnullRefPtr<CSSStyleValue const> rotation_x, ValueComparingNonnullRefPtr<CSSStyleValue const> rotation_y, ValueComparingNonnullRefPtr<CSSStyleValue const> rotation_z)
{
return adopt_ref(*new (nothrow) RotationStyleValue(move(angle), move(rotation_x), move(rotation_y), move(rotation_z)));
}
virtual ~RotationStyleValue() override = default;
ValueComparingNonnullRefPtr<CSSStyleValue const> const& angle() const { return m_properties.angle; }
ValueComparingNonnullRefPtr<CSSStyleValue const> const& rotation_x() const { return m_properties.rotation_x; }
ValueComparingNonnullRefPtr<CSSStyleValue const> const& rotation_y() const { return m_properties.rotation_y; }
ValueComparingNonnullRefPtr<CSSStyleValue const> const& rotation_z() const { return m_properties.rotation_z; }
virtual String to_string() const override;
bool properties_equal(RotationStyleValue const& other) const { return m_properties == other.m_properties; }
private:
explicit RotationStyleValue(
ValueComparingNonnullRefPtr<CSSStyleValue const> angle,
ValueComparingNonnullRefPtr<CSSStyleValue const> rotation_x,
ValueComparingNonnullRefPtr<CSSStyleValue const> rotation_y,
ValueComparingNonnullRefPtr<CSSStyleValue const> rotation_z)
: StyleValueWithDefaultOperators(Type::Rotation)
, m_properties {
.angle = move(angle),
.rotation_x = move(rotation_x),
.rotation_y = move(rotation_y),
.rotation_z = move(rotation_z)
}
{
}
struct Properties {
ValueComparingNonnullRefPtr<CSSStyleValue const> angle;
ValueComparingNonnullRefPtr<CSSStyleValue const> rotation_x;
ValueComparingNonnullRefPtr<CSSStyleValue const> rotation_y;
ValueComparingNonnullRefPtr<CSSStyleValue const> rotation_z;
bool operator==(Properties const&) const = default;
} m_properties;
};
}

View file

@ -190,6 +190,7 @@ class RectStyleValue;
class Resolution;
class ResolutionOrCalculated;
class ResolutionStyleValue;
class RotationStyleValue;
class Screen;
class ScreenOrientation;
class ScrollbarGutterStyleValue;

View file

@ -709,6 +709,9 @@ void NodeWithStyle::apply_style(const CSS::StyleProperties& computed_style)
computed_values.set_box_shadow(computed_style.box_shadow(*this));
if (auto rotate_value = computed_style.rotate(*this); rotate_value.has_value())
computed_values.set_rotate(rotate_value.value());
computed_values.set_transformations(computed_style.transformations());
if (auto transform_box = computed_style.transform_box(); transform_box.has_value())
computed_values.set_transform_box(transform_box.value());