layout: Port border-image support for legacy layout (#32874)

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2024-07-30 08:41:23 +02:00 committed by GitHub
parent 29a4cca42d
commit e23dc0bf6f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 295 additions and 338 deletions

View file

@ -8,7 +8,8 @@ use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
use style::computed_values::text_decoration_style::T as ComputedTextDecorationStyle;
use style::computed_values::transform_style::T as ComputedTransformStyle;
use style::values::computed::Filter as ComputedFilter;
use webrender_api::{units, FilterOp, LineStyle, MixBlendMode, Shadow, TransformStyle};
use style::values::specified::border::BorderImageRepeatKeyword;
use webrender_api::{units, FilterOp, LineStyle, MixBlendMode, RepeatMode, Shadow, TransformStyle};
use crate::geom::{PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize};
@ -128,3 +129,16 @@ impl ToWebRender for ComputedTextDecorationStyle {
}
}
}
impl ToWebRender for BorderImageRepeatKeyword {
type Type = RepeatMode;
fn to_webrender(&self) -> Self::Type {
match *self {
BorderImageRepeatKeyword::Stretch => RepeatMode::Stretch,
BorderImageRepeatKeyword::Repeat => RepeatMode::Repeat,
BorderImageRepeatKeyword::Round => RepeatMode::Round,
BorderImageRepeatKeyword::Space => RepeatMode::Space,
}
}
}

View file

