From 69d4811ef7ba43a6028710d56493b33aee5adbd9 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Thu, 3 Jul 2025 14:31:26 +0100 Subject: [PATCH] LibWeb: Generate logical property mappings To support this, how we declare logical property aliases has changed. Instead of `logical-alias-for` being a list of properties, it's now an object with a `group` and `mapping`. The group is the name of a logical property group in LogicalPropertyGroups.json. The mapping is which side/dimension/corner this property is. Hopefully it's self-explanatory enough. The generated code is very much a copy of what was previously in `StyleComputer::map_logical_alias_to_physical_property_id()`, so there should be no behaviour change. --- Documentation/CSSGeneratedFiles.md | 70 +++- Documentation/CSSProperties.md | 3 +- Libraries/LibWeb/CSS/CSSStyleProperties.cpp | 2 +- .../LibWeb/CSS/LogicalPropertyGroups.json | 56 +++ Libraries/LibWeb/CSS/Properties.json | 328 ++++++--------- Libraries/LibWeb/CSS/StyleComputer.cpp | 395 +----------------- Libraries/LibWeb/CSS/StyleComputer.h | 7 - Meta/CMake/libweb_generators.cmake | 3 +- .../LibWeb/GenerateCSSPropertyID.cpp | 343 +++++++++++++-- 9 files changed, 561 insertions(+), 646 deletions(-) create mode 100644 Libraries/LibWeb/CSS/LogicalPropertyGroups.json diff --git a/Documentation/CSSGeneratedFiles.md b/Documentation/CSSGeneratedFiles.md index ab8b164a129..ea22f500e71 100644 --- a/Documentation/CSSGeneratedFiles.md +++ b/Documentation/CSSGeneratedFiles.md @@ -19,21 +19,21 @@ Each property will have some set of these fields on it: (Note that required fields are not required on properties with `legacy-alias-for` or `logical-alias-for` set.) -| Field | Required | Default | Description | Generated functions | -|----------------------------|----------|---------|-------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------| -| `affects-layout` | No | `true` | Boolean. Whether changing this property will invalidate the element's layout. | `bool property_affects_layout(PropertyID)` | -| `affects-stacking-context` | No | `false` | Boolean. Whether this property can cause a new stacking context for the element. | `bool property_affects_stacking_context(PropertyID)` | -| `animation-type` | Yes | | String. How the property should be animated. Defined by the spec. See below. | `AnimationType animation_type_from_longhand_property(PropertyID)` | -| `inherited` | Yes | | Boolean. Whether the property is inherited by its child elements. | `bool is_inherited_property(PropertyID)` | -| `initial` | Yes | | String. The property's initial value if it is not specified. | `NonnullRefPtr property_initial_value(PropertyID)` | -| `legacy-alias-for` | No | Nothing | String. The name of a property this is an alias for. See below. | | -| `logical-alias-for` | No | Nothing | Array of strings. The name of a property this is an alias for. See below. | `bool property_is_logical_alias(PropertyID);` | -| `longhands` | No | `[]` | Array of strings. If this is a shorthand, these are the property names that it expands out into. | `Vector longhands_for_shorthand(PropertyID)`
`Vector expanded_longhands_for_shorthand(PropertyID)`
`Vector shorthands_for_longhand(PropertyID)`| -| `max-values` | No | `1` | Integer. How many values can be parsed for this property. eg, `margin` can have up to 4 values. | `size_t property_maximum_value_count(PropertyID)` | -| `percentages-resolve-to` | No | Nothing | String. What type percentages get resolved to. eg, for `width` percentages are resolved to `length` values. | `Optional property_resolves_percentages_relative_to(PropertyID)` | -| `quirks` | No | `[]` | Array of strings. Some properties have special behavior in "quirks mode", which are listed here. See below. | `bool property_has_quirk(PropertyID, Quirk)` | -| `valid-identifiers` | No | `[]` | Array of strings. Which keywords the property accepts. Consider defining an enum instead and putting its name in the `valid-types` array. | `bool property_accepts_keyword(PropertyID, Keyword)` | -| `valid-types` | No | `[]` | Array of strings. Which value types the property accepts. See below. | `bool property_accepts_type(PropertyID, ValueType)` | +| Field | Required | Default | Description | Generated functions | +|----------------------------|----------|---------|-------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `affects-layout` | No | `true` | Boolean. Whether changing this property will invalidate the element's layout. | `bool property_affects_layout(PropertyID)` | +| `affects-stacking-context` | No | `false` | Boolean. Whether this property can cause a new stacking context for the element. | `bool property_affects_stacking_context(PropertyID)` | +| `animation-type` | Yes | | String. How the property should be animated. Defined by the spec. See below. | `AnimationType animation_type_from_longhand_property(PropertyID)` | +| `inherited` | Yes | | Boolean. Whether the property is inherited by its child elements. | `bool is_inherited_property(PropertyID)` | +| `initial` | Yes | | String. The property's initial value if it is not specified. | `NonnullRefPtr property_initial_value(PropertyID)` | +| `legacy-alias-for` | No | Nothing | String. The name of a property this is an alias for. See below. | | +| `logical-alias-for` | No | Nothing | An object. See below. | `bool property_is_logical_alias(PropertyID);`
`PropertyID map_logical_alias_to_physical_property(PropertyID, LogicalAliasMappingContext const&)` | +| `longhands` | No | `[]` | Array of strings. If this is a shorthand, these are the property names that it expands out into. | `Vector longhands_for_shorthand(PropertyID)`
`Vector expanded_longhands_for_shorthand(PropertyID)`
`Vector shorthands_for_longhand(PropertyID)` | +| `max-values` | No | `1` | Integer. How many values can be parsed for this property. eg, `margin` can have up to 4 values. | `size_t property_maximum_value_count(PropertyID)` | +| `percentages-resolve-to` | No | Nothing | String. What type percentages get resolved to. eg, for `width` percentages are resolved to `length` values. | `Optional property_resolves_percentages_relative_to(PropertyID)` | +| `quirks` | No | `[]` | Array of strings. Some properties have special behavior in "quirks mode", which are listed here. See below. | `bool property_has_quirk(PropertyID, Quirk)` | +| `valid-identifiers` | No | `[]` | Array of strings. Which keywords the property accepts. Consider defining an enum instead and putting its name in the `valid-types` array. | `bool property_accepts_keyword(PropertyID, Keyword)` | +| `valid-types` | No | `[]` | Array of strings. Which value types the property accepts. See below. | `bool property_accepts_type(PropertyID, ValueType)` | ### `animation-type` @@ -47,15 +47,25 @@ The [Web Animations spec](https://www.w3.org/TR/web-animations/#animation-type) | repeatable list | `repeatable-list` | | (See prose) | `custom` | -### `legacy-alias-for` and `logical-alias-for` +### `legacy-alias-for` +(Not to be confused with `logical-alias-for` below.) -These are two separate concepts, with unfortunately similar names: -- [Legacy name aliases](https://drafts.csswg.org/css-cascade-5/#legacy-name-alias) are properties whose spec names have changed, - but the syntax has not, so setting the old one is defined as setting the new one directly. - For example, `font-stretch` was renamed to `font-width`, so `font-stretch` is now a legacy name alias for `font-width`. -- Logical aliases are properties like `margin-block-start`, which may assign a value to one of several other properties - (`margin-top`, `margin-bottom`, `margin-left`, or `margin-right`) depending on the element they are applied to. - List all the properties that they can alias. +[Legacy name aliases](https://drafts.csswg.org/css-cascade-5/#legacy-name-alias) are properties whose spec names have changed, +but the syntax has not, so setting the old one is defined as setting the new one directly. +For example, `font-stretch` was renamed to `font-width`, so `font-stretch` is now a legacy name alias for `font-width`. + +### `logical-alias-for` +(Not to be confused with `legacy-alias-for` above.) + +Logical aliases are properties like `margin-block-start`, which may assign a value to one of several other properties +(`margin-top`, `margin-bottom`, `margin-left`, or `margin-right`) depending on the element they are applied to. + +`logical-alias-for` should be an object with two fields, both of which are required: + +| Field | Description | +|-----------|-------------------------------------------------------------------------------------------------------------------------------------| +| `group` | String. Name of the logical property group this is associated with. (See [LogicalPropertyGroups.json](#logicalpropertygroupsjson).) | +| `mapping` | String. How this relates to the group. eg, if it's the block end value, `block-end`. | ### `quirks` @@ -74,6 +84,20 @@ For numeric types, we use the [bracketed range notation](https://www.w3.org/TR/c for example `width` can take any non-negative length, so it has `"length [0,∞]"` in its `valid-types` array. For ``s, the excluded identifiers are placed within `![]`, for example `"custom-ident ![all,none]"`. +## LogicalPropertyGroups.json + +A set of matching CSS properties can be grouped together in what's called a +["logical property group"](https://drafts.csswg.org/css-logical-1/#logical-property-group). +For example, all of the `margin-*` properties are in the `margin` group. + +This data is used to map logical properties (such as `margin-block-start`) into their physical counterparts (like +`margin-top`) at runtime, depending on the writing direction. We don't generate any code directly from this file. +Instead, it's used as part of generating the PropertyID code. + +The file is a single object where the keys are the names of the logical property groups, and their values are objects +mapping physical dimensions, sides, or corners to the relevant property. Which keys are there depends on the group - for +example the `size` group has `width` and `height`. + ## Descriptors.json Descriptors are basically properties, but for at-rules instead of style. The overall structure is a JSON object, with diff --git a/Documentation/CSSProperties.md b/Documentation/CSSProperties.md index 9c105aadef0..29f462c54a7 100644 --- a/Documentation/CSSProperties.md +++ b/Documentation/CSSProperties.md @@ -7,7 +7,8 @@ These are listed below in the order that Ladybird deals with them, starting at p The first place you will need to go to is `CSS/Properties.json`. This file contains the definition for each property, and is used to generate the `PropertyID` enum and a selection of functions. You may also need to -modify `CSS/Keywords.json` and `CSS/Enums.json`. See [CSSGeneratedFiles.md](CSSGeneratedFiles.md) for details. +modify `CSS/Keywords.json`, `CSS/Enums.json`, and `CSS/LogicalPropertyGroups.json`. +See [CSSGeneratedFiles.md](CSSGeneratedFiles.md) for details. ## Parsing diff --git a/Libraries/LibWeb/CSS/CSSStyleProperties.cpp b/Libraries/LibWeb/CSS/CSSStyleProperties.cpp index 4fdbd024df8..bd92141a291 100644 --- a/Libraries/LibWeb/CSS/CSSStyleProperties.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.cpp @@ -700,7 +700,7 @@ RefPtr CSSStyleProperties::style_value_for_computed_propert return style_value_for_computed_property( layout_node, - StyleComputer::map_logical_alias_to_physical_property_id(property_id, StyleComputer::LogicalAliasMappingContext { computed_properties->writing_mode(), computed_properties->direction() })); + map_logical_alias_to_physical_property(property_id, LogicalAliasMappingContext { computed_properties->writing_mode(), computed_properties->direction() })); } // A limited number of properties have special rules for producing their "resolved value". diff --git a/Libraries/LibWeb/CSS/LogicalPropertyGroups.json b/Libraries/LibWeb/CSS/LogicalPropertyGroups.json new file mode 100644 index 00000000000..b2d4ec568eb --- /dev/null +++ b/Libraries/LibWeb/CSS/LogicalPropertyGroups.json @@ -0,0 +1,56 @@ +{ + "border-color": { + "top": "border-top-color", + "right": "border-right-color", + "bottom": "border-bottom-color", + "left": "border-left-color" + }, + "border-radius": { + "top-left": "border-top-left-radius", + "bottom-left": "border-bottom-left-radius", + "top-right": "border-top-right-radius", + "bottom-right": "border-bottom-right-radius" + }, + "border-style": { + "top": "border-top-style", + "right": "border-right-style", + "bottom": "border-bottom-style", + "left": "border-left-style" + }, + "border-width": { + "top": "border-top-width", + "right": "border-right-width", + "bottom": "border-bottom-width", + "left": "border-left-width" + }, + "inset": { + "top": "top", + "right": "right", + "bottom": "bottom", + "left": "left" + }, + "margin": { + "top": "margin-top", + "right": "margin-right", + "bottom": "margin-bottom", + "left": "margin-left" + }, + "max-size": { + "width": "max-width", + "height": "max-height" + }, + "min-size": { + "width": "min-width", + "height": "min-height" + }, + "padding": { + "top": "padding-top", + "right": "padding-right", + "bottom": "padding-bottom", + "left": "padding-left" + }, + "size": { + "width": "width", + "height": "height" + } +} diff --git a/Libraries/LibWeb/CSS/Properties.json b/Libraries/LibWeb/CSS/Properties.json index 2abee3ee398..6e145240247 100644 --- a/Libraries/LibWeb/CSS/Properties.json +++ b/Libraries/LibWeb/CSS/Properties.json @@ -481,10 +481,10 @@ "percentages-resolve-to": "length" }, "block-size": { - "logical-alias-for": [ - "height", - "width" - ], + "logical-alias-for": { + "group": "size", + "mapping": "block-size" + }, "initial": "auto", "max-values": 1 }, @@ -515,30 +515,24 @@ ] }, "border-block-end-color": { - "logical-alias-for": [ - "border-top-color", - "border-right-color", - "border-bottom-color", - "border-left-color" - ], + "logical-alias-for": { + "group": "border-color", + "mapping": "block-end" + }, "max-values": 1 }, "border-block-end-style": { - "logical-alias-for": [ - "border-top-style", - "border-right-style", - "border-bottom-style", - "border-left-style" - ], + "logical-alias-for": { + "group": "border-style", + "mapping": "block-end" + }, "max-values": 1 }, "border-block-end-width": { - "logical-alias-for": [ - "border-top-width", - "border-right-width", - "border-bottom-width", - "border-left-width" - ], + "logical-alias-for": { + "group": "border-width", + "mapping": "block-end" + }, "max-values": 1 }, "border-block-start": { @@ -551,30 +545,24 @@ ] }, "border-block-start-color": { - "logical-alias-for": [ - "border-top-color", - "border-right-color", - "border-bottom-color", - "border-left-color" - ], + "logical-alias-for": { + "group": "border-color", + "mapping": "block-start" + }, "max-values": 1 }, "border-block-start-style": { - "logical-alias-for": [ - "border-top-style", - "border-right-style", - "border-bottom-style", - "border-left-style" - ], + "logical-alias-for": { + "group": "border-style", + "mapping": "block-start" + }, "max-values": 1 }, "border-block-start-width": { - "logical-alias-for": [ - "border-top-width", - "border-right-width", - "border-bottom-width", - "border-left-width" - ], + "logical-alias-for": { + "group": "border-width", + "mapping": "block-start" + }, "max-values": 1 }, "border-block-style": { @@ -688,21 +676,17 @@ ] }, "border-end-end-radius": { - "logical-alias-for": [ - "border-top-left-radius", - "border-bottom-left-radius", - "border-top-right-radius", - "border-bottom-right-radius" - ], + "logical-alias-for": { + "group": "border-radius", + "mapping": "end-end" + }, "max-values": 1 }, "border-end-start-radius": { - "logical-alias-for": [ - "border-top-left-radius", - "border-bottom-left-radius", - "border-top-right-radius", - "border-bottom-right-radius" - ], + "logical-alias-for": { + "group": "border-radius", + "mapping": "end-start" + }, "max-values": 1 }, "border-image": { @@ -795,30 +779,24 @@ ] }, "border-inline-end-color": { - "logical-alias-for": [ - "border-top-color", - "border-right-color", - "border-bottom-color", - "border-left-color" - ], + "logical-alias-for": { + "group": "border-color", + "mapping": "inline-end" + }, "max-values": 1 }, "border-inline-end-style": { - "logical-alias-for": [ - "border-top-style", - "border-right-style", - "border-bottom-style", - "border-left-style" - ], + "logical-alias-for": { + "group": "border-style", + "mapping": "inline-end" + }, "max-values": 1 }, "border-inline-end-width": { - "logical-alias-for": [ - "border-top-width", - "border-right-width", - "border-bottom-width", - "border-left-width" - ], + "logical-alias-for": { + "group": "border-width", + "mapping": "inline-end" + }, "max-values": 1 }, "border-inline-start": { @@ -831,30 +809,24 @@ ] }, "border-inline-start-color": { - "logical-alias-for": [ - "border-top-color", - "border-right-color", - "border-bottom-color", - "border-left-color" - ], + "logical-alias-for": { + "group": "border-color", + "mapping": "inline-start" + }, "max-values": 1 }, "border-inline-start-style": { - "logical-alias-for": [ - "border-top-style", - "border-right-style", - "border-bottom-style", - "border-left-style" - ], + "logical-alias-for": { + "group": "border-style", + "mapping": "inline-start" + }, "max-values": 1 }, "border-inline-start-width": { - "logical-alias-for": [ - "border-top-width", - "border-right-width", - "border-bottom-width", - "border-left-width" - ], + "logical-alias-for": { + "group": "border-width", + "mapping": "inline-start" + }, "max-values": 1 }, "border-inline-style": { @@ -992,21 +964,17 @@ ] }, "border-start-end-radius": { - "logical-alias-for": [ - "border-top-left-radius", - "border-bottom-left-radius", - "border-top-right-radius", - "border-bottom-right-radius" - ], + "logical-alias-for": { + "group": "border-radius", + "mapping": "start-end" + }, "max-values": 1 }, "border-start-start-radius": { - "logical-alias-for": [ - "border-top-left-radius", - "border-bottom-left-radius", - "border-top-right-radius", - "border-bottom-right-radius" - ], + "logical-alias-for": { + "group": "border-radius", + "mapping": "start-start" + }, "max-values": 1 }, "border-style": { @@ -1997,10 +1965,10 @@ ] }, "inline-size": { - "logical-alias-for": [ - "width", - "height" - ], + "logical-alias-for": { + "group": "size", + "mapping": "inline-size" + }, "initial": "auto", "max-values": 1 }, @@ -2040,21 +2008,17 @@ "percentages-resolve-to": "length" }, "inset-block-end": { - "logical-alias-for": [ - "top", - "right", - "bottom", - "left" - ], + "logical-alias-for": { + "group": "inset", + "mapping": "block-end" + }, "max-values": 1 }, "inset-block-start": { - "logical-alias-for": [ - "top", - "right", - "bottom", - "left" - ], + "logical-alias-for": { + "group": "inset", + "mapping": "block-start" + }, "max-values": 1 }, "inset-inline": { @@ -2074,21 +2038,17 @@ "percentages-resolve-to": "length" }, "inset-inline-end": { - "logical-alias-for": [ - "top", - "right", - "bottom", - "left" - ], + "logical-alias-for": { + "group": "inset", + "mapping": "inline-end" + }, "max-values": 1 }, "inset-inline-start": { - "logical-alias-for": [ - "top", - "right", - "bottom", - "left" - ], + "logical-alias-for": { + "group": "inset", + "mapping": "inline-start" + }, "max-values": 1 }, "isolation": { @@ -2245,20 +2205,16 @@ "percentages-resolve-to": "length" }, "margin-block-end": { - "logical-alias-for": [ - "margin-top", - "margin-right", - "margin-bottom", - "margin-left" - ] + "logical-alias-for": { + "group": "margin", + "mapping": "block-end" + } }, "margin-block-start": { - "logical-alias-for": [ - "margin-top", - "margin-right", - "margin-bottom", - "margin-left" - ] + "logical-alias-for": { + "group": "margin", + "mapping": "block-start" + } }, "margin-bottom": { "animation-type": "by-computed-value", @@ -2293,20 +2249,16 @@ "percentages-resolve-to": "length" }, "margin-inline-end": { - "logical-alias-for": [ - "margin-top", - "margin-right", - "margin-bottom", - "margin-left" - ] + "logical-alias-for": { + "group": "margin", + "mapping": "inline-end" + } }, "margin-inline-start": { - "logical-alias-for": [ - "margin-top", - "margin-right", - "margin-bottom", - "margin-left" - ] + "logical-alias-for": { + "group": "margin", + "mapping": "inline-start" + } }, "margin-left": { "animation-type": "by-computed-value", @@ -2416,10 +2368,10 @@ ] }, "max-block-size": { - "logical-alias-for": [ - "max-height", - "max-width" - ], + "logical-alias-for": { + "group": "max-size", + "mapping": "block-size" + }, "initial": "none" }, "max-height": { @@ -2442,10 +2394,10 @@ ] }, "max-inline-size": { - "logical-alias-for": [ - "max-width", - "max-height" - ], + "logical-alias-for": { + "group": "max-size", + "mapping": "inline-size" + }, "initial": "none" }, "max-width": { @@ -2468,10 +2420,10 @@ ] }, "min-block-size": { - "logical-alias-for": [ - "min-height", - "min-width" - ], + "logical-alias-for": { + "group": "min-size", + "mapping": "block-size" + }, "initial": "0" }, "min-height": { @@ -2494,10 +2446,10 @@ ] }, "min-inline-size": { - "logical-alias-for": [ - "min-width", - "min-height" - ], + "logical-alias-for": { + "group": "min-size", + "mapping": "inline-size" + }, "initial": "0" }, "min-width": { @@ -2678,20 +2630,16 @@ "percentages-resolve-to": "length" }, "padding-block-end": { - "logical-alias-for": [ - "padding-top", - "padding-right", - "padding-bottom", - "padding-left" - ] + "logical-alias-for": { + "group": "padding", + "mapping": "block-end" + } }, "padding-block-start": { - "logical-alias-for": [ - "padding-top", - "padding-right", - "padding-bottom", - "padding-left" - ] + "logical-alias-for": { + "group": "padding", + "mapping": "block-start" + } }, "padding-bottom": { "animation-type": "by-computed-value", @@ -2720,20 +2668,16 @@ "percentages-resolve-to": "length" }, "padding-inline-end": { - "logical-alias-for": [ - "padding-top", - "padding-right", - "padding-bottom", - "padding-left" - ] + "logical-alias-for": { + "group": "padding", + "mapping": "inline-end" + } }, "padding-inline-start": { - "logical-alias-for": [ - "padding-top", - "padding-right", - "padding-bottom", - "padding-left" - ] + "logical-alias-for": { + "group": "padding", + "mapping": "inline-start" + } }, "padding-left": { "animation-type": "by-computed-value", diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index cfce7ad7fdc..425a712c838 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -888,395 +888,6 @@ void StyleComputer::for_each_property_expanding_shorthands(PropertyID property_i set_longhand_property(property_id, value); } -// https://drafts.csswg.org/css-writing-modes-4/#logical-to-physical -PropertyID StyleComputer::map_logical_alias_to_physical_property_id(PropertyID property_id, LogicalAliasMappingContext mapping_context) -{ - // 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) { - case PropertyID::BlockSize: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::Height; - return PropertyID::Width; - case PropertyID::BorderBlockEndColor: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::BorderBottomColor; - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl)) - return PropertyID::BorderLeftColor; - return PropertyID::BorderRightColor; - case PropertyID::BorderBlockEndStyle: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::BorderBottomStyle; - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl)) - return PropertyID::BorderLeftStyle; - return PropertyID::BorderRightStyle; - case PropertyID::BorderBlockEndWidth: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::BorderBottomWidth; - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl)) - return PropertyID::BorderLeftWidth; - return PropertyID::BorderRightWidth; - case PropertyID::BorderBlockStartColor: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::BorderTopColor; - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl)) - return PropertyID::BorderRightColor; - return PropertyID::BorderLeftColor; - case PropertyID::BorderBlockStartStyle: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::BorderTopStyle; - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl)) - return PropertyID::BorderRightStyle; - return PropertyID::BorderLeftStyle; - case PropertyID::BorderBlockStartWidth: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::BorderTopWidth; - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl)) - return PropertyID::BorderRightWidth; - return PropertyID::BorderLeftWidth; - case PropertyID::BorderEndEndRadius: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderBottomRightRadius; - return PropertyID::BorderBottomLeftRadius; - } - - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl)) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderBottomLeftRadius; - return PropertyID::BorderTopLeftRadius; - } - - if (mapping_context.writing_mode == WritingMode::VerticalLr) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderBottomRightRadius; - return PropertyID::BorderTopRightRadius; - } - - if (used_direction == Direction::Ltr) - return PropertyID::BorderTopRightRadius; - return PropertyID::BorderBottomRightRadius; - case PropertyID::BorderEndStartRadius: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderBottomLeftRadius; - return PropertyID::BorderBottomRightRadius; - } - - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl)) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderTopLeftRadius; - return PropertyID::BorderBottomLeftRadius; - } - - if (mapping_context.writing_mode == WritingMode::VerticalLr) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderTopRightRadius; - return PropertyID::BorderBottomRightRadius; - } - - if (used_direction == Direction::Ltr) - return PropertyID::BorderBottomRightRadius; - return PropertyID::BorderTopRightRadius; - case PropertyID::BorderInlineStartColor: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderLeftColor; - return PropertyID::BorderRightColor; - } - - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl, WritingMode::VerticalLr)) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderTopColor; - return PropertyID::BorderBottomColor; - } - - if (used_direction == Direction::Ltr) - return PropertyID::BorderBottomColor; - return PropertyID::BorderTopColor; - case PropertyID::BorderInlineStartStyle: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderLeftStyle; - return PropertyID::BorderRightStyle; - } - - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl, WritingMode::VerticalLr)) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderTopStyle; - return PropertyID::BorderBottomStyle; - } - - if (used_direction == Direction::Ltr) - return PropertyID::BorderBottomStyle; - return PropertyID::BorderTopStyle; - - case PropertyID::BorderInlineStartWidth: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderLeftWidth; - return PropertyID::BorderRightWidth; - } - - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl, WritingMode::VerticalLr)) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderTopWidth; - return PropertyID::BorderBottomWidth; - } - - if (used_direction == Direction::Ltr) - return PropertyID::BorderBottomWidth; - return PropertyID::BorderTopWidth; - case PropertyID::BorderInlineEndColor: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderRightColor; - return PropertyID::BorderLeftColor; - } - - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl, WritingMode::VerticalLr)) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderBottomColor; - return PropertyID::BorderTopColor; - } - - if (used_direction == Direction::Ltr) - return PropertyID::BorderTopColor; - return PropertyID::BorderBottomColor; - case PropertyID::BorderInlineEndStyle: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderRightStyle; - return PropertyID::BorderLeftStyle; - } - - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl, WritingMode::VerticalLr)) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderBottomStyle; - return PropertyID::BorderTopStyle; - } - - if (used_direction == Direction::Ltr) - return PropertyID::BorderTopStyle; - return PropertyID::BorderBottomStyle; - case PropertyID::BorderInlineEndWidth: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderRightWidth; - return PropertyID::BorderLeftWidth; - } - - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl, WritingMode::VerticalLr)) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderBottomWidth; - return PropertyID::BorderTopWidth; - } - - if (used_direction == Direction::Ltr) - return PropertyID::BorderTopWidth; - return PropertyID::BorderBottomWidth; - case PropertyID::BorderStartEndRadius: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderTopRightRadius; - return PropertyID::BorderTopLeftRadius; - } - - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl)) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderBottomRightRadius; - return PropertyID::BorderTopRightRadius; - } - - if (mapping_context.writing_mode == WritingMode::VerticalLr) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderBottomLeftRadius; - return PropertyID::BorderTopLeftRadius; - } - - if (used_direction == Direction::Ltr) - return PropertyID::BorderTopLeftRadius; - return PropertyID::BorderBottomLeftRadius; - case PropertyID::BorderStartStartRadius: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderTopLeftRadius; - return PropertyID::BorderTopRightRadius; - } - - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl)) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderTopRightRadius; - return PropertyID::BorderBottomRightRadius; - } - - if (mapping_context.writing_mode == WritingMode::VerticalLr) { - if (used_direction == Direction::Ltr) - return PropertyID::BorderTopLeftRadius; - return PropertyID::BorderBottomLeftRadius; - } - if (used_direction == Direction::Ltr) - return PropertyID::BorderBottomLeftRadius; - return PropertyID::BorderTopLeftRadius; - case PropertyID::MarginBlockStart: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::MarginTop; - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl)) - return PropertyID::MarginRight; - return PropertyID::MarginLeft; - case PropertyID::MarginBlockEnd: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::MarginBottom; - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl)) - return PropertyID::MarginLeft; - return PropertyID::MarginRight; - case PropertyID::MarginInlineStart: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) { - if (used_direction == Direction::Ltr) - return PropertyID::MarginLeft; - return PropertyID::MarginRight; - } - - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl, WritingMode::VerticalLr)) { - if (used_direction == Direction::Ltr) - return PropertyID::MarginTop; - return PropertyID::MarginBottom; - } - - if (used_direction == Direction::Ltr) - return PropertyID::MarginBottom; - return PropertyID::MarginTop; - case PropertyID::MarginInlineEnd: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) { - if (used_direction == Direction::Ltr) - return PropertyID::MarginRight; - return PropertyID::MarginLeft; - } - - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl, WritingMode::VerticalLr)) { - if (used_direction == Direction::Ltr) - return PropertyID::MarginBottom; - return PropertyID::MarginTop; - } - - if (used_direction == Direction::Ltr) - return PropertyID::MarginTop; - return PropertyID::MarginBottom; - case PropertyID::MaxBlockSize: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::MaxHeight; - return PropertyID::MaxWidth; - case PropertyID::MaxInlineSize: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::MaxWidth; - return PropertyID::MaxHeight; - case PropertyID::MinBlockSize: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::MinHeight; - return PropertyID::MinWidth; - case PropertyID::MinInlineSize: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::MinWidth; - return PropertyID::MinHeight; - case PropertyID::PaddingBlockStart: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::PaddingTop; - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl)) - return PropertyID::PaddingRight; - return PropertyID::PaddingLeft; - case PropertyID::PaddingBlockEnd: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::PaddingBottom; - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl)) - return PropertyID::PaddingLeft; - return PropertyID::PaddingRight; - case PropertyID::PaddingInlineStart: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) { - if (used_direction == Direction::Ltr) - return PropertyID::PaddingLeft; - return PropertyID::PaddingRight; - } - - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl, WritingMode::VerticalLr)) { - if (used_direction == Direction::Ltr) - return PropertyID::PaddingTop; - return PropertyID::PaddingBottom; - } - - if (used_direction == Direction::Ltr) - return PropertyID::PaddingBottom; - return PropertyID::PaddingTop; - case PropertyID::PaddingInlineEnd: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) { - if (used_direction == Direction::Ltr) - return PropertyID::PaddingRight; - return PropertyID::PaddingLeft; - } - - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl, WritingMode::VerticalLr)) { - if (used_direction == Direction::Ltr) - return PropertyID::PaddingBottom; - return PropertyID::PaddingTop; - } - - if (used_direction == Direction::Ltr) - return PropertyID::PaddingTop; - return PropertyID::PaddingBottom; - case PropertyID::InlineSize: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::Width; - return PropertyID::Height; - case PropertyID::InsetBlockStart: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::Top; - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl)) - return PropertyID::Right; - return PropertyID::Left; - case PropertyID::InsetBlockEnd: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) - return PropertyID::Bottom; - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl)) - return PropertyID::Left; - return PropertyID::Right; - case PropertyID::InsetInlineStart: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) { - if (used_direction == Direction::Ltr) - return PropertyID::Left; - return PropertyID::Right; - } - - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl, WritingMode::VerticalLr)) { - if (used_direction == Direction::Ltr) - return PropertyID::Top; - return PropertyID::Bottom; - } - - if (used_direction == Direction::Ltr) - return PropertyID::Bottom; - return PropertyID::Top; - case PropertyID::InsetInlineEnd: - if (mapping_context.writing_mode == WritingMode::HorizontalTb) { - if (used_direction == Direction::Ltr) - return PropertyID::Right; - return PropertyID::Left; - } - - if (first_is_one_of(mapping_context.writing_mode, WritingMode::VerticalRl, WritingMode::SidewaysRl, WritingMode::VerticalLr)) { - if (used_direction == Direction::Ltr) - return PropertyID::Bottom; - return PropertyID::Top; - } - - if (used_direction == Direction::Ltr) - return PropertyID::Top; - return PropertyID::Bottom; - default: - VERIFY(!property_is_logical_alias(property_id)); - return property_id; - } -} - void StyleComputer::cascade_declarations( CascadedProperties& cascaded_properties, DOM::Element& element, @@ -1334,7 +945,7 @@ void StyleComputer::cascade_declarations( if (property_is_logical_alias(longhand_id)) { if (!logical_alias_mapping_context.has_value()) return; - physical_property_id = map_logical_alias_to_physical_property_id(longhand_id, logical_alias_mapping_context.value()); + physical_property_id = map_logical_alias_to_physical_property(longhand_id, logical_alias_mapping_context.value()); } else { physical_property_id = longhand_id; } @@ -1523,7 +1134,7 @@ void StyleComputer::collect_animation_into(DOM::Element& element, Optionalas_unresolved()); for_each_property_expanding_shorthands(property_id, *style_value, [&](PropertyID longhand_id, CSSStyleValue const& longhand_value) { - auto physical_longhand_id = map_logical_alias_to_physical_property_id(longhand_id, LogicalAliasMappingContext { computed_properties.writing_mode(), computed_properties.direction() }); + auto physical_longhand_id = map_logical_alias_to_physical_property(longhand_id, LogicalAliasMappingContext { computed_properties.writing_mode(), computed_properties.direction() }); auto physical_longhand_id_bitmap_index = to_underlying(physical_longhand_id) - to_underlying(first_longhand_property_id); // Don't overwrite values if this is the result of a UseInitial @@ -2532,7 +2143,7 @@ void StyleComputer::compute_font(ComputedProperties& style, DOM::Element const* } } -StyleComputer::LogicalAliasMappingContext StyleComputer::compute_logical_alias_mapping_context(DOM::Element& element, Optional pseudo_element, ComputeStyleMode mode) const +LogicalAliasMappingContext StyleComputer::compute_logical_alias_mapping_context(DOM::Element& element, Optional pseudo_element, ComputeStyleMode mode) const { auto normalize_value = [&](auto property_id, auto value) { if (!value || value->is_inherit() || value->is_unset()) { diff --git a/Libraries/LibWeb/CSS/StyleComputer.h b/Libraries/LibWeb/CSS/StyleComputer.h index 0df8ee14ba6..4562b83e93f 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Libraries/LibWeb/CSS/StyleComputer.h @@ -131,13 +131,6 @@ public: static void for_each_property_expanding_shorthands(PropertyID, CSSStyleValue const&, Function const& set_longhand_property); static NonnullRefPtr get_inherit_value(CSS::PropertyID, DOM::Element const*, Optional = {}); - struct LogicalAliasMappingContext { - CSS::WritingMode writing_mode; - CSS::Direction direction; - // TODO: text-orientation - }; - static PropertyID map_logical_alias_to_physical_property_id(PropertyID, LogicalAliasMappingContext); - static Optional user_agent_style_sheet_source(StringView name); explicit StyleComputer(DOM::Document&); diff --git a/Meta/CMake/libweb_generators.cmake b/Meta/CMake/libweb_generators.cmake index 58fcfdc1534..4479156a342 100644 --- a/Meta/CMake/libweb_generators.cmake +++ b/Meta/CMake/libweb_generators.cmake @@ -44,7 +44,8 @@ function (generate_css_implementation) "${LIBWEB_INPUT_FOLDER}/CSS/Properties.json" "CSS/PropertyID.h" "CSS/PropertyID.cpp" - arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/Properties.json" + arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/Properties.json" -g "${LIBWEB_INPUT_FOLDER}/CSS/LogicalPropertyGroups.json" + dependencies "${LIBWEB_INPUT_FOLDER}/CSS/LogicalPropertyGroups.json" ) invoke_cpp_generator( diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPropertyID.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPropertyID.cpp index 030fdef78ce..d587622c963 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPropertyID.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/GenerateCSSPropertyID.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2021-2023, Sam Atkins + * Copyright (c) 2021-2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -14,10 +14,10 @@ #include #include -void replace_logical_aliases(JsonObject& properties); +void replace_logical_aliases(JsonObject& properties, JsonObject& logical_property_groups); void populate_all_property_longhands(JsonObject& properties); -ErrorOr generate_header_file(JsonObject& properties, Core::File& file); -ErrorOr generate_implementation_file(JsonObject& properties, Core::File& file); +ErrorOr generate_header_file(JsonObject& properties, JsonObject& logical_property_groups, Core::File& file); +ErrorOr generate_implementation_file(JsonObject& properties, JsonObject& logical_property_groups, Core::File& file); void generate_bounds_checking_function(JsonObject& properties, SourceGenerator& parent_generator, StringView css_type_name, StringView type_name, Optional default_unit_name = {}, Optional value_getter = {}); bool is_animatable_property(JsonObject& properties, StringView property_name); @@ -60,50 +60,79 @@ ErrorOr ladybird_main(Main::Arguments arguments) StringView generated_header_path; StringView generated_implementation_path; StringView properties_json_path; + StringView groups_json_path; 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"); - args_parser.add_option(properties_json_path, "Path to the JSON file to read from", "json-path", 'j', "json-path"); + args_parser.add_option(properties_json_path, "Path to the properties JSON file to read from", "properties-json-path", 'j', "properties-json-path"); + 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"); args_parser.parse(arguments); - auto json = TRY(read_entire_file_as_json(properties_json_path)); - VERIFY(json.is_object()); - auto properties = json.as_object(); + auto read_json_object = [](auto& path) -> ErrorOr { + auto json = TRY(read_entire_file_as_json(path)); + VERIFY(json.is_object()); - // Check we're in alphabetical order - String most_recent_name; - properties.for_each_member([&](auto& name, auto&) { - if (name < most_recent_name) { - warnln("`{}` is in the wrong position in `{}`. Please keep this list alphabetical!", name, properties_json_path); - VERIFY_NOT_REACHED(); - } - most_recent_name = name; - }); + // 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; + }); - replace_logical_aliases(properties); + return json.as_object(); + }; + + auto properties = TRY(read_json_object(properties_json_path)); + auto logical_property_groups = TRY(read_json_object(groups_json_path)); + + replace_logical_aliases(properties, logical_property_groups); populate_all_property_longhands(properties); 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(properties, *generated_header_file)); - TRY(generate_implementation_file(properties, *generated_implementation_file)); + TRY(generate_header_file(properties, logical_property_groups, *generated_header_file)); + TRY(generate_implementation_file(properties, logical_property_groups, *generated_implementation_file)); return 0; } -void replace_logical_aliases(JsonObject& properties) +void replace_logical_aliases(JsonObject& properties, JsonObject& logical_property_groups) { - AK::HashMap logical_aliases; - properties.for_each_member([&](auto& name, auto& value) { + // Grab the first property in each logical group, to use as the template + HashMap 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 logical_aliases; + properties.for_each_member([&](String const& name, JsonValue const& value) { VERIFY(value.is_object()); auto const& value_as_object = value.as_object(); - auto const logical_alias_for = value_as_object.get_array("logical-alias-for"sv); + auto const logical_alias_for = value_as_object.get_object("logical-alias-for"sv); if (logical_alias_for.has_value()) { - auto const& aliased_properties = logical_alias_for.value(); - for (auto const& aliased_property : aliased_properties.values()) { - logical_aliases.set(name, aliased_property.as_string()); + 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(); } } }); @@ -142,7 +171,7 @@ void populate_all_property_longhands(JsonObject& properties) }); } -ErrorOr generate_header_file(JsonObject& properties, Core::File& file) +ErrorOr generate_header_file(JsonObject& properties, JsonObject&, Core::File& file) { StringBuilder builder; SourceGenerator generator { builder }; @@ -154,6 +183,7 @@ ErrorOr generate_header_file(JsonObject& properties, Core::File& file) #include #include #include +#include #include namespace Web::CSS { @@ -313,7 +343,13 @@ enum class Quirk { }; bool property_has_quirk(PropertyID, Quirk); +struct LogicalAliasMappingContext { + WritingMode writing_mode; + Direction direction; + // TODO: text-orientation +}; bool property_is_logical_alias(PropertyID); +PropertyID map_logical_alias_to_physical_property(PropertyID logical_property_id, LogicalAliasMappingContext const&); } // namespace Web::CSS @@ -428,7 +464,7 @@ bool property_accepts_@css_type_name@(PropertyID property_id, [[maybe_unused]] @ )~~~"); } -ErrorOr generate_implementation_file(JsonObject& properties, Core::File& file) +ErrorOr generate_implementation_file(JsonObject& properties, JsonObject& logical_property_groups, Core::File& file) { StringBuilder builder; SourceGenerator generator { builder }; @@ -1315,6 +1351,255 @@ bool property_is_logical_alias(PropertyID property_id) return false; } } +)~~~"); + 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; + } +} )~~~"); generator.append(R"~~~(