LibWeb: Simplify creation of background layers

We can deduplicate some code by using `assemble_coordinated_value_list`,
also moves this to a method in `ComputedProperties` to be in line with
other values
This commit is contained in:
Callum Law 2025-12-08 00:55:32 +13:00 committed by Sam Atkins
parent 13291a9180
commit a11666e097
Notes: github-actions[bot] 2025-12-08 11:48:12 +00:00
4 changed files with 92 additions and 131 deletions

View file

@ -12,6 +12,7 @@
#include <LibWeb/CSS/Clip.h>
#include <LibWeb/CSS/ComputedProperties.h>
#include <LibWeb/CSS/FontComputer.h>
#include <LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.h>
#include <LibWeb/CSS/StyleValues/ColorSchemeStyleValue.h>
#include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
#include <LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h>
@ -33,6 +34,7 @@
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
#include <LibWeb/CSS/StyleValues/RectStyleValue.h>
#include <LibWeb/CSS/StyleValues/RepeatStyleStyleValue.h>
#include <LibWeb/CSS/StyleValues/ScrollbarColorStyleValue.h>
#include <LibWeb/CSS/StyleValues/ShadowStyleValue.h>
#include <LibWeb/CSS/StyleValues/StringStyleValue.h>
@ -630,6 +632,86 @@ ImageRendering ComputedProperties::image_rendering() const
return keyword_to_image_rendering(value.to_keyword()).release_value();
}
// https://drafts.csswg.org/css-backgrounds-4/#layering
Vector<BackgroundLayerData> ComputedProperties::background_layers() const
{
auto coordinated_value_list = assemble_coordinated_value_list(
PropertyID::BackgroundImage,
{
PropertyID::BackgroundAttachment,
PropertyID::BackgroundBlendMode,
PropertyID::BackgroundClip,
PropertyID::BackgroundImage,
PropertyID::BackgroundOrigin,
PropertyID::BackgroundPositionX,
PropertyID::BackgroundPositionY,
PropertyID::BackgroundRepeat,
PropertyID::BackgroundSize,
});
Vector<BackgroundLayerData> layers;
// The number of layers is determined by the number of comma-separated values in the background-image property
layers.ensure_capacity(coordinated_value_list.get(PropertyID::BackgroundImage)->size());
for (size_t i = 0; i < coordinated_value_list.get(PropertyID::BackgroundImage)->size(); i++) {
auto const& background_attachment_value = coordinated_value_list.get(PropertyID::BackgroundAttachment)->at(i);
auto const& background_blend_mode_value = coordinated_value_list.get(PropertyID::BackgroundBlendMode)->at(i);
auto const& background_clip_value = coordinated_value_list.get(PropertyID::BackgroundClip)->at(i);
auto const& background_image_value = coordinated_value_list.get(PropertyID::BackgroundImage)->at(i);
auto const& background_origin_value = coordinated_value_list.get(PropertyID::BackgroundOrigin)->at(i);
auto const& background_position_x_value = coordinated_value_list.get(PropertyID::BackgroundPositionX)->at(i);
auto const& background_position_y_value = coordinated_value_list.get(PropertyID::BackgroundPositionY)->at(i);
auto const& background_repeat_value = coordinated_value_list.get(PropertyID::BackgroundRepeat)->at(i);
auto const& background_size_value = coordinated_value_list.get(PropertyID::BackgroundSize)->at(i);
BackgroundLayerData layer;
layer.attachment = keyword_to_background_attachment(background_attachment_value->to_keyword()).value();
layer.blend_mode = keyword_to_mix_blend_mode(background_blend_mode_value->to_keyword()).value();
layer.clip = keyword_to_background_box(background_clip_value->to_keyword()).value();
if (background_image_value->is_abstract_image())
layer.background_image = background_image_value->as_abstract_image();
else
VERIFY(background_image_value->to_keyword() == Keyword::None);
layer.origin = keyword_to_background_box(background_origin_value->to_keyword()).value();
layer.position_edge_x = background_position_x_value->as_edge().edge().value_or(PositionEdge::Left);
layer.position_offset_x = background_position_x_value->as_edge().offset();
layer.position_edge_y = background_position_y_value->as_edge().edge().value_or(PositionEdge::Top);
layer.position_offset_y = background_position_y_value->as_edge().offset();
layer.repeat_x = background_repeat_value->as_repeat_style().repeat_x();
layer.repeat_y = background_repeat_value->as_repeat_style().repeat_y();
if (background_size_value->is_background_size()) {
layer.size_type = CSS::BackgroundSize::LengthPercentage;
layer.size_x = CSS::LengthPercentageOrAuto::from_style_value(background_size_value->as_background_size().size_x());
layer.size_y = CSS::LengthPercentageOrAuto::from_style_value(background_size_value->as_background_size().size_y());
} else if (background_size_value->is_keyword()) {
switch (background_size_value->to_keyword()) {
case CSS::Keyword::Contain:
layer.size_type = CSS::BackgroundSize::Contain;
break;
case CSS::Keyword::Cover:
layer.size_type = CSS::BackgroundSize::Cover;
break;
default:
VERIFY_NOT_REACHED();
break;
}
} else {
VERIFY_NOT_REACHED();
}
layers.unchecked_append(layer);
}
return layers;
}
Length ComputedProperties::border_spacing_horizontal(Layout::Node const& layout_node) const
{
auto resolve_value = [&](auto const& style_value) -> Optional<Length> {

View file

@ -97,6 +97,7 @@ public:
TextRendering text_rendering() const;
CSSPixels text_underline_offset() const;
TextUnderlinePosition text_underline_position() const;
Vector<BackgroundLayerData> background_layers() const;
Length border_spacing_horizontal(Layout::Node const&) const;
Length border_spacing_vertical(Layout::Node const&) const;
CaptionSide caption_side() const;

View file

@ -435,6 +435,7 @@
"inherited": false,
"initial": "border-box",
"multiplicity": "coordinating-list",
"__comment": "FIXME: Support `border-area`",
"valid-types": [
"background-box"
]
@ -470,6 +471,7 @@
"inherited": false,
"initial": "padding-box",
"multiplicity": "coordinating-list",
"__comment": "FIXME: This shouldn't allow `text`",
"valid-types": [
"background-box"
]

View file

@ -9,7 +9,6 @@
#include <AK/Demangle.h>
#include <LibWeb/CSS/ComputedProperties.h>
#include <LibWeb/CSS/StyleValues/AbstractImageStyleValue.h>
#include <LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.h>
#include <LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h>
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
#include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
@ -17,7 +16,6 @@
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
#include <LibWeb/CSS/StyleValues/RatioStyleValue.h>
#include <LibWeb/CSS/StyleValues/RepeatStyleStyleValue.h>
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
#include <LibWeb/CSS/StyleValues/TimeStyleValue.h>
#include <LibWeb/CSS/StyleValues/URLStyleValue.h>
@ -412,137 +410,15 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
computed_values.set_vertical_align(computed_style.vertical_align());
{
// FIXME: Use `ComputedProperties::assemble_coordinated_value_list()` for this
auto const& attachments = computed_style.property(CSS::PropertyID::BackgroundAttachment);
auto const& clips = computed_style.property(CSS::PropertyID::BackgroundClip);
auto const& images = computed_style.property(CSS::PropertyID::BackgroundImage);
auto const& origins = computed_style.property(CSS::PropertyID::BackgroundOrigin);
auto const& x_positions = computed_style.property(CSS::PropertyID::BackgroundPositionX);
auto const& y_positions = computed_style.property(CSS::PropertyID::BackgroundPositionY);
auto const& repeats = computed_style.property(CSS::PropertyID::BackgroundRepeat);
auto const& sizes = computed_style.property(CSS::PropertyID::BackgroundSize);
auto const& background_blend_modes = computed_style.property(CSS::PropertyID::BackgroundBlendMode);
auto background_layers = computed_style.background_layers();
auto count_layers = [](auto const& maybe_style_value) -> size_t {
if (maybe_style_value.is_value_list())
return maybe_style_value.as_value_list().size();
else
return 1;
};
auto value_for_layer = [](auto const& style_value, size_t layer_index) -> RefPtr<CSS::StyleValue const> {
if (style_value.is_value_list())
return style_value.as_value_list().value_at(layer_index, true);
return style_value;
};
size_t layer_count = 1;
layer_count = max(layer_count, count_layers(attachments));
layer_count = max(layer_count, count_layers(clips));
layer_count = max(layer_count, count_layers(images));
layer_count = max(layer_count, count_layers(origins));
layer_count = max(layer_count, count_layers(x_positions));
layer_count = max(layer_count, count_layers(y_positions));
layer_count = max(layer_count, count_layers(repeats));
layer_count = max(layer_count, count_layers(sizes));
Vector<CSS::BackgroundLayerData> layers;
layers.ensure_capacity(layer_count);
for (size_t layer_index = 0; layer_index < layer_count; layer_index++) {
CSS::BackgroundLayerData layer;
if (auto image_value = value_for_layer(images, layer_index); image_value) {
if (image_value->is_abstract_image()) {
layer.background_image = image_value->as_abstract_image();
const_cast<CSS::AbstractImageStyleValue&>(*layer.background_image).load_any_resources(document());
}
}
if (auto attachment_value = value_for_layer(attachments, layer_index); attachment_value && attachment_value->is_keyword()) {
switch (attachment_value->to_keyword()) {
case CSS::Keyword::Fixed:
layer.attachment = CSS::BackgroundAttachment::Fixed;
break;
case CSS::Keyword::Local:
layer.attachment = CSS::BackgroundAttachment::Local;
break;
case CSS::Keyword::Scroll:
layer.attachment = CSS::BackgroundAttachment::Scroll;
break;
default:
break;
}
}
auto as_box = [](auto keyword) {
switch (keyword) {
case CSS::Keyword::BorderBox:
return CSS::BackgroundBox::BorderBox;
case CSS::Keyword::ContentBox:
return CSS::BackgroundBox::ContentBox;
case CSS::Keyword::PaddingBox:
return CSS::BackgroundBox::PaddingBox;
case CSS::Keyword::Text:
return CSS::BackgroundBox::Text;
default:
VERIFY_NOT_REACHED();
}
};
if (auto origin_value = value_for_layer(origins, layer_index); origin_value && origin_value->is_keyword()) {
layer.origin = as_box(origin_value->to_keyword());
}
if (auto clip_value = value_for_layer(clips, layer_index); clip_value && clip_value->is_keyword()) {
layer.clip = as_box(clip_value->to_keyword());
}
if (auto position_value = value_for_layer(x_positions, layer_index); position_value && position_value->is_edge()) {
auto& position = position_value->as_edge();
layer.position_edge_x = position.edge().value_or(CSS::PositionEdge::Left);
layer.position_offset_x = position.offset();
}
if (auto position_value = value_for_layer(y_positions, layer_index); position_value && position_value->is_edge()) {
auto& position = position_value->as_edge();
layer.position_edge_y = position.edge().value_or(CSS::PositionEdge::Top);
layer.position_offset_y = position.offset();
};
if (auto size_value = value_for_layer(sizes, layer_index); size_value) {
if (size_value->is_background_size()) {
auto& size = size_value->as_background_size();
layer.size_type = CSS::BackgroundSize::LengthPercentage;
layer.size_x = CSS::LengthPercentageOrAuto::from_style_value(size.size_x());
layer.size_y = CSS::LengthPercentageOrAuto::from_style_value(size.size_y());
} else if (size_value->is_keyword()) {
switch (size_value->to_keyword()) {
case CSS::Keyword::Contain:
layer.size_type = CSS::BackgroundSize::Contain;
break;
case CSS::Keyword::Cover:
layer.size_type = CSS::BackgroundSize::Cover;
break;
default:
break;
}
}
}
if (auto repeat_value = value_for_layer(repeats, layer_index); repeat_value && repeat_value->is_repeat_style()) {
layer.repeat_x = repeat_value->as_repeat_style().repeat_x();
layer.repeat_y = repeat_value->as_repeat_style().repeat_y();
}
layer.blend_mode = CSS::keyword_to_mix_blend_mode(value_for_layer(background_blend_modes, layer_index)->to_keyword()).value_or(CSS::MixBlendMode::Normal);
layers.append(move(layer));
}
computed_values.set_background_layers(move(layers));
for (auto const& layer : background_layers) {
if (layer.background_image)
const_cast<CSS::AbstractImageStyleValue&>(*layer.background_image).load_any_resources(document());
}
computed_values.set_background_layers(move(background_layers));
computed_values.set_background_color(computed_style.color_or_fallback(CSS::PropertyID::BackgroundColor, color_resolution_context, CSS::InitialValues::background_color()));
computed_values.set_box_sizing(computed_style.box_sizing());