layout: Enable rendering of conical-gradient and repeating-conical-gradient (#31597)

This also ignores a clippy warning for a new function (and a similar
existing one), until this code can be refactored to use temporary Rust
strutures to carry display list building state.

There are a few new test failures here:

 - FAIL [expected PASS] /css/css-images/image-set/image-set-conic-gradient-rendering.html
 - FAIL [expected PASS] /css/css-images/image-set/image-set-repeating-conic-gradient-rendering.html

 These fail because Servo does not yet support `image-set()`.

 - FAIL [expected PASS] /css/filter-effects/filter-function/filter-function-conic-gradient.html
 - FAIL [expected PASS] /css/filter-effects/filter-function/filter-function-repeating-conic-gradient.html

 These fail because Servo does not support the very early filter effects
 specification.

 - FAIL [expected PASS] /html/canvas/element/manual/fill-and-stroke-styles/conic-gradient-rotation.html
 - FAIL [expected PASS] /html/canvas/element/manual/fill-and-stroke-styles/conic-gradient.html

 These fail because this change only adds support for CSS conical
 gradients. Another set of changes will be necessary to support this for
 Canvas.
This commit is contained in:
Martin Robinson 2024-03-11 13:58:44 +01:00 committed by GitHub
parent de7b9bed85
commit 1d1f239ecc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 161 additions and 405 deletions

View file

@ -1019,7 +1019,7 @@ impl Fragment {
};
DisplayItem::RadialGradient(CommonDisplayItem::with_data(base, item, stops))
},
Gradient::Conic { .. } => unimplemented!(),
Gradient::Conic { .. } => return,
};
state.add_display_item(display_item);
});
@ -1273,7 +1273,7 @@ impl Fragment {
stops = radial_stops;
NinePatchBorderSource::RadialGradient(wr_gradient)
},
Gradient::Conic { .. } => unimplemented!(),
Gradient::Conic { .. } => return None,
},
_ => return None,
};

View file

