Auto merge of #21608 - pyfisch:border-gradients, r=emilio

Improve border images

Respect CSS border-image-width.
Properly support gradients as a border-image-source.

Add a new test and mark two more as passing.

<!-- 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/21608)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2018-09-28 16:36:53 -04:00 committed by GitHub
commit 97e3c5f3a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 261 additions and 160 deletions

View file

@ -9,11 +9,11 @@
#![deny(unsafe_code)]
// FIXME(rust-lang/rust#26264): Remove GenericEndingShape and GenericGradientItem.
// FIXME(rust-lang/rust#26264): Remove GenericEndingShape, GenericGradientItem,
// GenericBackgroundSize and GenericBorderImageSideWidth.
use app_units::Au;
use display_list::ToLayout;
use display_list::items::WebRenderImageInfo;
use euclid::{Point2D, Rect, SideOffsets2D, Size2D, Vector2D};
use model::{self, MaybeAuto};
use style::computed_values::background_attachment::single_value::T as BackgroundAttachment;
@ -23,19 +23,21 @@ use style::computed_values::border_image_outset::T as BorderImageOutset;
use style::properties::ComputedValues;
use style::properties::style_structs::{self, Background};
use style::values::Either;
use style::values::computed::{Angle, GradientItem, BackgroundSize as ComputedBackgroundSize};
use style::values::computed::{Angle, GradientItem, BackgroundSize};
use style::values::computed::{BorderImageWidth, BorderImageSideWidth};
use style::values::computed::{LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
use style::values::computed::{NumberOrPercentage, Percentage, Position};
use style::values::computed::image::{EndingShape, LineDirection};
use style::values::generics::NonNegative;
use style::values::generics::background::BackgroundSize;
use style::values::generics::background::BackgroundSize as GenericBackgroundSize;
use style::values::generics::border::{BorderImageSideWidth as GenericBorderImageSideWidth};
use style::values::generics::image::{Circle, Ellipse, ShapeExtent};
use style::values::generics::image::EndingShape as GenericEndingShape;
use style::values::generics::image::GradientItem as GenericGradientItem;
use style::values::specified::background::BackgroundRepeatKeyword;
use style::values::specified::position::{X, Y};
use webrender_api::{BorderDetails, BorderRadius, BorderSide, BorderStyle, ColorF, ExtendMode};
use webrender_api::{Gradient, GradientStop, LayoutSize, NinePatchBorder, NinePatchBorderSource};
use webrender_api::{BorderRadius, BorderSide, BorderStyle, BorderWidths, ColorF};
use webrender_api::{ExtendMode, Gradient, GradientStop, LayoutSize};
use webrender_api::{NormalBorder, RadialGradient};
/// A helper data structure for gradients.
@ -68,7 +70,7 @@ pub struct BackgroundPlacement {
pub fixed: bool,
}
trait ResolvePercentage {
pub trait ResolvePercentage {
fn resolve(&self, length: u32) -> u32;
}
@ -94,14 +96,14 @@ pub fn get_cyclic<T>(arr: &[T], index: usize) -> &T {
/// For a given area and an image compute how big the
/// image should be displayed on the background.
fn compute_background_image_size(
bg_size: ComputedBackgroundSize,
bg_size: BackgroundSize,
bounds_size: Size2D<Au>,
intrinsic_size: Option<Size2D<Au>>,
) -> Size2D<Au> {
match intrinsic_size {
None => match bg_size {
BackgroundSize::Cover | BackgroundSize::Contain => bounds_size,
BackgroundSize::Explicit { width, height } => Size2D::new(
GenericBackgroundSize::Cover | GenericBackgroundSize::Contain => bounds_size,
GenericBackgroundSize::Explicit { width, height } => Size2D::new(
MaybeAuto::from_style(width.0, bounds_size.width)
.specified_or_default(bounds_size.width),
MaybeAuto::from_style(height.0, bounds_size.height)
@ -115,16 +117,20 @@ fn compute_background_image_size(
let bounds_aspect_ratio =
bounds_size.width.to_f32_px() / bounds_size.height.to_f32_px();
match (bg_size, image_aspect_ratio < bounds_aspect_ratio) {
(BackgroundSize::Contain, false) | (BackgroundSize::Cover, true) => Size2D::new(
bounds_size.width,
bounds_size.width.scale_by(image_aspect_ratio.recip()),
),
(BackgroundSize::Contain, true) | (BackgroundSize::Cover, false) => Size2D::new(
bounds_size.height.scale_by(image_aspect_ratio),
bounds_size.height,
),
(GenericBackgroundSize::Contain, false) | (GenericBackgroundSize::Cover, true) => {
Size2D::new(
bounds_size.width,
bounds_size.width.scale_by(image_aspect_ratio.recip()),
)
},
(GenericBackgroundSize::Contain, true) | (GenericBackgroundSize::Cover, false) => {
Size2D::new(
bounds_size.height.scale_by(image_aspect_ratio),
bounds_size.height,
)
},
(
BackgroundSize::Explicit {
GenericBackgroundSize::Explicit {
width,
height: NonNegative(LengthOrPercentageOrAuto::Auto),
},
@ -135,7 +141,7 @@ fn compute_background_image_size(
Size2D::new(width, width.scale_by(image_aspect_ratio.recip()))
},
(
BackgroundSize::Explicit {
GenericBackgroundSize::Explicit {
width: NonNegative(LengthOrPercentageOrAuto::Auto),
height,
},
@ -145,7 +151,7 @@ fn compute_background_image_size(
.specified_or_default(own_size.height);
Size2D::new(height.scale_by(image_aspect_ratio), height)
},
(BackgroundSize::Explicit { width, height }, _) => Size2D::new(
(GenericBackgroundSize::Explicit { width, height }, _) => Size2D::new(
MaybeAuto::from_style(width.0, bounds_size.width)
.specified_or_default(own_size.width),
MaybeAuto::from_style(height.0, bounds_size.height)
@ -795,37 +801,6 @@ pub fn calculate_inner_border_radii(
radii
}
/// Given an image and a border style constructs a border image.
///
/// See: https://drafts.csswg.org/css-backgrounds-3/#border-images
pub fn build_image_border_details(
webrender_image: WebRenderImageInfo,
border_style_struct: &style_structs::Border,
outset: SideOffsets2D<f32>,
) -> Option<BorderDetails> {
let corners = &border_style_struct.border_image_slice.offsets;
let border_image_repeat = &border_style_struct.border_image_repeat;
if let Some(image_key) = webrender_image.key {
Some(BorderDetails::NinePatch(NinePatchBorder {
source: NinePatchBorderSource::Image(image_key),
width: webrender_image.width,
height: webrender_image.height,
slice: SideOffsets2D::new(
corners.0.resolve(webrender_image.height),
corners.1.resolve(webrender_image.width),
corners.2.resolve(webrender_image.height),
corners.3.resolve(webrender_image.width),
),
fill: border_style_struct.border_image_slice.fill,
repeat_horizontal: border_image_repeat.0.to_layout(),
repeat_vertical: border_image_repeat.1.to_layout(),
outset: outset,
}))
} else {
None
}
}
fn calculate_border_image_outset_side(outset: LengthOrNumber, border_width: Au) -> Au {
match outset {
Either::First(length) => length.into(),
@ -844,3 +819,28 @@ pub fn calculate_border_image_outset(
calculate_border_image_outset_side(outset.3, border.left),
)
}
fn calculate_border_image_width_side(
border_image_width: BorderImageSideWidth,
border_width: f32,
total_length: Au,
) -> f32 {
match border_image_width {
GenericBorderImageSideWidth::Length(v) => v.to_used_value(total_length).to_f32_px(),
GenericBorderImageSideWidth::Number(x) => border_width * x,
GenericBorderImageSideWidth::Auto => border_width,
}
}
pub fn calculate_border_image_width(
width: &BorderImageWidth,
border: BorderWidths,
border_area: Size2D<Au>,
) -> BorderWidths {
BorderWidths {
left: calculate_border_image_width_side(width.3, border.left, border_area.width),
top: calculate_border_image_width_side(width.0, border.top, border_area.height),
right: calculate_border_image_width_side(width.1, border.right, border_area.width),
bottom: calculate_border_image_width_side(width.2, border.bottom, border_area.height),
}
}

View file

@ -15,11 +15,11 @@ use block::BlockFlow;
use canvas_traits::canvas::{CanvasMsg, FromLayoutMsg};
use context::LayoutContext;
use display_list::ToLayout;
use display_list::background::{build_border_radius, build_image_border_details};
use display_list::background::{calculate_border_image_outset, calculate_inner_border_radii};
use display_list::background::{build_border_radius, calculate_inner_border_radii};
use display_list::background::{calculate_border_image_outset, calculate_border_image_width};
use display_list::background::{compute_background_clip, compute_background_placement};
use display_list::background::{convert_linear_gradient, convert_radial_gradient, get_cyclic};
use display_list::background::simple_normal_border;
use display_list::background::{simple_normal_border, ResolvePercentage};
use display_list::items::{BaseDisplayItem, BLUR_INFLATION_FACTOR, ClipScrollNode};
use display_list::items::{ClipScrollNodeIndex, ClipScrollNodeType, ClippingAndScrolling};
use display_list::items::{ClippingRegion, DisplayItem, DisplayItemMetadata, DisplayList};
@ -62,6 +62,7 @@ use style::servo::restyle_damage::ServoRestyleDamage;
use style::values::{Either, RGBA};
use style::values::computed::Gradient;
use style::values::computed::effects::SimpleShadow;
use style::values::computed::image::Image as ComputedImage;
use style::values::generics::background::BackgroundSize;
use style::values::generics::image::{GradientKind, Image, PaintWorklet};
use style::values::generics::ui::Cursor;
@ -71,8 +72,8 @@ use style_traits::cursor::CursorKind;
use table_cell::CollapsedBordersForCell;
use webrender_api::{self, BorderDetails, BorderRadius, BorderSide, BoxShadowClipMode, ColorF};
use webrender_api::{ExternalScrollId, FilterOp, GlyphInstance, ImageRendering, LayoutRect};
use webrender_api::{LayoutSize, LayoutTransform, LayoutVector2D, LineStyle, NormalBorder};
use webrender_api::{StickyOffsetBounds, ScrollSensitivity};
use webrender_api::{LayoutSize, LayoutTransform, LayoutVector2D, LineStyle, NinePatchBorder};
use webrender_api::{NinePatchBorderSource, NormalBorder, StickyOffsetBounds, ScrollSensitivity};
fn establishes_containing_block_for_absolute(
flags: StackingContextCollectionFlags,
@ -666,6 +667,19 @@ pub trait FragmentDisplayListBuilding {
clip: Rect<Au>,
);
/// Add display item for image border.
///
/// Returns `Some` if the addition was successful.
fn build_display_list_for_border_image(
&self,
state: &mut DisplayListBuildState,
style: &ComputedValues,
base: BaseDisplayItem,
bounds: Rect<Au>,
image: &ComputedImage,
border_width: SideOffsets2D<Au>,
) -> Option<()>;
/// Adds the display items necessary to paint the outline of this fragment to the display list
/// if necessary.
fn build_display_list_for_outline_if_applicable(
@ -1276,10 +1290,6 @@ impl FragmentDisplayListBuilding for Fragment {
},
BorderPaintingMode::Hidden => return,
}
if border.is_zero() {
// TODO: check if image-border-outset is zero
return;
}
let border_style_struct = style.get_border();
let mut colors = SideOffsets2D::new(
@ -1319,115 +1329,159 @@ impl FragmentDisplayListBuilding for Fragment {
let border_radius = build_border_radius(bounds, border_style_struct);
let border_widths = border.to_physical(style.writing_mode);
let outset =
calculate_border_image_outset(border_style_struct.border_image_outset, border_widths);
let outset_layout = SideOffsets2D::new(
outset.top.to_f32_px(),
outset.right.to_f32_px(),
outset.bottom.to_f32_px(),
outset.left.to_f32_px(),
if let Either::Second(ref image) = border_style_struct.border_image_source {
if self
.build_display_list_for_border_image(
state,
style,
base.clone(),
bounds,
image,
border_widths,
).is_some()
{
return;
}
// Fallback to rendering a solid border.
}
if border_widths == SideOffsets2D::zero() {
return;
}
let details = BorderDetails::Normal(NormalBorder {
left: BorderSide {
color: style.resolve_color(colors.left).to_layout(),
style: border_style.left.to_layout(),
},
right: BorderSide {
color: style.resolve_color(colors.right).to_layout(),
style: border_style.right.to_layout(),
},
top: BorderSide {
color: style.resolve_color(colors.top).to_layout(),
style: border_style.top.to_layout(),
},
bottom: BorderSide {
color: style.resolve_color(colors.bottom).to_layout(),
style: border_style.bottom.to_layout(),
},
radius: border_radius,
});
state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data(
base,
webrender_api::BorderDisplayItem {
widths: border_widths.to_layout(),
details,
},
Vec::new(),
)));
}
fn build_display_list_for_border_image(
&self,
state: &mut DisplayListBuildState,
style: &ComputedValues,
base: BaseDisplayItem,
bounds: Rect<Au>,
image: &ComputedImage,
border_width: SideOffsets2D<Au>,
) -> Option<()> {
let border_style_struct = style.get_border();
let border_image_outset =
calculate_border_image_outset(border_style_struct.border_image_outset, border_width);
let border_image_area = bounds.outer_rect(border_image_outset).size;
let border_image_width = calculate_border_image_width(
&border_style_struct.border_image_width,
border_width.to_layout(),
border_image_area,
);
let size = bounds.outer_rect(outset).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 mut stops = Vec::new();
let details = match border_style_struct.border_image_source {
Either::First(_) => Some(BorderDetails::Normal(NormalBorder {
left: BorderSide {
color: style.resolve_color(colors.left).to_layout(),
style: border_style.left.to_layout(),
},
right: BorderSide {
color: style.resolve_color(colors.right).to_layout(),
style: border_style.right.to_layout(),
},
top: BorderSide {
color: style.resolve_color(colors.top).to_layout(),
style: border_style.top.to_layout(),
},
bottom: BorderSide {
color: style.resolve_color(colors.bottom).to_layout(),
style: border_style.bottom.to_layout(),
},
radius: border_radius,
})),
Either::Second(Image::Gradient(ref gradient)) => Some(match gradient.kind {
let mut width = border_image_area.width.to_px() as u32;
let mut height = border_image_area.height.to_px() as u32;
let source = match image {
Image::Url(ref image_url) => {
let url = image_url.url()?;
let image = state.layout_context.get_webrender_image_for_url(
self.node,
url.clone(),
UsePlaceholder::No,
)?;
width = image.width;
height = image.height;
NinePatchBorderSource::Image(image.key?)
},
Image::PaintWorklet(ref paint_worklet) => {
let image = self.get_webrender_image_for_paint_worklet(
state,
style,
paint_worklet,
border_image_area,
)?;
width = image.width;
height = image.height;
NinePatchBorderSource::Image(image.key?)
},
Image::Gradient(ref gradient) => match gradient.kind {
GradientKind::Linear(angle_or_corner) => {
let (wr_gradient, linear_stops) = convert_linear_gradient(
style,
bounds.size,
border_image_area,
&gradient.items[..],
angle_or_corner,
gradient.repeating,
);
stops = linear_stops;
BorderDetails::NinePatch(webrender_api::NinePatchBorder {
source: webrender_api::NinePatchBorderSource::Gradient(wr_gradient),
width: 0,
height: 0,
slice: SideOffsets2D::zero(),
fill: false,
repeat_horizontal: webrender_api::RepeatMode::Stretch,
repeat_vertical: webrender_api::RepeatMode::Stretch,
outset: outset_layout,
})
NinePatchBorderSource::Gradient(wr_gradient)
},
GradientKind::Radial(shape, center, _angle) => {
let (wr_gradient, radial_stops) = convert_radial_gradient(
style,
bounds.size,
border_image_area,
&gradient.items[..],
shape,
center,
gradient.repeating,
);
stops = radial_stops;
BorderDetails::NinePatch(webrender_api::NinePatchBorder {
source: webrender_api::NinePatchBorderSource::RadialGradient(wr_gradient),
width: 0,
height: 0,
slice: SideOffsets2D::zero(),
fill: false,
repeat_horizontal: webrender_api::RepeatMode::Stretch,
repeat_vertical: webrender_api::RepeatMode::Stretch,
outset: outset_layout,
})
NinePatchBorderSource::RadialGradient(wr_gradient)
},
}),
Either::Second(Image::PaintWorklet(ref paint_worklet)) => self
.get_webrender_image_for_paint_worklet(state, style, paint_worklet, size)
.and_then(|image| {
build_image_border_details(image, border_style_struct, outset_layout)
}),
Either::Second(Image::Rect(..)) => {
// TODO: Handle border-image with `-moz-image-rect`.
None
},
Either::Second(Image::Element(..)) => {
// TODO: Handle border-image with `-moz-element`.
None
},
Either::Second(Image::Url(ref image_url)) => image_url
.url()
.and_then(|url| {
state.layout_context.get_webrender_image_for_url(
self.node,
url.clone(),
UsePlaceholder::No,
)
}).and_then(|image| {
build_image_border_details(image, border_style_struct, outset_layout)
}),
_ => return None,
};
if let Some(details) = details {
state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data(
base,
webrender_api::BorderDisplayItem {
widths: border_widths.to_layout(),
details,
},
stops,
)));
}
let details = BorderDetails::NinePatch(NinePatchBorder {
source,
width,
height,
slice: SideOffsets2D::new(
border_image_slice.0.resolve(height),
border_image_slice.1.resolve(width),
border_image_slice.2.resolve(height),
border_image_slice.3.resolve(width),
),
fill: border_image_fill,
repeat_horizontal: border_image_repeat.0.to_layout(),
repeat_vertical: border_image_repeat.1.to_layout(),
outset: SideOffsets2D::new(
border_image_outset.top.to_f32_px(),
border_image_outset.right.to_f32_px(),
border_image_outset.bottom.to_f32_px(),
border_image_outset.left.to_f32_px(),
),
});
state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data(
base,
webrender_api::BorderDisplayItem {
widths: border_image_width,
details,
},
stops,
)));
Some(())
}
fn build_display_list_for_outline_if_applicable(

View file

@ -106813,6 +106813,18 @@
{}
]
],
"css/css-backgrounds/border-image-width-008.html": [
[
"/css/css-backgrounds/border-image-width-008.html",
[
[
"/css/css-backgrounds/border-image-width-008-ref.html",
"=="
]
],
{}
]
],
"css/css-backgrounds/border-radius-001.xht": [
[
"/css/css-backgrounds/border-radius-001.xht",
@ -247073,6 +247085,11 @@
{}
]
],
"css/css-backgrounds/border-image-width-008-ref.html": [
[
{}
]
],
"css/css-backgrounds/border-radius-001-ref.xht": [
[
{}
@ -528366,6 +528383,14 @@
"61726c00873739c076812f72645d8324494ff44c",
"reftest"
],
"css/css-backgrounds/border-image-width-008-ref.html": [
"9a066443920726b79d69a0301d814b5211b7df0a",
"support"
],
"css/css-backgrounds/border-image-width-008.html": [
"3158cdb5717b873065bc70537075234492796501",
"reftest"
],
"css/css-backgrounds/border-images.html": [
"930a1df3b7fda4098f36cc9691a544f46e2311d5",
"visual"

View file

@ -1,3 +0,0 @@
[border-image-width-005.xht]
type: reftest
expected: FAIL

View file

@ -1,3 +0,0 @@
[border-image-width-006.xht]
type: reftest
expected: FAIL

View file

@ -1,4 +0,0 @@
[geometry-border-image-002.https.html]
type: reftest
expected: FAIL
bug: https://github.com/servo/servo/issues/17860

View file

@ -1,4 +0,0 @@
[geometry-border-image-003.https.html]
type: reftest
expected: FAIL
bug: https://github.com/servo/servo/issues/17860

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>border-image with different widths</title>
<style>
#ref {
width: 360px;
height: 240px;
border-style: solid;
border-width: 40px 40px 20px 0px;
border-image-source: url("support/border.png");
border-image-slice: 27;
}
</style>
<body>
<div id="ref"></div>
</body>

View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>border-image-width has the same effect as a border-width and the image is displayed even if border-width is zero</title>
<link rel="match" href="border-image-width-008-ref.html">
<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#propdef-border-image-width">
<style>
#test {
width: 400px;
height: 300px;
border-style: solid;
/* Note: Chrome does not display an image if border-width is 0 */
border-width: 0px;
border-image-source: url("support/border.png");
border-image-width: 40px 40px 20px 0px;
border-image-slice: 27;
}
</style>
<body>
<div id="test"></div>
</body>