@ -2,6 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use euclid::Size2D;
use style::color::mix::ColorInterpolationMethod;
use style::properties::ComputedValues;
use style::values::computed::image::{EndingShape, Gradient, LineDirection};
@ -11,15 +12,25 @@ use style::values::computed::{
use style::values::generics::image::{
Circle, ColorStop, Ellipse, GradientFlags, GradientItem, ShapeExtent,
};
use webrender_api::{self as wr, units};
use webrender_api::units::LayoutPixel;
use webrender_api::{
self as wr, units, ConicGradient as WebRenderConicGradient,
Gradient as WebRenderLinearGradient, RadialGradient as WebRenderRadialGradient,
};
use wr::ColorF;
pub(super) enum WebRenderGradient {
Linear(WebRenderLinearGradient),
Radial(WebRenderRadialGradient),
Conic(WebRenderConicGradient),
}
pub(super) fn build(
style: &ComputedValues,
gradient: &Gradient,
layer: &super::background::BackgroundLayer,
size: Size2D<f32, LayoutPixel>,
builder: &mut super::DisplayListBuilder,
) {
) -> WebRenderGradient {
match gradient {
Gradient::Linear {
ref items,
@ -33,7 +44,7 @@ pub(super) fn build(
direction,
color_interpolation_method,
*flags,
layer,
size,
builder,
),
Gradient::Radial {
@ -50,7 +61,7 @@ pub(super) fn build(
position,
color_interpolation_method,
*flags,
layer,
size,
builder,
),
Gradient::Conic {
@ -66,7 +77,7 @@ pub(super) fn build(
*color_interpolation_method,
items,
*flags,
layer,
size,
builder,
),
}
@ -79,13 +90,12 @@ pub(super) fn build_linear(
line_direction: &LineDirection,
_color_interpolation_method: &ColorInterpolationMethod,
flags: GradientFlags,
layer: &super::background::BackgroundLayer,
gradient_box: Size2D<f32, LayoutPixel>,
builder: &mut super::DisplayListBuilder,
) {
) -> WebRenderGradient {
use style::values::specified::position::HorizontalPositionKeyword::*;
use style::values::specified::position::VerticalPositionKeyword::*;
use units::LayoutVector2D as Vec2;
let gradient_box = layer.tile_size;
// A vector of length 1.0 in the direction of the gradient line
let direction = match line_direction {
@ -168,16 +178,12 @@ pub(super) fn build_linear(
} else {
wr::ExtendMode::Clamp
};
let linear_gradient = builder
.wr()
.create_gradient(start_point, end_point, stops, extend_mode);
builder.wr().push_gradient(
&layer.common,
layer.bounds,
linear_gradient,
layer.tile_size,
layer.tile_spacing,
)
WebRenderGradient::Linear(builder.wr().create_gradient(
start_point,
end_point,
stops,
extend_mode,
))
}
/// <https://drafts.csswg.org/css-images-3/#radial-gradients>
@ -189,10 +195,9 @@ pub(super) fn build_radial(
center: &Position,
_color_interpolation_method: &ColorInterpolationMethod,
flags: GradientFlags,
layer: &super::background::BackgroundLayer,
gradient_box: Size2D<f32, LayoutPixel>,
builder: &mut super::DisplayListBuilder,
) {
let gradient_box = layer.tile_size;
) -> WebRenderGradient {
let center = units::LayoutPoint::new(
center
.horizontal
@ -277,16 +282,12 @@ pub(super) fn build_radial(
} else {
wr::ExtendMode::Clamp
};
let radial_gradient = builder
.wr()
.create_radial_gradient(center, radii, stops, extend_mode);
builder.wr().push_radial_gradient(
&layer.common,
layer.bounds,
radial_gradient,
layer.tile_size,
layer.tile_spacing,
)
WebRenderGradient::Radial(builder.wr().create_radial_gradient(
center,
radii,
stops,
extend_mode,
))
}
/// <https://drafts.csswg.org/css-images-4/#conic-gradients>
@ -298,10 +299,9 @@ fn build_conic(
_color_interpolation_method: ColorInterpolationMethod,
items: &[GradientItem<Color, AngleOrPercentage>],
flags: GradientFlags,
layer: &super::background::BackgroundLayer,
gradient_box: Size2D<f32, LayoutPixel>,
builder: &mut super::DisplayListBuilder<'_>,
) {
let gradient_box = layer.tile_size;
) -> WebRenderGradient {
let center = units::LayoutPoint::new(
center
.horizontal
@ -319,17 +319,12 @@ fn build_conic(
} else {
wr::ExtendMode::Clamp
};
let conic_gradient =
builder
.wr()
.create_conic_gradient(center, angle.radians(), stops, extend_mode);
builder.wr().push_conic_gradient(
&layer.common,
layer.bounds,
conic_gradient,
layer.tile_size,
layer.tile_spacing,
)
WebRenderGradient::Conic(builder.wr().create_conic_gradient(
center,
angle.radians(),
stops,
extend_mode,
))
}
fn conic_gradient_items_to_color_stops(

View file

@ -9,21 +9,34 @@ use app_units::Au;
use base::id::BrowsingContextId;
use base::WebRenderEpochToU16;
use embedder_traits::Cursor;
use euclid::{Point2D, SideOffsets2D, Size2D};
use euclid::{Point2D, SideOffsets2D, Size2D, UnknownUnit};
use fnv::FnvHashMap;
use fonts::GlyphStore;
use gradient::WebRenderGradient;
use net_traits::image_cache::UsePlaceholder;
use servo_geometry::MaxRect;
use style::color::{AbsoluteColor, ColorSpace};
use style::computed_values::border_image_outset::T as BorderImageOutset;
use style::computed_values::text_decoration_style::T as ComputedTextDecorationStyle;
use style::dom::OpaqueNode;
use style::properties::longhands::visibility::computed_value::T as Visibility;
use style::properties::style_structs::Border;
use style::properties::ComputedValues;
use style::values::computed::{BorderStyle, Color, Length, LengthPercentage, OutlineStyle};
use style::values::computed::image::Image;
use style::values::computed::{
BorderImageSideWidth, BorderImageWidth, BorderStyle, Color, Length, LengthPercentage,
NonNegativeLengthOrNumber, NumberOrPercentage, OutlineStyle,
};
use style::values::generics::rect::Rect;
use style::values::generics::NonNegative;
use style::values::specified::text::TextDecorationLine;
use style::values::specified::ui::CursorKind;
use style_traits::CSSPixel;
use webrender_api::{self as wr, units, BoxShadowClipMode, ClipChainId};
use style_traits::{CSSPixel, DevicePixel};
use webrender_api::units::LayoutPixel;
use webrender_api::{
self as wr, units, BorderDetails, BoxShadowClipMode, ClipChainId, CommonItemProperties,
ImageRendering, NinePatchBorder, NinePatchBorderSource,
};
use webrender_traits::display_list::{
CompositorDisplayListInfo, ScrollSensitivity, ScrollTreeNodeId,
};
@ -740,7 +753,6 @@ impl<'a> BuilderForBoxFragment<'a> {
builder: &mut DisplayListBuilder,
painter: &BackgroundPainter,
) {
use style::values::computed::image::Image;
let style = painter.style;
let b = style.get_background();
// Reverse because the property is top layer first, we want to paint bottom layer first.
@ -749,10 +761,38 @@ impl<'a> BuilderForBoxFragment<'a> {
Image::None => {},
Image::Gradient(ref gradient) => {
let intrinsic = IntrinsicSizes::empty();
if let Some(layer) =
let Some(layer) =
&background::layout_layer(self, painter, builder, index, intrinsic)
{
gradient::build(style, gradient, layer, builder)
else {
continue;
};
match gradient::build(style, gradient, layer.tile_size, builder) {
WebRenderGradient::Linear(linear_gradient) => builder.wr().push_gradient(
&layer.common,
layer.bounds,
linear_gradient,
layer.tile_size,
layer.tile_spacing,
),
WebRenderGradient::Radial(radial_gradient) => {
builder.wr().push_radial_gradient(
&layer.common,
layer.bounds,
radial_gradient,
layer.tile_size,
layer.tile_spacing,
)
},
WebRenderGradient::Conic(conic_gradient) => {
builder.wr().push_conic_gradient(
&layer.common,
layer.bounds,
conic_gradient,
layer.tile_size,
layer.tile_spacing,
)
},
}
},
Image::Url(ref image_url) => {
@ -857,7 +897,12 @@ impl<'a> BuilderForBoxFragment<'a> {
return;
}
// `border-image` replaces an element's border entirely.
let common = builder.common_properties(self.border_rect, &self.fragment.style);
if self.build_border_image(builder, &common, border, border_widths) {
return;
}
let details = wr::BorderDetails::Normal(wr::NormalBorder {
top: self.build_border_side(border.border_top_style, border.border_top_color.clone()),
right: self
@ -876,6 +921,100 @@ impl<'a> BuilderForBoxFragment<'a> {
.push_border(&common, self.border_rect, border_widths, details)
}
/// Add a display item for image borders if necessary.
fn build_border_image(
&self,
builder: &mut DisplayListBuilder,
common: &CommonItemProperties,
border: &Border,
border_widths: SideOffsets2D<f32, LayoutPixel>,
) -> bool {
let border_style_struct = self.fragment.style.get_border();
let border_image_outset =
resolve_border_image_outset(border_style_struct.border_image_outset, border_widths);
let border_image_area = self.border_rect.to_rect().outer_rect(border_image_outset);
let border_image_size = border_image_area.size;
let border_image_widths = resolve_border_image_width(
&border_style_struct.border_image_width,
border_widths,
border_image_size,
);
let border_image_repeat = &border_style_struct.border_image_repeat;
let border_image_fill = border_style_struct.border_image_slice.fill;
let border_image_slice = &border_style_struct.border_image_slice.offsets;
let stops = Vec::new();
let mut width = border_image_size.width;
let mut height = border_image_size.height;
let source = match border.border_image_source {
Image::Url(ref image_url) => {
// FIXME: images wont always have in intrinsic width or
// height when support for SVG is added, or a WebRender
// `ImageKey`, for that matter.
//
// FIXME: It feels like this should take into account the pseudo
// element and not just the node.
let Some(tag) = self.fragment.base.tag else {
return false;
};
let Some(image_url) = image_url.url() else {
return false;
};
let Some(image_info) = builder.context.get_webrender_image_for_url(
tag.node,
image_url.clone().into(),
UsePlaceholder::No,
) else {
return false;
};
let Some(key) = image_info.key else {
return false;
};
width = image_info.width as f32;
height = image_info.height as f32;
NinePatchBorderSource::Image(key, ImageRendering::Auto)
},
Image::Gradient(ref gradient) => {
match gradient::build(&self.fragment.style, gradient, border_image_size, builder) {
WebRenderGradient::Linear(gradient) => {
NinePatchBorderSource::Gradient(gradient)
},
WebRenderGradient::Radial(gradient) => {
NinePatchBorderSource::RadialGradient(gradient)
},
WebRenderGradient::Conic(gradient) => {
NinePatchBorderSource::ConicGradient(gradient)
},
}
},
Image::CrossFade(_) | Image::ImageSet(_) | Image::None | Image::PaintWorklet(_) => {
return false
},
};
let size = euclid::Size2D::new(width as i32, height as i32);
let details = BorderDetails::NinePatch(NinePatchBorder {
source,
width: size.width,
height: size.height,
slice: resolve_border_image_slice(border_image_slice, size),
fill: border_image_fill,
repeat_horizontal: border_image_repeat.0.to_webrender(),
repeat_vertical: border_image_repeat.1.to_webrender(),
});
builder.wr().push_border(
&common,
border_image_area.to_box2d(),
border_image_widths,
details,
);
builder.wr().push_stops(&stops);
true
}
fn build_outline(&mut self, builder: &mut DisplayListBuilder) {
let outline = self.fragment.style.get_outline();
let width = outline.outline_width.to_f32_px();
@ -1118,3 +1257,71 @@ fn create_clip_chain(
.define_clip_chain(parent_clip_chain_id, [new_clip_id]),
)
}
/// Resolve the WebRender border-image outset area from the style values.
fn resolve_border_image_outset(
outset: BorderImageOutset,
border: SideOffsets2D<f32, LayoutPixel>,
) -> SideOffsets2D<f32, LayoutPixel> {
fn image_outset_for_side(outset: NonNegativeLengthOrNumber, border_width: f32) -> f32 {
match outset {
NonNegativeLengthOrNumber::Length(length) => length.px(),
NonNegativeLengthOrNumber::Number(factor) => border_width * factor.0,
}
}
SideOffsets2D::new(
image_outset_for_side(outset.0, border.top),
image_outset_for_side(outset.1, border.right),
image_outset_for_side(outset.2, border.bottom),
image_outset_for_side(outset.3, border.left),
)
}
/// Resolve the WebRender border-image width from the style values.
fn resolve_border_image_width(
width: &BorderImageWidth,
border: SideOffsets2D<f32, LayoutPixel>,
border_area: Size2D<f32, LayoutPixel>,
) -> SideOffsets2D<f32, LayoutPixel> {
fn image_width_for_side(
border_image_width: &BorderImageSideWidth,
border_width: f32,
total_length: f32,
) -> f32 {
match border_image_width {
BorderImageSideWidth::LengthPercentage(v) => {
v.to_used_value(Au::from_f32_px(total_length)).to_f32_px()
},
BorderImageSideWidth::Number(x) => border_width * x.0,
BorderImageSideWidth::Auto => border_width,
}
}
SideOffsets2D::new(
image_width_for_side(&width.0, border.top, border_area.height),
image_width_for_side(&width.1, border.right, border_area.width),
image_width_for_side(&width.2, border.bottom, border_area.height),
image_width_for_side(&width.3, border.left, border_area.width),
)
}
/// Resolve the WebRender border-image slice from the style values.
fn resolve_border_image_slice(
border_image_slice: &Rect<NonNegative<NumberOrPercentage>>,
size: Size2D<i32, UnknownUnit>,
) -> SideOffsets2D<i32, DevicePixel> {
fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 {
match value.0 {
NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32,
NumberOrPercentage::Number(n) => n.round() as i32,
}
}
SideOffsets2D::new(
resolve_percentage(border_image_slice.0, size.height),
resolve_percentage(border_image_slice.1, size.width),
resolve_percentage(border_image_slice.2, size.height),
resolve_percentage(border_image_slice.3, size.width),
)
}