@ -5,9 +5,12 @@
use style::color::mix::ColorInterpolationMethod;
use style::properties::ComputedValues;
use style::values::computed::image::{EndingShape, Gradient, LineDirection};
use style::values::computed::{Color, Length, LengthPercentage, Position};
use style::values::computed::{
Angle, AngleOrPercentage, Color, Length, LengthPercentage, Position,
};
use style::values::generics::image::{Circle, ColorStop, Ellipse, GradientItem, ShapeExtent};
use webrender_api::{self as wr, units};
use wr::ColorF;
pub(super) fn build(
style: &ComputedValues,
@ -56,7 +59,26 @@ pub(super) fn build(
layer,
builder,
),
Gradient::Conic { .. } => unimplemented!(),
Gradient::Conic {
angle,
position,
color_interpolation_method,
items,
repeating,
} => build_conic(
style,
*angle,
position,
*color_interpolation_method,
items,
if *repeating {
wr::ExtendMode::Repeat
} else {
wr::ExtendMode::Clamp
},
layer,
builder,
),
}
}
@ -148,7 +170,9 @@ pub(super) fn build_linear(
let start_point = center - half_gradient_line;
let end_point = center + half_gradient_line;
let stops = fixup_stops(style, items, Length::new(gradient_line_length));
let mut color_stops =
gradient_items_to_color_stops(style, items, Length::new(gradient_line_length));
let stops = fixup_stops(&mut color_stops);
let linear_gradient = builder
.wr()
.create_gradient(start_point, end_point, stops, extend_mode);
@ -162,6 +186,7 @@ pub(super) fn build_linear(
}
/// <https://drafts.csswg.org/css-images-3/#radial-gradients>
#[allow(clippy::too_many_arguments)]
pub(super) fn build_radial(
style: &ComputedValues,
items: &[GradientItem<Color, LengthPercentage>],
@ -249,7 +274,9 @@ pub(super) fn build_radial(
// where the gradient line intersects the ending shape.”
let gradient_line_length = radii.width;
let stops = fixup_stops(style, items, Length::new(gradient_line_length));
let mut color_stops =
gradient_items_to_color_stops(style, items, Length::new(gradient_line_length));
let stops = fixup_stops(&mut color_stops);
let radial_gradient = builder
.wr()
.create_radial_gradient(center, radii, stops, extend_mode);
@ -262,12 +289,48 @@ pub(super) fn build_radial(
)
}
/// <https://drafts.csswg.org/css-images-4/#color-stop-fixup>
fn fixup_stops(
/// <https://drafts.csswg.org/css-images-4/#conic-gradients>
#[allow(clippy::too_many_arguments)]
fn build_conic(
style: &ComputedValues,
items: &[GradientItem<Color, LengthPercentage>],
gradient_line_length: Length,
) -> Vec<wr::GradientStop> {
angle: Angle,
center: &Position,
_color_interpolation_method: ColorInterpolationMethod,
items: &[GradientItem<Color, AngleOrPercentage>],
extend_mode: wr::ExtendMode,
layer: &super::background::BackgroundLayer,
builder: &mut super::DisplayListBuilder<'_>,
) {
let gradient_box = layer.tile_size;
let center = units::LayoutPoint::new(
center
.horizontal
.percentage_relative_to(Length::new(gradient_box.width))
.px(),
center
.vertical
.percentage_relative_to(Length::new(gradient_box.height))
.px(),
);
let mut color_stops = conic_gradient_items_to_color_stops(style, items);
let stops = fixup_stops(&mut color_stops);
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,
)
}
fn conic_gradient_items_to_color_stops(
style: &ComputedValues,
items: &[GradientItem<Color, AngleOrPercentage>],
) -> Vec<ColorStop<ColorF, f32>> {
// Remove color transititon hints, which are not supported yet.
// https://drafts.csswg.org/css-images-4/#color-transition-hint
//
@ -278,28 +341,71 @@ fn fixup_stops(
// Either way, the best outcome is to add support.
// Gecko does so by approximating the non-linear interpolation
// by up to 10 piece-wise linear segments (9 intermediate color stops)
let mut stops = Vec::with_capacity(items.len());
for item in items {
match item {
GradientItem::SimpleColorStop(color) => stops.push(ColorStop {
color: super::rgba(style.resolve_color(color.clone())),
position: None,
}),
GradientItem::ComplexColorStop { color, position } => stops.push(ColorStop {
color: super::rgba(style.resolve_color(color.clone())),
position: Some(if gradient_line_length.px() == 0. {
0.
} else {
position.percentage_relative_to(gradient_line_length).px() /
gradient_line_length.px()
items
.iter()
.filter_map(|item| {
match item {
GradientItem::SimpleColorStop(color) => Some(ColorStop {
color: super::rgba(style.resolve_color(color.clone())),
position: None,
}),
GradientItem::ComplexColorStop { color, position } => Some(ColorStop {
color: super::rgba(style.resolve_color(color.clone())),
position: match position {
AngleOrPercentage::Percentage(percentage) => Some(percentage.0),
AngleOrPercentage::Angle(angle) => Some(angle.degrees() / 360.),
},
}),
}),
GradientItem::InterpolationHint(_) => {
// FIXME: approximate like in:
// https://searchfox.org/mozilla-central/rev/f98dad153b59a985efd4505912588d4651033395/layout/painting/nsCSSRenderingGradients.cpp#315-391
},
}
}
GradientItem::InterpolationHint(_) => None,
}
})
.collect()
}
fn gradient_items_to_color_stops(
style: &ComputedValues,
items: &[GradientItem<Color, LengthPercentage>],
gradient_line_length: Length,
) -> Vec<ColorStop<ColorF, f32>> {
// Remove color transititon hints, which are not supported yet.
// https://drafts.csswg.org/css-images-4/#color-transition-hint
//
// This gives an approximation of the gradient that might be visibly wrong,
// but maybe better than not parsing that value at all?
// Its debatble whether thats better or worse
// than not parsing and allowing authors to set a fallback.
// Either way, the best outcome is to add support.
// Gecko does so by approximating the non-linear interpolation
// by up to 10 piece-wise linear segments (9 intermediate color stops)
items
.iter()
.filter_map(|item| {
match item {
GradientItem::SimpleColorStop(color) => Some(ColorStop {
color: super::rgba(style.resolve_color(color.clone())),
position: None,
}),
GradientItem::ComplexColorStop { color, position } => Some(ColorStop {
color: super::rgba(style.resolve_color(color.clone())),
position: Some(if gradient_line_length.px() == 0. {
0.
} else {
position.percentage_relative_to(gradient_line_length).px() /
gradient_line_length.px()
}),
}),
// FIXME: approximate like in:
// https://searchfox.org/mozilla-central/rev/f98dad153b59a985efd4505912588d4651033395/layout/painting/nsCSSRenderingGradients.cpp#315-391
GradientItem::InterpolationHint(_) => None,
}
})
.collect()
}
/// <https://drafts.csswg.org/css-images-4/#color-stop-fixup>
fn fixup_stops(stops: &mut Vec<ColorStop<ColorF, f32>>) -> Vec<wr::GradientStop> {
assert!(stops.len() >= 2);
// https://drafts.csswg.org/css-images-4/#color-stop-fixup