diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 565542c078b..bf68ac318be 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -952,6 +952,7 @@ invoke_generator( set(GENERATED_SOURCES ARIA/AriaRoles.cpp CSS/DefaultStyleSheetSource.cpp + CSS/DescriptorID.cpp CSS/Enums.cpp CSS/GeneratedCSSStyleProperties.cpp CSS/GeneratedCSSStyleProperties.idl diff --git a/Libraries/LibWeb/CSS/Descriptors.json b/Libraries/LibWeb/CSS/Descriptors.json new file mode 100644 index 00000000000..a89ab80b2ff --- /dev/null +++ b/Libraries/LibWeb/CSS/Descriptors.json @@ -0,0 +1,129 @@ +{ + "font-face": { + "spec": "https://drafts.csswg.org/css-fonts-4/#at-font-face-rule", + "descriptors": { + "ascent-override": { + "initial": "normal", + "syntax": [ + "normal", + "" + ] + }, + "descent-override": { + "initial": "normal", + "syntax": [ + "normal", + "" + ] + }, + "font-display": { + "initial": "auto", + "syntax": [ + "auto", + "block", + "swap", + "fallback", + "optional" + ] + }, + "font-family": { + "syntax": [ + "" + ] + }, + "font-feature-settings": { + "initial": "normal", + "syntax": [ + "<'font-feature-settings'>" + ] + }, + "font-language-override": { + "initial": "normal", + "syntax": [ + "normal", + "" + ] + }, + "font-named-instance": { + "initial": "auto", + "syntax": [ + "auto", + "" + ] + }, + "font-stretch": { + "legacy-alias-for": "font-width" + }, + "font-style": { + "initial": "auto", + "FIXME": "Support angles for oblique", + "syntax": [ + "auto", + "<'font-style'>" + ] + }, + "font-variation-settings": { + "initial": "normal", + "syntax": [ + "<'font-variation-settings'>" + ] + }, + "font-weight": { + "initial": "auto", + "FIXME": "Support multiple font-weight values; disallow relative font-weights", + "syntax": [ + "auto", + "<'font-weight'>" + ] + }, + "font-width": { + "initial": "auto", + "FIXME": "Support multiple font-width values", + "syntax": [ + "auto", + "<'font-width'>" + ] + }, + "line-gap-override": { + "initial": "normal", + "syntax": [ + "normal", + "" + ] + }, + "src": { + "syntax": [ + "" + ] + }, + "unicode-range": { + "initial": "U+0-10FFFF", + "NOTE": "unicode-range has special parsing rules so it's parsed manually.", + "syntax": [ + "#" + ] + } + } + }, + "property": { + "spec": "https://drafts.css-houdini.org/css-properties-values-api/#at-property-rule", + "descriptors": { + "inherits": { + "syntax": [ + "true", + "false" + ] + }, + "initial-value": { + "syntax": [ + "?" + ] + }, + "syntax": { + "syntax": [ + "" + ] + } + } + } +} diff --git a/Libraries/LibWeb/CSS/Keywords.json b/Libraries/LibWeb/CSS/Keywords.json index 82a1d38bd59..682ab24b758 100644 --- a/Libraries/LibWeb/CSS/Keywords.json +++ b/Libraries/LibWeb/CSS/Keywords.json @@ -170,6 +170,7 @@ "extra-condensed", "extra-expanded", "fallback", + "false", "fantasy", "fast", "field", @@ -451,6 +452,7 @@ "to-zero", "top", "traditional", + "true", "ui-monospace", "ui-rounded", "ui-sans-serif", diff --git a/Meta/CMake/libweb_generators.cmake b/Meta/CMake/libweb_generators.cmake index 6cb0a3766f3..a5888db22e6 100644 --- a/Meta/CMake/libweb_generators.cmake +++ b/Meta/CMake/libweb_generators.cmake @@ -1,6 +1,16 @@ function (generate_css_implementation) set(LIBWEB_INPUT_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}") + + invoke_generator( + "DescriptorID.cpp" + Lagom::GenerateCSSDescriptors + "${LIBWEB_INPUT_FOLDER}/CSS/Descriptors.json" + "CSS/DescriptorID.h" + "CSS/DescriptorID.cpp" + arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/Descriptors.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 c154e5e3229..3acf6ccd86d 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt @@ -1,5 +1,6 @@ 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(GenerateCSSKeyword SOURCES GenerateCSSKeyword.cpp LIBS LibMain) lagom_tool(GenerateCSSMathFunctions SOURCES GenerateCSSMathFunctions.cpp LIBS LibMain) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSDescriptors.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSDescriptors.cpp new file mode 100644 index 00000000000..d1238f94af6 --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSDescriptors.cpp @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2022-2025, Sam Atkins + * Copyright (c) 2024, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "GeneratorUtil.h" + +#include +#include +#include +#include + +ErrorOr generate_header_file(JsonObject const& at_rules_data, Core::File& file); +ErrorOr generate_implementation_file(JsonObject const& at_rules_data, Core::File& file); + +static bool is_legacy_alias(JsonObject const& descriptor) +{ + return descriptor.has_string("legacy-alias-for"sv); +} + +ErrorOr serenity_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 DescriptorID header file to generate", "generated-header-path", 'h', "generated-header-path"); + args_parser.add_option(generated_implementation_path, "Path to the DescriptorID 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 json = TRY(read_entire_file_as_json(json_path)); + VERIFY(json.is_object()); + auto 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(data, *generated_header_file)); + TRY(generate_implementation_file(data, *generated_implementation_file)); + + return 0; +} + +Vector all_descriptors; + +ErrorOr generate_header_file(JsonObject const& at_rules_data, Core::File& file) +{ + StringBuilder builder; + SourceGenerator generator { builder }; + + // DescriptorID is a set of all descriptor names used by any at-rules, so gather them up. + auto at_rule_count = 0u; + HashTable descriptors_set; + at_rules_data.for_each_member([&](auto const&, JsonValue const& value) { + auto const& at_rule = value.as_object(); + ++at_rule_count; + + if (auto descriptors = at_rule.get_object("descriptors"sv); descriptors.has_value()) { + descriptors.value().for_each_member([&](auto const& descriptor_name, JsonValue const& descriptor_value) { + if (is_legacy_alias(descriptor_value.as_object())) + return; + descriptors_set.set(descriptor_name); + }); + } + }); + + all_descriptors.ensure_capacity(descriptors_set.size()); + for (auto name : descriptors_set) + all_descriptors.unchecked_append(name); + quick_sort(all_descriptors); + + generator.set("at_rule_id_underlying_type", underlying_type_for_enum(at_rule_count)); + generator.set("descriptor_id_underlying_type", underlying_type_for_enum(all_descriptors.size())); + + generator.append(R"~~~( +#pragma once + +#include +#include +#include + +namespace Web::CSS { + +enum class AtRuleID : @at_rule_id_underlying_type@ { +)~~~"); + at_rules_data.for_each_member([&](auto const& name, auto const&) { + auto member_generator = generator.fork(); + member_generator.set("name:titlecase", title_casify(name)); + member_generator.appendln(" @name:titlecase@,"); + }); + generator.append(R"~~~( +}; + +FlyString to_string(AtRuleID); + +enum class DescriptorID : @descriptor_id_underlying_type@ { +)~~~"); + for (auto const& descriptor_name : all_descriptors) { + auto member_generator = generator.fork(); + member_generator.set("name:titlecase", title_casify(descriptor_name)); + member_generator.appendln(" @name:titlecase@,"); + } + generator.append(R"~~~( +}; + +Optional descriptor_id_from_string(AtRuleID, StringView); +FlyString to_string(DescriptorID); + +bool at_rule_supports_descriptor(AtRuleID, DescriptorID); +} +)~~~"); + + TRY(file.write_until_depleted(generator.as_string_view().bytes())); + return {}; +} + +ErrorOr generate_implementation_file(JsonObject const& at_rules_data, Core::File& file) +{ + StringBuilder builder; + SourceGenerator generator { builder }; + + generator.append(R"~~~( +#include + +namespace Web::CSS { + +FlyString to_string(AtRuleID at_rule_id) +{ + switch (at_rule_id) { +)~~~"); + + at_rules_data.for_each_member([&](auto const& at_rule_name, JsonValue const&) { + auto at_rule_generator = generator.fork(); + at_rule_generator.set("at_rule", at_rule_name); + at_rule_generator.set("at_rule:titlecase", title_casify(at_rule_name)); + at_rule_generator.append(R"~~~( + case AtRuleID::@at_rule:titlecase@: + return "\@@at_rule@"_fly_string; +)~~~"); + }); + + generator.append(R"~~~( + } + VERIFY_NOT_REACHED(); +} + +Optional descriptor_id_from_string(AtRuleID at_rule_id, StringView string) +{ + switch (at_rule_id) { +)~~~"); + at_rules_data.for_each_member([&](auto const& at_rule_name, JsonValue const& value) { + auto const& at_rule = value.as_object(); + + auto at_rule_generator = generator.fork(); + at_rule_generator.set("at_rule:titlecase", title_casify(at_rule_name)); + at_rule_generator.append(R"~~~( + case AtRuleID::@at_rule:titlecase@: +)~~~"); + + auto const& descriptors = at_rule.get_object("descriptors"sv).value(); + + descriptors.for_each_member([&](auto const& descriptor_name, JsonValue const& descriptor_value) { + auto const& descriptor = descriptor_value.as_object(); + auto descriptor_generator = at_rule_generator.fork(); + + descriptor_generator.set("descriptor", descriptor_name); + if (auto alias_for = descriptor.get_string("legacy-alias-for"sv); alias_for.has_value()) { + descriptor_generator.set("result:titlecase", title_casify(alias_for.value())); + } else { + descriptor_generator.set("result:titlecase", title_casify(descriptor_name)); + } + descriptor_generator.append(R"~~~( + if (string.equals_ignoring_ascii_case("@descriptor@"sv)) + return DescriptorID::@result:titlecase@; +)~~~"); + }); + + at_rule_generator.append(R"~~~( + break; +)~~~"); + }); + + generator.append(R"~~~( + } + return {}; +} + +FlyString to_string(DescriptorID descriptor_id) +{ + switch (descriptor_id) { +)~~~"); + + for (auto const& descriptor_name : all_descriptors) { + auto member_generator = generator.fork(); + member_generator.set("name", descriptor_name); + member_generator.set("name:titlecase", title_casify(descriptor_name)); + + member_generator.append(R"~~~( + case DescriptorID::@name:titlecase@: + return "@name@"_fly_string; +)~~~"); + } + + generator.append(R"~~~( + } + VERIFY_NOT_REACHED(); +} + +bool at_rule_supports_descriptor(AtRuleID at_rule_id, DescriptorID descriptor_id) +{ + switch (at_rule_id) { +)~~~"); + + at_rules_data.for_each_member([&](auto const& at_rule_name, JsonValue const& value) { + auto const& at_rule = value.as_object(); + + auto at_rule_generator = generator.fork(); + at_rule_generator.set("at_rule:titlecase", title_casify(at_rule_name)); + at_rule_generator.append(R"~~~( + case AtRuleID::@at_rule:titlecase@: + switch (descriptor_id) { +)~~~"); + + auto const& descriptors = at_rule.get_object("descriptors"sv).value(); + descriptors.for_each_member([&](auto const& descriptor_name, JsonValue const& descriptor_value) { + if (is_legacy_alias(descriptor_value.as_object())) + return; + + auto descriptor_generator = at_rule_generator.fork(); + descriptor_generator.set("descriptor:titlecase", title_casify(descriptor_name)); + descriptor_generator.appendln(" case DescriptorID::@descriptor:titlecase@:"); + }); + + at_rule_generator.append(R"~~~( + return true; + default: + return false; + } +)~~~"); + }); + + generator.append(R"~~~( + } + VERIFY_NOT_REACHED(); +} + +} +)~~~"); + + TRY(file.write_until_depleted(generator.as_string_view().bytes())); + return {}; +}