diff --git a/Meta/CMake/libweb_generators.cmake b/Meta/CMake/libweb_generators.cmake index 05b0f47195..200716be80 100644 --- a/Meta/CMake/libweb_generators.cmake +++ b/Meta/CMake/libweb_generators.cmake @@ -1,5 +1,14 @@ function (generate_css_implementation) set(LIBWEB_INPUT_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}") + invoke_generator( + "EasingFunctions.cpp" + Lagom::GenerateCSSEasingFunctions + "${LIBWEB_INPUT_FOLDER}/CSS/EasingFunctions.json" + "CSS/EasingFunctions.h" + "CSS/EasingFunctions.cpp" + arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/EasingFunctions.json" + ) + invoke_generator( "Enums.cpp" Lagom::GenerateCSSEnums diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt index e3d1b715bc..45b4cd368c 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt @@ -1,10 +1,11 @@ set(SOURCES "") # avoid pulling SOURCES from parent scope -lagom_tool(GenerateCSSEnums SOURCES GenerateCSSEnums.cpp LIBS LibMain) -lagom_tool(GenerateCSSMediaFeatureID SOURCES GenerateCSSMediaFeatureID.cpp LIBS LibMain) -lagom_tool(GenerateCSSPropertyID SOURCES GenerateCSSPropertyID.cpp LIBS LibMain) -lagom_tool(GenerateCSSTransformFunctions SOURCES GenerateCSSTransformFunctions.cpp LIBS LibMain) -lagom_tool(GenerateCSSValueID SOURCES GenerateCSSValueID.cpp LIBS LibMain) +lagom_tool(GenerateCSSEasingFunctions SOURCES GenerateCSSEasingFunctions.cpp LIBS LibMain) +lagom_tool(GenerateCSSEnums SOURCES GenerateCSSEnums.cpp LIBS LibMain) +lagom_tool(GenerateCSSMediaFeatureID SOURCES GenerateCSSMediaFeatureID.cpp LIBS LibMain) +lagom_tool(GenerateCSSPropertyID SOURCES GenerateCSSPropertyID.cpp LIBS LibMain) +lagom_tool(GenerateCSSTransformFunctions SOURCES GenerateCSSTransformFunctions.cpp LIBS LibMain) +lagom_tool(GenerateCSSValueID SOURCES GenerateCSSValueID.cpp LIBS LibMain) lagom_tool(GenerateWindowOrWorkerInterfaces SOURCES GenerateWindowOrWorkerInterfaces.cpp LIBS LibMain LibIDL) lagom_tool(GenerateAriaRoles SOURCES GenerateAriaRoles.cpp LIBS LibMain) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSEasingFunctions.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSEasingFunctions.cpp new file mode 100644 index 0000000000..6f39c88c79 --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSEasingFunctions.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2022-2023, Sam Atkins + * Copyright (c) 2023, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "GeneratorUtil.h" +#include +#include +#include +#include + +ErrorOr generate_header_file(JsonObject&, Core::File&); +ErrorOr generate_implementation_file(JsonObject&, Core::File&); + +ErrorOr serenity_main(Main::Arguments arguments) +{ + StringView generated_header_path; + StringView generated_implementation_path; + StringView functions_json_path; + + Core::ArgsParser args_parser; + args_parser.add_option(generated_header_path, "Path to the EasingFunctions header file to generate", "generated-header-path", 'h', "generated-header-path"); + args_parser.add_option(generated_implementation_path, "Path to the EasingFunctions implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path"); + args_parser.add_option(functions_json_path, "Path to the JSON file to read from", "json-path", 'j', "json-path"); + args_parser.parse(arguments); + + auto json = TRY(read_entire_file_as_json(functions_json_path)); + VERIFY(json.is_object()); + auto easing_data = json.as_object(); + + auto generated_header_file = TRY(Core::File::open(generated_header_path, Core::File::OpenMode::Write)); + auto generated_implementation_file = TRY(Core::File::open(generated_implementation_path, Core::File::OpenMode::Write)); + + TRY(generate_header_file(easing_data, *generated_header_file)); + TRY(generate_implementation_file(easing_data, *generated_implementation_file)); + + return 0; +} + +ErrorOr generate_header_file(JsonObject& easing_data, Core::File& file) +{ + StringBuilder builder; + SourceGenerator generator { builder }; + + TRY(generator.try_append(R"~~~( +#pragma once + +#include +#include +#include + +namespace Web::CSS { + +)~~~")); + + TRY(generator.try_appendln("enum class EasingFunction {")); + TRY(easing_data.try_for_each_member([&](auto& name, auto&) -> ErrorOr { + auto member_generator = TRY(generator.fork()); + TRY(member_generator.set("name:titlecase", TRY(title_casify(name)))); + TRY(member_generator.try_appendln(" @name:titlecase@,")); + return {}; + })); + TRY(generator.try_appendln("};")); + + TRY(generator.try_appendln("Optional easing_function_from_string(StringView);")); + TRY(generator.try_appendln("StringView to_string(EasingFunction);")); + + TRY(generator.try_append(R"~~~( +enum class EasingFunctionParameterType { + Integer, + Number, + NumberZeroToOne, + StepPosition, +}; + +struct EasingFunctionParameter { + EasingFunctionParameterType type; + bool is_optional { false }; +}; + +struct EasingFunctionMetadata { + Vector parameters; +}; +EasingFunctionMetadata easing_function_metadata(EasingFunction); +)~~~")); + + TRY(generator.try_appendln("\n}")); + + TRY(file.write_until_depleted(generator.as_string_view().bytes())); + return {}; +} + +ErrorOr generate_implementation_file(JsonObject& easing_data, Core::File& file) +{ + StringBuilder builder; + SourceGenerator generator { builder }; + + TRY(generator.try_append(R"~~~( +#include +#include + +namespace Web::CSS { +)~~~")); + + TRY(generator.try_append(R"~~~( +Optional easing_function_from_string(StringView name) +{ +)~~~")); + TRY(easing_data.try_for_each_member([&](auto& name, auto&) -> ErrorOr { + auto member_generator = TRY(generator.fork()); + TRY(member_generator.set("name", TRY(String::from_deprecated_string(name)))); + TRY(member_generator.set("name:titlecase", TRY(title_casify(name)))); + TRY(member_generator.try_append(R"~~~( + if (name.equals_ignoring_ascii_case("@name@"sv)) + return EasingFunction::@name:titlecase@; +)~~~")); + return {}; + })); + TRY(generator.try_append(R"~~~( + return {}; +} +)~~~")); + + TRY(generator.try_append(R"~~~( +StringView to_string(EasingFunction easing_function) +{ + switch (easing_function) { +)~~~")); + TRY(easing_data.try_for_each_member([&](auto& name, auto&) -> ErrorOr { + auto member_generator = TRY(generator.fork()); + TRY(member_generator.set("name", TRY(String::from_deprecated_string(name)))); + TRY(member_generator.set("name:titlecase", TRY(title_casify(name)))); + TRY(member_generator.try_append(R"~~~( + case EasingFunction::@name:titlecase@: + return "@name@"sv; +)~~~")); + return {}; + })); + TRY(generator.try_append(R"~~~( + default: + VERIFY_NOT_REACHED(); + } +} +)~~~")); + + TRY(generator.try_append(R"~~~( +EasingFunctionMetadata easing_function_metadata(EasingFunction easing_function) +{ + switch (easing_function) { +)~~~")); + TRY(easing_data.try_for_each_member([&](auto& name, auto& value) -> ErrorOr { + VERIFY(value.is_object()); + + auto member_generator = TRY(generator.fork()); + TRY(member_generator.set("name:titlecase", TRY(title_casify(name)))); + TRY(member_generator.try_append(R"~~~( + case EasingFunction::@name:titlecase@: + return EasingFunctionMetadata { + .parameters = {)~~~")); + + if (auto parameters = value.as_object().get_array("parameters"sv); parameters.has_value()) { + bool first = true; + // parameters: [ "", "" ] + TRY(parameters.value().try_for_each([&](JsonValue const& value) -> ErrorOr { + GenericLexer lexer { value.as_string() }; + VERIFY(lexer.consume_specific('<')); + auto parameter_type_name = lexer.consume_until([](char ch) { return ch == ' ' || ch == '>'; }); + auto has_bounds = false; + auto is_optional = false; + if (lexer.consume_specific(" [")) { + has_bounds = true; + auto contents = lexer.consume_until(']'); + VERIFY(contents == "0, 1"sv); + VERIFY(lexer.consume_specific(']')); + } + VERIFY(lexer.consume_specific('>')); + if (lexer.consume_specific('?')) + is_optional = true; + + StringView parameter_type = ""sv; + if (parameter_type_name == "number"sv) + parameter_type = has_bounds ? "NumberZeroToOne"sv : "Number"sv; + else if (parameter_type_name == "integer"sv) + parameter_type = "Integer"sv; + else if (parameter_type_name == "step-position"sv) + parameter_type = "StepPosition"sv; + else + VERIFY_NOT_REACHED(); + + TRY(member_generator.try_append(first ? " "sv : ", "sv)); + first = false; + + TRY(member_generator.try_append(TRY(String::formatted( + "{{ EasingFunctionParameterType::{}, {} }}", + parameter_type, + is_optional ? "true"sv : "false"sv)))); + return {}; + })); + } + + TRY(member_generator.try_append(R"~~~( } + }; +)~~~")); + return {}; + })); + TRY(generator.try_append(R"~~~( + default: + VERIFY_NOT_REACHED(); + } +} +)~~~")); + + TRY(generator.try_appendln("\n}")); + + TRY(file.write_until_depleted(generator.as_string_view().bytes())); + return {}; +} diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index ee2acf6dd6..08ecafd6f6 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -89,6 +89,7 @@ set(SOURCES CSS/StyleValues/ConicGradientStyleValue.cpp CSS/StyleValues/ContentStyleValue.cpp CSS/StyleValues/DisplayStyleValue.cpp + CSS/StyleValues/EasingStyleValue.cpp CSS/StyleValues/EdgeStyleValue.cpp CSS/StyleValues/FilterValueListStyleValue.cpp CSS/StyleValues/FlexFlowStyleValue.cpp @@ -612,6 +613,7 @@ generate_css_implementation() set(GENERATED_SOURCES ARIA/AriaRoles.cpp CSS/DefaultStyleSheetSource.cpp + CSS/EasingFunctions.cpp CSS/Enums.cpp CSS/MediaFeatureID.cpp CSS/PropertyID.cpp diff --git a/Userland/Libraries/LibWeb/CSS/EasingFunctions.json b/Userland/Libraries/LibWeb/CSS/EasingFunctions.json new file mode 100644 index 0000000000..bb9d5585a8 --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/EasingFunctions.json @@ -0,0 +1,23 @@ +{ + "linear": {}, + "ease": {}, + "ease-in": {}, + "ease-out": {}, + "ease-in-out": {}, + "cubic-bezier": { + "parameters": [ + "", + "", + "", + "" + ] + }, + "step-start": {}, + "step-end": {}, + "steps": { + "parameters": [ + "", + "?" + ] + } +} diff --git a/Userland/Libraries/LibWeb/CSS/Identifiers.json b/Userland/Libraries/LibWeb/CSS/Identifiers.json index 0ac0e6f519..635cf1bb82 100644 --- a/Userland/Libraries/LibWeb/CSS/Identifiers.json +++ b/Userland/Libraries/LibWeb/CSS/Identifiers.json @@ -166,6 +166,10 @@ "invert", "inverted", "italic", + "jump-both", + "jump-end", + "jump-none", + "jump-start", "justify", "landscape", "large", diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index e92f1f1f82..4470515286 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -7039,6 +7040,108 @@ ErrorOr> Parser::parse_text_decoration_line_value(TokenStream return StyleValueList::create(move(style_values), StyleValueList::Separator::Space); } +ErrorOr> Parser::parse_easing_value(TokenStream& tokens) +{ + auto transaction = tokens.begin_transaction(); + + tokens.skip_whitespace(); + + auto const& part = tokens.next_token(); + + StringView name; + Optional const&> arguments; + if (part.is(Token::Type::Ident)) { + name = part.token().ident(); + } else if (part.is_function()) { + name = part.function().name(); + arguments = part.function().values(); + } else { + return nullptr; + } + + auto maybe_function = easing_function_from_string(name); + if (!maybe_function.has_value()) + return nullptr; + + auto function = maybe_function.release_value(); + auto function_metadata = easing_function_metadata(function); + + if (function_metadata.parameters.is_empty() && arguments.has_value()) { + dbgln_if(CSS_PARSER_DEBUG, "Too many arguments to {}. max: 0", name); + return nullptr; + } + + StyleValueVector values; + size_t argument_index = 0; + if (arguments.has_value()) { + auto argument_tokens = TokenStream { *arguments }; + auto arguments_values = parse_a_comma_separated_list_of_component_values(argument_tokens); + if (arguments_values.size() > function_metadata.parameters.size()) { + dbgln_if(CSS_PARSER_DEBUG, "Too many arguments to {}. max: {}", name, function_metadata.parameters.size()); + return nullptr; + } + for (auto& argument_values : arguments_values) { + if (argument_values.size() != 1) { + dbgln_if(CSS_PARSER_DEBUG, "Too many values in argument to {}. max: 1", name); + return nullptr; + } + + auto& value = argument_values[0]; + switch (function_metadata.parameters[argument_index].type) { + case EasingFunctionParameterType::Number: { + if (value.is(Token::Type::Number)) + values.append(TRY(NumberStyleValue::create(value.token().number().value()))); + else + return nullptr; + break; + } + case EasingFunctionParameterType::NumberZeroToOne: { + if (value.is(Token::Type::Number) && value.token().number_value() >= 0 && value.token().number_value() <= 1) + values.append(TRY(NumberStyleValue::create(value.token().number().value()))); + else + return nullptr; + break; + } + case EasingFunctionParameterType::Integer: { + if (value.is(Token::Type::Number) && value.token().number().is_integer()) + values.append(TRY(IntegerStyleValue::create(value.token().number().integer_value()))); + else + return nullptr; + break; + } + case EasingFunctionParameterType::StepPosition: { + if (!value.is(Token::Type::Ident)) + return nullptr; + auto ident = TRY(parse_identifier_value(value)); + if (!ident) + return nullptr; + switch (ident->to_identifier()) { + case ValueID::JumpStart: + case ValueID::JumpEnd: + case ValueID::JumpNone: + case ValueID::Start: + case ValueID::End: + TRY(values.try_append(*ident)); + break; + default: + return nullptr; + } + } + } + + ++argument_index; + } + } + + if (argument_index < function_metadata.parameters.size() && !function_metadata.parameters[argument_index].is_optional) { + dbgln_if(CSS_PARSER_DEBUG, "Required parameter at position {} is missing", argument_index); + return nullptr; + } + + transaction.commit(); + return EasingStyleValue::create(function, move(values)); +} + ErrorOr> Parser::parse_transform_value(Vector const& component_values) { StyleValueVector transformations; diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index 2dbcdc7980..bc6fb428a8 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -352,6 +352,7 @@ private: ErrorOr> parse_single_shadow_value(TokenStream&, AllowInsetKeyword); ErrorOr> parse_text_decoration_value(Vector const&); ErrorOr> parse_text_decoration_line_value(TokenStream&); + ErrorOr> parse_easing_value(TokenStream&); ErrorOr> parse_transform_value(Vector const&); ErrorOr> parse_transform_origin_value(Vector const&); ErrorOr> parse_grid_track_size_list(Vector const&, bool allow_separate_line_name_blocks = false); diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp index 340f97889e..9818569c64 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -113,6 +114,12 @@ BorderRadiusStyleValue const& StyleValue::as_border_radius() const return static_cast(*this); } +EasingStyleValue const& StyleValue::as_easing() const +{ + VERIFY(is_easing()); + return static_cast(*this); +} + BorderRadiusShorthandStyleValue const& StyleValue::as_border_radius_shorthand() const { VERIFY(is_border_radius_shorthand()); diff --git a/Userland/Libraries/LibWeb/CSS/StyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValue.h index fcad90c013..513dfa2f5e 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValue.h +++ b/Userland/Libraries/LibWeb/CSS/StyleValue.h @@ -101,6 +101,7 @@ public: Content, CustomIdent, Display, + Easing, Edge, FilterValueList, Flex, @@ -158,6 +159,7 @@ public: bool is_content() const { return type() == Type::Content; } bool is_custom_ident() const { return type() == Type::CustomIdent; } bool is_display() const { return type() == Type::Display; } + bool is_easing() const { return type() == Type::Easing; } bool is_edge() const { return type() == Type::Edge; } bool is_filter_value_list() const { return type() == Type::FilterValueList; } bool is_flex() const { return type() == Type::Flex; } @@ -214,6 +216,7 @@ public: ContentStyleValue const& as_content() const; CustomIdentStyleValue const& as_custom_ident() const; DisplayStyleValue const& as_display() const; + EasingStyleValue const& as_easing() const; EdgeStyleValue const& as_edge() const; FilterValueListStyleValue const& as_filter_value_list() const; FlexFlowStyleValue const& as_flex_flow() const; @@ -267,6 +270,7 @@ public: ContentStyleValue& as_content() { return const_cast(const_cast(*this).as_content()); } CustomIdentStyleValue& as_custom_ident() { return const_cast(const_cast(*this).as_custom_ident()); } DisplayStyleValue& as_display() { return const_cast(const_cast(*this).as_display()); } + EasingStyleValue& as_easing() { return const_cast(const_cast(*this).as_easing()); } EdgeStyleValue& as_edge() { return const_cast(const_cast(*this).as_edge()); } FilterValueListStyleValue& as_filter_value_list() { return const_cast(const_cast(*this).as_filter_value_list()); } FlexFlowStyleValue& as_flex_flow() { return const_cast(const_cast(*this).as_flex_flow()); } diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp b/Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp new file mode 100644 index 0000000000..762b7e76d8 --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Tobias Christiansen + * Copyright (c) 2021-2023, Sam Atkins + * Copyright (c) 2022-2023, MacDue + * Copyright (c) 2023, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "EasingStyleValue.h" +#include + +namespace Web::CSS { + +ErrorOr EasingStyleValue::to_string() const +{ + if (m_properties.easing_function == EasingFunction::StepStart) + return "steps(1, start)"_string; + if (m_properties.easing_function == EasingFunction::StepEnd) + return "steps(1, end)"_string; + + StringBuilder builder; + TRY(builder.try_append(CSS::to_string(m_properties.easing_function))); + + if (m_properties.values.is_empty()) + return builder.to_string(); + + TRY(builder.try_append('(')); + for (size_t i = 0; i < m_properties.values.size(); ++i) { + TRY(builder.try_append(TRY(m_properties.values[i]->to_string()))); + if (i != m_properties.values.size() - 1) + TRY(builder.try_append(", "sv)); + } + TRY(builder.try_append(')')); + + return builder.to_string(); +} + +bool EasingStyleValue::Properties::operator==(Properties const& other) const +{ + return easing_function == other.easing_function && values == other.values; +} + +} diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h b/Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h new file mode 100644 index 0000000000..c22bd3c10f --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Tobias Christiansen + * Copyright (c) 2021-2023, Sam Atkins + * Copyright (c) 2022-2023, MacDue + * Copyright (c) 2023, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::CSS { + +class EasingStyleValue final : public StyleValueWithDefaultOperators { +public: + static ErrorOr> create(CSS::EasingFunction easing_function, StyleValueVector&& values) + { + return adopt_nonnull_ref_or_enomem(new (nothrow) EasingStyleValue(easing_function, move(values))); + } + virtual ~EasingStyleValue() override = default; + + CSS::EasingFunction easing_function() const { return m_properties.easing_function; } + StyleValueVector values() const { return m_properties.values; } + + virtual ErrorOr to_string() const override; + + bool properties_equal(EasingStyleValue const& other) const { return m_properties == other.m_properties; } + +private: + EasingStyleValue(CSS::EasingFunction easing_function, StyleValueVector&& values) + : StyleValueWithDefaultOperators(Type::Easing) + , m_properties { .easing_function = easing_function, .values = move(values) } + { + } + + struct Properties { + CSS::EasingFunction easing_function; + StyleValueVector values; + bool operator==(Properties const& other) const; + } m_properties; +}; + +} diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index e4069095ad..ab6d0dde55 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -98,6 +98,7 @@ class ContentStyleValue; class CustomIdentStyleValue; class Display; class DisplayStyleValue; +class EasingStyleValue; class EdgeStyleValue; class ElementInlineCSSStyleDeclaration; class ExplicitGridTrack;