Support multiple backgrounds in servo layout

This commit is contained in:
Manish Goregaokar 2016-08-20 00:20:16 +05:30
parent 66cdf9ae4f
commit 69ada0d7a3
5 changed files with 125 additions and 95 deletions

View file

@ -60,6 +60,10 @@ use table_cell::CollapsedBordersForCell;
use url::Url; use url::Url;
use util::opts; use util::opts;
fn get_cyclic<T>(arr: &[T], index: usize) -> &T {
&arr[index % arr.len()]
}
pub struct DisplayListBuildState<'a> { pub struct DisplayListBuildState<'a> {
pub layout_context: &'a LayoutContext<'a>, pub layout_context: &'a LayoutContext<'a>,
pub items: Vec<DisplayItem>, pub items: Vec<DisplayItem>,
@ -146,7 +150,7 @@ pub trait FragmentDisplayListBuilding {
fn compute_background_image_size(&self, fn compute_background_image_size(&self,
style: &ServoComputedValues, style: &ServoComputedValues,
bounds: &Rect<Au>, bounds: &Rect<Au>,
image: &WebRenderImageInfo) image: &WebRenderImageInfo, index: usize)
-> Size2D<Au>; -> Size2D<Au>;
/// Adds the display items necessary to paint the background image of this fragment to the /// Adds the display items necessary to paint the background image of this fragment to the
@ -157,7 +161,8 @@ pub trait FragmentDisplayListBuilding {
display_list_section: DisplayListSection, display_list_section: DisplayListSection,
absolute_bounds: &Rect<Au>, absolute_bounds: &Rect<Au>,
clip: &ClippingRegion, clip: &ClippingRegion,
image_url: &Url); image_url: &Url,
background_index: usize);
/// 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.
@ -344,27 +349,32 @@ impl FragmentDisplayListBuilding for Fragment {
if !border_radii.is_square() { if !border_radii.is_square() {
clip.intersect_with_rounded_rect(absolute_bounds, &border_radii) clip.intersect_with_rounded_rect(absolute_bounds, &border_radii)
} }
let background = style.get_background();
// FIXME: This causes a lot of background colors to be displayed when they are clearly not // FIXME: This causes a lot of background colors to be displayed when they are clearly not
// needed. We could use display list optimization to clean this up, but it still seems // needed. We could use display list optimization to clean this up, but it still seems
// inefficient. What we really want is something like "nearest ancestor element that // inefficient. What we really want is something like "nearest ancestor element that
// doesn't have a fragment". // doesn't have a fragment".
let background_color = style.resolve_color(style.get_background().background_color); let background_color = style.resolve_color(background.background_color);
// 'background-clip' determines the area within which the background is painted. // 'background-clip' determines the area within which the background is painted.
// http://dev.w3.org/csswg/css-backgrounds-3/#the-background-clip // http://dev.w3.org/csswg/css-backgrounds-3/#the-background-clip
let mut bounds = *absolute_bounds; let mut bounds = *absolute_bounds;
match style.get_background().background_clip { // This is the clip for the color (which is the last element in the bg array)
background_clip::T::border_box => {} let color_clip = get_cyclic(&background.background_clip.0,
background_clip::T::padding_box => { background.background_image.0.len() - 1);
match *color_clip {
background_clip::single_value::T::border_box => {}
background_clip::single_value::T::padding_box => {
let border = style.logical_border_width().to_physical(style.writing_mode); let border = style.logical_border_width().to_physical(style.writing_mode);
bounds.origin.x = bounds.origin.x + border.left; bounds.origin.x = bounds.origin.x + border.left;
bounds.origin.y = bounds.origin.y + border.top; bounds.origin.y = bounds.origin.y + border.top;
bounds.size.width = bounds.size.width - border.horizontal(); bounds.size.width = bounds.size.width - border.horizontal();
bounds.size.height = bounds.size.height - border.vertical(); bounds.size.height = bounds.size.height - border.vertical();
} }
background_clip::T::content_box => { background_clip::single_value::T::content_box => {
let border_padding = self.border_padding.to_physical(style.writing_mode); let border_padding = self.border_padding.to_physical(style.writing_mode);
bounds.origin.x = bounds.origin.x + border_padding.left; bounds.origin.x = bounds.origin.x + border_padding.left;
bounds.origin.y = bounds.origin.y + border_padding.top; bounds.origin.y = bounds.origin.y + border_padding.top;
@ -388,23 +398,26 @@ impl FragmentDisplayListBuilding for Fragment {
// Implements background image, per spec: // Implements background image, per spec:
// http://www.w3.org/TR/CSS21/colors.html#background // http://www.w3.org/TR/CSS21/colors.html#background
let background = style.get_background(); let background = style.get_background();
match background.background_image.0 { for (i, background_image) in background.background_image.0.iter().enumerate().rev() {
None => {} match background_image.0 {
Some(computed::Image::LinearGradient(ref gradient)) => { None => {}
self.build_display_list_for_background_linear_gradient(state, Some(computed::Image::LinearGradient(ref gradient)) => {
display_list_section, self.build_display_list_for_background_linear_gradient(state,
&bounds, display_list_section,
&clip, &bounds,
gradient, &clip,
style); gradient,
} style);
Some(computed::Image::Url(ref image_url, ref _extra_data)) => { }
self.build_display_list_for_background_image(state, Some(computed::Image::Url(ref image_url, ref _extra_data)) => {
style, self.build_display_list_for_background_image(state,
display_list_section, style,
&bounds, display_list_section,
&clip, &bounds,
image_url); &clip,
image_url,
i);
}
} }
} }
} }
@ -412,7 +425,8 @@ impl FragmentDisplayListBuilding for Fragment {
fn compute_background_image_size(&self, fn compute_background_image_size(&self,
style: &ServoComputedValues, style: &ServoComputedValues,
bounds: &Rect<Au>, bounds: &Rect<Au>,
image: &WebRenderImageInfo) image: &WebRenderImageInfo,
index: usize)
-> Size2D<Au> { -> Size2D<Au> {
// If `image_aspect_ratio` < `bounds_aspect_ratio`, the image is tall; otherwise, it is // If `image_aspect_ratio` < `bounds_aspect_ratio`, the image is tall; otherwise, it is
// wide. // wide.
@ -420,19 +434,22 @@ impl FragmentDisplayListBuilding for Fragment {
let bounds_aspect_ratio = bounds.size.width.to_f64_px() / bounds.size.height.to_f64_px(); let bounds_aspect_ratio = bounds.size.width.to_f64_px() / bounds.size.height.to_f64_px();
let intrinsic_size = Size2D::new(Au::from_px(image.width as i32), let intrinsic_size = Size2D::new(Au::from_px(image.width as i32),
Au::from_px(image.height as i32)); Au::from_px(image.height as i32));
match (style.get_background().background_size.clone(), let background_size = get_cyclic(&style.get_background().background_size.0, index).clone();
image_aspect_ratio < bounds_aspect_ratio) { match (background_size, image_aspect_ratio < bounds_aspect_ratio) {
(background_size::T::Contain, false) | (background_size::T::Cover, true) => { (background_size::single_value::T::Contain, false) |
(background_size::single_value::T::Cover, true) => {
Size2D::new(bounds.size.width, Size2D::new(bounds.size.width,
Au::from_f64_px(bounds.size.width.to_f64_px() / image_aspect_ratio)) Au::from_f64_px(bounds.size.width.to_f64_px() / image_aspect_ratio))
} }
(background_size::T::Contain, true) | (background_size::T::Cover, false) => { (background_size::single_value::T::Contain, true) |
(background_size::single_value::T::Cover, false) => {
Size2D::new(Au::from_f64_px(bounds.size.height.to_f64_px() * image_aspect_ratio), Size2D::new(Au::from_f64_px(bounds.size.height.to_f64_px() * image_aspect_ratio),
bounds.size.height) bounds.size.height)
} }
(background_size::T::Explicit(background_size::ExplicitSize { (background_size::single_value::T::Explicit(background_size::single_value
::ExplicitSize {
width, width,
height: LengthOrPercentageOrAuto::Auto, height: LengthOrPercentageOrAuto::Auto,
}), _) => { }), _) => {
@ -441,7 +458,8 @@ impl FragmentDisplayListBuilding for Fragment {
Size2D::new(width, Au::from_f64_px(width.to_f64_px() / image_aspect_ratio)) Size2D::new(width, Au::from_f64_px(width.to_f64_px() / image_aspect_ratio))
} }
(background_size::T::Explicit(background_size::ExplicitSize { (background_size::single_value::T::Explicit(background_size::single_value
::ExplicitSize {
width: LengthOrPercentageOrAuto::Auto, width: LengthOrPercentageOrAuto::Auto,
height height
}), _) => { }), _) => {
@ -450,7 +468,8 @@ impl FragmentDisplayListBuilding for Fragment {
Size2D::new(Au::from_f64_px(height.to_f64_px() * image_aspect_ratio), height) Size2D::new(Au::from_f64_px(height.to_f64_px() * image_aspect_ratio), height)
} }
(background_size::T::Explicit(background_size::ExplicitSize { (background_size::single_value::T::Explicit(background_size::single_value
::ExplicitSize {
width, width,
height height
}), _) => { }), _) => {
@ -468,19 +487,22 @@ impl FragmentDisplayListBuilding for Fragment {
display_list_section: DisplayListSection, display_list_section: DisplayListSection,
absolute_bounds: &Rect<Au>, absolute_bounds: &Rect<Au>,
clip: &ClippingRegion, clip: &ClippingRegion,
image_url: &Url) { image_url: &Url,
index: usize) {
let background = style.get_background(); let background = style.get_background();
let fetch_image_data_as_well = !opts::get().use_webrender; let fetch_image_data_as_well = !opts::get().use_webrender;
let webrender_image = let webrender_image =
state.layout_context.get_webrender_image_for_url(image_url, state.layout_context.get_webrender_image_for_url(image_url,
UsePlaceholder::No, UsePlaceholder::No,
fetch_image_data_as_well); fetch_image_data_as_well);
if let Some((webrender_image, image_data)) = webrender_image { if let Some((webrender_image, image_data)) = webrender_image {
debug!("(building display list) building background image"); debug!("(building display list) building background image");
// Use `background-size` to get the size. // Use `background-size` to get the size.
let mut bounds = *absolute_bounds; let mut bounds = *absolute_bounds;
let image_size = self.compute_background_image_size(style, &bounds, &webrender_image); let image_size = self.compute_background_image_size(style, &bounds,
&webrender_image, index);
// Clip. // Clip.
// //
@ -492,51 +514,54 @@ impl FragmentDisplayListBuilding for Fragment {
let border = style.logical_border_width().to_physical(style.writing_mode); let border = style.logical_border_width().to_physical(style.writing_mode);
// Use 'background-origin' to get the origin value. // Use 'background-origin' to get the origin value.
let (mut origin_x, mut origin_y) = match background.background_origin { let origin = get_cyclic(&background.background_origin.0, index);
background_origin::T::padding_box => { let (mut origin_x, mut origin_y) = match *origin {
(Au(0), Au(0)) background_origin::single_value::T::padding_box => {
} (Au(0), Au(0))
background_origin::T::border_box => { }
(-border.left, -border.top) background_origin::single_value::T::border_box => {
} (-border.left, -border.top)
background_origin::T::content_box => { }
let border_padding = self.border_padding.to_physical(self.style.writing_mode); background_origin::single_value::T::content_box => {
(border_padding.left - border.left, border_padding.top - border.top) let border_padding = self.border_padding.to_physical(self.style.writing_mode);
} (border_padding.left - border.left, border_padding.top - border.top)
}
}; };
// Use `background-attachment` to get the initial virtual origin // Use `background-attachment` to get the initial virtual origin
let (virtual_origin_x, virtual_origin_y) = match background.background_attachment { let attachment = get_cyclic(&background.background_attachment.0, index);
background_attachment::T::scroll => { let (virtual_origin_x, virtual_origin_y) = match *attachment {
(absolute_bounds.origin.x, absolute_bounds.origin.y) background_attachment::single_value::T::scroll => {
} (absolute_bounds.origin.x, absolute_bounds.origin.y)
background_attachment::T::fixed => { }
// If the background-attachment value for this image is fixed, then background_attachment::single_value::T::fixed => {
// 'background-origin' has no effect. // If the background-attachment value for this image is fixed, then
origin_x = Au(0); // 'background-origin' has no effect.
origin_y = Au(0); origin_x = Au(0);
(Au(0), Au(0)) origin_y = Au(0);
} (Au(0), Au(0))
}
}; };
let position = *get_cyclic(&background.background_position.0, index);
// Use `background-position` to get the offset. // Use `background-position` to get the offset.
let horizontal_position = model::specified(background.background_position.horizontal, let horizontal_position = model::specified(position.horizontal,
bounds.size.width - image_size.width); bounds.size.width - image_size.width);
let vertical_position = model::specified(background.background_position.vertical, let vertical_position = model::specified(position.vertical,
bounds.size.height - image_size.height); bounds.size.height - image_size.height);
let abs_x = border.left + virtual_origin_x + horizontal_position + origin_x; let abs_x = border.left + virtual_origin_x + horizontal_position + origin_x;
let abs_y = border.top + virtual_origin_y + vertical_position + origin_y; let abs_y = border.top + virtual_origin_y + vertical_position + origin_y;
// Adjust origin and size based on background-repeat // Adjust origin and size based on background-repeat
match background.background_repeat { match *get_cyclic(&background.background_repeat.0, index) {
background_repeat::T::no_repeat => { background_repeat::single_value::T::no_repeat => {
bounds.origin.x = abs_x; bounds.origin.x = abs_x;
bounds.origin.y = abs_y; bounds.origin.y = abs_y;
bounds.size.width = image_size.width; bounds.size.width = image_size.width;
bounds.size.height = image_size.height; bounds.size.height = image_size.height;
} }
background_repeat::T::repeat_x => { background_repeat::single_value::T::repeat_x => {
bounds.origin.y = abs_y; bounds.origin.y = abs_y;
bounds.size.height = image_size.height; bounds.size.height = image_size.height;
ImageFragmentInfo::tile_image(&mut bounds.origin.x, ImageFragmentInfo::tile_image(&mut bounds.origin.x,
@ -544,7 +569,7 @@ impl FragmentDisplayListBuilding for Fragment {
abs_x, abs_x,
image_size.width.to_nearest_px() as u32); image_size.width.to_nearest_px() as u32);
} }
background_repeat::T::repeat_y => { background_repeat::single_value::T::repeat_y => {
bounds.origin.x = abs_x; bounds.origin.x = abs_x;
bounds.size.width = image_size.width; bounds.size.width = image_size.width;
ImageFragmentInfo::tile_image(&mut bounds.origin.y, ImageFragmentInfo::tile_image(&mut bounds.origin.y,
@ -552,31 +577,32 @@ impl FragmentDisplayListBuilding for Fragment {
abs_y, abs_y,
image_size.height.to_nearest_px() as u32); image_size.height.to_nearest_px() as u32);
} }
background_repeat::T::repeat => { background_repeat::single_value::T::repeat => {
ImageFragmentInfo::tile_image(&mut bounds.origin.x, ImageFragmentInfo::tile_image(&mut bounds.origin.x,
&mut bounds.size.width, &mut bounds.size.width,
abs_x, abs_x,
image_size.width.to_nearest_px() as u32); image_size.width.to_nearest_px() as u32);
ImageFragmentInfo::tile_image(&mut bounds.origin.y, ImageFragmentInfo::tile_image(&mut bounds.origin.y,
&mut bounds.size.height, &mut bounds.size.height,
abs_y, abs_y,
image_size.height.to_nearest_px() as u32); image_size.height.to_nearest_px() as u32);
} }
}; };
// Create the image display item. // Create the image display item.
let base = state.create_base_display_item(&bounds, let base = state.create_base_display_item(&bounds,
&clip, &clip,
self.node, self.node,
style.get_cursor(Cursor::Default), style.get_cursor(Cursor::Default),
display_list_section); display_list_section);
state.add_display_item(DisplayItem::ImageClass(box ImageDisplayItem { state.add_display_item(DisplayItem::ImageClass(box ImageDisplayItem {
base: base, base: base,
webrender_image: webrender_image, webrender_image: webrender_image,
image_data: image_data.map(Arc::new), image_data: image_data.map(Arc::new),
stretch_size: Size2D::new(image_size.width, image_size.height), stretch_size: Size2D::new(image_size.width, image_size.height),
image_rendering: style.get_inheritedbox().image_rendering.clone(), image_rendering: style.get_inheritedbox().image_rendering.clone(),
})); }));
} }
} }

View file

@ -362,8 +362,11 @@ impl LayoutElementHelpers for LayoutJS<Element> {
if let Some(url) = background { if let Some(url) = background {
hints.push(from_declaration( hints.push(from_declaration(
PropertyDeclaration::BackgroundImage(DeclaredValue::Value( PropertyDeclaration::BackgroundImage(DeclaredValue::Value(
background_image::SpecifiedValue(Some( background_image::SpecifiedValue(vec![
specified::Image::Url(url, specified::UrlExtraData { }))))))); background_image::single_value::SpecifiedValue(Some(
specified::Image::Url(url, specified::UrlExtraData { })
))
])))));
} }
let color = if let Some(this) = self.downcast::<HTMLFontElement>() { let color = if let Some(this) = self.downcast::<HTMLFontElement>() {

View file

@ -69,10 +69,10 @@
${caller.body()} ${caller.body()}
} }
pub mod computed_value { pub mod computed_value {
use super::single_value; pub use super::single_value::computed_value as single_value;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct T(pub Vec<single_value::computed_value::T>); pub struct T(pub Vec<single_value::T>);
} }
impl ToCss for computed_value::T { impl ToCss for computed_value::T {

View file

@ -10,7 +10,7 @@ ${helpers.predefined_type("background-color", "CSSColor",
"::cssparser::Color::RGBA(::cssparser::RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */", "::cssparser::Color::RGBA(::cssparser::RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */",
animatable=True)} animatable=True)}
<%helpers:vector_longhand gecko_only="True" name="background-image" animatable="False"> <%helpers:vector_longhand name="background-image" animatable="False">
use cssparser::ToCss; use cssparser::ToCss;
use std::fmt; use std::fmt;
use values::specified::Image; use values::specified::Image;
@ -79,7 +79,7 @@ ${helpers.predefined_type("background-color", "CSSColor",
} }
</%helpers:vector_longhand> </%helpers:vector_longhand>
<%helpers:vector_longhand name="background-position" gecko_only="True" animatable="True"> <%helpers:vector_longhand name="background-position" animatable="True">
use cssparser::ToCss; use cssparser::ToCss;
use std::fmt; use std::fmt;
use values::LocalToCss; use values::LocalToCss;
@ -121,25 +121,21 @@ ${helpers.predefined_type("background-color", "CSSColor",
${helpers.single_keyword("background-repeat", ${helpers.single_keyword("background-repeat",
"repeat repeat-x repeat-y no-repeat", "repeat repeat-x repeat-y no-repeat",
vector=True, vector=True,
gecko_only=True,
animatable=False)} animatable=False)}
${helpers.single_keyword("background-attachment", ${helpers.single_keyword("background-attachment",
"scroll fixed" + (" local" if product == "gecko" else ""), "scroll fixed" + (" local" if product == "gecko" else ""),
vector=True, vector=True,
gecko_only=True,
animatable=False)} animatable=False)}
${helpers.single_keyword("background-clip", ${helpers.single_keyword("background-clip",
"border-box padding-box content-box", "border-box padding-box content-box",
vector=True, vector=True,
gecko_only=True,
animatable=False)} animatable=False)}
${helpers.single_keyword("background-origin", ${helpers.single_keyword("background-origin",
"padding-box border-box content-box", "padding-box border-box content-box",
vector=True, vector=True,
gecko_only=True,
animatable=False)} animatable=False)}
<%helpers:vector_longhand name="background-size" animatable="True"> <%helpers:vector_longhand name="background-size" animatable="True">

View file

@ -23,14 +23,14 @@
% endfor % endfor
loop { loop {
if background_color.is_none() { if let Ok(value) = input.try(|input| background_color::parse(context, input)) {
if let Ok(value) = input.try(|input| background_color::parse(context, input)) { if background_color.is_none() {
background_color = Some(value); background_color = Some(value);
continue continue
} else {
// color can only be the last element
return Err(())
} }
} else {
// color can only be the last element
return Err(())
} }
if position.is_none() { if position.is_none() {
if let Ok(value) = input.try(|input| background_position::single_value::parse(context, input)) { if let Ok(value) = input.try(|input| background_position::single_value::parse(context, input)) {
@ -118,6 +118,11 @@
.unwrap_or(0)); .unwrap_or(0));
% endfor % endfor
// There should be at least one declared value
if len == 0 {
return Err(())
}
let iter = repeat(None).take(len - 1).chain(once(Some(self.background_color))) let iter = repeat(None).take(len - 1).chain(once(Some(self.background_color)))
% for name in "image position repeat size attachment origin clip".split(): % for name in "image position repeat size attachment origin clip".split():
.zip(extract_value(self.background_${name}).into_iter() .zip(extract_value(self.background_${name}).into_iter()