diff --git a/servo/components/gfx/display_list/mod.rs b/servo/components/gfx/display_list/mod.rs index af4b793c6b24..d75e46dd3822 100644 --- a/servo/components/gfx/display_list/mod.rs +++ b/servo/components/gfx/display_list/mod.rs @@ -891,15 +891,9 @@ pub struct GradientDisplayItem { pub stops: Vec, } -/// Paints a border. +/// A normal border, supporting CSS border styles. #[derive(Clone, HeapSizeOf, Deserialize, Serialize)] -pub struct BorderDisplayItem { - /// Fields common to all display items. - pub base: BaseDisplayItem, - - /// Border widths. - pub border_widths: SideOffsets2D, - +pub struct NormalBorder { /// Border colors. pub color: SideOffsets2D, @@ -912,6 +906,50 @@ pub struct BorderDisplayItem { pub radius: BorderRadii, } +/// A border that is made of image segments. +#[derive(Clone, HeapSizeOf, Deserialize, Serialize)] +pub struct ImageBorder { + /// The image this border uses, border-image-source. + pub image: WebRenderImageInfo, + + /// How to slice the image, as per border-image-slice. + pub slice: SideOffsets2D, + + /// Outsets for the border, as per border-image-outset. + pub outset: SideOffsets2D, + + /// If fill is true, draw the center patch of the image. + pub fill: bool, + + /// How to repeat or stretch horizontal edges (border-image-repeat). + #[ignore_heap_size_of = "WebRender traits type, and tiny"] + pub repeat_horizontal: webrender_traits::RepeatMode, + + /// How to repeat or stretch vertical edges (border-image-repeat). + #[ignore_heap_size_of = "WebRender traits type, and tiny"] + pub repeat_vertical: webrender_traits::RepeatMode, +} + +/// Specifies the type of border +#[derive(Clone, HeapSizeOf, Deserialize, Serialize)] +pub enum BorderDetails { + Normal(NormalBorder), + Image(ImageBorder), +} + +/// Paints a border. +#[derive(Clone, HeapSizeOf, Deserialize, Serialize)] +pub struct BorderDisplayItem { + /// Fields common to all display items. + pub base: BaseDisplayItem, + + /// Border widths. + pub border_widths: SideOffsets2D, + + /// Details for specific border type + pub details: BorderDetails, +} + /// Information about the border radii. /// /// TODO(pcwalton): Elliptical radii. diff --git a/servo/components/layout/display_list_builder.rs b/servo/components/layout/display_list_builder.rs index 5eb4534a0c5e..4f45438122ca 100644 --- a/servo/components/layout/display_list_builder.rs +++ b/servo/components/layout/display_list_builder.rs @@ -20,7 +20,8 @@ use flow::{BaseFlow, Flow, IS_ABSOLUTELY_POSITIONED}; use flow_ref::FlowRef; use fragment::{CoordinateSystem, Fragment, ImageFragmentInfo, ScannedTextFragmentInfo}; use fragment::{SpecificFragmentInfo, TruncatedFragmentInfo}; -use gfx::display_list::{BLUR_INFLATION_FACTOR, BaseDisplayItem, BorderDisplayItem}; +use gfx::display_list::{BLUR_INFLATION_FACTOR, BaseDisplayItem, BorderDetails}; +use gfx::display_list::{BorderDisplayItem, ImageBorder, NormalBorder}; use gfx::display_list::{BorderRadii, BoxShadowClipMode, BoxShadowDisplayItem, ClippingRegion}; use gfx::display_list::{DisplayItem, DisplayItemMetadata, DisplayList, DisplayListSection}; use gfx::display_list::{GradientDisplayItem, IframeDisplayItem, ImageDisplayItem}; @@ -45,13 +46,14 @@ use std::mem; use std::sync::Arc; use style::computed_values::{background_attachment, background_clip, background_origin}; use style::computed_values::{background_repeat, background_size, border_style}; -use style::computed_values::{cursor, image_rendering, overflow_x}; +use style::computed_values::{cursor, image_rendering, overflow_x, border_image_slice}; use style::computed_values::{pointer_events, position, transform_style, visibility}; use style::computed_values::_servo_overflow_clip_box as overflow_clip_box; use style::computed_values::filter::Filter; use style::computed_values::text_shadow::TextShadow; use style::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode}; use style::properties::{self, ServoComputedValues}; +use style::properties::longhands::border_image_repeat::computed_value::RepeatKeyword; use style::properties::style_structs; use style::servo::restyle_damage::REPAINT; use style::values::{RGBA, computed}; @@ -60,7 +62,33 @@ use style::values::specified::{HorizontalDirection, VerticalDirection}; use style_traits::PagePx; use style_traits::cursor::Cursor; use table_cell::CollapsedBordersForCell; -use webrender_traits::{ColorF, GradientStop, ScrollPolicy}; +use webrender_traits::{ColorF, GradientStop, RepeatMode, ScrollPolicy}; + +trait ResolvePercentage { + fn resolve(&self, length: u32) -> u32; +} + +impl ResolvePercentage for border_image_slice::PercentageOrNumber { + fn resolve(&self, length: u32) -> u32 { + match *self { + border_image_slice::PercentageOrNumber::Percentage(p) => { + (p.0 * length as f32).round() as u32 + } + border_image_slice::PercentageOrNumber::Number(n) => { + n.round() as u32 + } + } + } +} + +fn convert_repeat_mode(from: RepeatKeyword) -> RepeatMode { + match from { + RepeatKeyword::Stretch => RepeatMode::Stretch, + RepeatKeyword::Repeat => RepeatMode::Repeat, + RepeatKeyword::Round => RepeatMode::Round, + RepeatKeyword::Space => RepeatMode::Space, + } +} trait RgbColor { fn rgb(r: u8, g: u8, b: u8) -> Self; @@ -1056,16 +1084,55 @@ impl FragmentDisplayListBuilding for Fragment { self.node, style.get_cursor(Cursor::Default), display_list_section); - state.add_display_item(DisplayItem::Border(box BorderDisplayItem { - base: base, - border_widths: border.to_physical(style.writing_mode), - color: SideOffsets2D::new(colors.top.to_gfx_color(), - colors.right.to_gfx_color(), - colors.bottom.to_gfx_color(), - colors.left.to_gfx_color()), - style: border_style, - radius: build_border_radius(&bounds, border_style_struct), - })); + + match border_style_struct.border_image_source.0 { + None => { + state.add_display_item(DisplayItem::Border(box BorderDisplayItem { + base: base, + border_widths: border.to_physical(style.writing_mode), + details: BorderDetails::Normal(NormalBorder { + color: SideOffsets2D::new(colors.top.to_gfx_color(), + colors.right.to_gfx_color(), + colors.bottom.to_gfx_color(), + colors.left.to_gfx_color()), + style: border_style, + radius: build_border_radius(&bounds, border_style_struct), + }), + })); + } + Some(computed::Image::Gradient(..)) => { + // TODO(gw): Handle border-image with gradient. + } + Some(computed::Image::Url(ref image_url)) => { + if let Some(url) = image_url.url() { + let webrender_image = state.layout_context + .get_webrender_image_for_url(self.node, + url.clone(), + UsePlaceholder::No); + if let Some(webrender_image) = webrender_image { + // The corners array is guaranteed to be len=4 by the css parser. + let corners = &border_style_struct.border_image_slice.corners; + + state.add_display_item(DisplayItem::Border(box BorderDisplayItem { + base: base, + border_widths: border.to_physical(style.writing_mode), + details: BorderDetails::Image(ImageBorder { + image: webrender_image, + fill: border_style_struct.border_image_slice.fill, + 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)), + // TODO(gw): Support border-image-outset + outset: SideOffsets2D::zero(), + repeat_horizontal: convert_repeat_mode(border_style_struct.border_image_repeat.0), + repeat_vertical: convert_repeat_mode(border_style_struct.border_image_repeat.1), + }), + })); + } + } + } + } } fn build_display_list_for_outline_if_applicable(&self, @@ -1105,9 +1172,11 @@ impl FragmentDisplayListBuilding for Fragment { state.add_display_item(DisplayItem::Border(box BorderDisplayItem { base: base, border_widths: SideOffsets2D::new_all_same(width), - color: SideOffsets2D::new_all_same(color), - style: SideOffsets2D::new_all_same(outline_style), - radius: Default::default(), + details: BorderDetails::Normal(NormalBorder { + color: SideOffsets2D::new_all_same(color), + style: SideOffsets2D::new_all_same(outline_style), + radius: Default::default(), + }), })); } @@ -1130,9 +1199,11 @@ impl FragmentDisplayListBuilding for Fragment { state.add_display_item(DisplayItem::Border(box BorderDisplayItem { base: base, border_widths: SideOffsets2D::new_all_same(Au::from_px(1)), - color: SideOffsets2D::new_all_same(ColorF::rgb(0, 0, 200)), - style: SideOffsets2D::new_all_same(border_style::T::solid), - radius: Default::default(), + details: BorderDetails::Normal(NormalBorder { + color: SideOffsets2D::new_all_same(ColorF::rgb(0, 0, 200)), + style: SideOffsets2D::new_all_same(border_style::T::solid), + radius: Default::default(), + }), })); // Draw a rectangle representing the baselines. @@ -1168,9 +1239,11 @@ impl FragmentDisplayListBuilding for Fragment { state.add_display_item(DisplayItem::Border(box BorderDisplayItem { base: base, border_widths: SideOffsets2D::new_all_same(Au::from_px(1)), - color: SideOffsets2D::new_all_same(ColorF::rgb(0, 0, 200)), - style: SideOffsets2D::new_all_same(border_style::T::solid), - radius: Default::default(), + details: BorderDetails::Normal(NormalBorder { + color: SideOffsets2D::new_all_same(ColorF::rgb(0, 0, 200)), + style: SideOffsets2D::new_all_same(border_style::T::solid), + radius: Default::default(), + }), })); } @@ -2129,9 +2202,11 @@ impl BaseFlowDisplayListBuilding for BaseFlow { state.add_display_item(DisplayItem::Border(box BorderDisplayItem { base: base, border_widths: SideOffsets2D::new_all_same(Au::from_px(2)), - color: SideOffsets2D::new_all_same(color), - style: SideOffsets2D::new_all_same(border_style::T::solid), - radius: BorderRadii::all_same(Au(0)), + details: BorderDetails::Normal(NormalBorder { + color: SideOffsets2D::new_all_same(color), + style: SideOffsets2D::new_all_same(border_style::T::solid), + radius: BorderRadii::all_same(Au(0)), + }), })); } } diff --git a/servo/components/layout/webrender_helpers.rs b/servo/components/layout/webrender_helpers.rs index de50d3d0e7ec..cc32efe00395 100644 --- a/servo/components/layout/webrender_helpers.rs +++ b/servo/components/layout/webrender_helpers.rs @@ -8,8 +8,8 @@ // completely converting layout to directly generate WebRender display lists, for example. use app_units::Au; -use euclid::{Point2D, Rect, Size2D}; -use gfx::display_list::{BorderRadii, BoxShadowClipMode, ClippingRegion}; +use euclid::{Point2D, Rect, SideOffsets2D, Size2D}; +use gfx::display_list::{BorderDetails, BorderRadii, BoxShadowClipMode, ClippingRegion}; use gfx::display_list::{DisplayItem, DisplayList, DisplayListTraversal, StackingContextType}; use gfx_traits::{FragmentType, ScrollRootId}; use msg::constellation_msg::PipelineId; @@ -46,6 +46,22 @@ impl ToBorderStyle for BorderStyle { } } } + +trait ToBorderWidths { + fn to_border_widths(&self) -> webrender_traits::BorderWidths; +} + +impl ToBorderWidths for SideOffsets2D { + fn to_border_widths(&self) -> webrender_traits::BorderWidths { + webrender_traits::BorderWidths { + left: self.left.to_f32_px(), + top: self.top.to_f32_px(), + right: self.right.to_f32_px(), + bottom: self.bottom.to_f32_px(), + } + } +} + trait ToBoxShadowClipMode { fn to_clip_mode(&self) -> webrender_traits::BoxShadowClipMode; } @@ -270,41 +286,57 @@ impl WebRenderDisplayItemConverter for DisplayItem { } DisplayItem::Border(ref item) => { let rect = item.base.bounds.to_rectf(); - let widths = webrender_traits::BorderWidths { - left: item.border_widths.left.to_f32_px(), - top: item.border_widths.top.to_f32_px(), - right: item.border_widths.right.to_f32_px(), - bottom: item.border_widths.bottom.to_f32_px(), - }; - let left = webrender_traits::BorderSide { - color: item.color.left, - style: item.style.left.to_border_style(), - }; - let top = webrender_traits::BorderSide { - color: item.color.top, - style: item.style.top.to_border_style(), - }; - let right = webrender_traits::BorderSide { - color: item.color.right, - style: item.style.right.to_border_style(), - }; - let bottom = webrender_traits::BorderSide { - color: item.color.bottom, - style: item.style.bottom.to_border_style(), - }; - let radius = item.radius.to_border_radius(); + let widths = item.border_widths.to_border_widths(); let clip = item.base.clip.to_clip_region(builder); - let details = webrender_traits::NormalBorder { - left: left, - top: top, - right: right, - bottom: bottom, - radius: radius, + + let details = match item.details { + BorderDetails::Normal(ref border) => { + let left = webrender_traits::BorderSide { + color: border.color.left, + style: border.style.left.to_border_style(), + }; + let top = webrender_traits::BorderSide { + color: border.color.top, + style: border.style.top.to_border_style(), + }; + let right = webrender_traits::BorderSide { + color: border.color.right, + style: border.style.right.to_border_style(), + }; + let bottom = webrender_traits::BorderSide { + color: border.color.bottom, + style: border.style.bottom.to_border_style(), + }; + let radius = border.radius.to_border_radius(); + webrender_traits::BorderDetails::Normal(webrender_traits::NormalBorder { + left: left, + top: top, + right: right, + bottom: bottom, + radius: radius, + }) + } + BorderDetails::Image(ref image) => { + match image.image.key { + None => return, + Some(key) => { + webrender_traits::BorderDetails::Image(webrender_traits::ImageBorder { + image_key: key, + patch: webrender_traits::NinePatchDescriptor { + width: image.image.width, + height: image.image.height, + slice: image.slice, + }, + outset: image.outset, + repeat_horizontal: image.repeat_horizontal, + repeat_vertical: image.repeat_vertical, + }) + } + } + } }; - builder.push_border(rect, - clip, - widths, - webrender_traits::BorderDetails::Normal(details)); + + builder.push_border(rect, clip, widths, details); } DisplayItem::Gradient(ref item) => { let rect = item.base.bounds.to_rectf(); diff --git a/servo/components/script/dom/webidls/CSSStyleDeclaration.webidl b/servo/components/script/dom/webidls/CSSStyleDeclaration.webidl index 3e40523cf397..c0d851b196d3 100644 --- a/servo/components/script/dom/webidls/CSSStyleDeclaration.webidl +++ b/servo/components/script/dom/webidls/CSSStyleDeclaration.webidl @@ -106,6 +106,18 @@ partial interface CSSStyleDeclaration { [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-top-style; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderTopWidth; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-top-width; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image-source; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImageSource; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image-slice; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImageSlice; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image-repeat; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImageRepeat; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image-outset; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImageOutset; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image-width; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImageWidth; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-image; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderImage; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString border-block-start-color; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString borderBlockStartColor; diff --git a/servo/components/style/properties/longhand/border.mako.rs b/servo/components/style/properties/longhand/border.mako.rs index 7739eb4d9d29..9594ac32127e 100644 --- a/servo/components/style/properties/longhand/border.mako.rs +++ b/servo/components/style/properties/longhand/border.mako.rs @@ -86,7 +86,7 @@ ${helpers.single_keyword("-moz-float-edge", "content-box margin-box", spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-float-edge)", animatable=False)} -<%helpers:longhand name="border-image-source" products="gecko" animatable="False" boxed="True" +<%helpers:longhand name="border-image-source" animatable="False" boxed="True" spec="https://drafts.csswg.org/css-backgrounds/#border-image-source"> use std::fmt; use style_traits::ToCss; @@ -162,7 +162,7 @@ ${helpers.single_keyword("-moz-float-edge", "content-box margin-box", } -<%helpers:longhand name="border-image-outset" products="gecko" animatable="False" +<%helpers:longhand name="border-image-outset" animatable="False" spec="https://drafts.csswg.org/css-backgrounds/#border-image-outset"> use std::fmt; use style_traits::ToCss; @@ -278,7 +278,7 @@ ${helpers.single_keyword("-moz-float-edge", "content-box margin-box", } -<%helpers:longhand name="border-image-repeat" products="gecko" animatable="False" +<%helpers:longhand name="border-image-repeat" animatable="False" spec="https://drafts.csswg.org/css-backgrounds/#border-image-repeat"> use std::fmt; use style_traits::ToCss; @@ -356,7 +356,7 @@ ${helpers.single_keyword("-moz-float-edge", "content-box margin-box", } -<%helpers:longhand name="border-image-width" products="gecko" animatable="False" +<%helpers:longhand name="border-image-width" animatable="False" spec="https://drafts.csswg.org/css-backgrounds/#border-image-width"> use std::fmt; use style_traits::ToCss; @@ -556,7 +556,7 @@ ${helpers.single_keyword("-moz-float-edge", "content-box margin-box", } -<%helpers:longhand name="border-image-slice" products="gecko" animatable="False" +<%helpers:longhand name="border-image-slice" animatable="False" spec="https://drafts.csswg.org/css-backgrounds/#border-image-slice"> use std::fmt; use style_traits::ToCss; diff --git a/servo/components/style/properties/shorthand/border.mako.rs b/servo/components/style/properties/shorthand/border.mako.rs index 3aefd6a8bc55..93f6222f852c 100644 --- a/servo/components/style/properties/shorthand/border.mako.rs +++ b/servo/components/style/properties/shorthand/border.mako.rs @@ -184,7 +184,7 @@ pub fn parse_border(context: &ParserContext, input: &mut Parser) } -<%helpers:shorthand name="border-image" products="gecko" sub_properties="border-image-outset +<%helpers:shorthand name="border-image" sub_properties="border-image-outset border-image-repeat border-image-slice border-image-source border-image-width" extra_prefixes="moz webkit" spec="https://drafts.csswg.org/css-backgrounds-3/#border-image"> use properties::longhands::{border_image_outset, border_image_repeat, border_image_slice};