2020-01-18 09:38:21 +01:00
/*
2024-10-04 13:19:50 +02:00
* Copyright ( c ) 2018 - 2020 , Andreas Kling < andreas @ ladybird . org >
2025-07-03 14:31:26 +01:00
* Copyright ( c ) 2021 - 2025 , Sam Atkins < sam @ ladybird . org >
2020-01-18 09:38:21 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 09:38:21 +01:00
*/
2022-03-08 14:36:18 +00:00
# include "GeneratorUtil.h"
2023-05-31 15:36:21 +01:00
# include <AK/CharacterTypes.h>
2023-05-10 17:28:15 +01:00
# include <AK/GenericShorthands.h>
2025-06-03 20:11:45 +12:00
# include <AK/QuickSort.h>
2020-10-23 18:37:35 +02:00
# include <AK/SourceGenerator.h>
2019-11-18 11:12:58 +01:00
# include <AK/StringBuilder.h>
2022-04-01 17:41:43 +01:00
# include <LibCore/ArgsParser.h>
2022-03-10 11:55:06 +00:00
# include <LibMain/Main.h>
2021-09-29 20:29:44 +02:00
2025-07-03 14:31:26 +01:00
void replace_logical_aliases ( JsonObject & properties , JsonObject & logical_property_groups ) ;
2025-06-10 01:31:14 +12:00
void populate_all_property_longhands ( JsonObject & properties ) ;
2025-07-03 14:31:26 +01:00
ErrorOr < void > generate_header_file ( JsonObject & properties , JsonObject & logical_property_groups , Core : : File & file ) ;
2025-07-10 15:05:02 +01:00
ErrorOr < void > generate_implementation_file ( JsonObject & properties , JsonObject & logical_property_groups , ReadonlySpan < StringView > enum_names , Core : : File & file ) ;
2023-08-22 09:09:07 +02:00
void generate_bounds_checking_function ( JsonObject & properties , SourceGenerator & parent_generator , StringView css_type_name , StringView type_name , Optional < StringView > default_unit_name = { } , Optional < StringView > value_getter = { } ) ;
2023-10-31 19:33:58 -07:00
bool is_animatable_property ( JsonObject & properties , StringView property_name ) ;
2022-04-01 17:41:43 +01:00
2024-09-27 12:54:08 +01:00
static bool is_legacy_alias ( JsonObject const & property )
{
return property . has_string ( " legacy-alias-for " sv ) ;
}
2025-07-08 12:14:08 +02:00
ErrorOr < int > ladybird_main ( Main : : Arguments arguments )
2019-11-18 11:12:58 +01:00
{
2022-04-01 17:41:43 +01:00
StringView generated_header_path ;
StringView generated_implementation_path ;
StringView properties_json_path ;
2025-07-03 14:31:26 +01:00
StringView groups_json_path ;
2025-07-10 15:05:02 +01:00
StringView enums_json_path ;
2022-04-01 17:41:43 +01:00
Core : : ArgsParser args_parser ;
args_parser . add_option ( generated_header_path , " Path to the PropertyID header file to generate " , " generated-header-path " , ' h ' , " generated-header-path " ) ;
args_parser . add_option ( generated_implementation_path , " Path to the PropertyID implementation file to generate " , " generated-implementation-path " , ' c ' , " generated-implementation-path " ) ;
2025-07-03 14:31:26 +01:00
args_parser . add_option ( properties_json_path , " Path to the properties JSON file to read from " , " properties-json-path " , ' j ' , " properties-json-path " ) ;
2025-07-10 15:05:02 +01:00
args_parser . add_option ( enums_json_path , " Path to the enums JSON file to read from " , " enums-json-path " , ' e ' , " enums-json-path " ) ;
2025-07-03 14:31:26 +01:00
args_parser . add_option ( groups_json_path , " Path to the logical property groups JSON file to read from " , " groups-json-path " , ' g ' , " groups-json-path " ) ;
2022-04-01 17:41:43 +01:00
args_parser . parse ( arguments ) ;
2019-11-18 11:12:58 +01:00
2025-07-03 14:31:26 +01:00
auto read_json_object = [ ] ( auto & path ) - > ErrorOr < JsonObject > {
auto json = TRY ( read_entire_file_as_json ( path ) ) ;
VERIFY ( json . is_object ( ) ) ;
2022-04-01 17:41:43 +01:00
2025-07-03 14:31:26 +01:00
// 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 ;
} ) ;
2023-09-08 20:20:17 +01:00
2025-07-03 14:31:26 +01:00
return json . as_object ( ) ;
} ;
auto properties = TRY ( read_json_object ( properties_json_path ) ) ;
auto logical_property_groups = TRY ( read_json_object ( groups_json_path ) ) ;
2025-07-10 15:05:02 +01:00
auto enums = TRY ( read_json_object ( enums_json_path ) ) ;
Vector < StringView > enum_names ;
enums . for_each_member ( [ & enum_names ] ( String const & key , auto const & ) {
enum_names . append ( key ) ;
} ) ;
2025-07-03 14:31:26 +01:00
replace_logical_aliases ( properties , logical_property_groups ) ;
2025-06-10 01:31:14 +12:00
populate_all_property_longhands ( properties ) ;
2023-05-25 23:57:09 -07:00
2023-02-09 03:02:46 +01:00
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 ) ) ;
2022-04-01 17:41:43 +01:00
2025-07-03 14:31:26 +01:00
TRY ( generate_header_file ( properties , logical_property_groups , * generated_header_file ) ) ;
2025-07-10 15:05:02 +01:00
TRY ( generate_implementation_file ( properties , logical_property_groups , enum_names , * generated_implementation_file ) ) ;
2022-04-01 17:41:43 +01:00
return 0 ;
}
2025-07-03 14:31:26 +01:00
void replace_logical_aliases ( JsonObject & properties , JsonObject & logical_property_groups )
2023-05-25 23:57:09 -07:00
{
2025-07-03 14:31:26 +01:00
// Grab the first property in each logical group, to use as the template
HashMap < String , String > first_property_in_logical_group ;
logical_property_groups . for_each_member ( [ & first_property_in_logical_group ] ( String const & name , JsonValue const & value ) {
bool found = false ;
value . as_object ( ) . for_each_member ( [ & ] ( String const & , JsonValue const & member_value ) {
if ( found )
return ;
first_property_in_logical_group . set ( name , member_value . as_string ( ) ) ;
found = true ;
} ) ;
VERIFY ( found ) ;
} ) ;
HashMap < String , String > logical_aliases ;
properties . for_each_member ( [ & ] ( String const & name , JsonValue const & value ) {
2023-05-25 23:57:09 -07:00
VERIFY ( value . is_object ( ) ) ;
2024-04-24 06:53:44 -04:00
auto const & value_as_object = value . as_object ( ) ;
2025-07-03 14:31:26 +01:00
auto const logical_alias_for = value_as_object . get_object ( " logical-alias-for " sv ) ;
2023-05-25 23:57:09 -07:00
if ( logical_alias_for . has_value ( ) ) {
2025-07-03 14:31:26 +01:00
auto const & group_name = logical_alias_for - > get_string ( " group " sv ) ;
if ( ! group_name . has_value ( ) ) {
dbgln ( " Logical alias '{}' is missing its group " , name ) ;
VERIFY_NOT_REACHED ( ) ;
}
if ( auto physical_property_name = first_property_in_logical_group . get ( group_name . value ( ) ) ; physical_property_name . has_value ( ) ) {
logical_aliases . set ( name , physical_property_name . value ( ) ) ;
} else {
dbgln ( " Logical property group '{}' not found! (Property: '{}') " , group_name . value ( ) , name ) ;
VERIFY_NOT_REACHED ( ) ;
2023-06-06 10:35:16 +02:00
}
2023-05-25 23:57:09 -07:00
}
} ) ;
for ( auto & [ name , alias ] : logical_aliases ) {
auto const maybe_alias_object = properties . get_object ( alias ) ;
if ( ! maybe_alias_object . has_value ( ) ) {
dbgln ( " No property '{}' found for logical alias '{}' " , alias , name ) ;
VERIFY_NOT_REACHED ( ) ;
}
JsonObject alias_object = maybe_alias_object . value ( ) ;
// Copy over anything the logical property overrides
properties . get_object ( name ) . value ( ) . for_each_member ( [ & ] ( auto & key , auto & value ) {
alias_object . set ( key , value ) ;
} ) ;
2025-06-20 01:44:48 +12:00
// Quirks don't carry across to logical aliases
alias_object . remove ( " quirks " sv ) ;
2023-05-25 23:57:09 -07:00
properties . set ( name , alias_object ) ;
}
}
2025-06-10 01:31:14 +12:00
void populate_all_property_longhands ( JsonObject & properties )
{
auto all_entry = properties . get_object ( " all " sv ) ;
VERIFY ( all_entry . has_value ( ) ) ;
properties . for_each_member ( [ & ] ( auto name , auto value ) {
2025-06-18 17:45:26 +12:00
if ( value . as_object ( ) . has_array ( " longhands " sv ) | | value . as_object ( ) . has_string ( " legacy-alias-for " sv ) | | name = = " direction " | | name = = " unicode-bidi " )
2025-06-10 01:31:14 +12:00
return ;
MUST ( all_entry - > get_array ( " longhands " sv ) - > append ( JsonValue { name } ) ) ;
} ) ;
}
2025-07-13 20:34:55 +12:00
ErrorOr < void > generate_header_file ( JsonObject & properties , JsonObject & logical_property_groups , Core : : File & file )
2022-04-01 17:41:43 +01:00
{
StringBuilder builder ;
SourceGenerator generator { builder } ;
2025-05-19 12:51:01 +01:00
generator . set ( " property_id_underlying_type " , underlying_type_for_enum ( properties . size ( ) ) ) ;
2025-07-13 20:34:55 +12:00
generator . set ( " logical_property_group_underlying_type " , underlying_type_for_enum ( logical_property_groups . size ( ) ) ) ;
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2022-04-01 17:41:43 +01:00
# pragma once
# include <AK/NonnullRefPtr.h>
# include <AK/StringView.h>
# include <AK/Traits.h>
2025-06-26 16:48:20 +01:00
# include <AK/Variant.h>
2023-02-28 16:54:25 +00:00
# include <LibJS/Forward.h>
2025-07-03 14:31:26 +01:00
# include <LibWeb/CSS/Enums.h>
2025-07-10 16:09:34 +01:00
# include <LibWeb/CSS/ValueType.h>
2022-04-01 17:41:43 +01:00
# include <LibWeb/Forward.h>
namespace Web : : CSS {
2025-05-19 12:51:01 +01:00
enum class PropertyID : @ property_id_underlying_type @ {
2022-04-01 17:41:43 +01:00
Invalid ,
Custom ,
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2022-04-01 17:41:43 +01:00
2025-02-17 12:18:27 -05:00
Vector < String > inherited_shorthand_property_ids ;
Vector < String > inherited_longhand_property_ids ;
Vector < String > noninherited_shorthand_property_ids ;
Vector < String > noninherited_longhand_property_ids ;
2022-04-01 17:41:43 +01:00
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
VERIFY ( value . is_object ( ) ) ;
2024-09-27 12:54:08 +01:00
// Legacy aliases don't get a PropertyID
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
2024-09-03 17:48:43 +02:00
bool inherited = value . as_object ( ) . get_bool ( " inherited " sv ) . value_or ( false ) ;
if ( value . as_object ( ) . has ( " longhands " sv ) ) {
if ( inherited )
inherited_shorthand_property_ids . append ( name ) ;
else
noninherited_shorthand_property_ids . append ( name ) ;
} else {
if ( inherited )
inherited_longhand_property_ids . append ( name ) ;
else
noninherited_longhand_property_ids . append ( name ) ;
}
2022-04-01 17:41:43 +01:00
} ) ;
2024-09-03 17:48:43 +02:00
// Section order:
// 1. inherited shorthand properties
// 2. noninherited shorthand properties
// 3. inherited longhand properties
// 4. noninherited longhand properties
2019-11-18 11:12:58 +01:00
2024-09-03 17:48:43 +02:00
auto first_property_id = inherited_shorthand_property_ids . first ( ) ;
auto last_property_id = noninherited_longhand_property_ids . last ( ) ;
2022-04-01 17:41:43 +01:00
2024-09-03 17:48:43 +02:00
auto emit_properties = [ & ] ( auto & property_ids ) {
for ( auto & name : property_ids ) {
auto member_generator = generator . fork ( ) ;
member_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
member_generator . append ( R " ~~~(
@ name : titlecase @ ,
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2024-09-03 17:48:43 +02:00
}
} ;
2022-04-01 17:41:43 +01:00
2024-09-03 17:48:43 +02:00
emit_properties ( inherited_shorthand_property_ids ) ;
emit_properties ( noninherited_shorthand_property_ids ) ;
emit_properties ( inherited_longhand_property_ids ) ;
emit_properties ( noninherited_longhand_property_ids ) ;
2021-09-11 17:26:38 +01:00
2023-08-21 16:59:41 +02:00
generator . set ( " first_property_id " , title_casify ( first_property_id ) ) ;
generator . set ( " last_property_id " , title_casify ( last_property_id ) ) ;
2022-04-01 17:41:43 +01:00
2024-09-03 17:48:43 +02:00
generator . set ( " first_longhand_property_id " , title_casify ( inherited_longhand_property_ids . first ( ) ) ) ;
generator . set ( " last_longhand_property_id " , title_casify ( noninherited_longhand_property_ids . last ( ) ) ) ;
2022-04-01 17:41:43 +01:00
2024-09-03 17:48:43 +02:00
generator . set ( " first_inherited_shorthand_property_id " , title_casify ( inherited_shorthand_property_ids . first ( ) ) ) ;
generator . set ( " last_inherited_shorthand_property_id " , title_casify ( inherited_shorthand_property_ids . last ( ) ) ) ;
generator . set ( " first_inherited_longhand_property_id " , title_casify ( inherited_longhand_property_ids . first ( ) ) ) ;
generator . set ( " last_inherited_longhand_property_id " , title_casify ( inherited_longhand_property_ids . last ( ) ) ) ;
2022-04-01 17:41:43 +01:00
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2022-04-01 17:41:43 +01:00
} ;
2025-06-26 16:48:20 +01:00
using PropertyIDOrCustomPropertyName = Variant < PropertyID , FlyString > ;
2023-10-31 19:33:58 -07:00
enum class AnimationType {
Discrete ,
ByComputedValue ,
RepeatableList ,
Custom ,
None ,
} ;
AnimationType animation_type_from_longhand_property ( PropertyID ) ;
bool is_animatable_property ( PropertyID ) ;
2023-05-10 13:01:30 +01:00
Optional < PropertyID > property_id_from_camel_case_string ( StringView ) ;
Optional < PropertyID > property_id_from_string ( StringView ) ;
2024-03-15 20:32:45 +01:00
[[nodiscard]] FlyString const & string_from_property_id ( PropertyID ) ;
2024-06-02 10:08:41 -07:00
[[nodiscard]] FlyString const & camel_case_string_from_property_id ( PropertyID ) ;
2022-04-01 17:41:43 +01:00
bool is_inherited_property ( PropertyID ) ;
2025-08-08 10:11:51 +01:00
NonnullRefPtr < StyleValue const > property_initial_value ( PropertyID ) ;
2022-04-01 17:41:43 +01:00
2023-05-10 17:28:15 +01:00
bool property_accepts_type ( PropertyID , ValueType ) ;
2024-08-14 14:06:03 +01:00
bool property_accepts_keyword ( PropertyID , Keyword ) ;
2025-07-21 12:22:18 +01:00
Optional < Keyword > resolve_legacy_value_alias ( PropertyID , Keyword ) ;
2023-06-22 16:22:16 +01:00
Optional < ValueType > property_resolves_percentages_relative_to ( PropertyID ) ;
2025-02-24 14:59:03 +00:00
Vector < StringView > property_custom_ident_blacklist ( PropertyID ) ;
2023-05-31 15:36:21 +01:00
// These perform range-checking, but are also safe to call with properties that don't accept that type. (They'll just return false.)
bool property_accepts_angle ( PropertyID , Angle const & ) ;
2023-09-28 15:18:14 +01:00
bool property_accepts_flex ( PropertyID , Flex const & ) ;
2023-05-31 15:36:21 +01:00
bool property_accepts_frequency ( PropertyID , Frequency const & ) ;
bool property_accepts_integer ( PropertyID , i64 const & ) ;
bool property_accepts_length ( PropertyID , Length const & ) ;
bool property_accepts_number ( PropertyID , double const & ) ;
bool property_accepts_percentage ( PropertyID , Percentage const & ) ;
bool property_accepts_resolution ( PropertyID , Resolution const & ) ;
bool property_accepts_time ( PropertyID , Time const & ) ;
2023-09-08 17:56:41 +01:00
bool property_is_shorthand ( PropertyID ) ;
2025-07-14 15:58:29 +12:00
Vector < PropertyID > const & longhands_for_shorthand ( PropertyID ) ;
Vector < PropertyID > const & expanded_longhands_for_shorthand ( PropertyID ) ;
2025-05-27 20:28:34 +12:00
bool property_maps_to_shorthand ( PropertyID ) ;
2025-07-14 15:58:29 +12:00
Vector < PropertyID > const & shorthands_for_longhand ( PropertyID ) ;
2025-07-10 20:21:45 +12:00
bool property_is_positional_value_list_shorthand ( PropertyID ) ;
2023-05-26 23:16:43 +03:30
2022-04-01 17:41:43 +01:00
size_t property_maximum_value_count ( PropertyID ) ;
bool property_affects_layout ( PropertyID ) ;
bool property_affects_stacking_context ( PropertyID ) ;
constexpr PropertyID first_property_id = PropertyID : : @ first_property_id @ ;
constexpr PropertyID last_property_id = PropertyID : : @ last_property_id @ ;
2024-09-03 17:48:43 +02:00
constexpr PropertyID first_inherited_shorthand_property_id = PropertyID : : @ first_inherited_shorthand_property_id @ ;
constexpr PropertyID last_inherited_shorthand_property_id = PropertyID : : @ last_inherited_shorthand_property_id @ ;
constexpr PropertyID first_inherited_longhand_property_id = PropertyID : : @ first_inherited_longhand_property_id @ ;
constexpr PropertyID last_inherited_longhand_property_id = PropertyID : : @ last_inherited_longhand_property_id @ ;
2022-04-01 17:41:43 +01:00
constexpr PropertyID first_longhand_property_id = PropertyID : : @ first_longhand_property_id @ ;
constexpr PropertyID last_longhand_property_id = PropertyID : : @ last_longhand_property_id @ ;
enum class Quirk {
// https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk
HashlessHexColor ,
// https://quirks.spec.whatwg.org/#the-unitless-length-quirk
UnitlessLength ,
} ;
bool property_has_quirk ( PropertyID , Quirk ) ;
2025-07-03 14:31:26 +01:00
struct LogicalAliasMappingContext {
WritingMode writing_mode ;
Direction direction ;
// TODO: text-orientation
} ;
2025-06-18 17:45:26 +12:00
bool property_is_logical_alias ( PropertyID ) ;
2025-07-03 14:31:26 +01:00
PropertyID map_logical_alias_to_physical_property ( PropertyID logical_property_id , LogicalAliasMappingContext const & ) ;
2025-06-18 17:45:26 +12:00
2025-07-13 20:34:55 +12:00
enum class LogicalPropertyGroup : @ logical_property_group_underlying_type @ {
) ~ ~ ~ " );
logical_property_groups . for_each_member ( [ & ] ( auto & name , auto & ) {
generator . set ( " logical_property_group_name:titlecase " , title_casify ( name ) ) ;
generator . append ( R " ~~~(
@ logical_property_group_name : titlecase @ ,
) ~ ~ ~ " );
} ) ;
generator . append ( R " ~~~(
} ;
Optional < LogicalPropertyGroup > logical_property_group_for_property ( PropertyID ) ;
2022-04-01 17:41:43 +01:00
} // namespace Web::CSS
namespace AK {
template < >
2023-11-08 20:29:12 +01:00
struct Traits < Web : : CSS : : PropertyID > : public DefaultTraits < Web : : CSS : : PropertyID > {
2022-04-01 17:41:43 +01:00
static unsigned hash ( Web : : CSS : : PropertyID property_id ) { return int_hash ( ( unsigned ) property_id ) ; }
} ;
2025-07-03 20:49:26 +01:00
template < >
struct Formatter < Web : : CSS : : PropertyID > : Formatter < StringView > {
ErrorOr < void > format ( FormatBuilder & builder , Web : : CSS : : PropertyID const & property_id )
{
return Formatter < StringView > : : format ( builder , Web : : CSS : : string_from_property_id ( property_id ) ) ;
}
} ;
2022-04-01 17:41:43 +01:00
} // namespace AK
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2022-04-01 17:41:43 +01:00
2023-03-01 16:28:32 +01:00
TRY ( file . write_until_depleted ( generator . as_string_view ( ) . bytes ( ) ) ) ;
2022-04-01 17:41:43 +01:00
return { } ;
}
2023-08-22 09:09:07 +02:00
void generate_bounds_checking_function ( JsonObject & properties , SourceGenerator & parent_generator , StringView css_type_name , StringView type_name , Optional < StringView > default_unit_name , Optional < StringView > value_getter )
2023-05-31 15:36:21 +01:00
{
2023-08-21 16:42:48 +02:00
auto generator = parent_generator . fork ( ) ;
2023-08-22 09:09:07 +02:00
generator . set ( " css_type_name " , css_type_name ) ;
generator . set ( " type_name " , type_name ) ;
2023-05-31 15:36:21 +01:00
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2023-05-31 15:36:21 +01:00
bool property_accepts_ @ css_type_name @ ( PropertyID property_id , [[maybe_unused]] @ type_name @ const & value )
{
switch ( property_id ) {
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2023-05-31 15:36:21 +01:00
2023-08-22 09:09:07 +02:00
properties . for_each_member ( [ & ] ( auto & name , JsonValue const & value ) - > void {
2023-05-31 15:36:21 +01:00
VERIFY ( value . is_object ( ) ) ;
2024-09-27 12:54:08 +01:00
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
2023-05-31 15:36:21 +01:00
if ( auto maybe_valid_types = value . as_object ( ) . get_array ( " valid-types " sv ) ; maybe_valid_types . has_value ( ) & & ! maybe_valid_types - > is_empty ( ) ) {
for ( auto valid_type : maybe_valid_types - > values ( ) ) {
2025-02-17 13:21:07 -05:00
auto type_and_range = MUST ( valid_type . as_string ( ) . split ( ' ' ) ) ;
2023-05-31 15:36:21 +01:00
if ( type_and_range . first ( ) ! = css_type_name )
continue ;
2023-08-21 16:42:48 +02:00
auto property_generator = generator . fork ( ) ;
2023-08-21 16:59:41 +02:00
property_generator . set ( " property_name:titlecase " , title_casify ( name ) ) ;
2023-05-31 15:36:21 +01:00
2023-08-21 16:39:43 +02:00
property_generator . append ( R " ~~~(
2023-05-31 15:36:21 +01:00
case PropertyID : : @ property_name : titlecase @ :
2023-08-21 16:39:43 +02:00
return ) ~ ~ ~ " );
2023-05-31 15:36:21 +01:00
if ( type_and_range . size ( ) > 1 ) {
auto range = type_and_range [ 1 ] ;
VERIFY ( range . starts_with ( ' [ ' ) & & range . ends_with ( ' ] ' ) & & range . contains ( ' , ' ) ) ;
2025-02-17 13:21:07 -05:00
auto comma_index = range . find_byte_offset ( ' , ' ) . value ( ) ;
StringView min_value_string = range . bytes_as_string_view ( ) . substring_view ( 1 , comma_index - 1 ) ;
StringView max_value_string = range . bytes_as_string_view ( ) . substring_view ( comma_index + 1 , range . byte_count ( ) - comma_index - 2 ) ;
2023-05-31 15:36:21 +01:00
// If the min/max value is infinite, we can just skip that side of the check.
if ( min_value_string = = " -∞ " )
min_value_string = { } ;
if ( max_value_string = = " ∞ " )
max_value_string = { } ;
2023-06-02 17:14:27 +01:00
if ( min_value_string . is_empty ( ) & & max_value_string . is_empty ( ) ) {
2023-08-21 16:06:29 +02:00
property_generator . appendln ( " true; " ) ;
2023-06-02 17:14:27 +01:00
break ;
}
2023-08-22 09:09:07 +02:00
auto output_check = [ & ] ( auto & value_string , StringView comparator ) {
2023-05-31 15:36:21 +01:00
if ( value_getter . has_value ( ) ) {
2023-08-22 09:09:07 +02:00
property_generator . set ( " value_number " , value_string ) ;
property_generator . set ( " value_getter " , value_getter . value ( ) ) ;
property_generator . set ( " comparator " , comparator ) ;
2023-08-21 16:39:43 +02:00
property_generator . append ( " @value_getter@ @comparator@ @value_number@ " ) ;
2023-08-22 09:09:07 +02:00
return ;
2023-05-31 15:36:21 +01:00
}
GenericLexer lexer { value_string } ;
auto value_number = lexer . consume_until ( is_ascii_alpha ) ;
auto value_unit = lexer . consume_while ( is_ascii_alpha ) ;
if ( value_unit . is_empty ( ) )
value_unit = default_unit_name . value ( ) ;
VERIFY ( lexer . is_eof ( ) ) ;
2023-08-22 09:09:07 +02:00
property_generator . set ( " value_number " , value_number ) ;
2023-08-21 16:59:41 +02:00
property_generator . set ( " value_unit " , title_casify ( value_unit ) ) ;
2023-08-22 09:09:07 +02:00
property_generator . set ( " comparator " , comparator ) ;
2023-08-21 16:39:43 +02:00
property_generator . append ( " value @comparator@ @type_name@(@value_number@, @type_name@::Type::@value_unit@) " ) ;
2023-05-31 15:36:21 +01:00
} ;
if ( ! min_value_string . is_empty ( ) )
2023-08-22 09:09:07 +02:00
output_check ( min_value_string , " >= " sv ) ;
2023-05-31 15:36:21 +01:00
if ( ! min_value_string . is_empty ( ) & & ! max_value_string . is_empty ( ) )
2023-08-21 16:39:43 +02:00
property_generator . append ( " && " ) ;
2023-05-31 15:36:21 +01:00
if ( ! max_value_string . is_empty ( ) )
2023-08-22 09:09:07 +02:00
output_check ( max_value_string , " <= " sv ) ;
2023-08-21 16:06:29 +02:00
property_generator . appendln ( " ; " ) ;
2023-05-31 15:36:21 +01:00
} else {
2023-08-21 16:06:29 +02:00
property_generator . appendln ( " true; " ) ;
2023-05-31 15:36:21 +01:00
}
break ;
}
}
2023-08-22 09:09:07 +02:00
} ) ;
2023-05-31 15:36:21 +01:00
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2023-05-31 15:36:21 +01:00
default :
return false ;
}
}
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2023-05-31 15:36:21 +01:00
}
2025-07-10 15:05:02 +01:00
ErrorOr < void > generate_implementation_file ( JsonObject & properties , JsonObject & logical_property_groups , ReadonlySpan < StringView > enum_names , Core : : File & file )
2022-04-01 17:41:43 +01:00
{
2020-10-23 18:37:35 +02:00
StringBuilder builder ;
SourceGenerator generator { builder } ;
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2020-10-23 18:37:35 +02:00
# include <AK/Assertions.h>
2022-04-14 11:23:23 +01:00
# include <LibWeb/CSS/Enums.h>
2021-08-16 17:42:09 +01:00
# include <LibWeb/CSS/Parser/Parser.h>
2020-10-23 18:37:35 +02:00
# include <LibWeb/CSS/PropertyID.h>
2024-11-20 17:28:23 +01:00
# include <LibWeb/CSS/PropertyName.h>
2023-03-24 17:28:43 +00:00
# include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
2025-08-08 10:11:51 +01:00
# include <LibWeb/CSS/StyleValues/StyleValue.h>
2023-05-26 23:16:43 +03:30
# include <LibWeb/CSS/StyleValues/TimeStyleValue.h>
2023-02-17 16:43:28 +00:00
# include <LibWeb/Infra/Strings.h>
2020-10-23 18:37:35 +02:00
namespace Web : : CSS {
2019-11-18 11:12:58 +01:00
2023-05-10 13:01:30 +01:00
Optional < PropertyID > property_id_from_camel_case_string ( StringView string )
2021-09-29 20:29:44 +02:00
{
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2021-09-29 20:29:44 +02:00
2023-08-22 09:09:07 +02:00
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
2021-09-29 20:29:44 +02:00
VERIFY ( value . is_object ( ) ) ;
2023-08-21 16:42:48 +02:00
auto member_generator = generator . fork ( ) ;
2023-08-22 09:09:07 +02:00
member_generator . set ( " name " , name ) ;
2023-08-21 16:59:41 +02:00
member_generator . set ( " name:camelcase " , camel_casify ( name ) ) ;
2025-02-17 15:04:56 -05:00
if ( auto legacy_alias_for = value . as_object ( ) . get_string ( " legacy-alias-for " sv ) ; legacy_alias_for . has_value ( ) ) {
2024-09-27 12:54:08 +01:00
member_generator . set ( " name:titlecase " , title_casify ( legacy_alias_for . value ( ) ) ) ;
} else {
member_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
}
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
2023-03-10 08:48:54 +01:00
if ( string . equals_ignoring_ascii_case ( " @name:camelcase@ " sv ) )
2021-09-29 20:29:44 +02:00
return PropertyID : : @ name : titlecase @ ;
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2023-08-22 09:09:07 +02:00
} ) ;
2021-09-29 20:29:44 +02:00
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2023-05-10 13:01:30 +01:00
return { } ;
2021-09-29 20:29:44 +02:00
}
2023-05-10 13:01:30 +01:00
Optional < PropertyID > property_id_from_string ( StringView string )
2020-10-23 18:37:35 +02:00
{
2024-11-20 17:28:23 +01:00
if ( is_a_custom_property_name_string ( string ) )
return PropertyID : : Custom ;
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2019-11-18 11:12:58 +01:00
2023-08-22 09:09:07 +02:00
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
2021-02-23 20:42:32 +01:00
VERIFY ( value . is_object ( ) ) ;
2020-10-23 18:37:35 +02:00
2023-08-21 16:42:48 +02:00
auto member_generator = generator . fork ( ) ;
2023-08-22 09:09:07 +02:00
member_generator . set ( " name " , name ) ;
2025-02-17 15:04:56 -05:00
if ( auto legacy_alias_for = value . as_object ( ) . get_string ( " legacy-alias-for " sv ) ; legacy_alias_for . has_value ( ) ) {
2024-09-27 12:54:08 +01:00
member_generator . set ( " name:titlecase " , title_casify ( legacy_alias_for . value ( ) ) ) ;
} else {
member_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
}
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
2025-05-18 15:04:56 +12:00
if ( string . equals_ignoring_ascii_case ( " @name@ " sv ) )
2020-10-23 18:37:35 +02:00
return PropertyID : : @ name : titlecase @ ;
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2023-08-22 09:09:07 +02:00
} ) ;
2019-11-18 11:12:58 +01:00
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2023-05-10 13:01:30 +01:00
return { } ;
2020-10-23 18:37:35 +02:00
}
2019-11-18 11:12:58 +01:00
2024-03-15 20:32:45 +01:00
FlyString const & string_from_property_id ( PropertyID property_id ) {
2020-10-23 18:37:35 +02:00
switch ( property_id ) {
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2019-11-18 11:12:58 +01:00
2023-08-22 09:09:07 +02:00
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
2021-02-23 20:42:32 +01:00
VERIFY ( value . is_object ( ) ) ;
2024-09-27 12:54:08 +01:00
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
2020-10-23 18:37:35 +02:00
2023-08-21 16:42:48 +02:00
auto member_generator = generator . fork ( ) ;
2023-08-22 09:09:07 +02:00
member_generator . set ( " name " , name ) ;
2023-08-21 16:59:41 +02:00
member_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
2024-03-15 20:32:45 +01:00
case PropertyID : : @ name : titlecase @ : {
static FlyString name = " @name@ " _fly_string ;
return name ;
}
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2023-08-22 09:09:07 +02:00
} ) ;
2019-11-18 11:12:58 +01:00
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2024-03-15 20:32:45 +01:00
default : {
static FlyString invalid_property_id_string = " (invalid CSS::PropertyID) " _fly_string ;
return invalid_property_id_string ;
}
2020-10-23 18:37:35 +02:00
}
}
2024-06-02 10:08:41 -07:00
FlyString const & camel_case_string_from_property_id ( PropertyID property_id ) {
switch ( property_id ) {
) ~ ~ ~ " );
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
VERIFY ( value . is_object ( ) ) ;
2024-09-27 12:54:08 +01:00
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
2024-06-02 10:08:41 -07:00
auto member_generator = generator . fork ( ) ;
member_generator . set ( " name " , name ) ;
member_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
member_generator . set ( " name:camelcase " , camel_casify ( name ) ) ;
member_generator . append ( R " ~~~(
case PropertyID : : @ name : titlecase @ : {
static FlyString name = " @name:camelcase@ " _fly_string ;
return name ;
}
) ~ ~ ~ " );
} ) ;
generator . append ( R " ~~~(
default : {
static FlyString invalid_property_id_string = " (invalid CSS::PropertyID) " _fly_string ;
return invalid_property_id_string ;
}
}
}
2023-10-31 19:33:58 -07:00
AnimationType animation_type_from_longhand_property ( PropertyID property_id )
{
switch ( property_id ) {
) ~ ~ ~ " );
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
VERIFY ( value . is_object ( ) ) ;
2024-09-27 12:54:08 +01:00
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
2023-10-31 19:33:58 -07:00
auto member_generator = generator . fork ( ) ;
member_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
// Shorthand properties should have already been expanded before calling into this function
if ( value . as_object ( ) . has ( " longhands " sv ) ) {
if ( value . as_object ( ) . has ( " animation-type " sv ) ) {
dbgln ( " Property '{}' with longhands cannot specify 'animation-type' " , name ) ;
VERIFY_NOT_REACHED ( ) ;
}
member_generator . append ( R " ~~~(
case PropertyID : : @ name : titlecase @ :
VERIFY_NOT_REACHED ( ) ;
) ~ ~ ~ " );
return ;
}
if ( ! value . as_object ( ) . has ( " animation-type " sv ) ) {
dbgln ( " No animation-type specified for property '{}' " , name ) ;
VERIFY_NOT_REACHED ( ) ;
}
2025-02-17 15:04:56 -05:00
auto animation_type = value . as_object ( ) . get_string ( " animation-type " sv ) . value ( ) ;
2023-10-31 19:33:58 -07:00
member_generator . set ( " value " , title_casify ( animation_type ) ) ;
member_generator . append ( R " ~~~(
case PropertyID : : @ name : titlecase @ :
return AnimationType : : @ value @ ;
) ~ ~ ~ " );
} ) ;
generator . append ( R " ~~~(
default :
return AnimationType : : None ;
}
}
bool is_animatable_property ( PropertyID property_id )
{
switch ( property_id ) {
) ~ ~ ~ " );
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
VERIFY ( value . is_object ( ) ) ;
2025-02-17 12:18:27 -05:00
VERIFY ( ! name . is_empty ( ) & & ! is_ascii_digit ( name . bytes_as_string_view ( ) [ 0 ] ) ) ; // Ensure `PropertyKey`s are not Numbers.
2024-09-27 12:54:08 +01:00
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
2023-10-31 19:33:58 -07:00
if ( is_animatable_property ( properties , name ) ) {
auto member_generator = generator . fork ( ) ;
member_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
member_generator . append ( R " ~~~(
case PropertyID : : @ name : titlecase @ :
) ~ ~ ~ " );
}
} ) ;
generator . append ( R " ~~~(
return true ;
default :
return false ;
}
}
2021-08-16 16:07:13 +01:00
bool is_inherited_property ( PropertyID property_id )
{
2024-12-20 02:04:02 +04:00
if ( property_id > = first_inherited_shorthand_property_id & & property_id < = last_inherited_shorthand_property_id )
2021-08-16 16:07:13 +01:00
return true ;
2024-09-03 17:48:43 +02:00
if ( property_id > = first_inherited_longhand_property_id & & property_id < = last_inherited_longhand_property_id )
return true ;
return false ;
2021-08-16 16:07:13 +01:00
}
2022-03-16 17:46:24 +01:00
bool property_affects_layout ( PropertyID property_id )
{
switch ( property_id ) {
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2022-03-16 17:46:24 +01:00
2023-08-22 09:09:07 +02:00
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
2022-03-16 17:46:24 +01:00
VERIFY ( value . is_object ( ) ) ;
2024-09-27 12:54:08 +01:00
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
2022-03-16 17:46:24 +01:00
bool affects_layout = true ;
2022-07-11 17:32:29 +00:00
if ( value . as_object ( ) . has ( " affects-layout " sv ) )
2022-12-21 14:37:27 +00:00
affects_layout = value . as_object ( ) . get_bool ( " affects-layout " sv ) . value_or ( false ) ;
2022-03-16 17:46:24 +01:00
if ( affects_layout ) {
2023-08-21 16:42:48 +02:00
auto member_generator = generator . fork ( ) ;
2023-08-21 16:59:41 +02:00
member_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
2022-03-16 17:46:24 +01:00
case PropertyID : : @ name : titlecase @ :
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2022-03-16 17:46:24 +01:00
}
2023-08-22 09:09:07 +02:00
} ) ;
2022-03-16 17:46:24 +01:00
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2022-03-16 17:46:24 +01:00
return true ;
default :
return false ;
}
}
2022-03-21 10:58:51 +01:00
bool property_affects_stacking_context ( PropertyID property_id )
{
switch ( property_id ) {
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2022-03-21 10:58:51 +01:00
2023-08-22 09:09:07 +02:00
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
2022-03-21 10:58:51 +01:00
VERIFY ( value . is_object ( ) ) ;
2024-09-27 12:54:08 +01:00
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
2022-03-21 10:58:51 +01:00
2022-03-25 00:59:42 +01:00
bool affects_stacking_context = false ;
2022-07-11 17:32:29 +00:00
if ( value . as_object ( ) . has ( " affects-stacking-context " sv ) )
2022-12-21 14:37:27 +00:00
affects_stacking_context = value . as_object ( ) . get_bool ( " affects-stacking-context " sv ) . value_or ( false ) ;
2022-03-21 10:58:51 +01:00
2022-03-25 00:59:42 +01:00
if ( affects_stacking_context ) {
2023-08-21 16:42:48 +02:00
auto member_generator = generator . fork ( ) ;
2023-08-21 16:59:41 +02:00
member_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
2023-08-21 16:39:43 +02:00
member_generator . append ( R " ~~~(
2022-03-21 10:58:51 +01:00
case PropertyID : : @ name : titlecase @ :
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2022-03-21 10:58:51 +01:00
}
2023-08-22 09:09:07 +02:00
} ) ;
2022-03-21 10:58:51 +01:00
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2022-03-21 10:58:51 +01:00
return true ;
default :
return false ;
}
}
2025-08-08 10:11:51 +01:00
NonnullRefPtr < StyleValue const > property_initial_value ( PropertyID property_id )
2021-08-16 17:42:09 +01:00
{
2025-08-08 10:11:51 +01:00
static Array < RefPtr < StyleValue const > , to_underlying ( last_property_id ) + 1 > initial_values ;
2023-04-02 22:46:22 +01:00
if ( auto initial_value = initial_values [ to_underlying ( property_id ) ] )
return initial_value . release_nonnull ( ) ;
// Lazily parse initial values as needed.
// This ensures the shorthands will always be able to get the initial values of their longhands.
// This also now allows a longhand have its own longhand (like background-position-x).
2021-08-16 17:42:09 +01:00
2025-02-05 12:08:27 +00:00
Parser : : ParsingParams parsing_params ;
2023-04-02 22:46:22 +01:00
switch ( property_id ) {
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
LibWeb: Generate shorthand initial values after their longhands
When parsing shorthand values, we'd like to use
`property_initial_value()` to get their longhand property values,
instead of hard-coding them as we currently do. That involves
recursively calling that function while the `initial_values` map is
being initialized, which causes problems because the shorthands appear
alphabetically before their longhand components, so the longhands aren't
initialized yet!
The solution here is to perform 2 passes when generating the code,
outputting properties without "longhands" first, and the rest after.
This could potentially cause issues when shorthands have multiple
levels, in particular `border` -> `border-color` -> `border-left-color`.
But, we do not currently define a default value for `border`, and
`border-color` takes only a single value, so it's fine for now. :^)
2021-09-17 16:53:43 +01:00
2023-08-22 09:09:07 +02:00
auto output_initial_value_code = [ & ] ( auto & name , auto & object ) {
2022-07-11 17:32:29 +00:00
if ( ! object . has ( " initial " sv ) ) {
2021-11-10 13:48:41 +00:00
dbgln ( " No initial value specified for property '{}' " , name ) ;
VERIFY_NOT_REACHED ( ) ;
}
2025-02-17 15:04:56 -05:00
auto initial_value = object . get_string ( " initial " sv ) ;
2022-12-21 14:37:27 +00:00
VERIFY ( initial_value . has_value ( ) ) ;
auto & initial_value_string = initial_value . value ( ) ;
2021-08-16 17:42:09 +01:00
2023-08-21 16:42:48 +02:00
auto member_generator = generator . fork ( ) ;
2023-08-21 16:59:41 +02:00
member_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
2023-08-22 09:09:07 +02:00
member_generator . set ( " initial_value_string " , initial_value_string ) ;
2023-08-21 16:39:43 +02:00
member_generator . append (
2023-04-02 22:46:22 +01:00
R " ~~~( case PropertyID::@name:titlecase@:
2021-11-10 13:48:41 +00:00
{
2025-02-05 12:08:27 +00:00
auto parsed_value = parse_css_value ( parsing_params , " @initial_value_string@ " sv , PropertyID : : @ name : titlecase @ ) ;
2021-11-10 13:48:41 +00:00
VERIFY ( ! parsed_value . is_null ( ) ) ;
2023-04-02 22:46:22 +01:00
auto initial_value = parsed_value . release_nonnull ( ) ;
initial_values [ to_underlying ( PropertyID : : @ name : titlecase @ ) ] = initial_value ;
return initial_value ;
2021-08-16 17:42:09 +01:00
}
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
LibWeb: Generate shorthand initial values after their longhands
When parsing shorthand values, we'd like to use
`property_initial_value()` to get their longhand property values,
instead of hard-coding them as we currently do. That involves
recursively calling that function while the `initial_values` map is
being initialized, which causes problems because the shorthands appear
alphabetically before their longhand components, so the longhands aren't
initialized yet!
The solution here is to perform 2 passes when generating the code,
outputting properties without "longhands" first, and the rest after.
This could potentially cause issues when shorthands have multiple
levels, in particular `border` -> `border-color` -> `border-left-color`.
But, we do not currently define a default value for `border`, and
`border-color` takes only a single value, so it's fine for now. :^)
2021-09-17 16:53:43 +01:00
} ;
2023-08-22 09:09:07 +02:00
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
LibWeb: Generate shorthand initial values after their longhands
When parsing shorthand values, we'd like to use
`property_initial_value()` to get their longhand property values,
instead of hard-coding them as we currently do. That involves
recursively calling that function while the `initial_values` map is
being initialized, which causes problems because the shorthands appear
alphabetically before their longhand components, so the longhands aren't
initialized yet!
The solution here is to perform 2 passes when generating the code,
outputting properties without "longhands" first, and the rest after.
This could potentially cause issues when shorthands have multiple
levels, in particular `border` -> `border-color` -> `border-left-color`.
But, we do not currently define a default value for `border`, and
`border-color` takes only a single value, so it's fine for now. :^)
2021-09-17 16:53:43 +01:00
VERIFY ( value . is_object ( ) ) ;
2024-09-27 12:54:08 +01:00
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
2023-08-22 09:09:07 +02:00
output_initial_value_code ( name , value . as_object ( ) ) ;
} ) ;
LibWeb: Generate shorthand initial values after their longhands
When parsing shorthand values, we'd like to use
`property_initial_value()` to get their longhand property values,
instead of hard-coding them as we currently do. That involves
recursively calling that function while the `initial_values` map is
being initialized, which causes problems because the shorthands appear
alphabetically before their longhand components, so the longhands aren't
initialized yet!
The solution here is to perform 2 passes when generating the code,
outputting properties without "longhands" first, and the rest after.
This could potentially cause issues when shorthands have multiple
levels, in particular `border` -> `border-color` -> `border-left-color`.
But, we do not currently define a default value for `border`, and
`border-color` takes only a single value, so it's fine for now. :^)
2021-09-17 16:53:43 +01:00
2023-08-21 16:39:43 +02:00
generator . append (
2023-04-02 22:46:22 +01:00
R " ~~~( default: VERIFY_NOT_REACHED();
2021-08-16 17:42:09 +01:00
}
2023-04-02 22:46:22 +01:00
VERIFY_NOT_REACHED ( ) ;
2021-08-16 17:42:09 +01:00
}
2021-09-11 17:26:38 +01:00
bool property_has_quirk ( PropertyID property_id , Quirk quirk )
{
switch ( property_id ) {
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2021-09-11 17:26:38 +01:00
2023-08-22 09:09:07 +02:00
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
2021-09-11 17:26:38 +01:00
VERIFY ( value . is_object ( ) ) ;
2024-09-27 12:54:08 +01:00
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
2022-07-11 17:32:29 +00:00
if ( value . as_object ( ) . has ( " quirks " sv ) ) {
2022-12-21 14:37:27 +00:00
auto quirks_value = value . as_object ( ) . get_array ( " quirks " sv ) ;
VERIFY ( quirks_value . has_value ( ) ) ;
auto & quirks = quirks_value . value ( ) ;
2021-09-11 17:26:38 +01:00
if ( ! quirks . is_empty ( ) ) {
2023-08-21 16:42:48 +02:00
auto property_generator = generator . fork ( ) ;
2023-08-21 16:59:41 +02:00
property_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
2023-08-21 16:39:43 +02:00
property_generator . append ( R " ~~~(
2021-09-11 17:26:38 +01:00
case PropertyID : : @ name : titlecase @ : {
switch ( quirk ) {
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2021-09-11 17:26:38 +01:00
for ( auto & quirk : quirks . values ( ) ) {
VERIFY ( quirk . is_string ( ) ) ;
2023-08-21 16:42:48 +02:00
auto quirk_generator = property_generator . fork ( ) ;
2023-08-21 16:59:41 +02:00
quirk_generator . set ( " quirk:titlecase " , title_casify ( quirk . as_string ( ) ) ) ;
2023-08-21 16:39:43 +02:00
quirk_generator . append ( R " ~~~(
2021-09-11 17:26:38 +01:00
case Quirk : : @ quirk : titlecase @ :
return true ;
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2021-09-11 17:26:38 +01:00
}
2023-08-21 16:39:43 +02:00
property_generator . append ( R " ~~~(
2021-09-11 17:26:38 +01:00
default :
return false ;
}
}
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2021-09-11 17:26:38 +01:00
}
}
2023-08-22 09:09:07 +02:00
} ) ;
2021-09-11 17:26:38 +01:00
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2021-09-11 17:26:38 +01:00
default :
return false ;
}
}
2023-05-10 17:28:15 +01:00
bool property_accepts_type ( PropertyID property_id , ValueType value_type )
{
switch ( property_id ) {
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2023-08-22 09:09:07 +02:00
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
2023-05-10 17:28:15 +01:00
VERIFY ( value . is_object ( ) ) ;
auto & object = value . as_object ( ) ;
2024-09-27 12:54:08 +01:00
if ( is_legacy_alias ( object ) )
return ;
2023-05-10 17:28:15 +01:00
if ( auto maybe_valid_types = object . get_array ( " valid-types " sv ) ; maybe_valid_types . has_value ( ) & & ! maybe_valid_types - > is_empty ( ) ) {
auto & valid_types = maybe_valid_types . value ( ) ;
2023-08-21 16:42:48 +02:00
auto property_generator = generator . fork ( ) ;
2023-08-21 16:59:41 +02:00
property_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
2023-08-21 16:39:43 +02:00
property_generator . append ( R " ~~~(
2023-05-10 17:28:15 +01:00
case PropertyID : : @ name : titlecase @ : {
switch ( value_type ) {
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2023-05-10 17:28:15 +01:00
bool did_output_accepted_type = false ;
for ( auto & type : valid_types . values ( ) ) {
VERIFY ( type . is_string ( ) ) ;
2025-02-17 13:21:07 -05:00
auto type_name = MUST ( type . as_string ( ) . split ( ' ' ) ) . first ( ) ;
2025-07-10 15:05:02 +01:00
if ( enum_names . contains_slow ( type_name ) )
2023-05-10 17:28:15 +01:00
continue ;
2025-08-06 15:21:41 +12:00
property_generator . set ( " type_name " , title_casify ( type_name ) ) ;
property_generator . appendln ( " case ValueType::@type_name@: " ) ;
2023-05-10 17:28:15 +01:00
did_output_accepted_type = true ;
}
if ( did_output_accepted_type )
2023-08-21 16:06:29 +02:00
property_generator . appendln ( " return true; " ) ;
2023-05-10 17:28:15 +01:00
2023-08-21 16:39:43 +02:00
property_generator . append ( R " ~~~(
2023-05-10 17:28:15 +01:00
default :
return false ;
}
}
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2023-05-10 17:28:15 +01:00
}
2023-08-22 09:09:07 +02:00
} ) ;
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2023-05-10 17:28:15 +01:00
default :
return false ;
}
}
2024-08-14 14:06:03 +01:00
bool property_accepts_keyword ( PropertyID property_id , Keyword keyword )
2023-05-10 17:28:15 +01:00
{
switch ( property_id ) {
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2025-07-21 12:22:18 +01:00
properties . for_each_member ( [ & ] ( auto & name , JsonValue const & value ) {
2023-05-10 17:28:15 +01:00
VERIFY ( value . is_object ( ) ) ;
auto & object = value . as_object ( ) ;
2024-09-27 12:54:08 +01:00
if ( is_legacy_alias ( object ) )
return ;
2023-05-10 17:28:15 +01:00
2023-08-21 16:42:48 +02:00
auto property_generator = generator . fork ( ) ;
2023-08-21 16:59:41 +02:00
property_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
2023-08-21 16:06:29 +02:00
property_generator . appendln ( " case PropertyID::@name:titlecase@: { " ) ;
2023-05-10 17:28:15 +01:00
if ( auto maybe_valid_identifiers = object . get_array ( " valid-identifiers " sv ) ; maybe_valid_identifiers . has_value ( ) & & ! maybe_valid_identifiers - > is_empty ( ) ) {
2024-08-14 14:06:03 +01:00
property_generator . appendln ( " switch (keyword) { " ) ;
2023-05-10 17:28:15 +01:00
auto & valid_identifiers = maybe_valid_identifiers . value ( ) ;
2025-07-21 12:22:18 +01:00
for ( auto & keyword_value : valid_identifiers . values ( ) ) {
2024-08-14 14:06:03 +01:00
auto keyword_generator = generator . fork ( ) ;
2025-07-21 12:22:18 +01:00
auto const & keyword_string = keyword_value . as_string ( ) ;
if ( keyword_string . contains ( ' > ' ) ) {
auto parts = MUST ( keyword_string . split_limit ( ' > ' , 2 ) ) ;
keyword_generator . set ( " keyword:titlecase " , title_casify ( parts [ 0 ] ) ) ;
} else {
keyword_generator . set ( " keyword:titlecase " , title_casify ( keyword_string ) ) ;
}
2024-08-14 14:06:03 +01:00
keyword_generator . appendln ( " case Keyword::@keyword:titlecase@: " ) ;
2023-05-10 17:28:15 +01:00
}
2023-08-21 16:39:43 +02:00
property_generator . append ( R " ~~~(
2023-05-10 17:28:15 +01:00
return true ;
default :
break ;
}
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2023-05-10 17:28:15 +01:00
}
if ( auto maybe_valid_types = object . get_array ( " valid-types " sv ) ; maybe_valid_types . has_value ( ) & & ! maybe_valid_types - > is_empty ( ) ) {
auto & valid_types = maybe_valid_types . value ( ) ;
for ( auto & valid_type : valid_types . values ( ) ) {
2025-02-17 13:21:07 -05:00
auto type_name = MUST ( valid_type . as_string ( ) . split ( ' ' ) ) . first ( ) ;
2025-07-10 15:05:02 +01:00
if ( ! enum_names . contains_slow ( type_name ) )
2023-05-10 17:28:15 +01:00
continue ;
2023-08-21 16:42:48 +02:00
auto type_generator = generator . fork ( ) ;
2023-08-21 16:59:41 +02:00
type_generator . set ( " type_name:snakecase " , snake_casify ( type_name ) ) ;
2023-08-21 16:39:43 +02:00
type_generator . append ( R " ~~~(
2024-08-14 14:06:03 +01:00
if ( keyword_to_ @ type_name : snakecase @ ( keyword ) . has_value ( ) )
2023-05-10 17:28:15 +01:00
return true ;
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2023-05-10 17:28:15 +01:00
}
}
2023-08-21 16:39:43 +02:00
property_generator . append ( R " ~~~(
2023-05-10 17:28:15 +01:00
return false ;
}
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2023-08-22 09:09:07 +02:00
} ) ;
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2023-05-10 17:28:15 +01:00
default :
return false ;
}
}
2025-07-21 12:22:18 +01:00
Optional < Keyword > resolve_legacy_value_alias ( PropertyID property_id , Keyword keyword )
{
switch ( property_id ) {
) ~ ~ ~ " );
properties . for_each_member ( [ & ] ( auto & name , JsonValue const & value ) {
VERIFY ( value . is_object ( ) ) ;
auto & object = value . as_object ( ) ;
if ( is_legacy_alias ( object ) )
return ;
if ( auto maybe_valid_identifiers = object . get_array ( " valid-identifiers " sv ) ; maybe_valid_identifiers . has_value ( ) & & ! maybe_valid_identifiers - > is_empty ( ) ) {
auto & valid_identifiers = maybe_valid_identifiers . value ( ) ;
bool has_any_legacy_value_aliases = false ;
for ( auto & keyword_value : valid_identifiers . values ( ) ) {
if ( keyword_value . as_string ( ) . contains ( ' > ' ) ) {
has_any_legacy_value_aliases = true ;
break ;
}
}
if ( ! has_any_legacy_value_aliases )
return ;
auto property_generator = generator . fork ( ) ;
property_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
property_generator . append ( R " ~~~(
case PropertyID : : @ name : titlecase @ :
switch ( keyword ) { ) ~ ~ ~ " );
for ( auto & keyword_value : valid_identifiers . values ( ) ) {
auto const & keyword_string = keyword_value . as_string ( ) ;
if ( ! keyword_string . contains ( ' > ' ) )
continue ;
auto keyword_generator = generator . fork ( ) ;
auto parts = MUST ( keyword_string . split_limit ( ' > ' , 2 ) ) ;
keyword_generator . set ( " from_keyword:titlecase " , title_casify ( parts [ 0 ] ) ) ;
keyword_generator . set ( " to_keyword:titlecase " , title_casify ( parts [ 1 ] ) ) ;
keyword_generator . append ( R " ~~~(
case Keyword : : @ from_keyword : titlecase @ :
return Keyword : : @ to_keyword : titlecase @ ; ) ~ ~ ~ " );
}
property_generator . append ( R " ~~~(
default :
break ;
}
break ;
) ~ ~ ~ " );
}
} ) ;
generator . append ( R " ~~~(
default :
break ;
}
return { } ;
}
2023-05-10 17:28:15 +01:00
2023-06-22 16:22:16 +01:00
Optional < ValueType > property_resolves_percentages_relative_to ( PropertyID property_id )
{
switch ( property_id ) {
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2023-06-22 16:22:16 +01:00
2023-08-22 09:09:07 +02:00
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
2023-06-22 16:22:16 +01:00
VERIFY ( value . is_object ( ) ) ;
2024-09-27 12:54:08 +01:00
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
2025-02-17 15:04:56 -05:00
if ( auto resolved_type = value . as_object ( ) . get_string ( " percentages-resolve-to " sv ) ; resolved_type . has_value ( ) ) {
2023-08-21 16:42:48 +02:00
auto property_generator = generator . fork ( ) ;
2023-08-21 16:59:41 +02:00
property_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
property_generator . set ( " resolved_type:titlecase " , title_casify ( resolved_type . value ( ) ) ) ;
2023-08-21 16:39:43 +02:00
property_generator . append ( R " ~~~(
2023-06-22 16:22:16 +01:00
case PropertyID : : @ name : titlecase @ :
return ValueType : : @ resolved_type : titlecase @ ;
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2023-06-22 16:22:16 +01:00
}
2023-08-22 09:09:07 +02:00
} ) ;
2023-06-22 16:22:16 +01:00
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2023-06-22 16:22:16 +01:00
default :
return { } ;
}
}
2025-02-24 14:59:03 +00:00
Vector < StringView > property_custom_ident_blacklist ( PropertyID property_id )
{
switch ( property_id ) {
) ~ ~ ~ " );
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
VERIFY ( value . is_object ( ) ) ;
auto & object = value . as_object ( ) ;
if ( is_legacy_alias ( object ) )
return ;
// We only have a custom-ident blacklist if we accept custom idents!
if ( auto maybe_valid_types = object . get_array ( " valid-types " sv ) ; maybe_valid_types . has_value ( ) & & ! maybe_valid_types - > is_empty ( ) ) {
auto & valid_types = maybe_valid_types . value ( ) ;
for ( auto const & valid_type : valid_types . values ( ) ) {
auto type_and_parameters = MUST ( valid_type . as_string ( ) . split ( ' ' ) ) ;
if ( type_and_parameters . first ( ) ! = " custom-ident " sv | | type_and_parameters . size ( ) = = 1 )
continue ;
VERIFY ( type_and_parameters . size ( ) = = 2 ) ;
auto parameters_string = type_and_parameters [ 1 ] . bytes_as_string_view ( ) ;
VERIFY ( parameters_string . starts_with ( " ![ " sv ) & & parameters_string . ends_with ( ' ] ' ) ) ;
auto blacklisted_keywords = parameters_string . substring_view ( 2 , parameters_string . length ( ) - 3 ) . split_view ( ' , ' ) ;
auto property_generator = generator . fork ( ) ;
property_generator . set ( " property_name:titlecase " , title_casify ( name ) ) ;
property_generator . append ( R " ~~~(
case PropertyID : : @ property_name : titlecase @ :
return Vector { ) ~ ~ ~ " );
for ( auto const & keyword : blacklisted_keywords ) {
auto value_generator = property_generator . fork ( ) ;
value_generator . set ( " keyword " , keyword ) ;
value_generator . append ( " \" @keyword@ \" sv, " ) ;
}
property_generator . appendln ( " }; " ) ;
}
}
} ) ;
generator . append ( R " ~~~(
default :
return { } ;
}
}
2021-09-22 12:24:33 +01:00
size_t property_maximum_value_count ( PropertyID property_id )
{
switch ( property_id ) {
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2021-09-22 12:24:33 +01:00
2023-08-22 09:09:07 +02:00
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
2021-09-22 12:24:33 +01:00
VERIFY ( value . is_object ( ) ) ;
2024-09-27 12:54:08 +01:00
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
2022-07-11 17:32:29 +00:00
if ( value . as_object ( ) . has ( " max-values " sv ) ) {
2023-11-14 01:15:54 -05:00
JsonValue max_values = value . as_object ( ) . get ( " max-values " sv ) . release_value ( ) ;
VERIFY ( max_values . is_integer < size_t > ( ) ) ;
2023-08-21 16:42:48 +02:00
auto property_generator = generator . fork ( ) ;
2023-08-21 16:59:41 +02:00
property_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
2023-11-14 01:15:54 -05:00
property_generator . set ( " max_values " , MUST ( String : : formatted ( " {} " , max_values . as_integer < size_t > ( ) ) ) ) ;
2023-08-21 16:39:43 +02:00
property_generator . append ( R " ~~~(
2021-09-22 12:24:33 +01:00
case PropertyID : : @ name : titlecase @ :
return @ max_values @ ;
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2021-09-22 12:24:33 +01:00
}
2023-08-22 09:09:07 +02:00
} ) ;
2021-09-22 12:24:33 +01:00
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2021-09-22 12:24:33 +01:00
default :
return 1 ;
}
2023-08-21 16:39:43 +02:00
} ) ~ ~ ~ " );
2021-09-22 12:24:33 +01:00
2023-08-22 09:09:07 +02:00
generate_bounds_checking_function ( properties , generator , " angle " sv , " Angle " sv , " Deg " sv ) ;
2023-09-28 15:18:14 +01:00
generate_bounds_checking_function ( properties , generator , " flex " sv , " Flex " sv , " Fr " sv ) ;
2023-08-22 09:09:07 +02:00
generate_bounds_checking_function ( properties , generator , " frequency " sv , " Frequency " sv , " Hertz " sv ) ;
generate_bounds_checking_function ( properties , generator , " integer " sv , " i64 " sv , { } , " value " sv ) ;
generate_bounds_checking_function ( properties , generator , " length " sv , " Length " sv , { } , " value.raw_value() " sv ) ;
generate_bounds_checking_function ( properties , generator , " number " sv , " double " sv , { } , " value " sv ) ;
generate_bounds_checking_function ( properties , generator , " percentage " sv , " Percentage " sv , { } , " value.value() " sv ) ;
generate_bounds_checking_function ( properties , generator , " resolution " sv , " Resolution " sv , " Dpi " sv ) ;
generate_bounds_checking_function ( properties , generator , " time " sv , " Time " sv , " S " sv ) ;
2023-05-31 15:36:21 +01:00
2023-09-08 17:56:41 +01:00
generator . append ( R " ~~~(
bool property_is_shorthand ( PropertyID property_id )
{
switch ( property_id ) {
) ~ ~ ~ " );
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
2024-09-27 12:54:08 +01:00
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
2023-09-08 17:56:41 +01:00
if ( value . as_object ( ) . has ( " longhands " sv ) ) {
auto property_generator = generator . fork ( ) ;
property_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
property_generator . append ( R " ~~~(
case PropertyID : : @ name : titlecase @ :
) ~ ~ ~ " );
}
} ) ;
generator . append ( R " ~~~(
return true ;
default :
return false ;
}
}
) ~ ~ ~ " );
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2025-07-14 15:58:29 +12:00
Vector < PropertyID > const & longhands_for_shorthand ( PropertyID property_id )
2023-05-26 23:16:43 +03:30
{
switch ( property_id ) {
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2025-06-03 20:11:45 +12:00
Function < Vector < String > ( String const & ) > get_longhands = [ & ] ( String const & property_id ) {
auto object = properties . get_object ( property_id ) ;
VERIFY ( object . has_value ( ) ) ;
auto longhands_json_array = object . value ( ) . get_array ( " longhands " sv ) ;
VERIFY ( longhands_json_array . has_value ( ) ) ;
Vector < String > longhands ;
longhands_json_array . value ( ) . for_each ( [ & ] ( auto longhand_value ) {
longhands . append ( longhand_value . as_string ( ) ) ;
} ) ;
return longhands ;
} ;
2023-08-22 09:09:07 +02:00
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
2024-09-27 12:54:08 +01:00
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
2023-05-26 23:16:43 +03:30
if ( value . as_object ( ) . has ( " longhands " sv ) ) {
2023-08-21 16:42:48 +02:00
auto property_generator = generator . fork ( ) ;
2023-08-21 16:59:41 +02:00
property_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
2023-05-26 23:16:43 +03:30
StringBuilder builder ;
2025-06-03 20:11:45 +12:00
for ( auto longhand : get_longhands ( name ) ) {
if ( ! builder . is_empty ( ) )
2023-08-21 16:39:43 +02:00
builder . append ( " , " sv ) ;
2025-06-03 20:11:45 +12:00
builder . appendff ( " PropertyID::{} " , title_casify ( longhand ) ) ;
}
2023-12-16 17:49:34 +03:30
property_generator . set ( " longhands " , builder . to_byte_string ( ) ) ;
2023-08-21 16:39:43 +02:00
property_generator . append ( R " ~~~(
2025-07-14 15:58:29 +12:00
case PropertyID : : @ name : titlecase @ : {
static Vector < PropertyID > longhands = { @ longhands @ } ;
return longhands ;
} ) ~ ~ ~ " );
2023-05-26 23:16:43 +03:30
}
2023-08-22 09:09:07 +02:00
} ) ;
2023-05-26 23:16:43 +03:30
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2023-05-26 23:16:43 +03:30
default :
2025-07-14 15:58:29 +12:00
static Vector < PropertyID > empty_longhands ;
return empty_longhands ;
2023-05-26 23:16:43 +03:30
}
2025-05-27 20:28:34 +12:00
}
2025-06-03 20:11:45 +12:00
) ~ ~ ~ " );
generator . append ( R " ~~~(
2025-07-14 15:58:29 +12:00
Vector < PropertyID > const & expanded_longhands_for_shorthand ( PropertyID property_id )
2025-06-03 20:11:45 +12:00
{
switch ( property_id ) {
) ~ ~ ~ " );
Function < Vector < String > ( String const & ) > get_expanded_longhands = [ & ] ( String const & property_id ) {
Vector < String > expanded_longhands ;
for ( auto const & longhand_id : get_longhands ( property_id ) ) {
auto property = properties . get_object ( longhand_id ) ;
VERIFY ( property . has_value ( ) ) ;
if ( property - > has_array ( " longhands " sv ) )
expanded_longhands . extend ( get_expanded_longhands ( longhand_id ) ) ;
else
expanded_longhands . append ( longhand_id ) ;
}
return expanded_longhands ;
} ;
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
if ( value . as_object ( ) . has ( " longhands " sv ) ) {
auto property_generator = generator . fork ( ) ;
property_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
StringBuilder builder ;
for ( auto longhand : get_expanded_longhands ( name ) ) {
if ( ! builder . is_empty ( ) )
builder . append ( " , " sv ) ;
builder . appendff ( " PropertyID::{} " , title_casify ( longhand ) ) ;
}
property_generator . set ( " longhands " , builder . to_byte_string ( ) ) ;
property_generator . append ( R " ~~~(
2025-07-14 15:58:29 +12:00
case PropertyID : : @ name : titlecase @ : {
static Vector < PropertyID > longhands = { @ longhands @ } ;
return longhands ;
} ) ~ ~ ~ " );
2025-06-03 20:11:45 +12:00
}
} ) ;
generator . append ( R " ~~~(
2025-07-14 15:58:29 +12:00
default : {
static Vector < PropertyID > empty_longhands ;
return empty_longhands ;
}
2025-06-03 20:11:45 +12:00
}
}
2025-05-27 20:28:34 +12:00
) ~ ~ ~ " );
HashMap < String , Vector < String > > shorthands_for_longhand_map ;
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
if ( value . as_object ( ) . has ( " longhands " sv ) ) {
auto longhands = value . as_object ( ) . get ( " longhands " sv ) ;
VERIFY ( longhands . has_value ( ) & & longhands - > is_array ( ) ) ;
auto longhand_values = longhands - > as_array ( ) ;
for ( auto & longhand : longhand_values . values ( ) ) {
VERIFY ( longhand . is_string ( ) ) ;
auto & longhand_name = longhand . as_string ( ) ;
shorthands_for_longhand_map . ensure ( longhand_name ) . append ( name ) ;
}
}
} ) ;
generator . append ( R " ~~~(
bool property_maps_to_shorthand ( PropertyID property_id )
{
switch ( property_id ) {
) ~ ~ ~ " );
for ( auto const & longhand : shorthands_for_longhand_map . keys ( ) ) {
auto property_generator = generator . fork ( ) ;
property_generator . set ( " name:titlecase " , title_casify ( longhand ) ) ;
property_generator . append ( R " ~~~(
case PropertyID : : @ name : titlecase @ :
) ~ ~ ~ " );
}
generator . append ( R " ~~~(
return true ;
default :
return false ;
}
}
) ~ ~ ~ " );
generator . append ( R " ~~~(
2025-07-14 15:58:29 +12:00
Vector < PropertyID > const & shorthands_for_longhand ( PropertyID property_id )
2025-05-27 20:28:34 +12:00
{
switch ( property_id ) {
) ~ ~ ~ " );
2025-06-03 20:11:45 +12:00
Function < Vector < String > ( String ) > get_shorthands_for_longhand = [ & ] ( auto const & longhand ) {
Vector < String > shorthands ;
for ( auto const & immediate_shorthand : shorthands_for_longhand_map . get ( longhand ) . value ( ) ) {
shorthands . append ( immediate_shorthand ) ;
if ( shorthands_for_longhand_map . get ( immediate_shorthand ) . has_value ( ) )
shorthands . extend ( get_shorthands_for_longhand ( immediate_shorthand ) ) ;
}
// https://www.w3.org/TR/cssom/#concept-shorthands-preferred-order
// NOTE: The steps are performed in a order different to the spec in order to complete this in a single sort.
AK : : quick_sort ( shorthands , [ & ] ( String a , String b ) {
auto shorthand_a_longhands = get_expanded_longhands ( a ) ;
auto shorthand_b_longhands = get_expanded_longhands ( b ) ;
// 4. Order shorthands by the number of longhand properties that map to it, with the greatest number first.
if ( shorthand_a_longhands . size ( ) ! = shorthand_b_longhands . size ( ) )
return shorthand_a_longhands . size ( ) > shorthand_b_longhands . size ( ) ;
// 2. Move all items in shorthands that begin with "-" (U+002D) last in the list, retaining their relative order.
if ( a . starts_with_bytes ( " - " sv ) ! = b . starts_with_bytes ( " - " sv ) )
return b . starts_with_bytes ( " - " sv ) ;
// 3. Move all items in shorthands that begin with "-" (U+002D) but do not begin with "-webkit-" last in the list, retaining their relative order.
if ( a . starts_with_bytes ( " -webkit- " sv ) ! = b . starts_with_bytes ( " -webkit- " sv ) )
return a . starts_with_bytes ( " -webkit- " sv ) ;
// 1. Order shorthands lexicographically.
return a < b ;
} ) ;
return shorthands ;
} ;
2025-05-27 20:28:34 +12:00
for ( auto const & longhand : shorthands_for_longhand_map . keys ( ) ) {
auto property_generator = generator . fork ( ) ;
property_generator . set ( " name:titlecase " , title_casify ( longhand ) ) ;
StringBuilder builder ;
2025-06-03 20:11:45 +12:00
for ( auto & shorthand : get_shorthands_for_longhand ( longhand ) ) {
if ( ! builder . is_empty ( ) )
2025-05-27 20:28:34 +12:00
builder . append ( " , " sv ) ;
builder . appendff ( " PropertyID::{} " , title_casify ( shorthand ) ) ;
}
property_generator . set ( " shorthands " , builder . to_byte_string ( ) ) ;
property_generator . append ( R " ~~~(
2025-07-14 15:58:29 +12:00
case PropertyID : : @ name : titlecase @ : {
static Vector < PropertyID > shorthands = { @ shorthands @ } ;
return shorthands ;
} ) ~ ~ ~ " );
2025-05-27 20:28:34 +12:00
}
generator . append ( R " ~~~(
2025-07-14 15:58:29 +12:00
default : {
static Vector < PropertyID > empty_shorthands ;
return empty_shorthands ;
}
2025-06-03 20:11:45 +12:00
}
2023-05-26 23:16:43 +03:30
}
2025-07-10 20:21:45 +12:00
) ~ ~ ~ " );
generator . append ( R " ~~~(
bool property_is_positional_value_list_shorthand ( PropertyID property_id )
{
switch ( property_id )
{
) ~ ~ ~ " );
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
if ( value . as_object ( ) . has ( " positional-value-list-shorthand " sv ) ) {
auto property_generator = generator . fork ( ) ;
property_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
property_generator . append ( R " ~~~(
case PropertyID : : @ name : titlecase @ :
) ~ ~ ~ " );
}
} ) ;
generator . append ( R " ~~~(
return true ;
default :
return false ;
}
}
2025-06-18 17:45:26 +12:00
) ~ ~ ~ " );
generator . append ( R " ~~~(
bool property_is_logical_alias ( PropertyID property_id )
{
switch ( property_id ) {
) ~ ~ ~ " );
properties . for_each_member ( [ & ] ( auto & name , auto & value ) {
if ( is_legacy_alias ( value . as_object ( ) ) )
return ;
if ( value . as_object ( ) . has ( " logical-alias-for " sv ) ) {
auto property_generator = generator . fork ( ) ;
property_generator . set ( " name:titlecase " , title_casify ( name ) ) ;
property_generator . append ( R " ~~~(
case PropertyID : : @ name : titlecase @ :
) ~ ~ ~ " );
}
} ) ;
generator . append ( R " ~~~(
return true ;
default :
return false ;
}
}
2025-07-03 14:31:26 +01:00
) ~ ~ ~ " );
generator . append ( R " ~~~(
PropertyID map_logical_alias_to_physical_property ( PropertyID property_id , LogicalAliasMappingContext const & mapping_context )
{
// https://drafts.csswg.org/css-writing-modes-4/#logical-to-physical
// FIXME: Note: The used direction depends on the computed writing-mode and text-orientation: in vertical writing
// modes, a text-orientation value of upright forces the used direction to ltr.
auto used_direction = mapping_context . direction ;
switch ( property_id ) {
) ~ ~ ~ " );
properties . for_each_member ( [ & ] ( auto & property_name , JsonValue const & value ) {
auto & property = value . as_object ( ) ;
if ( is_legacy_alias ( property ) )
return ;
if ( auto logical_alias_for = property . get_object ( " logical-alias-for " sv ) ; logical_alias_for . has_value ( ) ) {
auto group_name = logical_alias_for - > get_string ( " group " sv ) ;
auto mapping = logical_alias_for - > get_string ( " mapping " sv ) ;
if ( ! group_name . has_value ( ) | | ! mapping . has_value ( ) ) {
dbgln ( " Logical alias '{}' is missing either its group or its mapping! " , property_name ) ;
VERIFY_NOT_REACHED ( ) ;
}
auto maybe_group = logical_property_groups . get_object ( group_name . value ( ) ) ;
if ( ! maybe_group . has_value ( ) ) {
dbgln ( " Logical alias '{}' has unrecognized group '{}' " , property_name , group_name . value ( ) ) ;
VERIFY_NOT_REACHED ( ) ;
}
auto const & group = maybe_group . value ( ) ;
auto mapped_property = [ & ] ( StringView entry_name ) {
if ( auto maybe_string = group . get_string ( entry_name ) ; maybe_string . has_value ( ) ) {
return title_casify ( maybe_string . value ( ) ) ;
}
dbgln ( " Logical property group '{}' is missing entry for '{}', requested by property '{}'. " , group_name . value ( ) , entry_name , property_name ) ;
VERIFY_NOT_REACHED ( ) ;
} ;
auto property_generator = generator . fork ( ) ;
property_generator . set ( " name:titlecase " , title_casify ( property_name ) ) ;
property_generator . append ( R " ~~~(
case PropertyID : : @ name : titlecase @ :
) ~ ~ ~ " );
if ( mapping = = " block-end " sv ) {
property_generator . set ( " left:titlecase " , mapped_property ( " left " sv ) ) ;
property_generator . set ( " right:titlecase " , mapped_property ( " right " sv ) ) ;
property_generator . set ( " bottom:titlecase " , mapped_property ( " bottom " sv ) ) ;
property_generator . append ( R " ~~~(
if ( mapping_context . writing_mode = = WritingMode : : HorizontalTb )
return PropertyID : : @ bottom : titlecase @ ;
if ( first_is_one_of ( mapping_context . writing_mode , WritingMode : : VerticalRl , WritingMode : : SidewaysRl ) )
return PropertyID : : @ left : titlecase @ ;
return PropertyID : : @ right : titlecase @ ;
) ~ ~ ~ " );
} else if ( mapping = = " block-size " sv ) {
property_generator . set ( " height:titlecase " , mapped_property ( " height " sv ) ) ;
property_generator . set ( " width:titlecase " , mapped_property ( " width " sv ) ) ;
property_generator . append ( R " ~~~(
if ( mapping_context . writing_mode = = WritingMode : : HorizontalTb )
return PropertyID : : @ height : titlecase @ ;
return PropertyID : : @ width : titlecase @ ;
) ~ ~ ~ " );
} else if ( mapping = = " block-start " sv ) {
property_generator . set ( " left:titlecase " , mapped_property ( " left " sv ) ) ;
property_generator . set ( " right:titlecase " , mapped_property ( " right " sv ) ) ;
property_generator . set ( " top:titlecase " , mapped_property ( " top " sv ) ) ;
property_generator . append ( R " ~~~(
if ( mapping_context . writing_mode = = WritingMode : : HorizontalTb )
return PropertyID : : @ top : titlecase @ ;
if ( first_is_one_of ( mapping_context . writing_mode , WritingMode : : VerticalRl , WritingMode : : SidewaysRl ) )
return PropertyID : : @ right : titlecase @ ;
return PropertyID : : @ left : titlecase @ ;
) ~ ~ ~ " );
} else if ( mapping = = " end-end " sv ) {
property_generator . set ( " top-left:titlecase " , mapped_property ( " top-left " sv ) ) ;
property_generator . set ( " bottom-left:titlecase " , mapped_property ( " bottom-left " sv ) ) ;
property_generator . set ( " top-right:titlecase " , mapped_property ( " top-right " sv ) ) ;
property_generator . set ( " bottom-right:titlecase " , mapped_property ( " bottom-right " sv ) ) ;
property_generator . append ( R " ~~~(
if ( mapping_context . writing_mode = = WritingMode : : HorizontalTb ) {
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ bottom - right : titlecase @ ;
return PropertyID : : @ bottom - left : titlecase @ ;
}
if ( first_is_one_of ( mapping_context . writing_mode , WritingMode : : VerticalRl , WritingMode : : SidewaysRl ) ) {
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ bottom - left : titlecase @ ;
return PropertyID : : @ top - left : titlecase @ ;
}
if ( mapping_context . writing_mode = = WritingMode : : VerticalLr ) {
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ bottom - right : titlecase @ ;
return PropertyID : : @ top - right : titlecase @ ;
}
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ top - right : titlecase @ ;
return PropertyID : : @ bottom - right : titlecase @ ;
) ~ ~ ~ " );
} else if ( mapping = = " end-start " sv ) {
property_generator . set ( " top-left:titlecase " , mapped_property ( " top-left " sv ) ) ;
property_generator . set ( " bottom-left:titlecase " , mapped_property ( " bottom-left " sv ) ) ;
property_generator . set ( " top-right:titlecase " , mapped_property ( " top-right " sv ) ) ;
property_generator . set ( " bottom-right:titlecase " , mapped_property ( " bottom-right " sv ) ) ;
property_generator . append ( R " ~~~(
if ( mapping_context . writing_mode = = WritingMode : : HorizontalTb ) {
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ bottom - left : titlecase @ ;
return PropertyID : : @ bottom - right : titlecase @ ;
}
if ( first_is_one_of ( mapping_context . writing_mode , WritingMode : : VerticalRl , WritingMode : : SidewaysRl ) ) {
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ top - left : titlecase @ ;
return PropertyID : : @ bottom - left : titlecase @ ;
}
if ( mapping_context . writing_mode = = WritingMode : : VerticalLr ) {
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ top - right : titlecase @ ;
return PropertyID : : @ bottom - right : titlecase @ ;
}
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ bottom - right : titlecase @ ;
return PropertyID : : @ top - right : titlecase @ ;
) ~ ~ ~ " );
} else if ( mapping = = " inline-end " sv ) {
property_generator . set ( " left:titlecase " , mapped_property ( " left " sv ) ) ;
property_generator . set ( " right:titlecase " , mapped_property ( " right " sv ) ) ;
property_generator . set ( " top:titlecase " , mapped_property ( " top " sv ) ) ;
property_generator . set ( " bottom:titlecase " , mapped_property ( " bottom " sv ) ) ;
property_generator . append ( R " ~~~(
if ( mapping_context . writing_mode = = WritingMode : : HorizontalTb ) {
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ right : titlecase @ ;
return PropertyID : : @ left : titlecase @ ;
}
if ( first_is_one_of ( mapping_context . writing_mode , WritingMode : : VerticalRl , WritingMode : : SidewaysRl , WritingMode : : VerticalLr ) ) {
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ bottom : titlecase @ ;
return PropertyID : : @ top : titlecase @ ;
}
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ top : titlecase @ ;
return PropertyID : : @ bottom : titlecase @ ;
) ~ ~ ~ " );
} else if ( mapping = = " inline-size " sv ) {
property_generator . set ( " height:titlecase " , mapped_property ( " height " sv ) ) ;
property_generator . set ( " width:titlecase " , mapped_property ( " width " sv ) ) ;
property_generator . append ( R " ~~~(
if ( mapping_context . writing_mode = = WritingMode : : HorizontalTb )
return PropertyID : : @ width : titlecase @ ;
return PropertyID : : @ height : titlecase @ ;
) ~ ~ ~ " );
} else if ( mapping = = " inline-start " sv ) {
property_generator . set ( " left:titlecase " , mapped_property ( " left " sv ) ) ;
property_generator . set ( " right:titlecase " , mapped_property ( " right " sv ) ) ;
property_generator . set ( " top:titlecase " , mapped_property ( " top " sv ) ) ;
property_generator . set ( " bottom:titlecase " , mapped_property ( " bottom " sv ) ) ;
property_generator . append ( R " ~~~(
if ( mapping_context . writing_mode = = WritingMode : : HorizontalTb ) {
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ left : titlecase @ ;
return PropertyID : : @ right : titlecase @ ;
}
if ( first_is_one_of ( mapping_context . writing_mode , WritingMode : : VerticalRl , WritingMode : : SidewaysRl , WritingMode : : VerticalLr ) ) {
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ top : titlecase @ ;
return PropertyID : : @ bottom : titlecase @ ;
}
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ bottom : titlecase @ ;
return PropertyID : : @ top : titlecase @ ;
) ~ ~ ~ " );
} else if ( mapping = = " start-end " sv ) {
property_generator . set ( " top-left:titlecase " , mapped_property ( " top-left " sv ) ) ;
property_generator . set ( " bottom-left:titlecase " , mapped_property ( " bottom-left " sv ) ) ;
property_generator . set ( " top-right:titlecase " , mapped_property ( " top-right " sv ) ) ;
property_generator . set ( " bottom-right:titlecase " , mapped_property ( " bottom-right " sv ) ) ;
property_generator . append ( R " ~~~(
if ( mapping_context . writing_mode = = WritingMode : : HorizontalTb ) {
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ top - right : titlecase @ ;
return PropertyID : : @ top - left : titlecase @ ;
}
if ( first_is_one_of ( mapping_context . writing_mode , WritingMode : : VerticalRl , WritingMode : : SidewaysRl ) ) {
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ bottom - right : titlecase @ ;
return PropertyID : : @ top - right : titlecase @ ;
}
if ( mapping_context . writing_mode = = WritingMode : : VerticalLr ) {
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ bottom - left : titlecase @ ;
return PropertyID : : @ top - left : titlecase @ ;
}
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ top - left : titlecase @ ;
return PropertyID : : @ bottom - left : titlecase @ ;
) ~ ~ ~ " );
} else if ( mapping = = " start-start " sv ) {
property_generator . set ( " top-left:titlecase " , mapped_property ( " top-left " sv ) ) ;
property_generator . set ( " bottom-left:titlecase " , mapped_property ( " bottom-left " sv ) ) ;
property_generator . set ( " top-right:titlecase " , mapped_property ( " top-right " sv ) ) ;
property_generator . set ( " bottom-right:titlecase " , mapped_property ( " bottom-right " sv ) ) ;
property_generator . append ( R " ~~~(
if ( mapping_context . writing_mode = = WritingMode : : HorizontalTb ) {
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ top - left : titlecase @ ;
return PropertyID : : @ top - right : titlecase @ ;
}
if ( first_is_one_of ( mapping_context . writing_mode , WritingMode : : VerticalRl , WritingMode : : SidewaysRl ) ) {
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ top - right : titlecase @ ;
return PropertyID : : @ bottom - right : titlecase @ ;
}
if ( mapping_context . writing_mode = = WritingMode : : VerticalLr ) {
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ top - left : titlecase @ ;
return PropertyID : : @ bottom - left : titlecase @ ;
}
if ( used_direction = = Direction : : Ltr )
return PropertyID : : @ bottom - left : titlecase @ ;
return PropertyID : : @ top - left : titlecase @ ;
) ~ ~ ~ " );
} else {
dbgln ( " Logical alias '{}' has unrecognized mapping '{}' " , property_name , mapping . value ( ) ) ;
VERIFY_NOT_REACHED ( ) ;
}
}
} ) ;
generator . append ( R " ~~~(
default :
VERIFY ( ! property_is_logical_alias ( property_id ) ) ;
return property_id ;
}
}
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2021-04-05 10:12:37 -04:00
2023-08-21 16:39:43 +02:00
generator . append ( R " ~~~(
2025-07-13 20:34:55 +12:00
Optional < LogicalPropertyGroup > logical_property_group_for_property ( PropertyID property_id )
{
switch ( property_id ) {
) ~ ~ ~ " );
HashMap < String , Vector < String > > logical_property_group_members ;
logical_property_groups . for_each_member ( [ & ] ( auto & logical_property_group_name , auto & mapping ) {
auto & group_members = logical_property_group_members . ensure ( logical_property_group_name ) ;
mapping . as_object ( ) . for_each_member ( [ & ] ( auto & , auto & physical_property ) {
group_members . append ( physical_property . as_string ( ) ) ;
} ) ;
} ) ;
properties . for_each_member ( [ & ] ( auto & property_name , auto & value ) {
if ( auto maybe_logical_property_group = value . as_object ( ) . get_object ( " logical-alias-for " sv ) ; maybe_logical_property_group . has_value ( ) ) {
auto group = maybe_logical_property_group . value ( ) . get_string ( " group " sv ) . value ( ) ;
logical_property_group_members . get ( group ) . value ( ) . append ( property_name ) ;
}
} ) ;
for ( auto const & logical_property_group : logical_property_group_members . keys ( ) ) {
generator . set ( " logical_property_group_name:titlecase " , title_casify ( logical_property_group ) ) ;
for ( auto const & property : logical_property_group_members . get ( logical_property_group ) . value ( ) ) {
generator . set ( " property_name:titlecase " , title_casify ( property ) ) ;
generator . append ( R " ~~~(
case PropertyID : : @ property_name : titlecase @ :
) ~ ~ ~ " );
}
generator . append ( R " ~~~(
return LogicalPropertyGroup : : @ logical_property_group_name : titlecase @ ;
) ~ ~ ~ " );
}
generator . append ( R " ~~~(
default :
return { } ;
}
}
2023-05-26 23:16:43 +03:30
} // namespace Web::CSS
2023-08-21 16:39:43 +02:00
) ~ ~ ~ " );
2020-10-23 18:37:35 +02:00
2023-03-01 16:28:32 +01:00
TRY ( file . write_until_depleted ( generator . as_string_view ( ) . bytes ( ) ) ) ;
2022-04-01 17:41:43 +01:00
return { } ;
2019-11-18 11:12:58 +01:00
}
2023-10-31 19:33:58 -07:00
bool is_animatable_property ( JsonObject & properties , StringView property_name )
{
auto property = properties . get_object ( property_name ) ;
VERIFY ( property . has_value ( ) ) ;
2025-02-17 15:04:56 -05:00
if ( auto animation_type = property . value ( ) . get_string ( " animation-type " sv ) ; animation_type . has_value ( ) ) {
2023-10-31 19:33:58 -07:00
return animation_type ! = " none " ;
}
if ( ! property . value ( ) . has ( " longhands " sv ) ) {
2025-04-08 13:50:50 -04:00
dbgln ( " Property '{}' must specify either 'animation-type' or 'longhands' " , property_name ) ;
2023-10-31 19:33:58 -07:00
VERIFY_NOT_REACHED ( ) ;
}
auto longhands = property . value ( ) . get_array ( " longhands " sv ) ;
VERIFY ( longhands . has_value ( ) ) ;
for ( auto const & subproperty_name : longhands - > values ( ) ) {
VERIFY ( subproperty_name . is_string ( ) ) ;
if ( is_animatable_property ( properties , subproperty_name . as_string ( ) ) )
return true ;
}
return false ;
}