diff --git a/Documentation/CSSGeneratedFiles.md b/Documentation/CSSGeneratedFiles.md index ba167f89a31..0a746671995 100644 --- a/Documentation/CSSGeneratedFiles.md +++ b/Documentation/CSSGeneratedFiles.md @@ -312,3 +312,24 @@ The generated code provides: - `Optional transform_function_from_string(StringView)` to parse a string as a `TransformFunction` - `StringView to_string(TransformFunction)` to convert a `TransformFunction` back to a string - `TransformFunctionMetadata transform_function_metadata(TransformFunction)` to obtain metadata about the transform function, such as its parameter list + +## EnvironmentVariables.json + +This is a single JSON object, describing each [CSS environment variable](https://drafts.csswg.org/css-env/#css-environment-variable), +with the keys being the environment variable names, and the values being objects describing the variable's properties. +This generates `EnvironmentVariable.h` and `EnvironmentVariable.cpp`. + +Each entry has 3 properties, all taken from the + +| Field | Description | +|--------------|---------------------------------------------------------------------| +| `spec` | String. URL to the spec definition for this environment variable. | +| `type` | String. CSS value type of the variable, eg ``. | +| `dimensions` | Integer. Number of dimensions for the variable, or `0` for scalars. | + +The generated code provides: +- An `EnvironmentVariable` enum listing the environment variables +- `Optional environment_variable_from_string(StringView)` to parse a string as an `EnvironmentVariable` +- `StringView to_string(EnvironmentVariable)` to convert the `EnvironmentVariable` back to a string +- `ValueType environment_variable_type(EnvironmentVariable)` to get the variable's value type +- `u32 environment_variable_dimension_count(EnvironmentVariable)` to get its dimension count diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 6e8a3861862..12ada8f0f8e 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -1021,6 +1021,7 @@ set(GENERATED_SOURCES CSS/DefaultStyleSheetSource.cpp CSS/DescriptorID.cpp CSS/Enums.cpp + CSS/EnvironmentVariable.cpp CSS/GeneratedCSSStyleProperties.cpp CSS/GeneratedCSSStyleProperties.idl CSS/Keyword.cpp diff --git a/Libraries/LibWeb/CSS/EnvironmentVariables.json b/Libraries/LibWeb/CSS/EnvironmentVariables.json new file mode 100644 index 00000000000..ce25add2ff0 --- /dev/null +++ b/Libraries/LibWeb/CSS/EnvironmentVariables.json @@ -0,0 +1,77 @@ +{ + "preferred-text-scale": { + "spec": "https://drafts.csswg.org/css-env/#valdef-env-preferred-text-scale", + "type": "", + "dimensions": 0 + }, + "safe-area-inset-bottom": { + "spec": "https://drafts.csswg.org/css-env/#valdef-env-safe-area-inset-bottom", + "type": "", + "dimensions": 0 + }, + "safe-area-inset-left": { + "spec": "https://drafts.csswg.org/css-env/#valdef-env-safe-area-inset-left", + "type": "", + "dimensions": 0 + }, + "safe-area-inset-right": { + "spec": "https://drafts.csswg.org/css-env/#valdef-env-safe-area-inset-right", + "type": "", + "dimensions": 0 + }, + "safe-area-inset-top": { + "spec": "https://drafts.csswg.org/css-env/#valdef-env-safe-area-inset-top", + "type": "", + "dimensions": 0 + }, + "safe-area-max-inset-bottom": { + "spec": "https://drafts.csswg.org/css-env/#valdef-env-safe-area-max-inset-bottom", + "type": "", + "dimensions": 0 + }, + "safe-area-max-inset-left": { + "spec": "https://drafts.csswg.org/css-env/#valdef-env-safe-area-max-inset-left", + "type": "", + "dimensions": 0 + }, + "safe-area-max-inset-right": { + "spec": "https://drafts.csswg.org/css-env/#valdef-env-safe-area-max-inset-right", + "type": "", + "dimensions": 0 + }, + "safe-area-max-inset-top": { + "spec": "https://drafts.csswg.org/css-env/#valdef-env-safe-area-max-inset-top", + "type": "", + "dimensions": 0 + }, + "viewport-segment-bottom": { + "spec": "https://drafts.csswg.org/css-env/#valdef-env-viewport-segment-bottom", + "type": "", + "dimensions": 2 + }, + "viewport-segment-height": { + "spec": "https://drafts.csswg.org/css-env/#valdef-env-viewport-segment-height", + "type": "", + "dimensions": 2 + }, + "viewport-segment-left": { + "spec": "https://drafts.csswg.org/css-env/#valdef-env-viewport-segment-left", + "type": "", + "dimensions": 2 + }, + "viewport-segment-right": { + "spec": "https://drafts.csswg.org/css-env/#valdef-env-viewport-segment-right", + "type": "", + "dimensions": 2 + }, + "viewport-segment-top": { + "spec": "https://drafts.csswg.org/css-env/#valdef-env-viewport-segment-top", + "type": "", + "dimensions": 2 + }, + "viewport-segment-width": { + "spec": "https://drafts.csswg.org/css-env/#valdef-env-viewport-segment-width", + "type": "", + "dimensions": 2 + } +} diff --git a/Meta/CMake/libweb_generators.cmake b/Meta/CMake/libweb_generators.cmake index 37c222075bc..e4fec80a820 100644 --- a/Meta/CMake/libweb_generators.cmake +++ b/Meta/CMake/libweb_generators.cmake @@ -20,6 +20,15 @@ function (generate_css_implementation) arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/Enums.json" ) + invoke_cpp_generator( + "EnvironmentVariable.cpp" + Lagom::GenerateCSSEnvironmentVariable + "${LIBWEB_INPUT_FOLDER}/CSS/EnvironmentVariables.json" + "CSS/EnvironmentVariable.h" + "CSS/EnvironmentVariable.cpp" + arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/EnvironmentVariables.json" + ) + invoke_cpp_generator( "MathFunctions.cpp" Lagom::GenerateCSSMathFunctions @@ -131,6 +140,7 @@ function (generate_css_implementation) set(CSS_GENERATED_HEADERS "CSS/Enums.h" + "CSS/EnvironmentVariable.h" "CSS/GeneratedCSSStyleProperties.h" "CSS/Keyword.h" "CSS/MathFunctions.h" diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt index d208044780e..0c48becd42b 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt @@ -2,6 +2,7 @@ set(SOURCES "") # avoid pulling SOURCES from parent scope lagom_tool(GenerateCSSDescriptors SOURCES GenerateCSSDescriptors.cpp LIBS LibMain) lagom_tool(GenerateCSSEnums SOURCES GenerateCSSEnums.cpp LIBS LibMain) +lagom_tool(GenerateCSSEnvironmentVariable SOURCES GenerateCSSEnvironmentVariable.cpp LIBS LibMain) lagom_tool(GenerateCSSKeyword SOURCES GenerateCSSKeyword.cpp LIBS LibMain) lagom_tool(GenerateCSSMathFunctions SOURCES GenerateCSSMathFunctions.cpp LIBS LibMain) lagom_tool(GenerateCSSMediaFeatureID SOURCES GenerateCSSMediaFeatureID.cpp LIBS LibMain) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSEnvironmentVariable.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSEnvironmentVariable.cpp new file mode 100644 index 00000000000..2661037e5d2 --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSEnvironmentVariable.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2025, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "GeneratorUtil.h" +#include +#include +#include + +ErrorOr generate_header_file(JsonObject const& environment_variables, Core::File& file); +ErrorOr generate_implementation_file(JsonObject const& environment_variables, Core::File& file); + +ErrorOr ladybird_main(Main::Arguments arguments) +{ + StringView generated_header_path; + StringView generated_implementation_path; + StringView json_path; + + Core::ArgsParser args_parser; + args_parser.add_option(generated_header_path, "Path to the EnvironmentVariable header file to generate", "generated-header-path", 'h', "generated-header-path"); + args_parser.add_option(generated_implementation_path, "Path to the EnvironmentVariable implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path"); + args_parser.add_option(json_path, "Path to the JSON file to read from", "json-path", 'j', "json-path"); + args_parser.parse(arguments); + + auto read_json_object = [](auto& path) -> ErrorOr { + auto json = TRY(read_entire_file_as_json(path)); + VERIFY(json.is_object()); + + // Check we're in alphabetical order + String most_recent_name; + json.as_object().for_each_member([&](auto& name, auto&) { + if (name < most_recent_name) { + warnln("`{}` is in the wrong position in `{}`. Please keep this list alphabetical!", name, path); + VERIFY_NOT_REACHED(); + } + most_recent_name = name; + }); + + return json.as_object(); + }; + + auto environment_variables = TRY(read_json_object(json_path)); + + 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(environment_variables, *generated_header_file)); + TRY(generate_implementation_file(environment_variables, *generated_implementation_file)); + + return 0; +} + +ErrorOr generate_header_file(JsonObject const& environment_variables, Core::File& file) +{ + StringBuilder builder; + SourceGenerator generator { builder }; + + generator.set("environment_variable_underlying_type", underlying_type_for_enum(environment_variables.size())); + + generator.append(R"~~~( +#pragma once + +#include +#include +#include + +namespace Web::CSS { + +enum class EnvironmentVariable : @environment_variable_underlying_type@ { +)~~~"); + + environment_variables.for_each_member([&](auto& name, JsonValue const&) { + auto member_generator = generator.fork(); + member_generator.set("name:titlecase", title_casify(name)); + member_generator.appendln(" @name:titlecase@,"); + }); + generator.append(R"~~~( +}; + +Optional environment_variable_from_string(StringView); +StringView to_string(EnvironmentVariable); + +ValueType environment_variable_type(EnvironmentVariable); +u32 environment_variable_dimension_count(EnvironmentVariable); +} +)~~~"); + + TRY(file.write_until_depleted(generator.as_string_view().bytes())); + return {}; +} + +ErrorOr generate_implementation_file(JsonObject const& environment_variables, Core::File& file) +{ + StringBuilder builder; + SourceGenerator generator { builder }; + + generator.append(R"~~~( +#include + +namespace Web::CSS { + +Optional environment_variable_from_string(StringView string) +{ +)~~~"); + environment_variables.for_each_member([&](auto& name, JsonValue const&) { + auto member_generator = generator.fork(); + member_generator.set("name", name); + member_generator.set("name:titlecase", title_casify(name)); + + member_generator.append(R"~~~( + if (string.equals_ignoring_ascii_case("@name@"sv)) + return EnvironmentVariable::@name:titlecase@; +)~~~"); + }); + + generator.append(R"~~~( + + return {}; +} + +StringView to_string(EnvironmentVariable environment_variable) +{ + switch (environment_variable) { +)~~~"); + + environment_variables.for_each_member([&](auto& name, JsonValue const&) { + auto member_generator = generator.fork(); + member_generator.set("name", name); + member_generator.set("name:titlecase", title_casify(name)); + + member_generator.append(R"~~~( + case EnvironmentVariable::@name:titlecase@: + return "@name@"sv; +)~~~"); + }); + + generator.append(R"~~~( + } + VERIFY_NOT_REACHED(); +} + +ValueType environment_variable_type(EnvironmentVariable environment_variable) +{ + switch (environment_variable) { +)~~~"); + + environment_variables.for_each_member([&](auto& name, JsonValue const& value) { + auto& variable = value.as_object(); + auto member_generator = generator.fork(); + member_generator.set("name", name); + member_generator.set("name:titlecase", title_casify(name)); + + auto value_type = MUST(variable.get_string("type"sv)->trim("<>"sv)); + member_generator.set("value_type:titlecase", title_casify(value_type)); + + member_generator.append(R"~~~( + case EnvironmentVariable::@name:titlecase@: + return ValueType::@value_type:titlecase@; +)~~~"); + }); + + generator.append(R"~~~( + } + VERIFY_NOT_REACHED(); +} + +u32 environment_variable_dimension_count(EnvironmentVariable environment_variable) +{ + switch (environment_variable) { +)~~~"); + + environment_variables.for_each_member([&](auto& name, JsonValue const& value) { + auto& variable = value.as_object(); + auto member_generator = generator.fork(); + member_generator.set("name", name); + member_generator.set("name:titlecase", title_casify(name)); + member_generator.set("dimension_count", String::number(variable.get_u32("dimensions"sv).value())); + + member_generator.append(R"~~~( + case EnvironmentVariable::@name:titlecase@: + return @dimension_count@; +)~~~"); + }); + + generator.append(R"~~~( + } + VERIFY_NOT_REACHED(); +} + +} +)~~~"); + + TRY(file.write_until_depleted(generator.as_string_view().bytes())); + return {}; +}