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.
This commit is contained in:
Sam Atkins 2025-07-03 14:31:26 +01:00 committed by Andrew Kaster
commit 69d4811ef7
Notes: github-actions[bot] 2025-07-08 17:46:32 +00:00
9 changed files with 561 additions and 646 deletions

View file

@ -20,14 +20,14 @@ 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.) (Note that required fields are not required on properties with `legacy-alias-for` or `logical-alias-for` set.)
| Field | Required | Default | Description | Generated functions | | 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-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)` | | `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)` | | `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)` | | `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<CSSStyleValue const> property_initial_value(PropertyID)` | | `initial` | Yes | | String. The property's initial value if it is not specified. | `NonnullRefPtr<CSSStyleValue const> property_initial_value(PropertyID)` |
| `legacy-alias-for` | No | Nothing | String. The name of a property this is an alias for. See below. | | | `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);` | | `logical-alias-for` | No | Nothing | An object. See below. | `bool property_is_logical_alias(PropertyID);`<br/>`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<PropertyID> longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> expanded_longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> shorthands_for_longhand(PropertyID)` | | `longhands` | No | `[]` | Array of strings. If this is a shorthand, these are the property names that it expands out into. | `Vector<PropertyID> longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> expanded_longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> 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)` | | `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<ValueType> property_resolves_percentages_relative_to(PropertyID)` | | `percentages-resolve-to` | No | Nothing | String. What type percentages get resolved to. eg, for `width` percentages are resolved to `length` values. | `Optional<ValueType> property_resolves_percentages_relative_to(PropertyID)` |
@ -47,15 +47,25 @@ The [Web Animations spec](https://www.w3.org/TR/web-animations/#animation-type)
| repeatable list | `repeatable-list` | | repeatable list | `repeatable-list` |
| (See prose) | `custom` | | (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,
- [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. 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`. 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
### `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. (`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.
`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` ### `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 example `width` can take any non-negative length, so it has `"length [0,∞]"` in its `valid-types` array.
For `<custom-ident>`s, the excluded identifiers are placed within `![]`, for example `"custom-ident ![all,none]"`. For `<custom-ident>`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.json
Descriptors are basically properties, but for at-rules instead of style. The overall structure is a JSON object, with Descriptors are basically properties, but for at-rules instead of style. The overall structure is a JSON object, with

View file

@ -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 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 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 ## Parsing

View file

@ -700,7 +700,7 @@ RefPtr<CSSStyleValue const> CSSStyleProperties::style_value_for_computed_propert
return style_value_for_computed_property( return style_value_for_computed_property(
layout_node, 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". // A limited number of properties have special rules for producing their "resolved value".

View file

@ -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"
}
}

View file

