diff --git a/Meta/CMake/libweb_generators.cmake b/Meta/CMake/libweb_generators.cmake index 3e2d8c708f6..4362a855515 100644 --- a/Meta/CMake/libweb_generators.cmake +++ b/Meta/CMake/libweb_generators.cmake @@ -68,9 +68,9 @@ function (generate_css_implementation) arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/PseudoClasses.json" ) - invoke_cpp_generator( + invoke_py_generator( "PseudoElement.cpp" - Lagom::GenerateCSSPseudoElement + "generate_libweb_css_pseudo_element.py" "${LIBWEB_INPUT_FOLDER}/CSS/PseudoElements.json" "CSS/PseudoElement.h" "CSS/PseudoElement.cpp" diff --git a/Meta/Generators/generate_libweb_css_pseudo_element.py b/Meta/Generators/generate_libweb_css_pseudo_element.py new file mode 100644 index 00000000000..117b46da857 --- /dev/null +++ b/Meta/Generators/generate_libweb_css_pseudo_element.py @@ -0,0 +1,545 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2022-2026, Sam Atkins +# Copyright (c) 2026-present, the Ladybird developers. +# +# SPDX-License-Identifier: BSD-2-Clause + +import argparse +import json +import sys + +from pathlib import Path +from typing import TextIO + +sys.path.append(str(Path(__file__).resolve().parent.parent)) + +from Utils.utils import title_casify +from Utils.utils import underlying_type_for_enum + +PARAMETER_TYPES = { + "": "CompoundSelector", + "+": "IdentList", + "": "PTNameSelector", +} + + +PROPERTY_GROUPS = { + "#background-properties": [ + # https://drafts.csswg.org/css-backgrounds/#property-index + "background", + "background-attachment", + "background-clip", + "background-color", + "background-image", + "background-origin", + "background-position", + "background-position-x", + "background-position-y", + "background-repeat", + "background-size", + ], + "#border-properties": [ + # https://drafts.csswg.org/css-backgrounds/#property-index + "border", + "border-block-end", + "border-block-end-color", + "border-block-end-style", + "border-block-end-width", + "border-block-start", + "border-block-start-color", + "border-block-start-style", + "border-block-start-width", + "border-bottom", + "border-bottom-color", + "border-bottom-left-radius", + "border-bottom-right-radius", + "border-bottom-style", + "border-bottom-width", + "border-color", + "border-image-outset", + "border-image-repeat", + "border-image-slice", + "border-image-source", + "border-image-width", + "border-inline-end", + "border-inline-end-color", + "border-inline-end-style", + "border-inline-end-width", + "border-inline-start", + "border-inline-start-color", + "border-inline-start-style", + "border-inline-start-width", + "border-left", + "border-left-color", + "border-left-style", + "border-left-width", + "border-radius", + "border-right", + "border-right-color", + "border-right-style", + "border-right-width", + "border-style", + "border-top", + "border-top-color", + "border-top-left-radius", + "border-top-right-radius", + "border-top-style", + "border-top-width", + "border-width", + ], + "#custom-properties": [ + "custom", + ], + "#font-properties": [ + # https://drafts.csswg.org/css-fonts/#property-index + "font", + "font-family", + "font-feature-settings", + # FIXME: font-kerning + "font-language-override", + # FIXME: font-optical-sizing + # FIXME: font-palette + "font-size", + # FIXME: font-size-adjust + "font-style", + # FIXME: font-synthesis and longhands + "font-variant", + "font-variant-alternates", + "font-variant-caps", + "font-variant-east-asian", + "font-variant-emoji", + "font-variant-ligatures", + "font-variant-numeric", + "font-variant-position", + "font-variation-settings", + "font-weight", + "font-width", + ], + "#inline-layout-properties": [ + # https://drafts.csswg.org/css-inline/#property-index + # FIXME: alignment-baseline + # FIXME: baseline-shift + # FIXME: baseline-source + # FIXME: dominant-baseline + # FIXME: initial-letter + # FIXME: initial-letter-align + # FIXME: initial-letter-wrap + # FIXME: inline-sizing + # FIXME: line-edge-fit + "line-height", + # FIXME: text-box + # FIXME: text-box-edge + # FIXME: text-box-trim + "vertical-align", + ], + "#inline-typesetting-properties": [ + # https://drafts.csswg.org/css-text-4/#property-index + # FIXME: hanging-punctuation + # FIXME: hyphenate-character + # FIXME: hyphenate-limit-chars + # FIXME: hyphenate-limit-last + # FIXME: hyphenate-limit-lines + # FIXME: hyphenate-limit-zone + # FIXME: hyphens + "letter-spacing", + # FIXME: line-break + # FIXME: line-padding + "overflow-wrap", + "tab-size", + "text-align", + # FIXME: text-align-all + # FIXME: text-align-last + # FIXME: text-autospace + # FIXME: text-group-align + "text-indent", + "text-justify", + # FIXME: text-spacing + # FIXME: text-spacing-trim + "text-transform", + "text-wrap", + "text-wrap-mode", + "text-wrap-style", + "white-space", + "white-space-collapse", + "white-space-trim", + "word-break", + # FIXME: word-space-transform + "word-spacing", + # FIXME: wrap-after + # FIXME: wrap-before + # FIXME: wrap-inside + ], + "#margin-properties": [ + "margin", + "margin-block", + "margin-block-end", + "margin-block-start", + "margin-bottom", + "margin-inline", + "margin-inline-end", + "margin-inline-start", + "margin-left", + "margin-right", + "margin-top", + ], + "#padding-properties": [ + "padding", + "padding-block", + "padding-block-end", + "padding-block-start", + "padding-bottom", + "padding-inline", + "padding-inline-end", + "padding-inline-start", + "padding-left", + "padding-right", + "padding-top", + ], + "#text-decoration-properties": [ + "text-decoration", + "text-decoration-color", + "text-decoration-line", + "text-decoration-style", + "text-decoration-thickness", + ], +} + + +def is_alias(pseudo_element: dict) -> bool: + return "alias-for" in pseudo_element + + +def write_header_file(out: TextIO, pseudo_elements_data: dict) -> None: + pseudo_element_count = len(pseudo_elements_data) + pseudo_element_underlying_type = underlying_type_for_enum(pseudo_element_count) + + out.write(f""" +#pragma once + +#include +#include +#include +#include + +namespace Web::CSS {{ + +enum class PseudoElement : {pseudo_element_underlying_type} {{ +""") + + for name, pseudo_element in pseudo_elements_data.items(): + if is_alias(pseudo_element): + continue + out.write(f" {title_casify(name)},\n") + + out.write(""" + KnownPseudoElementCount, + + UnknownWebKit, +}; + +Optional pseudo_element_from_string(StringView); +Optional aliased_pseudo_element_from_string(StringView); +WEB_API StringView pseudo_element_name(PseudoElement); + +bool is_has_allowed_pseudo_element(PseudoElement); +bool is_tree_abiding_pseudo_element(PseudoElement); +bool is_element_backed_pseudo_element(PseudoElement); +bool is_pseudo_element_root(PseudoElement); +bool pseudo_element_supports_property(PseudoElement, PropertyID); + +struct PseudoElementMetadata { + enum class ParameterType { + None, + CompoundSelector, + IdentList, + PTNameSelector, + } parameter_type; + bool is_valid_as_function; + bool is_valid_as_identifier; +}; +PseudoElementMetadata pseudo_element_metadata(PseudoElement); + +} +""") + + +def write_implementation_file(out: TextIO, pseudo_elements_data: dict) -> None: + out.write(""" +#include +#include + +namespace Web::CSS { + +Optional pseudo_element_from_string(StringView string) +{ +""") + + for name, pseudo_element in pseudo_elements_data.items(): + if is_alias(pseudo_element): + continue + out.write(f""" + if (string.equals_ignoring_ascii_case("{name}"sv)) + return PseudoElement::{title_casify(name)}; +""") + + out.write(""" + + return {}; +} + +Optional aliased_pseudo_element_from_string(StringView string) +{ +""") + + for name, pseudo_element in pseudo_elements_data.items(): + alias_for = pseudo_element.get("alias-for") + if alias_for is None: + continue + out.write(f""" + if (string.equals_ignoring_ascii_case("{name}"sv)) + return PseudoElement::{title_casify(alias_for)}; +""") + + out.write(""" + + return {}; +} + +StringView pseudo_element_name(PseudoElement pseudo_element) +{ + switch (pseudo_element) { +""") + + for name, pseudo_element in pseudo_elements_data.items(): + if is_alias(pseudo_element): + continue + out.write(f""" + case PseudoElement::{title_casify(name)}: + return "{name}"sv; +""") + + out.write(""" + case PseudoElement::KnownPseudoElementCount: + case PseudoElement::UnknownWebKit: + VERIFY_NOT_REACHED(); + } + VERIFY_NOT_REACHED(); +} + +bool is_has_allowed_pseudo_element(PseudoElement pseudo_element) +{ + switch (pseudo_element) { +""") + + for name, pseudo_element in pseudo_elements_data.items(): + if is_alias(pseudo_element): + continue + if not pseudo_element.get("is-allowed-in-has", False): + continue + out.write(f""" + case PseudoElement::{title_casify(name)}: + return true; +""") + + out.write(""" + default: + return false; + } +} + +bool is_tree_abiding_pseudo_element(PseudoElement pseudo_element) +{ + // Element-backed pseudo-elements are always tree-abiding. + // https://drafts.csswg.org/css-pseudo-4/#element-backed + if (is_element_backed_pseudo_element(pseudo_element)) + return true; + + switch (pseudo_element) { +""") + + for name, pseudo_element in pseudo_elements_data.items(): + if is_alias(pseudo_element): + continue + if not pseudo_element.get("is-tree-abiding", False): + continue + out.write(f""" + case PseudoElement::{title_casify(name)}: + return true; +""") + + out.write(""" + default: + return false; + } +} + +bool is_element_backed_pseudo_element(PseudoElement pseudo_element) +{ + switch (pseudo_element) { +""") + + for name, pseudo_element in pseudo_elements_data.items(): + if is_alias(pseudo_element): + continue + if not pseudo_element.get("is-element-backed", False): + continue + out.write(f""" + case PseudoElement::{title_casify(name)}: + return true; +""") + + out.write(""" + default: + return false; + } +} + +bool is_pseudo_element_root(PseudoElement pseudo_element) +{ + switch (pseudo_element) { +""") + + for name, pseudo_element in pseudo_elements_data.items(): + if is_alias(pseudo_element): + continue + if not pseudo_element.get("is-pseudo-root", False): + continue + out.write(f""" + case PseudoElement::{title_casify(name)}: + return true; +""") + + out.write(""" + default: + return false; + } +} + +bool pseudo_element_supports_property(PseudoElement pseudo_element, PropertyID property_id) +{ + switch (pseudo_element) { +""") + + for name, pseudo_element in pseudo_elements_data.items(): + if is_alias(pseudo_element): + continue + property_whitelist = pseudo_element.get("property-whitelist") + # No whitelist = accept everything, by falling back to the default case. + if property_whitelist is None: + continue + + out.write(f""" + case PseudoElement::{title_casify(name)}: + switch (property_id) {{ +""") + + for entry in property_whitelist: + if entry.startswith("FIXME:"): + continue + if not entry.startswith("#"): + out.write(f" case PropertyID::{title_casify(entry)}:\n") + continue + # Categories + # TODO: Maybe define these in data somewhere too? + if entry not in PROPERTY_GROUPS: + print(f"Error: Unrecognized property group name '{entry}' in {name}", file=sys.stderr) + sys.exit(1) + for property_name in PROPERTY_GROUPS[entry]: + out.write(f" case PropertyID::{title_casify(property_name)}:\n") + + out.write(""" + return true; + default: + return false; + } +""") + + out.write(""" + default: + return true; + } +} + +PseudoElementMetadata pseudo_element_metadata(PseudoElement pseudo_element) +{ + switch (pseudo_element) { +""") + + for name, pseudo_element in pseudo_elements_data.items(): + if is_alias(pseudo_element): + continue + + pe_type = pseudo_element.get("type") + if pe_type == "function": + is_valid_as_function = True + is_valid_as_identifier = False + elif pe_type == "both": + is_valid_as_function = True + is_valid_as_identifier = True + else: + is_valid_as_function = False + is_valid_as_identifier = True + + parameter_type = "None" + if is_valid_as_function: + function_syntax = pseudo_element["function-syntax"] + if function_syntax not in PARAMETER_TYPES: + print(f"Unrecognized pseudo-element parameter type: `{function_syntax}`", file=sys.stderr) + sys.exit(1) + parameter_type = PARAMETER_TYPES[function_syntax] + elif "function-syntax" in pseudo_element: + print(f"Pseudo-element `::{name}` has `function-syntax` but is not a function type.", file=sys.stderr) + sys.exit(1) + + is_valid_as_function_str = "true" if is_valid_as_function else "false" + is_valid_as_identifier_str = "true" if is_valid_as_identifier else "false" + + out.write(f""" + case PseudoElement::{title_casify(name)}: + return {{ + .parameter_type = PseudoElementMetadata::ParameterType::{parameter_type}, + .is_valid_as_function = {is_valid_as_function_str}, + .is_valid_as_identifier = {is_valid_as_identifier_str}, + }}; +""") + + out.write(""" + case PseudoElement::UnknownWebKit: + return { + .parameter_type = PseudoElementMetadata::ParameterType::None, + .is_valid_as_function = false, + .is_valid_as_identifier = true, + }; + case PseudoElement::KnownPseudoElementCount: + break; + } + VERIFY_NOT_REACHED(); +} + +} +""") + + +def main(): + parser = argparse.ArgumentParser(description="Generate CSS PseudoElement", add_help=False) + parser.add_argument("--help", action="help", help="Show this help message and exit") + parser.add_argument("-h", "--header", required=True, help="Path to the PseudoElement header file to generate") + parser.add_argument( + "-c", "--implementation", required=True, help="Path to the PseudoElement implementation file to generate" + ) + parser.add_argument("-j", "--json", required=True, help="Path to the JSON file to read from") + args = parser.parse_args() + + with open(args.json, "r", encoding="utf-8") as input_file: + pseudo_elements_data = json.load(input_file) + + with open(args.header, "w", encoding="utf-8") as output_file: + write_header_file(output_file, pseudo_elements_data) + + with open(args.implementation, "w", encoding="utf-8") as output_file: + write_implementation_file(output_file, pseudo_elements_data) + + +if __name__ == "__main__": + main() diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt index 60d348aeeee..4af9729a6e4 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/CMakeLists.txt @@ -2,7 +2,6 @@ set(SOURCES "") # avoid pulling SOURCES from parent scope lagom_tool(GenerateCSSNumericFactoryMethods SOURCES GenerateCSSNumericFactoryMethods.cpp LIBS LibMain) lagom_tool(GenerateCSSPropertyID SOURCES GenerateCSSPropertyID.cpp LIBS LibMain) -lagom_tool(GenerateCSSPseudoElement SOURCES GenerateCSSPseudoElement.cpp LIBS LibMain) lagom_tool(GenerateCSSStyleProperties SOURCES GenerateCSSStyleProperties.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/GenerateCSSPseudoElement.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPseudoElement.cpp deleted file mode 100644 index 30f48803272..00000000000 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPseudoElement.cpp +++ /dev/null @@ -1,629 +0,0 @@ -/* - * Copyright (c) 2022-2025, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "GeneratorUtil.h" -#include -#include -#include - -ErrorOr generate_header_file(JsonObject& pseudo_elements_data, Core::File& file); -ErrorOr generate_implementation_file(JsonObject& pseudo_elements_data, 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 PseudoElements header file to generate", "generated-header-path", 'h', "generated-header-path"); - args_parser.add_option(generated_implementation_path, "Path to the PseudoElements 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; -} - -ErrorOr generate_header_file(JsonObject& pseudo_elements_data, Core::File& file) -{ - StringBuilder builder; - SourceGenerator generator { builder }; - - auto pseudo_element_count = 0u; - auto generated_pseudo_element_count = 0u; - pseudo_elements_data.for_each_member([&](auto const&, JsonValue const& value) { - auto& pseudo_element = value.as_object(); - ++pseudo_element_count; - if (pseudo_element.get_bool("is-generated"sv).value_or(false)) - ++generated_pseudo_element_count; - }); - generator.set("pseudo_element_underlying_type", underlying_type_for_enum(pseudo_element_count)); - generator.set("generated_pseudo_element_underlying_type", underlying_type_for_enum(generated_pseudo_element_count)); - - generator.append(R"~~~( -#pragma once - -#include -#include -#include -#include - -namespace Web::CSS { - -enum class PseudoElement : @pseudo_element_underlying_type@ { -)~~~"); - - pseudo_elements_data.for_each_member([&](auto& name, JsonValue const& value) { - auto& pseudo_element = value.as_object(); - if (pseudo_element.has("alias-for"sv)) - return; - - auto member_generator = generator.fork(); - member_generator.set("name:titlecase", title_casify(name)); - - member_generator.appendln(" @name:titlecase@,"); - }); - generator.append(R"~~~( - KnownPseudoElementCount, - - UnknownWebKit, -}; - -Optional pseudo_element_from_string(StringView); -Optional aliased_pseudo_element_from_string(StringView); -WEB_API StringView pseudo_element_name(PseudoElement); - -bool is_has_allowed_pseudo_element(PseudoElement); -bool is_tree_abiding_pseudo_element(PseudoElement); -bool is_element_backed_pseudo_element(PseudoElement); -bool is_pseudo_element_root(PseudoElement); -bool pseudo_element_supports_property(PseudoElement, PropertyID); - -struct PseudoElementMetadata { - enum class ParameterType { - None, - CompoundSelector, - IdentList, - PTNameSelector, - } parameter_type; - bool is_valid_as_function; - bool is_valid_as_identifier; -}; -PseudoElementMetadata pseudo_element_metadata(PseudoElement); - -} -)~~~"); - - TRY(file.write_until_depleted(generator.as_string_view().bytes())); - return {}; -} - -ErrorOr generate_implementation_file(JsonObject& pseudo_elements_data, Core::File& file) -{ - StringBuilder builder; - SourceGenerator generator { builder }; - - generator.append(R"~~~( -#include -#include - -namespace Web::CSS { - -Optional pseudo_element_from_string(StringView string) -{ -)~~~"); - - pseudo_elements_data.for_each_member([&](auto& name, JsonValue const& value) { - auto& pseudo_element = value.as_object(); - if (pseudo_element.has("alias-for"sv)) - return; - 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 PseudoElement::@name:titlecase@; -)~~~"); - }); - - generator.append(R"~~~( - - return {}; -} - -Optional aliased_pseudo_element_from_string(StringView string) -{ -)~~~"); - - pseudo_elements_data.for_each_member([&](auto& name, JsonValue const& value) { - auto& pseudo_element = value.as_object(); - auto alias_for = pseudo_element.get_string("alias-for"sv); - if (!alias_for.has_value()) - return; - - auto member_generator = generator.fork(); - member_generator.set("name", name); - member_generator.set("alias:titlecase", title_casify(alias_for.value())); - - member_generator.append(R"~~~( - if (string.equals_ignoring_ascii_case("@name@"sv)) - return PseudoElement::@alias:titlecase@; -)~~~"); - }); - - generator.append(R"~~~( - - return {}; -} - -StringView pseudo_element_name(PseudoElement pseudo_element) -{ - switch (pseudo_element) { -)~~~"); - - pseudo_elements_data.for_each_member([&](auto& name, JsonValue const& value) { - auto& pseudo_element = value.as_object(); - if (pseudo_element.has("alias-for"sv)) - return; - auto member_generator = generator.fork(); - member_generator.set("name", name); - member_generator.set("name:titlecase", title_casify(name)); - - member_generator.append(R"~~~( - case PseudoElement::@name:titlecase@: - return "@name@"sv; -)~~~"); - }); - - generator.append(R"~~~( - case PseudoElement::KnownPseudoElementCount: - case PseudoElement::UnknownWebKit: - VERIFY_NOT_REACHED(); - } - VERIFY_NOT_REACHED(); -} - -bool is_has_allowed_pseudo_element(PseudoElement pseudo_element) -{ - switch (pseudo_element) { -)~~~"); - - pseudo_elements_data.for_each_member([&](auto& name, JsonValue const& value) { - auto& pseudo_element = value.as_object(); - if (pseudo_element.has("alias-for"sv)) - return; - if (!pseudo_element.get_bool("is-allowed-in-has"sv).value_or(false)) - return; - - auto member_generator = generator.fork(); - member_generator.set("name:titlecase", title_casify(name)); - - member_generator.append(R"~~~( - case PseudoElement::@name:titlecase@: - return true; -)~~~"); - }); - - generator.append(R"~~~( - default: - return false; - } -} - -bool is_tree_abiding_pseudo_element(PseudoElement pseudo_element) -{ - // Element-backed pseudo-elements are always tree-abiding. - // https://drafts.csswg.org/css-pseudo-4/#element-backed - if (is_element_backed_pseudo_element(pseudo_element)) - return true; - - switch (pseudo_element) { -)~~~"); - - pseudo_elements_data.for_each_member([&](auto& name, JsonValue const& value) { - auto& pseudo_element = value.as_object(); - if (pseudo_element.has("alias-for"sv)) - return; - if (!pseudo_element.get_bool("is-tree-abiding"sv).value_or(false)) - return; - - auto member_generator = generator.fork(); - member_generator.set("name:titlecase", title_casify(name)); - - member_generator.append(R"~~~( - case PseudoElement::@name:titlecase@: - return true; -)~~~"); - }); - - generator.append(R"~~~( - default: - return false; - } -} - -bool is_element_backed_pseudo_element(PseudoElement pseudo_element) -{ - switch (pseudo_element) { -)~~~"); - - pseudo_elements_data.for_each_member([&](auto& name, JsonValue const& value) { - auto& pseudo_element = value.as_object(); - if (pseudo_element.has("alias-for"sv)) - return; - if (!pseudo_element.get_bool("is-element-backed"sv).value_or(false)) - return; - - auto member_generator = generator.fork(); - member_generator.set("name:titlecase", title_casify(name)); - - member_generator.append(R"~~~( - case PseudoElement::@name:titlecase@: - return true; -)~~~"); - }); - - generator.append(R"~~~( - default: - return false; - } -} - -bool is_pseudo_element_root(PseudoElement pseudo_element) -{ - switch (pseudo_element) { -)~~~"); - - pseudo_elements_data.for_each_member([&](auto& name, JsonValue const& value) { - auto& pseudo_element = value.as_object(); - if (pseudo_element.has("alias-for"sv)) - return; - if (!pseudo_element.get_bool("is-pseudo-root"sv).value_or(false)) - return; - - auto member_generator = generator.fork(); - member_generator.set("name:titlecase", title_casify(name)); - - member_generator.append(R"~~~( - case PseudoElement::@name:titlecase@: - return true; -)~~~"); - }); - - generator.append(R"~~~( - default: - return false; - } -} - -bool pseudo_element_supports_property(PseudoElement pseudo_element, PropertyID property_id) -{ - switch (pseudo_element) { -)~~~"); - - pseudo_elements_data.for_each_member([&](auto& name, JsonValue const& value) { - auto& pseudo_element = value.as_object(); - if (pseudo_element.has("alias-for"sv)) - return; - auto property_whitelist = pseudo_element.get_array("property-whitelist"sv); - // No whitelist = accept everything, by falling back to the default case. - if (!property_whitelist.has_value()) - return; - - auto member_generator = generator.fork(); - member_generator.set("name:titlecase", title_casify(name)); - member_generator.append(R"~~~( - case PseudoElement::@name:titlecase@: - switch (property_id) { -)~~~"); - - property_whitelist->for_each([&](JsonValue const& entry) { - auto& property = entry.as_string(); - if (property.starts_with_bytes("FIXME:"sv)) - return; - - auto append_property = [&](StringView const& property_name) { - auto property_generator = member_generator.fork(); - property_generator.set("property:titlecase", title_casify(property_name)); - property_generator.appendln(" case PropertyID::@property:titlecase@:"); - }; - - if (!property.starts_with('#')) { - append_property(property); - return; - } - // Categories - // TODO: Maybe define these in data somewhere too? - if (property == "#background-properties"sv) { - // https://drafts.csswg.org/css-backgrounds/#property-index - append_property("background"sv); - append_property("background-attachment"sv); - append_property("background-clip"sv); - append_property("background-color"sv); - append_property("background-image"sv); - append_property("background-origin"sv); - append_property("background-position"sv); - append_property("background-position-x"sv); - append_property("background-position-y"sv); - append_property("background-repeat"sv); - append_property("background-size"sv); - return; - } - if (property == "#border-properties"sv) { - // https://drafts.csswg.org/css-backgrounds/#property-index - append_property("border"sv); - append_property("border-block-end"sv); - append_property("border-block-end-color"sv); - append_property("border-block-end-style"sv); - append_property("border-block-end-width"sv); - append_property("border-block-start"sv); - append_property("border-block-start-color"sv); - append_property("border-block-start-style"sv); - append_property("border-block-start-width"sv); - append_property("border-bottom"sv); - append_property("border-bottom-color"sv); - append_property("border-bottom-left-radius"sv); - append_property("border-bottom-right-radius"sv); - append_property("border-bottom-style"sv); - append_property("border-bottom-width"sv); - append_property("border-color"sv); - append_property("border-image-outset"sv); - append_property("border-image-repeat"sv); - append_property("border-image-slice"sv); - append_property("border-image-source"sv); - append_property("border-image-width"sv); - append_property("border-inline-end"sv); - append_property("border-inline-end-color"sv); - append_property("border-inline-end-style"sv); - append_property("border-inline-end-width"sv); - append_property("border-inline-start"sv); - append_property("border-inline-start-color"sv); - append_property("border-inline-start-style"sv); - append_property("border-inline-start-width"sv); - append_property("border-left"sv); - append_property("border-left-color"sv); - append_property("border-left-style"sv); - append_property("border-left-width"sv); - append_property("border-radius"sv); - append_property("border-right"sv); - append_property("border-right-color"sv); - append_property("border-right-style"sv); - append_property("border-right-width"sv); - append_property("border-style"sv); - append_property("border-top"sv); - append_property("border-top-color"sv); - append_property("border-top-left-radius"sv); - append_property("border-top-right-radius"sv); - append_property("border-top-style"sv); - append_property("border-top-width"sv); - append_property("border-width"sv); - return; - } - if (property == "#custom-properties"sv) { - append_property("custom"sv); - return; - } - if (property == "#font-properties"sv) { - // https://drafts.csswg.org/css-fonts/#property-index - append_property("font"sv); - append_property("font-family"sv); - append_property("font-feature-settings"sv); - // FIXME: font-kerning - append_property("font-language-override"sv); - // FIXME: font-optical-sizing - // FIXME: font-palette - append_property("font-size"sv); - // FIXME: font-size-adjust - append_property("font-style"sv); - // FIXME: font-synthesis and longhands - append_property("font-variant"sv); - append_property("font-variant-alternates"sv); - append_property("font-variant-caps"sv); - append_property("font-variant-east-asian"sv); - append_property("font-variant-emoji"sv); - append_property("font-variant-ligatures"sv); - append_property("font-variant-numeric"sv); - append_property("font-variant-position"sv); - append_property("font-variation-settings"sv); - append_property("font-weight"sv); - append_property("font-width"sv); - return; - } - if (property == "#inline-layout-properties"sv) { - // https://drafts.csswg.org/css-inline/#property-index - // FIXME: alignment-baseline - // FIXME: baseline-shift - // FIXME: baseline-source - // FIXME: dominant-baseline - // FIXME: initial-letter - // FIXME: initial-letter-align - // FIXME: initial-letter-wrap - // FIXME: inline-sizing - // FIXME: line-edge-fit - append_property("line-height"sv); - // FIXME: text-box - // FIXME: text-box-edge - // FIXME: text-box-trim - append_property("vertical-align"sv); - return; - } - if (property == "#inline-typesetting-properties"sv) { - // https://drafts.csswg.org/css-text-4/#property-index - // FIXME: hanging-punctuation - // FIXME: hyphenate-character - // FIXME: hyphenate-limit-chars - // FIXME: hyphenate-limit-last - // FIXME: hyphenate-limit-lines - // FIXME: hyphenate-limit-zone - // FIXME: hyphens - append_property("letter-spacing"sv); - // FIXME: line-break - // FIXME: line-padding - append_property("overflow-wrap"sv); - append_property("tab-size"sv); - append_property("text-align"sv); - // FIXME: text-align-all - // FIXME: text-align-last - // FIXME: text-autospace - // FIXME: text-group-align - append_property("text-indent"sv); - append_property("text-justify"sv); - // FIXME: text-spacing - // FIXME: text-spacing-trim - append_property("text-transform"sv); - append_property("text-wrap"sv); - append_property("text-wrap-mode"sv); - append_property("text-wrap-style"sv); - append_property("white-space"sv); - append_property("white-space-collapse"sv); - append_property("white-space-trim"sv); - append_property("word-break"sv); - // FIXME: word-space-transform - append_property("word-spacing"sv); - // FIXME: wrap-after - // FIXME: wrap-before - // FIXME: wrap-inside - return; - } - if (property == "#margin-properties"sv) { - append_property("margin"sv); - append_property("margin-block"sv); - append_property("margin-block-end"sv); - append_property("margin-block-start"sv); - append_property("margin-bottom"sv); - append_property("margin-inline"sv); - append_property("margin-inline-end"sv); - append_property("margin-inline-start"sv); - append_property("margin-left"sv); - append_property("margin-right"sv); - append_property("margin-top"sv); - return; - } - if (property == "#padding-properties"sv) { - append_property("padding"sv); - append_property("padding-block"sv); - append_property("padding-block-end"sv); - append_property("padding-block-start"sv); - append_property("padding-bottom"sv); - append_property("padding-inline"sv); - append_property("padding-inline-end"sv); - append_property("padding-inline-start"sv); - append_property("padding-left"sv); - append_property("padding-right"sv); - append_property("padding-top"sv); - return; - } - if (property == "#text-decoration-properties"sv) { - append_property("text-decoration"sv); - append_property("text-decoration-color"sv); - append_property("text-decoration-line"sv); - append_property("text-decoration-style"sv); - append_property("text-decoration-thickness"sv); - return; - } - outln("Error: Unrecognized property group name '{}' in {}", property, name); - exit(1); - }); - - member_generator.append(R"~~~( - return true; - default: - return false; - } -)~~~"); - }); - - generator.append(R"~~~( - default: - return true; - } -} - -PseudoElementMetadata pseudo_element_metadata(PseudoElement pseudo_element) -{ - switch (pseudo_element) { -)~~~"); - pseudo_elements_data.for_each_member([&](auto& name, JsonValue const& value) { - auto& pseudo_element = value.as_object(); - if (pseudo_element.has("alias-for"sv)) - return; - - bool is_valid_as_function = false; - bool is_valid_as_identifier = false; - auto const& type = pseudo_element.get_string("type"sv); - if (type == "function"sv) { - is_valid_as_function = true; - } else if (type == "both"sv) { - is_valid_as_function = true; - is_valid_as_identifier = true; - } else { - is_valid_as_identifier = true; - } - - String parameter_type = "None"_string; - if (is_valid_as_function) { - auto const& function_syntax = pseudo_element.get_string("function-syntax"sv).value(); - if (function_syntax == ""sv) { - parameter_type = "CompoundSelector"_string; - } else if (function_syntax == "+"sv) { - parameter_type = "IdentList"_string; - } else if (function_syntax == ""sv) { - parameter_type = "PTNameSelector"_string; - } else { - warnln("Unrecognized pseudo-element parameter type: `{}`", function_syntax); - VERIFY_NOT_REACHED(); - } - } else if (pseudo_element.has("function-syntax"sv)) { - warnln("Pseudo-element `::{}` has `function-syntax` but is not a function type.", name); - VERIFY_NOT_REACHED(); - } - - auto member_generator = generator.fork(); - member_generator.set("name:titlecase", title_casify(name)); - member_generator.set("parameter_type", parameter_type); - member_generator.set("is_valid_as_function", is_valid_as_function ? "true"_string : "false"_string); - member_generator.set("is_valid_as_identifier", is_valid_as_identifier ? "true"_string : "false"_string); - - member_generator.append(R"~~~( - case PseudoElement::@name:titlecase@: - return { - .parameter_type = PseudoElementMetadata::ParameterType::@parameter_type@, - .is_valid_as_function = @is_valid_as_function@, - .is_valid_as_identifier = @is_valid_as_identifier@, - }; -)~~~"); - }); - - generator.append(R"~~~( - case PseudoElement::UnknownWebKit: - return { - .parameter_type = PseudoElementMetadata::ParameterType::None, - .is_valid_as_function = false, - .is_valid_as_identifier = true, - }; - case PseudoElement::KnownPseudoElementCount: - break; - } - VERIFY_NOT_REACHED(); -} - -} -)~~~"); - - TRY(file.write_until_depleted(generator.as_string_view().bytes())); - return {}; -}