mirror of
https://github.com/servo/servo.git
synced 2025-08-05 05:30:08 +01:00
Auto merge of #16385 - pyfisch:radial-gradient, r=emilio
Implement radial gradients <!-- Please describe your changes on the following line: --> This PR passes the `radial-gradient` function down from CSS parsed properties to webrender. It currently lacks tests but the examples from MDN and some other tests work fine. Probably there are some wpt tests I can enable? <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #11779 (github issue number if applicable). <!-- Either: --> - [ ] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/16385) <!-- Reviewable:end -->
This commit is contained in:
commit
53c62e890a
5 changed files with 353 additions and 153 deletions
|
@ -528,6 +528,7 @@ pub enum DisplayItem {
|
||||||
WebGL(Box<WebGLDisplayItem>),
|
WebGL(Box<WebGLDisplayItem>),
|
||||||
Border(Box<BorderDisplayItem>),
|
Border(Box<BorderDisplayItem>),
|
||||||
Gradient(Box<GradientDisplayItem>),
|
Gradient(Box<GradientDisplayItem>),
|
||||||
|
RadialGradient(Box<RadialGradientDisplayItem>),
|
||||||
Line(Box<LineDisplayItem>),
|
Line(Box<LineDisplayItem>),
|
||||||
BoxShadow(Box<BoxShadowDisplayItem>),
|
BoxShadow(Box<BoxShadowDisplayItem>),
|
||||||
Iframe(Box<IframeDisplayItem>),
|
Iframe(Box<IframeDisplayItem>),
|
||||||
|
@ -889,6 +890,9 @@ pub struct Gradient {
|
||||||
|
|
||||||
/// A list of color stops.
|
/// A list of color stops.
|
||||||
pub stops: Vec<GradientStop>,
|
pub stops: Vec<GradientStop>,
|
||||||
|
|
||||||
|
/// True if gradient repeats infinitly.
|
||||||
|
pub repeating: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, HeapSizeOf, Serialize)]
|
#[derive(Clone, Deserialize, HeapSizeOf, Serialize)]
|
||||||
|
@ -900,6 +904,31 @@ pub struct GradientDisplayItem {
|
||||||
pub gradient: Gradient,
|
pub gradient: Gradient,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Paints a radial gradient.
|
||||||
|
#[derive(Clone, Deserialize, HeapSizeOf, Serialize)]
|
||||||
|
pub struct RadialGradient {
|
||||||
|
/// The center point of the gradient.
|
||||||
|
pub center: Point2D<Au>,
|
||||||
|
|
||||||
|
/// The radius of the gradient with an x and an y component.
|
||||||
|
pub radius: Size2D<Au>,
|
||||||
|
|
||||||
|
/// A list of color stops.
|
||||||
|
pub stops: Vec<GradientStop>,
|
||||||
|
|
||||||
|
/// True if gradient repeats infinitly.
|
||||||
|
pub repeating: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, HeapSizeOf, Serialize)]
|
||||||
|
pub struct RadialGradientDisplayItem {
|
||||||
|
/// Fields common to all display item.
|
||||||
|
pub base: BaseDisplayItem,
|
||||||
|
|
||||||
|
/// Contains all gradient data.
|
||||||
|
pub gradient: RadialGradient,
|
||||||
|
}
|
||||||
|
|
||||||
/// A normal border, supporting CSS border styles.
|
/// A normal border, supporting CSS border styles.
|
||||||
#[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
|
#[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
|
||||||
pub struct NormalBorder {
|
pub struct NormalBorder {
|
||||||
|
@ -1124,6 +1153,7 @@ impl DisplayItem {
|
||||||
DisplayItem::WebGL(ref webgl_item) => &webgl_item.base,
|
DisplayItem::WebGL(ref webgl_item) => &webgl_item.base,
|
||||||
DisplayItem::Border(ref border) => &border.base,
|
DisplayItem::Border(ref border) => &border.base,
|
||||||
DisplayItem::Gradient(ref gradient) => &gradient.base,
|
DisplayItem::Gradient(ref gradient) => &gradient.base,
|
||||||
|
DisplayItem::RadialGradient(ref gradient) => &gradient.base,
|
||||||
DisplayItem::Line(ref line) => &line.base,
|
DisplayItem::Line(ref line) => &line.base,
|
||||||
DisplayItem::BoxShadow(ref box_shadow) => &box_shadow.base,
|
DisplayItem::BoxShadow(ref box_shadow) => &box_shadow.base,
|
||||||
DisplayItem::Iframe(ref iframe) => &iframe.base,
|
DisplayItem::Iframe(ref iframe) => &iframe.base,
|
||||||
|
@ -1241,6 +1271,7 @@ impl fmt::Debug for DisplayItem {
|
||||||
DisplayItem::WebGL(_) => "WebGL".to_owned(),
|
DisplayItem::WebGL(_) => "WebGL".to_owned(),
|
||||||
DisplayItem::Border(_) => "Border".to_owned(),
|
DisplayItem::Border(_) => "Border".to_owned(),
|
||||||
DisplayItem::Gradient(_) => "Gradient".to_owned(),
|
DisplayItem::Gradient(_) => "Gradient".to_owned(),
|
||||||
|
DisplayItem::RadialGradient(_) => "RadialGradient".to_owned(),
|
||||||
DisplayItem::Line(_) => "Line".to_owned(),
|
DisplayItem::Line(_) => "Line".to_owned(),
|
||||||
DisplayItem::BoxShadow(_) => "BoxShadow".to_owned(),
|
DisplayItem::BoxShadow(_) => "BoxShadow".to_owned(),
|
||||||
DisplayItem::Iframe(_) => "Iframe".to_owned(),
|
DisplayItem::Iframe(_) => "Iframe".to_owned(),
|
||||||
|
|
|
@ -25,7 +25,7 @@ use gfx::display_list::{BLUR_INFLATION_FACTOR, BaseDisplayItem, BorderDetails};
|
||||||
use gfx::display_list::{BorderDisplayItem, ImageBorder, NormalBorder};
|
use gfx::display_list::{BorderDisplayItem, ImageBorder, NormalBorder};
|
||||||
use gfx::display_list::{BorderRadii, BoxShadowClipMode, BoxShadowDisplayItem, ClippingRegion};
|
use gfx::display_list::{BorderRadii, BoxShadowClipMode, BoxShadowDisplayItem, ClippingRegion};
|
||||||
use gfx::display_list::{DisplayItem, DisplayItemMetadata, DisplayList, DisplayListSection};
|
use gfx::display_list::{DisplayItem, DisplayItemMetadata, DisplayList, DisplayListSection};
|
||||||
use gfx::display_list::{GradientDisplayItem, IframeDisplayItem, ImageDisplayItem};
|
use gfx::display_list::{GradientDisplayItem, RadialGradientDisplayItem, IframeDisplayItem, ImageDisplayItem};
|
||||||
use gfx::display_list::{LineDisplayItem, OpaqueNode};
|
use gfx::display_list::{LineDisplayItem, OpaqueNode};
|
||||||
use gfx::display_list::{SolidColorDisplayItem, ScrollRoot, StackingContext, StackingContextType};
|
use gfx::display_list::{SolidColorDisplayItem, ScrollRoot, StackingContext, StackingContextType};
|
||||||
use gfx::display_list::{TextDisplayItem, TextOrientation, WebGLDisplayItem, WebRenderImageInfo};
|
use gfx::display_list::{TextDisplayItem, TextOrientation, WebGLDisplayItem, WebRenderImageInfo};
|
||||||
|
@ -33,7 +33,7 @@ use gfx_traits::{combine_id_with_fragment_type, FragmentType, StackingContextId}
|
||||||
use inline::{FIRST_FRAGMENT_OF_ELEMENT, InlineFlow, LAST_FRAGMENT_OF_ELEMENT};
|
use inline::{FIRST_FRAGMENT_OF_ELEMENT, InlineFlow, LAST_FRAGMENT_OF_ELEMENT};
|
||||||
use ipc_channel::ipc;
|
use ipc_channel::ipc;
|
||||||
use list_item::ListItemFlow;
|
use list_item::ListItemFlow;
|
||||||
use model::{self, MaybeAuto};
|
use model::{self, MaybeAuto, specified};
|
||||||
use msg::constellation_msg::PipelineId;
|
use msg::constellation_msg::PipelineId;
|
||||||
use net_traits::image::base::PixelFormat;
|
use net_traits::image::base::PixelFormat;
|
||||||
use net_traits::image_cache::UsePlaceholder;
|
use net_traits::image_cache::UsePlaceholder;
|
||||||
|
@ -58,8 +58,10 @@ use style::properties::longhands::border_image_repeat::computed_value::RepeatKey
|
||||||
use style::properties::style_structs;
|
use style::properties::style_structs;
|
||||||
use style::servo::restyle_damage::REPAINT;
|
use style::servo::restyle_damage::REPAINT;
|
||||||
use style::values::{Either, RGBA, computed};
|
use style::values::{Either, RGBA, computed};
|
||||||
use style::values::computed::{AngleOrCorner, Gradient, GradientItem, GradientKind};
|
use style::values::computed::{AngleOrCorner, Gradient, GradientItem, GradientKind, LengthOrPercentage};
|
||||||
use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto, NumberOrPercentage};
|
use style::values::computed::{LengthOrPercentageOrAuto, LengthOrKeyword, LengthOrPercentageOrKeyword};
|
||||||
|
use style::values::computed::{NumberOrPercentage, Position};
|
||||||
|
use style::values::computed::image::{EndingShape, SizeKeyword};
|
||||||
use style::values::specified::{HorizontalDirection, VerticalDirection};
|
use style::values::specified::{HorizontalDirection, VerticalDirection};
|
||||||
use style_traits::CSSPixel;
|
use style_traits::CSSPixel;
|
||||||
use style_traits::cursor::Cursor;
|
use style_traits::cursor::Cursor;
|
||||||
|
@ -385,11 +387,22 @@ pub trait FragmentDisplayListBuilding {
|
||||||
image_url: &ServoUrl,
|
image_url: &ServoUrl,
|
||||||
background_index: usize);
|
background_index: usize);
|
||||||
|
|
||||||
fn convert_gradient(&self,
|
fn convert_linear_gradient(&self,
|
||||||
absolute_bounds: &Rect<Au>,
|
bounds: &Rect<Au>,
|
||||||
gradient: &Gradient,
|
stops: &[GradientItem],
|
||||||
|
angle_or_corner: &AngleOrCorner,
|
||||||
|
repeating: bool,
|
||||||
style: &ServoComputedValues)
|
style: &ServoComputedValues)
|
||||||
-> Option<display_list::Gradient>;
|
-> display_list::Gradient;
|
||||||
|
|
||||||
|
fn convert_radial_gradient(&self,
|
||||||
|
bounds: &Rect<Au>,
|
||||||
|
stops: &[GradientItem],
|
||||||
|
shape: &EndingShape,
|
||||||
|
center: &Position,
|
||||||
|
repeating: bool,
|
||||||
|
style: &ServoComputedValues)
|
||||||
|
-> display_list::RadialGradient;
|
||||||
|
|
||||||
/// Adds the display items necessary to paint the background linear gradient of this fragment
|
/// Adds the display items necessary to paint the background linear gradient of this fragment
|
||||||
/// to the appropriate section of the display list.
|
/// to the appropriate section of the display list.
|
||||||
|
@ -397,6 +410,7 @@ pub trait FragmentDisplayListBuilding {
|
||||||
state: &mut DisplayListBuildState,
|
state: &mut DisplayListBuildState,
|
||||||
display_list_section: DisplayListSection,
|
display_list_section: DisplayListSection,
|
||||||
absolute_bounds: &Rect<Au>,
|
absolute_bounds: &Rect<Au>,
|
||||||
|
clip_bounds: &Rect<Au>,
|
||||||
clip: &ClippingRegion,
|
clip: &ClippingRegion,
|
||||||
gradient: &Gradient,
|
gradient: &Gradient,
|
||||||
style: &ServoComputedValues);
|
style: &ServoComputedValues);
|
||||||
|
@ -588,6 +602,152 @@ fn build_border_radius_for_inner_rect(outer_rect: &Rect<Au>,
|
||||||
radii
|
radii
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn convert_gradient_stops(gradient_items: &[GradientItem],
|
||||||
|
length: Au,
|
||||||
|
style: &ServoComputedValues) -> Vec<GradientStop> {
|
||||||
|
// Determine the position of each stop per CSS-IMAGES § 3.4.
|
||||||
|
//
|
||||||
|
// FIXME(#3908, pcwalton): Make sure later stops can't be behind earlier stops.
|
||||||
|
let stop_items = gradient_items.iter().filter_map(|item| {
|
||||||
|
match *item {
|
||||||
|
GradientItem::ColorStop(ref stop) => Some(stop),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}).collect::<Vec<_>>();
|
||||||
|
let mut stops = Vec::with_capacity(stop_items.len());
|
||||||
|
let mut stop_run = None;
|
||||||
|
for (i, stop) in stop_items.iter().enumerate() {
|
||||||
|
let offset = match stop.position {
|
||||||
|
None => {
|
||||||
|
if stop_run.is_none() {
|
||||||
|
// Initialize a new stop run.
|
||||||
|
let start_offset = if i == 0 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
// `unwrap()` here should never fail because this is the beginning of
|
||||||
|
// a stop run, which is always bounded by a length or percentage.
|
||||||
|
position_to_offset(stop_items[i - 1].position.unwrap(), length)
|
||||||
|
};
|
||||||
|
let (end_index, end_offset) =
|
||||||
|
match stop_items[i..]
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|&(_, ref stop)| stop.position.is_some()) {
|
||||||
|
None => (stop_items.len() - 1, 1.0),
|
||||||
|
Some((end_index, end_stop)) => {
|
||||||
|
// `unwrap()` here should never fail because this is the end of
|
||||||
|
// a stop run, which is always bounded by a length or
|
||||||
|
// percentage.
|
||||||
|
(end_index,
|
||||||
|
position_to_offset(end_stop.position.unwrap(), length))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
stop_run = Some(StopRun {
|
||||||
|
start_offset: start_offset,
|
||||||
|
end_offset: end_offset,
|
||||||
|
start_index: i,
|
||||||
|
stop_count: end_index - i,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let stop_run = stop_run.unwrap();
|
||||||
|
let stop_run_length = stop_run.end_offset - stop_run.start_offset;
|
||||||
|
if stop_run.stop_count == 0 {
|
||||||
|
stop_run.end_offset
|
||||||
|
} else {
|
||||||
|
stop_run.start_offset +
|
||||||
|
stop_run_length * (i - stop_run.start_index) as f32 /
|
||||||
|
(stop_run.stop_count as f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(position) => {
|
||||||
|
stop_run = None;
|
||||||
|
position_to_offset(position, length)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
stops.push(GradientStop {
|
||||||
|
offset: offset,
|
||||||
|
color: style.resolve_color(stop.color).to_gfx_color()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
stops
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the the distance to the nearest or farthest corner depending on the comperator.
|
||||||
|
fn get_distance_to_corner<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Au
|
||||||
|
where F: Fn(Au, Au) -> Au
|
||||||
|
{
|
||||||
|
let dist = get_distance_to_sides(size, center, cmp);
|
||||||
|
Au::from_f32_px(dist.width.to_f32_px().hypot(dist.height.to_f32_px()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the distance to the nearest or farthest sides depending on the comparator.
|
||||||
|
///
|
||||||
|
/// The first return value is horizontal distance the second vertical distance.
|
||||||
|
fn get_distance_to_sides<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
|
||||||
|
where F: Fn(Au, Au) -> Au
|
||||||
|
{
|
||||||
|
let top_side = center.y;
|
||||||
|
let right_side = size.width - center.x;
|
||||||
|
let bottom_side = size.height - center.y;
|
||||||
|
let left_side = center.x;
|
||||||
|
Size2D::new(cmp(left_side, right_side), cmp(top_side, bottom_side))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the radius for an ellipse with the same ratio as if it was matched to the sides.
|
||||||
|
fn get_ellipse_radius<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
|
||||||
|
where F: Fn(Au, Au) -> Au
|
||||||
|
{
|
||||||
|
let dist = get_distance_to_sides(size, center, cmp);
|
||||||
|
Size2D::new(dist.width.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
|
||||||
|
dist.height.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines the radius of a circle if it was not explictly provided.
|
||||||
|
/// https://drafts.csswg.org/css-images-3/#typedef-size
|
||||||
|
fn convert_circle_size_keyword(keyword: SizeKeyword,
|
||||||
|
size: &Size2D<Au>,
|
||||||
|
center: &Point2D<Au>) -> Size2D<Au> {
|
||||||
|
use style::values::computed::image::SizeKeyword::*;
|
||||||
|
let radius = match keyword {
|
||||||
|
ClosestSide => {
|
||||||
|
let dist = get_distance_to_sides(size, center, ::std::cmp::min);
|
||||||
|
::std::cmp::min(dist.width, dist.height)
|
||||||
|
}
|
||||||
|
FarthestSide => {
|
||||||
|
let dist = get_distance_to_sides(size, center, ::std::cmp::max);
|
||||||
|
::std::cmp::max(dist.width, dist.height)
|
||||||
|
}
|
||||||
|
ClosestCorner => get_distance_to_corner(size, center, ::std::cmp::min),
|
||||||
|
FarthestCorner => get_distance_to_corner(size, center, ::std::cmp::max),
|
||||||
|
_ => {
|
||||||
|
// TODO(#16542)
|
||||||
|
println!("TODO: implement size keyword {:?} for circles", keyword);
|
||||||
|
Au::new(0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Size2D::new(radius, radius)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines the radius of an ellipse if it was not explictly provided.
|
||||||
|
/// https://drafts.csswg.org/css-images-3/#typedef-size
|
||||||
|
fn convert_ellipse_size_keyword(keyword: SizeKeyword,
|
||||||
|
size: &Size2D<Au>,
|
||||||
|
center: &Point2D<Au>) -> Size2D<Au> {
|
||||||
|
use style::values::computed::image::SizeKeyword::*;
|
||||||
|
match keyword {
|
||||||
|
ClosestSide => get_distance_to_sides(size, center, ::std::cmp::min),
|
||||||
|
FarthestSide => get_distance_to_sides(size, center, ::std::cmp::max),
|
||||||
|
ClosestCorner => get_ellipse_radius(size, center, ::std::cmp::min),
|
||||||
|
FarthestCorner => get_ellipse_radius(size, center, ::std::cmp::max),
|
||||||
|
_ => {
|
||||||
|
// TODO(#16542)
|
||||||
|
println!("TODO: implement size keyword {:?} for ellipses", keyword);
|
||||||
|
Size2D::new(Au::new(0), Au::new(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FragmentDisplayListBuilding for Fragment {
|
impl FragmentDisplayListBuilding for Fragment {
|
||||||
fn build_display_list_for_background_if_applicable(&self,
|
fn build_display_list_for_background_if_applicable(&self,
|
||||||
state: &mut DisplayListBuildState,
|
state: &mut DisplayListBuildState,
|
||||||
|
@ -653,16 +813,14 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
match background_image.0 {
|
match background_image.0 {
|
||||||
None => {}
|
None => {}
|
||||||
Some(computed::Image::Gradient(ref gradient)) => {
|
Some(computed::Image::Gradient(ref gradient)) => {
|
||||||
// FIXME: Radial gradients aren't implemented yet.
|
|
||||||
if let GradientKind::Linear(_) = gradient.gradient_kind {
|
|
||||||
self.build_display_list_for_background_gradient(state,
|
self.build_display_list_for_background_gradient(state,
|
||||||
display_list_section,
|
display_list_section,
|
||||||
|
&absolute_bounds,
|
||||||
&bounds,
|
&bounds,
|
||||||
&clip,
|
&clip,
|
||||||
gradient,
|
gradient,
|
||||||
style);
|
style);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Some(computed::Image::Url(ref image_url)) => {
|
Some(computed::Image::Url(ref image_url)) => {
|
||||||
if let Some(url) = image_url.url() {
|
if let Some(url) = image_url.url() {
|
||||||
self.build_display_list_for_background_image(state,
|
self.build_display_list_for_background_image(state,
|
||||||
|
@ -893,23 +1051,21 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_gradient(&self,
|
fn convert_linear_gradient(&self,
|
||||||
absolute_bounds: &Rect<Au>,
|
bounds: &Rect<Au>,
|
||||||
gradient: &Gradient,
|
stops: &[GradientItem],
|
||||||
style: &ServoComputedValues) -> Option<display_list::Gradient> {
|
angle_or_corner: &AngleOrCorner,
|
||||||
// FIXME: Repeating gradients aren't implemented yet.
|
repeating: bool,
|
||||||
if gradient.repeating {
|
style: &ServoComputedValues)
|
||||||
return None;
|
-> display_list::Gradient {
|
||||||
}
|
let angle = match *angle_or_corner {
|
||||||
let angle = if let GradientKind::Linear(angle_or_corner) = gradient.gradient_kind {
|
|
||||||
match angle_or_corner {
|
|
||||||
AngleOrCorner::Angle(angle) => angle.radians(),
|
AngleOrCorner::Angle(angle) => angle.radians(),
|
||||||
AngleOrCorner::Corner(horizontal, vertical) => {
|
AngleOrCorner::Corner(horizontal, vertical) => {
|
||||||
// This the angle for one of the diagonals of the box. Our angle
|
// This the angle for one of the diagonals of the box. Our angle
|
||||||
// will either be this one, this one + PI, or one of the other
|
// will either be this one, this one + PI, or one of the other
|
||||||
// two perpendicular angles.
|
// two perpendicular angles.
|
||||||
let atan = (absolute_bounds.size.height.to_f32_px() /
|
let atan = (bounds.size.height.to_f32_px() /
|
||||||
absolute_bounds.size.width.to_f32_px()).atan();
|
bounds.size.width.to_f32_px()).atan();
|
||||||
match (horizontal, vertical) {
|
match (horizontal, vertical) {
|
||||||
(HorizontalDirection::Right, VerticalDirection::Bottom)
|
(HorizontalDirection::Right, VerticalDirection::Bottom)
|
||||||
=> f32::consts::PI - atan,
|
=> f32::consts::PI - atan,
|
||||||
|
@ -921,18 +1077,14 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
=> -atan,
|
=> -atan,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// FIXME: Radial gradients aren't implemented yet.
|
|
||||||
return None;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get correct gradient line length, based on:
|
// Get correct gradient line length, based on:
|
||||||
// https://drafts.csswg.org/css-images-3/#linear-gradients
|
// https://drafts.csswg.org/css-images-3/#linear-gradients
|
||||||
let dir = Point2D::new(angle.sin(), -angle.cos());
|
let dir = Point2D::new(angle.sin(), -angle.cos());
|
||||||
|
|
||||||
let line_length = (dir.x * absolute_bounds.size.width.to_f32_px()).abs() +
|
let line_length = (dir.x * bounds.size.width.to_f32_px()).abs() +
|
||||||
(dir.y * absolute_bounds.size.height.to_f32_px()).abs();
|
(dir.y * bounds.size.height.to_f32_px()).abs();
|
||||||
|
|
||||||
let inv_dir_length = 1.0 / (dir.x * dir.x + dir.y * dir.y).sqrt();
|
let inv_dir_length = 1.0 / (dir.x * dir.x + dir.y * dir.y).sqrt();
|
||||||
|
|
||||||
|
@ -945,108 +1097,100 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
let length = Au::from_f32_px(
|
let length = Au::from_f32_px(
|
||||||
(delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0));
|
(delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0));
|
||||||
|
|
||||||
// Determine the position of each stop per CSS-IMAGES § 3.4.
|
let stops = convert_gradient_stops(stops, length, style);
|
||||||
//
|
|
||||||
// FIXME(#3908, pcwalton): Make sure later stops can't be behind earlier stops.
|
|
||||||
let stop_items = gradient.items.iter().filter_map(|item| {
|
|
||||||
match *item {
|
|
||||||
GradientItem::ColorStop(ref stop) => Some(stop),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}).collect::<Vec<_>>();
|
|
||||||
let mut stops = Vec::with_capacity(stop_items.len());
|
|
||||||
let mut stop_run = None;
|
|
||||||
for (i, stop) in stop_items.iter().enumerate() {
|
|
||||||
let offset = match stop.position {
|
|
||||||
None => {
|
|
||||||
if stop_run.is_none() {
|
|
||||||
// Initialize a new stop run.
|
|
||||||
let start_offset = if i == 0 {
|
|
||||||
0.0
|
|
||||||
} else {
|
|
||||||
// `unwrap()` here should never fail because this is the beginning of
|
|
||||||
// a stop run, which is always bounded by a length or percentage.
|
|
||||||
position_to_offset(stop_items[i - 1].position.unwrap(), length)
|
|
||||||
};
|
|
||||||
let (end_index, end_offset) =
|
|
||||||
match stop_items[i..]
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|&(_, ref stop)| stop.position.is_some()) {
|
|
||||||
None => (stop_items.len() - 1, 1.0),
|
|
||||||
Some((end_index, end_stop)) => {
|
|
||||||
// `unwrap()` here should never fail because this is the end of
|
|
||||||
// a stop run, which is always bounded by a length or
|
|
||||||
// percentage.
|
|
||||||
(end_index,
|
|
||||||
position_to_offset(end_stop.position.unwrap(), length))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
stop_run = Some(StopRun {
|
|
||||||
start_offset: start_offset,
|
|
||||||
end_offset: end_offset,
|
|
||||||
start_index: i,
|
|
||||||
stop_count: end_index - i,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let stop_run = stop_run.unwrap();
|
let center = Point2D::new(bounds.size.width / 2, bounds.size.height / 2);
|
||||||
let stop_run_length = stop_run.end_offset - stop_run.start_offset;
|
|
||||||
if stop_run.stop_count == 0 {
|
|
||||||
stop_run.end_offset
|
|
||||||
} else {
|
|
||||||
stop_run.start_offset +
|
|
||||||
stop_run_length * (i - stop_run.start_index) as f32 /
|
|
||||||
(stop_run.stop_count as f32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(position) => {
|
|
||||||
stop_run = None;
|
|
||||||
position_to_offset(position, length)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
stops.push(GradientStop {
|
|
||||||
offset: offset,
|
|
||||||
color: style.resolve_color(stop.color).to_gfx_color()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let center = Point2D::new(absolute_bounds.size.width / 2,
|
display_list::Gradient {
|
||||||
absolute_bounds.size.height / 2);
|
|
||||||
|
|
||||||
Some(display_list::Gradient {
|
|
||||||
start_point: center - delta,
|
start_point: center - delta,
|
||||||
end_point: center + delta,
|
end_point: center + delta,
|
||||||
stops: stops,
|
stops: stops,
|
||||||
})
|
repeating: repeating,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_radial_gradient(&self,
|
||||||
|
bounds: &Rect<Au>,
|
||||||
|
stops: &[GradientItem],
|
||||||
|
shape: &EndingShape,
|
||||||
|
center: &Position,
|
||||||
|
repeating: bool,
|
||||||
|
style: &ServoComputedValues)
|
||||||
|
-> display_list::RadialGradient {
|
||||||
|
let center = Point2D::new(specified(center.horizontal.0, bounds.size.width),
|
||||||
|
specified(center.vertical.0, bounds.size.height));
|
||||||
|
let radius = match *shape {
|
||||||
|
EndingShape::Circle(LengthOrKeyword::Length(length))
|
||||||
|
=> Size2D::new(length, length),
|
||||||
|
EndingShape::Circle(LengthOrKeyword::Keyword(word))
|
||||||
|
=> convert_circle_size_keyword(word, &bounds.size, ¢er),
|
||||||
|
EndingShape::Ellipse(LengthOrPercentageOrKeyword::LengthOrPercentage(horizontal,
|
||||||
|
vertical))
|
||||||
|
=> Size2D::new(specified(horizontal, bounds.size.width),
|
||||||
|
specified(vertical, bounds.size.height)),
|
||||||
|
EndingShape::Ellipse(LengthOrPercentageOrKeyword::Keyword(word))
|
||||||
|
=> convert_ellipse_size_keyword(word, &bounds.size, ¢er),
|
||||||
|
};
|
||||||
|
let length = Au::from_f32_px(radius.width.to_f32_px().hypot(radius.height.to_f32_px()));
|
||||||
|
let stops = convert_gradient_stops(stops, length, style);
|
||||||
|
display_list::RadialGradient {
|
||||||
|
center: center,
|
||||||
|
radius: radius,
|
||||||
|
stops: stops,
|
||||||
|
repeating: repeating,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_display_list_for_background_gradient(&self,
|
fn build_display_list_for_background_gradient(&self,
|
||||||
state: &mut DisplayListBuildState,
|
state: &mut DisplayListBuildState,
|
||||||
display_list_section: DisplayListSection,
|
display_list_section: DisplayListSection,
|
||||||
absolute_bounds: &Rect<Au>,
|
absolute_bounds: &Rect<Au>,
|
||||||
|
clip_bounds: &Rect<Au>,
|
||||||
clip: &ClippingRegion,
|
clip: &ClippingRegion,
|
||||||
gradient: &Gradient,
|
gradient: &Gradient,
|
||||||
style: &ServoComputedValues) {
|
style: &ServoComputedValues) {
|
||||||
let mut clip = clip.clone();
|
let mut clip = clip.clone();
|
||||||
clip.intersect_rect(absolute_bounds);
|
clip.intersect_rect(clip_bounds);
|
||||||
|
|
||||||
let grad = self.convert_gradient(absolute_bounds, gradient, style);
|
let border_padding = self.border_padding.to_physical(style.writing_mode);
|
||||||
|
let mut bounds = *absolute_bounds;
|
||||||
|
bounds.origin.x = bounds.origin.x + border_padding.left;
|
||||||
|
bounds.origin.y = bounds.origin.y + border_padding.top;
|
||||||
|
bounds.size.width = bounds.size.width - border_padding.horizontal();
|
||||||
|
bounds.size.height = bounds.size.height - border_padding.vertical();
|
||||||
|
|
||||||
if let Some(x) = grad {
|
let base = state.create_base_display_item(&bounds,
|
||||||
let base = state.create_base_display_item(absolute_bounds,
|
|
||||||
&clip,
|
&clip,
|
||||||
self.node,
|
self.node,
|
||||||
style.get_cursor(Cursor::Default),
|
style.get_cursor(Cursor::Default),
|
||||||
display_list_section);
|
display_list_section);
|
||||||
|
|
||||||
let gradient_display_item = DisplayItem::Gradient(box GradientDisplayItem {
|
let display_item = match gradient.gradient_kind {
|
||||||
|
GradientKind::Linear(ref angle_or_corner) => {
|
||||||
|
let gradient = self.convert_linear_gradient(&bounds,
|
||||||
|
&gradient.items[..],
|
||||||
|
angle_or_corner,
|
||||||
|
gradient.repeating,
|
||||||
|
style);
|
||||||
|
DisplayItem::Gradient(box GradientDisplayItem {
|
||||||
base: base,
|
base: base,
|
||||||
gradient: x,
|
gradient: gradient,
|
||||||
});
|
})
|
||||||
|
|
||||||
state.add_display_item(gradient_display_item);
|
|
||||||
}
|
}
|
||||||
|
GradientKind::Radial(ref shape, ref center) => {
|
||||||
|
let gradient = self.convert_radial_gradient(&bounds,
|
||||||
|
&gradient.items[..],
|
||||||
|
shape,
|
||||||
|
center,
|
||||||
|
gradient.repeating,
|
||||||
|
style);
|
||||||
|
DisplayItem::RadialGradient(box RadialGradientDisplayItem {
|
||||||
|
base: base,
|
||||||
|
gradient: gradient,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
state.add_display_item(display_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_display_list_for_box_shadow_if_applicable(&self,
|
fn build_display_list_for_box_shadow_if_applicable(&self,
|
||||||
|
@ -1159,24 +1303,26 @@ impl FragmentDisplayListBuilding for Fragment {
|
||||||
}
|
}
|
||||||
Some(computed::Image::Gradient(ref gradient)) => {
|
Some(computed::Image::Gradient(ref gradient)) => {
|
||||||
match gradient.gradient_kind {
|
match gradient.gradient_kind {
|
||||||
GradientKind::Linear(_) => {
|
GradientKind::Linear(angle_or_corner) => {
|
||||||
let grad = self.convert_gradient(&bounds, gradient, style);
|
let grad = self.convert_linear_gradient(&bounds,
|
||||||
|
&gradient.items[..],
|
||||||
|
&angle_or_corner,
|
||||||
|
gradient.repeating,
|
||||||
|
style);
|
||||||
|
|
||||||
if let Some(x) = grad {
|
|
||||||
state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
|
state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
|
||||||
base: base,
|
base: base,
|
||||||
border_widths: border.to_physical(style.writing_mode),
|
border_widths: border.to_physical(style.writing_mode),
|
||||||
details: BorderDetails::Gradient(display_list::GradientBorder {
|
details: BorderDetails::Gradient(display_list::GradientBorder {
|
||||||
gradient: x,
|
gradient: grad,
|
||||||
|
|
||||||
// TODO(gw): Support border-image-outset
|
// TODO(gw): Support border-image-outset
|
||||||
outset: SideOffsets2D::zero(),
|
outset: SideOffsets2D::zero(),
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
GradientKind::Radial(_, _) => {
|
GradientKind::Radial(_, _) => {
|
||||||
// TODO(gw): Handle border-image with radial gradient.
|
// TODO(#16638): Handle border-image with radial gradient.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -370,16 +370,41 @@ impl WebRenderDisplayItemConverter for DisplayItem {
|
||||||
let start_point = item.gradient.start_point.to_pointf();
|
let start_point = item.gradient.start_point.to_pointf();
|
||||||
let end_point = item.gradient.end_point.to_pointf();
|
let end_point = item.gradient.end_point.to_pointf();
|
||||||
let clip = item.base.clip.to_clip_region(builder);
|
let clip = item.base.clip.to_clip_region(builder);
|
||||||
|
let extend_mode = if item.gradient.repeating {
|
||||||
|
ExtendMode::Repeat
|
||||||
|
} else {
|
||||||
|
ExtendMode::Clamp
|
||||||
|
};
|
||||||
let gradient = builder.create_gradient(start_point,
|
let gradient = builder.create_gradient(start_point,
|
||||||
end_point,
|
end_point,
|
||||||
item.gradient.stops.clone(),
|
item.gradient.stops.clone(),
|
||||||
ExtendMode::Clamp);
|
extend_mode);
|
||||||
builder.push_gradient(rect,
|
builder.push_gradient(rect,
|
||||||
clip,
|
clip,
|
||||||
gradient,
|
gradient,
|
||||||
rect.size,
|
rect.size,
|
||||||
webrender_traits::LayoutSize::zero());
|
webrender_traits::LayoutSize::zero());
|
||||||
}
|
}
|
||||||
|
DisplayItem::RadialGradient(ref item) => {
|
||||||
|
let rect = item.base.bounds.to_rectf();
|
||||||
|
let center = item.gradient.center.to_pointf();
|
||||||
|
let radius = item.gradient.radius.to_sizef();
|
||||||
|
let clip = item.base.clip.to_clip_region(builder);
|
||||||
|
let extend_mode = if item.gradient.repeating {
|
||||||
|
ExtendMode::Repeat
|
||||||
|
} else {
|
||||||
|
ExtendMode::Clamp
|
||||||
|
};
|
||||||
|
let gradient = builder.create_radial_gradient(center,
|
||||||
|
radius,
|
||||||
|
item.gradient.stops.clone(),
|
||||||
|
extend_mode);
|
||||||
|
builder.push_radial_gradient(rect,
|
||||||
|
clip,
|
||||||
|
gradient,
|
||||||
|
rect.size,
|
||||||
|
webrender_traits::LayoutSize::zero());
|
||||||
|
}
|
||||||
DisplayItem::Line(..) => {
|
DisplayItem::Line(..) => {
|
||||||
println!("TODO DisplayItem::Line");
|
println!("TODO DisplayItem::Line");
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,11 @@ use std::fmt;
|
||||||
use style_traits::ToCss;
|
use style_traits::ToCss;
|
||||||
use values::computed::{Angle, Context, Length, LengthOrPercentage, NumberOrPercentage, ToComputedValue};
|
use values::computed::{Angle, Context, Length, LengthOrPercentage, NumberOrPercentage, ToComputedValue};
|
||||||
use values::computed::position::Position;
|
use values::computed::position::Position;
|
||||||
use values::specified::{self, HorizontalDirection, SizeKeyword, VerticalDirection};
|
use values::specified::{self, HorizontalDirection, VerticalDirection};
|
||||||
use values::specified::image::CompatMode;
|
use values::specified::image::CompatMode;
|
||||||
use values::specified::url::SpecifiedUrl;
|
use values::specified::url::SpecifiedUrl;
|
||||||
|
|
||||||
|
pub use values::specified::SizeKeyword;
|
||||||
|
|
||||||
impl ToComputedValue for specified::Image {
|
impl ToComputedValue for specified::Image {
|
||||||
type ComputedValue = Image;
|
type ComputedValue = Image;
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[align-content-004.htm]
|
|
||||||
type: reftest
|
|
||||||
expected: FAIL
|
|
Loading…
Add table
Add a link
Reference in a new issue