@ -481,10 +481,10 @@
"percentages-resolve-to": "length" "percentages-resolve-to": "length"
}, },
"block-size": { "block-size": {
"logical-alias-for": [ "logical-alias-for": {
"height", "group": "size",
"width" "mapping": "block-size"
], },
"initial": "auto", "initial": "auto",
"max-values": 1 "max-values": 1
}, },
@ -515,30 +515,24 @@
] ]
}, },
"border-block-end-color": { "border-block-end-color": {
"logical-alias-for": [ "logical-alias-for": {
"border-top-color", "group": "border-color",
"border-right-color", "mapping": "block-end"
"border-bottom-color", },
"border-left-color"
],
"max-values": 1 "max-values": 1
}, },
"border-block-end-style": { "border-block-end-style": {
"logical-alias-for": [ "logical-alias-for": {
"border-top-style", "group": "border-style",
"border-right-style", "mapping": "block-end"
"border-bottom-style", },
"border-left-style"
],
"max-values": 1 "max-values": 1
}, },
"border-block-end-width": { "border-block-end-width": {
"logical-alias-for": [ "logical-alias-for": {
"border-top-width", "group": "border-width",
"border-right-width", "mapping": "block-end"
"border-bottom-width", },
"border-left-width"
],
"max-values": 1 "max-values": 1
}, },
"border-block-start": { "border-block-start": {
@ -551,30 +545,24 @@
] ]
}, },
"border-block-start-color": { "border-block-start-color": {
"logical-alias-for": [ "logical-alias-for": {
"border-top-color", "group": "border-color",
"border-right-color", "mapping": "block-start"
"border-bottom-color", },
"border-left-color"
],
"max-values": 1 "max-values": 1
}, },
"border-block-start-style": { "border-block-start-style": {
"logical-alias-for": [ "logical-alias-for": {
"border-top-style", "group": "border-style",
"border-right-style", "mapping": "block-start"
"border-bottom-style", },
"border-left-style"
],
"max-values": 1 "max-values": 1
}, },
"border-block-start-width": { "border-block-start-width": {
"logical-alias-for": [ "logical-alias-for": {
"border-top-width", "group": "border-width",
"border-right-width", "mapping": "block-start"
"border-bottom-width", },
"border-left-width"
],
"max-values": 1 "max-values": 1
}, },
"border-block-style": { "border-block-style": {
@ -688,21 +676,17 @@
] ]
}, },
"border-end-end-radius": { "border-end-end-radius": {
"logical-alias-for": [ "logical-alias-for": {
"border-top-left-radius", "group": "border-radius",
"border-bottom-left-radius", "mapping": "end-end"
"border-top-right-radius", },
"border-bottom-right-radius"
],
"max-values": 1 "max-values": 1
}, },
"border-end-start-radius": { "border-end-start-radius": {
"logical-alias-for": [ "logical-alias-for": {
"border-top-left-radius", "group": "border-radius",
"border-bottom-left-radius", "mapping": "end-start"
"border-top-right-radius", },
"border-bottom-right-radius"
],
"max-values": 1 "max-values": 1
}, },
"border-image": { "border-image": {
@ -795,30 +779,24 @@
] ]
}, },
"border-inline-end-color": { "border-inline-end-color": {
"logical-alias-for": [ "logical-alias-for": {
"border-top-color", "group": "border-color",
"border-right-color", "mapping": "inline-end"
"border-bottom-color", },
"border-left-color"
],
"max-values": 1 "max-values": 1
}, },
"border-inline-end-style": { "border-inline-end-style": {
"logical-alias-for": [ "logical-alias-for": {
"border-top-style", "group": "border-style",
"border-right-style", "mapping": "inline-end"
"border-bottom-style", },
"border-left-style"
],
"max-values": 1 "max-values": 1
}, },
"border-inline-end-width": { "border-inline-end-width": {
"logical-alias-for": [ "logical-alias-for": {
"border-top-width", "group": "border-width",
"border-right-width", "mapping": "inline-end"
"border-bottom-width", },
"border-left-width"
],
"max-values": 1 "max-values": 1
}, },
"border-inline-start": { "border-inline-start": {
@ -831,30 +809,24 @@
] ]
}, },
"border-inline-start-color": { "border-inline-start-color": {
"logical-alias-for": [ "logical-alias-for": {
"border-top-color", "group": "border-color",
"border-right-color", "mapping": "inline-start"
"border-bottom-color", },
"border-left-color"
],
"max-values": 1 "max-values": 1
}, },
"border-inline-start-style": { "border-inline-start-style": {
"logical-alias-for": [ "logical-alias-for": {
"border-top-style", "group": "border-style",
"border-right-style", "mapping": "inline-start"
"border-bottom-style", },
"border-left-style"
],
"max-values": 1 "max-values": 1
}, },
"border-inline-start-width": { "border-inline-start-width": {
"logical-alias-for": [ "logical-alias-for": {
"border-top-width", "group": "border-width",
"border-right-width", "mapping": "inline-start"
"border-bottom-width", },
"border-left-width"
],
"max-values": 1 "max-values": 1
}, },
"border-inline-style": { "border-inline-style": {
@ -992,21 +964,17 @@
] ]
}, },
"border-start-end-radius": { "border-start-end-radius": {
"logical-alias-for": [ "logical-alias-for": {
"border-top-left-radius", "group": "border-radius",
"border-bottom-left-radius", "mapping": "start-end"
"border-top-right-radius", },
"border-bottom-right-radius"
],
"max-values": 1 "max-values": 1
}, },
"border-start-start-radius": { "border-start-start-radius": {
"logical-alias-for": [ "logical-alias-for": {
"border-top-left-radius", "group": "border-radius",
"border-bottom-left-radius", "mapping": "start-start"
"border-top-right-radius", },
"border-bottom-right-radius"
],
"max-values": 1 "max-values": 1
}, },
"border-style": { "border-style": {
@ -1997,10 +1965,10 @@
] ]
}, },
"inline-size": { "inline-size": {
"logical-alias-for": [ "logical-alias-for": {
"width", "group": "size",
"height" "mapping": "inline-size"
], },
"initial": "auto", "initial": "auto",
"max-values": 1 "max-values": 1
}, },
@ -2040,21 +2008,17 @@
"percentages-resolve-to": "length" "percentages-resolve-to": "length"
}, },
"inset-block-end": { "inset-block-end": {
"logical-alias-for": [ "logical-alias-for": {
"top", "group": "inset",
"right", "mapping": "block-end"
"bottom", },
"left"
],
"max-values": 1 "max-values": 1
}, },
"inset-block-start": { "inset-block-start": {
"logical-alias-for": [ "logical-alias-for": {
"top", "group": "inset",
"right", "mapping": "block-start"
"bottom", },
"left"
],
"max-values": 1 "max-values": 1
}, },
"inset-inline": { "inset-inline": {
@ -2074,21 +2038,17 @@
"percentages-resolve-to": "length" "percentages-resolve-to": "length"
}, },
"inset-inline-end": { "inset-inline-end": {
"logical-alias-for": [ "logical-alias-for": {
"top", "group": "inset",
"right", "mapping": "inline-end"
"bottom", },
"left"
],
"max-values": 1 "max-values": 1
}, },
"inset-inline-start": { "inset-inline-start": {
"logical-alias-for": [ "logical-alias-for": {
"top", "group": "inset",
"right", "mapping": "inline-start"
"bottom", },
"left"
],
"max-values": 1 "max-values": 1
}, },
"isolation": { "isolation": {
@ -2245,20 +2205,16 @@
"percentages-resolve-to": "length" "percentages-resolve-to": "length"
}, },
"margin-block-end": { "margin-block-end": {
"logical-alias-for": [ "logical-alias-for": {
"margin-top", "group": "margin",
"margin-right", "mapping": "block-end"
"margin-bottom", }
"margin-left"
]
}, },
"margin-block-start": { "margin-block-start": {
"logical-alias-for": [ "logical-alias-for": {
"margin-top", "group": "margin",
"margin-right", "mapping": "block-start"
"margin-bottom", }
"margin-left"
]
}, },
"margin-bottom": { "margin-bottom": {
"animation-type": "by-computed-value", "animation-type": "by-computed-value",
@ -2293,20 +2249,16 @@
"percentages-resolve-to": "length" "percentages-resolve-to": "length"
}, },
"margin-inline-end": { "margin-inline-end": {
"logical-alias-for": [ "logical-alias-for": {
"margin-top", "group": "margin",
"margin-right", "mapping": "inline-end"
"margin-bottom", }
"margin-left"
]
}, },
"margin-inline-start": { "margin-inline-start": {
"logical-alias-for": [ "logical-alias-for": {
"margin-top", "group": "margin",
"margin-right", "mapping": "inline-start"
"margin-bottom", }
"margin-left"
]
}, },
"margin-left": { "margin-left": {
"animation-type": "by-computed-value", "animation-type": "by-computed-value",
@ -2416,10 +2368,10 @@
] ]
}, },
"max-block-size": { "max-block-size": {
"logical-alias-for": [ "logical-alias-for": {
"max-height", "group": "max-size",
"max-width" "mapping": "block-size"
], },
"initial": "none" "initial": "none"
}, },
"max-height": { "max-height": {
@ -2442,10 +2394,10 @@
] ]
}, },
"max-inline-size": { "max-inline-size": {
"logical-alias-for": [ "logical-alias-for": {
"max-width", "group": "max-size",
"max-height" "mapping": "inline-size"
], },
"initial": "none" "initial": "none"
}, },
"max-width": { "max-width": {
@ -2468,10 +2420,10 @@
] ]
}, },
"min-block-size": { "min-block-size": {
"logical-alias-for": [ "logical-alias-for": {
"min-height", "group": "min-size",
"min-width" "mapping": "block-size"
], },
"initial": "0" "initial": "0"
}, },
"min-height": { "min-height": {
@ -2494,10 +2446,10 @@
] ]
}, },
"min-inline-size": { "min-inline-size": {
"logical-alias-for": [ "logical-alias-for": {
"min-width", "group": "min-size",
"min-height" "mapping": "inline-size"
], },
"initial": "0" "initial": "0"
}, },
"min-width": { "min-width": {
@ -2678,20 +2630,16 @@
"percentages-resolve-to": "length" "percentages-resolve-to": "length"
}, },
"padding-block-end": { "padding-block-end": {
"logical-alias-for": [ "logical-alias-for": {
"padding-top", "group": "padding",
"padding-right", "mapping": "block-end"
"padding-bottom", }
"padding-left"
]
}, },
"padding-block-start": { "padding-block-start": {
"logical-alias-for": [ "logical-alias-for": {
"padding-top", "group": "padding",
"padding-right", "mapping": "block-start"
"padding-bottom", }
"padding-left"
]
}, },
"padding-bottom": { "padding-bottom": {
"animation-type": "by-computed-value", "animation-type": "by-computed-value",
@ -2720,20 +2668,16 @@
"percentages-resolve-to": "length" "percentages-resolve-to": "length"
}, },
"padding-inline-end": { "padding-inline-end": {
"logical-alias-for": [ "logical-alias-for": {
"padding-top", "group": "padding",
"padding-right", "mapping": "inline-end"
"padding-bottom", }
"padding-left"
]
}, },
"padding-inline-start": { "padding-inline-start": {
"logical-alias-for": [ "logical-alias-for": {
"padding-top", "group": "padding",
"padding-right", "mapping": "inline-start"
"padding-bottom", }
"padding-left"
]
}, },
"padding-left": { "padding-left": {
"animation-type": "by-computed-value", "animation-type": "by-computed-value",

View file

@ -888,395 +888,6 @@ void StyleComputer::for_each_property_expanding_shorthands(PropertyID property_i
set_longhand_property(property_id, value); 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( void StyleComputer::cascade_declarations(
CascadedProperties& cascaded_properties, CascadedProperties& cascaded_properties,
DOM::Element& element, DOM::Element& element,
@ -1334,7 +945,7 @@ void StyleComputer::cascade_declarations(
if (property_is_logical_alias(longhand_id)) { if (property_is_logical_alias(longhand_id)) {
if (!logical_alias_mapping_context.has_value()) if (!logical_alias_mapping_context.has_value())
return; 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 { } else {
physical_property_id = longhand_id; physical_property_id = longhand_id;
} }
@ -1523,7 +1134,7 @@ void StyleComputer::collect_animation_into(DOM::Element& element, Optional<CSS::
style_value = Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { element.document() }, element, pseudo_element, property_id, style_value->as_unresolved()); style_value = Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { element.document() }, element, pseudo_element, property_id, style_value->as_unresolved());
for_each_property_expanding_shorthands(property_id, *style_value, [&](PropertyID longhand_id, CSSStyleValue const& longhand_value) { 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); 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 // 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<CSS::PseudoElement> pseudo_element, ComputeStyleMode mode) const LogicalAliasMappingContext StyleComputer::compute_logical_alias_mapping_context(DOM::Element& element, Optional<PseudoElement> pseudo_element, ComputeStyleMode mode) const
{ {
auto normalize_value = [&](auto property_id, auto value) { auto normalize_value = [&](auto property_id, auto value) {
if (!value || value->is_inherit() || value->is_unset()) { if (!value || value->is_inherit() || value->is_unset()) {

View file

@ -131,13 +131,6 @@ public:
static void for_each_property_expanding_shorthands(PropertyID, CSSStyleValue const&, Function<void(PropertyID, CSSStyleValue const&)> const& set_longhand_property); static void for_each_property_expanding_shorthands(PropertyID, CSSStyleValue const&, Function<void(PropertyID, CSSStyleValue const&)> const& set_longhand_property);
static NonnullRefPtr<CSSStyleValue const> get_inherit_value(CSS::PropertyID, DOM::Element const*, Optional<CSS::PseudoElement> = {}); static NonnullRefPtr<CSSStyleValue const> get_inherit_value(CSS::PropertyID, DOM::Element const*, Optional<CSS::PseudoElement> = {});
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<String> user_agent_style_sheet_source(StringView name); static Optional<String> user_agent_style_sheet_source(StringView name);
explicit StyleComputer(DOM::Document&); explicit StyleComputer(DOM::Document&);

View file

@ -44,7 +44,8 @@ function (generate_css_implementation)
"${LIBWEB_INPUT_FOLDER}/CSS/Properties.json" "${LIBWEB_INPUT_FOLDER}/CSS/Properties.json"
"CSS/PropertyID.h" "CSS/PropertyID.h"
"CSS/PropertyID.cpp" "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( invoke_cpp_generator(

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org> * Copyright (c) 2018-2020, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org> * Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -14,10 +14,10 @@
#include <LibCore/ArgsParser.h> #include <LibCore/ArgsParser.h>
#include <LibMain/Main.h> #include <LibMain/Main.h>
void replace_logical_aliases(JsonObject& properties); void replace_logical_aliases(JsonObject& properties, JsonObject& logical_property_groups);
void populate_all_property_longhands(JsonObject& properties); void populate_all_property_longhands(JsonObject& properties);
ErrorOr<void> generate_header_file(JsonObject& properties, Core::File& file); ErrorOr<void> generate_header_file(JsonObject& properties, JsonObject& logical_property_groups, Core::File& file);
ErrorOr<void> generate_implementation_file(JsonObject& properties, Core::File& file); ErrorOr<void> 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<StringView> default_unit_name = {}, Optional<StringView> value_getter = {}); 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 = {});
bool is_animatable_property(JsonObject& properties, StringView property_name); bool is_animatable_property(JsonObject& properties, StringView property_name);
@ -60,50 +60,79 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
StringView generated_header_path; StringView generated_header_path;
StringView generated_implementation_path; StringView generated_implementation_path;
StringView properties_json_path; StringView properties_json_path;
StringView groups_json_path;
Core::ArgsParser args_parser; 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_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(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); args_parser.parse(arguments);
auto json = TRY(read_entire_file_as_json(properties_json_path)); auto read_json_object = [](auto& path) -> ErrorOr<JsonObject> {
auto json = TRY(read_entire_file_as_json(path));
VERIFY(json.is_object()); VERIFY(json.is_object());
auto properties = json.as_object();
// Check we're in alphabetical order // Check we're in alphabetical order
String most_recent_name; String most_recent_name;
properties.for_each_member([&](auto& name, auto&) { json.as_object().for_each_member([&](auto& name, auto&) {
if (name < most_recent_name) { if (name < most_recent_name) {
warnln("`{}` is in the wrong position in `{}`. Please keep this list alphabetical!", name, properties_json_path); warnln("`{}` is in the wrong position in `{}`. Please keep this list alphabetical!", name, path);
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
most_recent_name = name; 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); populate_all_property_longhands(properties);
auto generated_header_file = TRY(Core::File::open(generated_header_path, Core::File::OpenMode::Write)); 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)); 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_header_file(properties, logical_property_groups, *generated_header_file));
TRY(generate_implementation_file(properties, *generated_implementation_file)); TRY(generate_implementation_file(properties, logical_property_groups, *generated_implementation_file));
return 0; return 0;
} }
void replace_logical_aliases(JsonObject& properties) void replace_logical_aliases(JsonObject& properties, JsonObject& logical_property_groups)
{ {
AK::HashMap<String, String> logical_aliases; // Grab the first property in each logical group, to use as the template
properties.for_each_member([&](auto& name, auto& value) { 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) {
VERIFY(value.is_object()); VERIFY(value.is_object());
auto const& value_as_object = value.as_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()) { if (logical_alias_for.has_value()) {
auto const& aliased_properties = logical_alias_for.value(); auto const& group_name = logical_alias_for->get_string("group"sv);
for (auto const& aliased_property : aliased_properties.values()) { if (!group_name.has_value()) {
logical_aliases.set(name, aliased_property.as_string()); 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<void> generate_header_file(JsonObject& properties, Core::File& file) ErrorOr<void> generate_header_file(JsonObject& properties, JsonObject&, Core::File& file)
{ {
StringBuilder builder; StringBuilder builder;
SourceGenerator generator { builder }; SourceGenerator generator { builder };
@ -154,6 +183,7 @@ ErrorOr<void> generate_header_file(JsonObject& properties, Core::File& file)
#include <AK/StringView.h> #include <AK/StringView.h>
#include <AK/Traits.h> #include <AK/Traits.h>
#include <LibJS/Forward.h> #include <LibJS/Forward.h>
#include <LibWeb/CSS/Enums.h>
#include <LibWeb/Forward.h> #include <LibWeb/Forward.h>
namespace Web::CSS { namespace Web::CSS {
@ -313,7 +343,13 @@ enum class Quirk {
}; };
bool property_has_quirk(PropertyID, Quirk); bool property_has_quirk(PropertyID, Quirk);
struct LogicalAliasMappingContext {
WritingMode writing_mode;
Direction direction;
// TODO: text-orientation
};
bool property_is_logical_alias(PropertyID); bool property_is_logical_alias(PropertyID);
PropertyID map_logical_alias_to_physical_property(PropertyID logical_property_id, LogicalAliasMappingContext const&);
} // namespace Web::CSS } // namespace Web::CSS
@ -428,7 +464,7 @@ bool property_accepts_@css_type_name@(PropertyID property_id, [[maybe_unused]] @
)~~~"); )~~~");
} }
ErrorOr<void> generate_implementation_file(JsonObject& properties, Core::File& file) ErrorOr<void> generate_implementation_file(JsonObject& properties, JsonObject& logical_property_groups, Core::File& file)
{ {
StringBuilder builder; StringBuilder builder;
SourceGenerator generator { builder }; SourceGenerator generator { builder };
@ -1315,6 +1351,255 @@ bool property_is_logical_alias(PropertyID property_id)
return false; 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"~~~( generator.append(R"~~~(