use app_units::Au;
use euclid::Size2D;
use style::color::mix::ColorInterpolationMethod;
use style::properties::ComputedValues;
use style::values::computed::image::{EndingShape, Gradient, LineDirection};
use style::values::computed::{Angle, AngleOrPercentage, Color, LengthPercentage, Position};
use style::values::generics::image::{
Circle, ColorStop, Ellipse, GradientFlags, GradientItem, ShapeExtent,
};
use style::Zero;
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,
size: Size2D<f32, LayoutPixel>,
builder: &mut super::DisplayListBuilder,
) -> WebRenderGradient {
match gradient {
Gradient::Linear {
ref items,
ref direction,
ref color_interpolation_method,
ref flags,
compat_mode: _,
} => build_linear(
style,
items,
direction,
color_interpolation_method,
*flags,
size,
builder,
),
Gradient::Radial {
ref shape,
ref position,
ref color_interpolation_method,
ref items,
ref flags,
compat_mode: _,
} => build_radial(
style,
items,
shape,
position,
color_interpolation_method,
*flags,
size,
builder,
),
Gradient::Conic {
angle,
position,
color_interpolation_method,
items,
flags,
} => build_conic(
style,
*angle,
position,
*color_interpolation_method,
items,
*flags,
size,
builder,
),
}
}
pub(super) fn build_linear(
style: &ComputedValues,
items: &[GradientItem<Color, LengthPercentage>],
line_direction: &LineDirection,
_color_interpolation_method: &ColorInterpolationMethod,
flags: GradientFlags,
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 direction = match line_direction {
LineDirection::Horizontal(Right) => Vec2::new(1., 0.),
LineDirection::Vertical(Top) => Vec2::new(0., -1.),
LineDirection::Horizontal(Left) => Vec2::new(-1., 0.),
LineDirection::Vertical(Bottom) => Vec2::new(0., 1.),
LineDirection::Angle(angle) => {
let radians = angle.radians();
Vec2::new(radians.sin(), -radians.cos())
},
LineDirection::Corner(horizontal, vertical) => {
let x = match horizontal {
Right => gradient_box.height,
Left => -gradient_box.height,
};
let y = match vertical {
Top => gradient_box.width,
Bottom => -gradient_box.width,
};
Vec2::new(x, y).normalize()
},
};
let gradient_line_length =
(gradient_box.width * direction.x).abs() + (gradient_box.height * direction.y).abs();
let half_gradient_line = direction * (gradient_line_length / 2.);
let center = (gradient_box / 2.).to_vector().to_point();
let start_point = center - half_gradient_line;
let end_point = center + half_gradient_line;
let mut color_stops =
gradient_items_to_color_stops(style, items, Au::from_f32_px(gradient_line_length));
let stops = fixup_stops(&mut color_stops);
let extend_mode = if flags.contains(GradientFlags::REPEATING) {
wr::ExtendMode::Repeat
} else {
wr::ExtendMode::Clamp
};
WebRenderGradient::Linear(builder.wr().create_gradient(
start_point,
end_point,
stops,
extend_mode,
))
}
#[allow(clippy::too_many_arguments)]
pub(super) fn build_radial(
style: &ComputedValues,
items: &[GradientItem<Color, LengthPercentage>],
shape: &EndingShape,
center: &Position,
_color_interpolation_method: &ColorInterpolationMethod,
flags: GradientFlags,
gradient_box: Size2D<f32, LayoutPixel>,
builder: &mut super::DisplayListBuilder,
) -> WebRenderGradient {
let center = units::LayoutPoint::new(
center
.horizontal
.to_used_value(Au::from_f32_px(gradient_box.width))
.to_f32_px(),
center
.vertical
.to_used_value(Au::from_f32_px(gradient_box.height))
.to_f32_px(),
);
let radii = match shape {
EndingShape::Circle(circle) => {
let radius = match circle {
Circle::Radius(r) => r.0.px(),
Circle::Extent(extent) => match extent {
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
let vec = abs_vector_to_corner(gradient_box, center, f32::min);
vec.x.min(vec.y)
},
ShapeExtent::FarthestSide => {
let vec = abs_vector_to_corner(gradient_box, center, f32::max);
vec.x.max(vec.y)
},
ShapeExtent::ClosestCorner => {
abs_vector_to_corner(gradient_box, center, f32::min).length()
},
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
abs_vector_to_corner(gradient_box, center, f32::max).length()
},
},
};
units::LayoutSize::new(radius, radius)
},
EndingShape::Ellipse(Ellipse::Radii(rx, ry)) => units::LayoutSize::new(
rx.0.to_used_value(Au::from_f32_px(gradient_box.width))
.to_f32_px(),
ry.0.to_used_value(Au::from_f32_px(gradient_box.height))
.to_f32_px(),
),
EndingShape::Ellipse(Ellipse::Extent(extent)) => match extent {
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
abs_vector_to_corner(gradient_box, center, f32::min).to_size()
},
ShapeExtent::FarthestSide => {
abs_vector_to_corner(gradient_box, center, f32::max).to_size()
},
ShapeExtent::ClosestCorner => {
abs_vector_to_corner(gradient_box, center, f32::min).to_size() *
(std::f32::consts::FRAC_1_SQRT_2 * 2.0)
},
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
abs_vector_to_corner(gradient_box, center, f32::max).to_size() *
(std::f32::consts::FRAC_1_SQRT_2 * 2.0)
},
},
};
fn abs_vector_to_corner(
gradient_box: units::LayoutSize,
center: units::LayoutPoint,
select: impl Fn(f32, f32) -> f32,
) -> units::LayoutVector2D {
let left = center.x.abs();
let top = center.y.abs();
let right = (gradient_box.width - center.x).abs();
let bottom = (gradient_box.height - center.y).abs();
units::LayoutVector2D::new(select(left, right), select(top, bottom))
}
let gradient_line_length = radii.width;
let mut color_stops =
gradient_items_to_color_stops(style, items, Au::from_f32_px(gradient_line_length));
let stops = fixup_stops(&mut color_stops);
let extend_mode = if flags.contains(GradientFlags::REPEATING) {
wr::ExtendMode::Repeat
} else {
wr::ExtendMode::Clamp
};
WebRenderGradient::Radial(builder.wr().create_radial_gradient(
center,
radii,
stops,
extend_mode,
))
}
#[allow(clippy::too_many_arguments)]
fn build_conic(
style: &ComputedValues,
angle: Angle,
center: &Position,
_color_interpolation_method: ColorInterpolationMethod,
items: &[GradientItem<Color, AngleOrPercentage>],
flags: GradientFlags,
gradient_box: Size2D<f32, LayoutPixel>,
builder: &mut super::DisplayListBuilder<'_>,
) -> WebRenderGradient {
let center = units::LayoutPoint::new(
center
.horizontal
.to_used_value(Au::from_f32_px(gradient_box.width))
.to_f32_px(),
center
.vertical
.to_used_value(Au::from_f32_px(gradient_box.height))
.to_f32_px(),
);
let mut color_stops = conic_gradient_items_to_color_stops(style, items);
let stops = fixup_stops(&mut color_stops);
let extend_mode = if flags.contains(GradientFlags::REPEATING) {
wr::ExtendMode::Repeat
} else {
wr::ExtendMode::Clamp
};
WebRenderGradient::Conic(builder.wr().create_conic_gradient(
center,
angle.radians(),
stops,
extend_mode,
))
}
fn conic_gradient_items_to_color_stops(
style: &ComputedValues,
items: &[GradientItem<Color, AngleOrPercentage>],
) -> Vec<ColorStop<ColorF, f32>> {
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(_) => None,
}
})
.collect()
}
fn gradient_items_to_color_stops(
style: &ComputedValues,
items: &[GradientItem<Color, LengthPercentage>],
gradient_line_length: Au,
) -> Vec<ColorStop<ColorF, f32>> {
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.is_zero() {
0.
} else {
position
.to_used_value(gradient_line_length)
.scale_by(1. / gradient_line_length.to_f32_px())
.to_f32_px()
}),
}),
GradientItem::InterpolationHint(_) => None,
}
})
.collect()
}
fn fixup_stops(stops: &mut [ColorStop<ColorF, f32>]) -> Vec<wr::GradientStop> {
assert!(stops.len() >= 2);
if let first_position @ None = &mut stops.first_mut().unwrap().position {
*first_position = Some(0.);
}
if let last_position @ None = &mut stops.last_mut().unwrap().position {
*last_position = Some(1.);
}
let mut iter = stops.iter_mut();
let mut max_so_far = iter.next().unwrap().position.unwrap();
for stop in iter {
if let Some(position) = &mut stop.position {
if *position < max_so_far {
*position = max_so_far
} else {
max_so_far = *position
}
}
}
let mut wr_stops = Vec::with_capacity(stops.len());
let mut iter = stops.iter().enumerate();
let (_, first) = iter.next().unwrap();
let first_stop_position = first.position.unwrap();
wr_stops.push(wr::GradientStop {
offset: first_stop_position,
color: first.color,
});
let mut last_positioned_stop_index = 0;
let mut last_positioned_stop_position = first_stop_position;
for (i, stop) in iter {
if let Some(position) = stop.position {
let step_count = i - last_positioned_stop_index;
if step_count > 1 {
let step = (position - last_positioned_stop_position) / step_count as f32;
for j in 1..step_count {
let color = stops[last_positioned_stop_index + j].color;
let offset = last_positioned_stop_position + j as f32 * step;
wr_stops.push(wr::GradientStop { offset, color })
}
}
last_positioned_stop_index = i;
last_positioned_stop_position = position;
wr_stops.push(wr::GradientStop {
offset: position,
color: stop.color,
})
}
}
wr_stops
}