Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LibWeb: Parse the rotate css property #1823

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ source_set("StyleValues") {
"PositionStyleValue.cpp",
"RadialGradientStyleValue.cpp",
"RectStyleValue.cpp",
"RotationStyleValue.cpp",
"ShadowStyleValue.cpp",
"ShorthandStyleValue.cpp",
"StyleValueList.cpp",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ padding-top: 0px
position: static
r: 0px
right: auto
rotate: none
row-gap: auto
rx: auto
ry: auto
Expand Down
1 change: 1 addition & 0 deletions Userland/Libraries/LibWeb/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions Userland/Libraries/LibWeb/CSS/CSSStyleValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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());
Expand Down
5 changes: 5 additions & 0 deletions Userland/Libraries/LibWeb/CSS/CSSStyleValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ class CSSStyleValue : public RefCounted<CSSStyleValue> {
Ratio,
Rect,
Resolution,
Rotation,
ScrollbarGutter,
Shadow,
Shorthand,
Expand Down Expand Up @@ -289,6 +290,10 @@ class CSSStyleValue : public RefCounted<CSSStyleValue> {
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()); }
Expand Down
11 changes: 11 additions & 0 deletions Userland/Libraries/LibWeb/CSS/ComputedValues.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,15 @@ class InitialValues {
static CSS::TransformBox transform_box() { return CSS::TransformBox::ViewBox; }
static CSS::Direction direction() { return CSS::Direction::Ltr; }
static CSS::UnicodeBidi unicode_bidi() { return CSS::UnicodeBidi::Normal; }
static CSS::Transformation rotate()
{
Vector<TransformValue> values;
values.append({ Number(Number::Type::Number, 0) });
values.append({ Number(Number::Type::Number, 0) });
values.append({ Number(Number::Type::Number, 0) });
values.append({ Angle::make_degrees(0) });
return CSS::Transformation(CSS::TransformFunction::Rotate3d, move(values));
}

// https://www.w3.org/TR/SVG/geometry.html
static LengthPercentage cx() { return CSS::Length::make_px(0); }
Expand Down Expand Up @@ -653,6 +662,7 @@ class ComputedValues {
CSS::ObjectFit object_fit { InitialValues::object_fit() };
CSS::ObjectPosition object_position { InitialValues::object_position() };
CSS::UnicodeBidi unicode_bidi { InitialValues::unicode_bidi() };
CSS::Transformation rotate { InitialValues::rotate() };

Optional<MaskReference> mask;
CSS::MaskType mask_type { InitialValues::mask_type() };
Expand Down Expand Up @@ -760,6 +770,7 @@ class MutableComputedValues final : public ComputedValues {
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; }
Expand Down
92 changes: 92 additions & 0 deletions Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,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>
Expand Down Expand Up @@ -4725,6 +4726,93 @@ 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>

// "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.
Expand Down Expand Up @@ -7879,6 +7967,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();
Expand Down
1 change: 1 addition & 0 deletions Userland/Libraries/LibWeb/CSS/Parser/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ class Parser {
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>&);
Expand Down
7 changes: 7 additions & 0 deletions Userland/Libraries/LibWeb/CSS/Properties.json
Original file line number Diff line number Diff line change
Expand Up @@ -2287,6 +2287,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,
Expand Down
39 changes: 39 additions & 0 deletions Userland/Libraries/LibWeb/CSS/StyleProperties.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -528,6 +529,44 @@ Vector<CSS::Transformation> StyleProperties::transformations() const
return transformations_for_style_value(property(CSS::PropertyID::Transform));
}

Optional<CSS::Transformation> StyleProperties::rotate() const
{
auto value = property(CSS::PropertyID::Rotate);
if (!value->is_rotation())
return {};

auto& rotation = value->as_rotation();

auto resolve_angle = [&](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().value();
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().value();
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())
Expand Down
1 change: 1 addition & 0 deletions Userland/Libraries/LibWeb/CSS/StyleProperties.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ class StyleProperties : public RefCounted<StyleProperties> {
Vector<CSS::Transformation> transformations() const;
Optional<CSS::TransformBox> transform_box() const;
CSS::TransformOrigin transform_origin() const;
Optional<CSS::Transformation> rotate() const;

Optional<CSS::MaskType> mask_type() const;
Color stop_color() const;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2024, Steffen T. Larssen <[email protected]>
*
* 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) -> 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().value();

VERIFY_NOT_REACHED();
};

auto x_value = resolve_to_number(m_properties.rotation_x);
auto y_value = resolve_to_number(m_properties.rotation_y);
auto z_value = resolve_to_number(m_properties.rotation_z);

// 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 == 1)
return m_properties.angle->to_string();

// If the axis is parallel with the x or y axes, it must serialize as the appropriate keyword.
if (x_value == 1 && y_value == 0 && z_value == 0)
return MUST(String::formatted("x {}", m_properties.angle->to_string()));

if (x_value == 0 && y_value == 1 && z_value == 0)
return MUST(String::formatted("y {}", 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("{} {} {} {}", x_value, y_value, z_value, m_properties.angle->to_string()));
}

}
56 changes: 56 additions & 0 deletions Userland/Libraries/LibWeb/CSS/StyleValues/RotationStyleValue.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2024, Steffen T. Larssen <[email protected]>
*
* 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;
};

}
Loading
Loading