diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index 0f3e484b816e..3bcc44014e79 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -57,7 +57,8 @@ use style::properties::style_structs; use style::servo::restyle_damage::REPAINT; use style::values::{Either, RGBA}; use style::values::computed::{Gradient, GradientItem, LengthOrPercentage}; -use style::values::computed::{LengthOrPercentageOrAuto, NumberOrPercentage, Position, Shadow}; +use style::values::computed::{LengthOrPercentageOrAuto, NumberOrPercentage, Position}; +use style::values::computed::effects::SimpleShadow; use style::values::computed::image::{EndingShape, LineDirection}; use style::values::generics::background::BackgroundSize; use style::values::generics::effects::Filter; @@ -525,7 +526,7 @@ pub trait FragmentDisplayListBuilding { state: &mut DisplayListBuildState, text_fragment: &ScannedTextFragmentInfo, stacking_relative_content_box: &Rect, - text_shadow: Option<&Shadow>, + text_shadow: Option<&SimpleShadow>, clip: &Rect); /// Creates the display item for a text decoration: underline, overline, or line-through. @@ -534,7 +535,7 @@ pub trait FragmentDisplayListBuilding { color: &RGBA, stacking_relative_box: &LogicalRect, clip: &Rect, - blur_radius: Au); + blur: Au); /// A helper method that `build_display_list` calls to create per-fragment-type display items. fn build_fragment_type_specific_display_items(&mut self, @@ -1351,11 +1352,14 @@ impl FragmentDisplayListBuilding for Fragment { clip: &Rect) { // NB: According to CSS-BACKGROUNDS, box shadows render in *reverse* order (front to back). for box_shadow in style.get_effects().box_shadow.0.iter().rev() { - let bounds = - shadow_bounds(&absolute_bounds.translate(&Vector2D::new(box_shadow.offset_x, - box_shadow.offset_y)), - box_shadow.blur_radius, - box_shadow.spread_radius); + let bounds = shadow_bounds( + &absolute_bounds.translate(&Vector2D::new( + box_shadow.base.horizontal, + box_shadow.base.vertical, + )), + box_shadow.base.blur, + box_shadow.spread, + ); // TODO(pcwalton): Multiple border radii; elliptical border radii. let base = state.create_base_display_item(&bounds, @@ -1366,10 +1370,10 @@ impl FragmentDisplayListBuilding for Fragment { state.add_display_item(DisplayItem::BoxShadow(box BoxShadowDisplayItem { base: base, box_bounds: *absolute_bounds, - color: style.resolve_color(box_shadow.color).to_gfx_color(), - offset: Vector2D::new(box_shadow.offset_x, box_shadow.offset_y), - blur_radius: box_shadow.blur_radius, - spread_radius: box_shadow.spread_radius, + color: style.resolve_color(box_shadow.base.color).to_gfx_color(), + offset: Vector2D::new(box_shadow.base.horizontal, box_shadow.base.vertical), + blur_radius: box_shadow.base.blur, + spread_radius: box_shadow.spread, border_radius: model::specified_border_radius(style.get_border() .border_top_left_radius, absolute_bounds.size).width, @@ -2039,7 +2043,7 @@ impl FragmentDisplayListBuilding for Fragment { state: &mut DisplayListBuildState, text_fragment: &ScannedTextFragmentInfo, stacking_relative_content_box: &Rect, - text_shadow: Option<&Shadow>, + text_shadow: Option<&SimpleShadow>, clip: &Rect) { // TODO(emilio): Allow changing more properties by ::selection let text_color = if let Some(shadow) = text_shadow { @@ -2051,8 +2055,10 @@ impl FragmentDisplayListBuilding for Fragment { } else { self.style().get_color().color }; - let offset = text_shadow.map(|s| Vector2D::new(s.offset_x, s.offset_y)).unwrap_or_else(Vector2D::zero); - let shadow_blur_radius = text_shadow.map(|s| s.blur_radius).unwrap_or(Au(0)); + let offset = text_shadow.map_or(Vector2D::zero(), |s| { + Vector2D::new(s.horizontal, s.vertical) + }); + let shadow_blur_radius = text_shadow.map(|s| s.blur).unwrap_or(Au(0)); // Determine the orientation and cursor to use. let (orientation, cursor) = if self.style.writing_mode.is_vertical() { @@ -2885,8 +2891,8 @@ fn position_to_offset(position: LengthOrPercentage, total_length: Au) -> f32 { /// Adjusts `content_rect` as necessary for the given spread, and blur so that the resulting /// bounding rect contains all of a shadow's ink. -fn shadow_bounds(content_rect: &Rect, blur_radius: Au, spread_radius: Au) -> Rect { - let inflation = spread_radius + blur_radius * BLUR_INFLATION_FACTOR; +fn shadow_bounds(content_rect: &Rect, blur: Au, spread: Au) -> Rect { + let inflation = spread + blur * BLUR_INFLATION_FACTOR; content_rect.inflate(inflation, inflation) } diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 416a1502598a..d54853fa7df6 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -2551,9 +2551,8 @@ impl Fragment { // Box shadows cause us to draw outside our border box. for box_shadow in &self.style().get_effects().box_shadow.0 { - let offset = Vector2D::new(box_shadow.offset_x, box_shadow.offset_y); - let inflation = box_shadow.spread_radius + box_shadow.blur_radius * - BLUR_INFLATION_FACTOR; + let offset = Vector2D::new(box_shadow.base.horizontal, box_shadow.base.vertical); + let inflation = box_shadow.spread + box_shadow.base.blur * BLUR_INFLATION_FACTOR; overflow.paint = overflow.paint.union(&border_box.translate(&offset) .inflate(inflation, inflation)) } diff --git a/components/style/gecko_bindings/sugar/ns_css_shadow_item.rs b/components/style/gecko_bindings/sugar/ns_css_shadow_item.rs index c186945a47cc..0efebc94c193 100644 --- a/components/style/gecko_bindings/sugar/ns_css_shadow_item.rs +++ b/components/style/gecko_bindings/sugar/ns_css_shadow_item.rs @@ -7,37 +7,30 @@ use app_units::Au; use gecko::values::{convert_rgba_to_nscolor, convert_nscolor_to_rgba}; use gecko_bindings::structs::nsCSSShadowItem; -use values::computed::{Color, Shadow}; -use values::computed::effects::SimpleShadow; +use values::computed::Color; +use values::computed::effects::{BoxShadow, SimpleShadow}; impl nsCSSShadowItem { - /// Set this item to the given shadow value. - pub fn set_from_shadow(&mut self, other: Shadow) { - self.mXOffset = other.offset_x.0; - self.mYOffset = other.offset_y.0; - self.mRadius = other.blur_radius.0; - self.mSpread = other.spread_radius.0; - self.mInset = other.inset; - if other.color.is_currentcolor() { - // TODO handle currentColor - // https://bugzilla.mozilla.org/show_bug.cgi?id=760345 - self.mHasColor = false; - self.mColor = 0; - } else { - self.mHasColor = true; - self.mColor = convert_rgba_to_nscolor(&other.color.color); - } + /// Sets this item from the given box shadow. + #[inline] + pub fn set_from_box_shadow(&mut self, shadow: BoxShadow) { + self.set_from_simple_shadow(shadow.base); + self.mSpread = shadow.spread.0; + self.mInset = shadow.inset; } - /// Generate shadow value from this shadow item. - pub fn to_shadow(&self) -> Shadow { - Shadow { - offset_x: Au(self.mXOffset), - offset_y: Au(self.mYOffset), - blur_radius: Au(self.mRadius), - spread_radius: Au(self.mSpread), + /// Returns this item as a box shadow. + #[inline] + pub fn to_box_shadow(&self) -> BoxShadow { + BoxShadow { + base: SimpleShadow { + color: Color::rgba(convert_nscolor_to_rgba(self.mColor)), + horizontal: Au(self.mXOffset), + vertical: Au(self.mYOffset), + blur: Au(self.mRadius), + }, + spread: Au(self.mSpread), inset: self.mInset, - color: Color::rgba(convert_nscolor_to_rgba(self.mColor)), } } diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 25611a3925c1..3d972aeadfa7 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -60,7 +60,7 @@ use std::ptr; use stylearc::Arc; use std::cmp; use values::{Auto, CustomIdent, Either, KeyframesName}; -use values::computed::{Filter, Shadow}; +use values::computed::effects::{BoxShadow, Filter, SimpleShadow}; use values::specified::length::Percentage; use computed_values::border_style; @@ -3173,13 +3173,13 @@ fn static_assert() { <%self:impl_trait style_struct_name="Effects" skip_longhands="box-shadow clip filter"> pub fn set_box_shadow(&mut self, v: I) - where I: IntoIterator, + where I: IntoIterator, I::IntoIter: ExactSizeIterator { let v = v.into_iter(); self.gecko.mBoxShadow.replace_with_new(v.len() as u32); for (servo, gecko_shadow) in v.zip(self.gecko.mBoxShadow.iter_mut()) { - gecko_shadow.set_from_shadow(servo); + gecko_shadow.set_from_box_shadow(servo); } } @@ -3188,7 +3188,7 @@ fn static_assert() { } pub fn clone_box_shadow(&self) -> longhands::box_shadow::computed_value::T { - let buf = self.gecko.mBoxShadow.iter().map(|v| v.to_shadow()).collect(); + let buf = self.gecko.mBoxShadow.iter().map(|v| v.to_box_shadow()).collect(); longhands::box_shadow::computed_value::T(buf) } @@ -3494,13 +3494,13 @@ fn static_assert() { ${impl_keyword_clone('text_align', 'mTextAlign', text_align_keyword)} pub fn set_text_shadow(&mut self, v: I) - where I: IntoIterator, + where I: IntoIterator, I::IntoIter: ExactSizeIterator { let v = v.into_iter(); self.gecko.mTextShadow.replace_with_new(v.len() as u32); for (servo, gecko_shadow) in v.zip(self.gecko.mTextShadow.iter_mut()) { - gecko_shadow.set_from_shadow(servo); + gecko_shadow.set_from_simple_shadow(servo); } } @@ -3509,7 +3509,7 @@ fn static_assert() { } pub fn clone_text_shadow(&self) -> longhands::text_shadow::computed_value::T { - let buf = self.gecko.mTextShadow.iter().map(|v| v.to_shadow()).collect(); + let buf = self.gecko.mTextShadow.iter().map(|v| v.to_simple_shadow()).collect(); longhands::text_shadow::computed_value::T(buf) } diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index 9b882af01500..9e25244fe967 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -19,8 +19,6 @@ use properties::longhands; use properties::longhands::background_size::computed_value::T as BackgroundSizeList; use properties::longhands::font_weight::computed_value::T as FontWeight; use properties::longhands::font_stretch::computed_value::T as FontStretch; -use properties::longhands::text_shadow::computed_value::T as TextShadowList; -use properties::longhands::box_shadow::computed_value::T as BoxShadowList; use properties::longhands::transform::computed_value::ComputedMatrix; use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation; use properties::longhands::transform::computed_value::T as TransformList; @@ -34,11 +32,14 @@ use std::cmp; use style_traits::ParseError; use super::ComputedValues; use values::{Auto, CSSFloat, CustomIdent, Either}; -use values::animated::effects::{Filter as AnimatedFilter, FilterList as AnimatedFilterList}; +use values::animated::effects::BoxShadowList as AnimatedBoxShadowList; +use values::animated::effects::Filter as AnimatedFilter; +use values::animated::effects::FilterList as AnimatedFilterList; +use values::animated::effects::TextShadowList as AnimatedTextShadowList; use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone}; use values::computed::{BorderCornerRadius, ClipRect}; use values::computed::{CalcLengthOrPercentage, Color, Context, ComputedValueAsSpecified}; -use values::computed::{LengthOrPercentage, MaxLength, MozLength, Shadow, ToComputedValue}; +use values::computed::{LengthOrPercentage, MaxLength, MozLength, ToComputedValue}; use values::generics::{SVGPaint, SVGPaintKind}; use values::generics::border::BorderCornerRadius as GenericBorderCornerRadius; use values::generics::effects::Filter; @@ -2832,7 +2833,8 @@ impl IntermediateColor { } } - fn transparent() -> Self { + /// Returns a transparent intermediate color. + pub fn transparent() -> Self { IntermediateColor { color: IntermediateRGBA::transparent(), foreground_ratio: 0., @@ -3040,179 +3042,6 @@ impl Animatable for IntermediateSVGPaintKind { } } -#[derive(Copy, Clone, Debug, PartialEq)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -/// Intermediate type for box-shadow and text-shadow. -/// The difference from normal shadow type is that this type uses -/// IntermediateColor instead of ParserColor. -pub struct IntermediateShadow { - pub offset_x: Au, - pub offset_y: Au, - pub blur_radius: Au, - pub spread_radius: Au, - pub color: IntermediateColor, - pub inset: bool, -} - -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -/// Intermediate type for box-shadow list and text-shadow list. -pub struct IntermediateShadowList(pub Vec); - -type ShadowList = Vec; - -impl From for ShadowList { - fn from(shadow_list: IntermediateShadowList) -> Self { - shadow_list.0.into_iter().map(|s| s.into()).collect() - } -} - -impl From for IntermediateShadowList { - fn from(shadow_list: ShadowList) -> IntermediateShadowList { - IntermediateShadowList(shadow_list.into_iter().map(|s| s.into()).collect()) - } -} - -% for ty in "Box Text".split(): -impl From for ${ty}ShadowList { - #[inline] - fn from(shadow_list: IntermediateShadowList) -> Self { - ${ty}ShadowList(shadow_list.into()) - } -} - -impl From<${ty}ShadowList> for IntermediateShadowList { - #[inline] - fn from(shadow_list: ${ty}ShadowList) -> IntermediateShadowList { - shadow_list.0.into() - } -} -% endfor - -impl From for Shadow { - fn from(shadow: IntermediateShadow) -> Shadow { - Shadow { - offset_x: shadow.offset_x, - offset_y: shadow.offset_y, - blur_radius: shadow.blur_radius, - spread_radius: shadow.spread_radius, - color: shadow.color.into(), - inset: shadow.inset, - } - } -} - -impl From for IntermediateShadow { - fn from(shadow: Shadow) -> IntermediateShadow { - IntermediateShadow { - offset_x: shadow.offset_x, - offset_y: shadow.offset_y, - blur_radius: shadow.blur_radius, - spread_radius: shadow.spread_radius, - color: shadow.color.into(), - inset: shadow.inset, - } - } -} - -impl Animatable for IntermediateShadow { - #[inline] - fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result { - // It can't be interpolated if inset does not match. - if self.inset != other.inset { - return Err(()); - } - - let x = self.offset_x.add_weighted(&other.offset_x, self_portion, other_portion)?; - let y = self.offset_y.add_weighted(&other.offset_y, self_portion, other_portion)?; - let color = self.color.add_weighted(&other.color, self_portion, other_portion)?; - let blur = self.blur_radius.add_weighted(&other.blur_radius, self_portion, other_portion)?; - let spread = self.spread_radius.add_weighted(&other.spread_radius, self_portion, other_portion)?; - - Ok(IntermediateShadow { - offset_x: x, - offset_y: y, - blur_radius: blur, - spread_radius: spread, - color: color, - inset: self.inset, - }) - } - - #[inline] - fn compute_distance(&self, other: &Self) -> Result { - self.compute_squared_distance(other).map(|sd| sd.sqrt()) - } - - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result { - if self.inset != other.inset { - return Err(()); - } - let list = [ - self.offset_x.compute_distance(&other.offset_x)?, - self.offset_y.compute_distance(&other.offset_y)?, - self.blur_radius.compute_distance(&other.blur_radius)?, - self.color.compute_distance(&other.color)?, - self.spread_radius.compute_distance(&other.spread_radius)?, - ]; - Ok(list.iter().fold(0.0f64, |sum, diff| sum + diff * diff)) - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list -impl Animatable for IntermediateShadowList { - #[inline] - fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result { - // The inset value must change - let mut zero = IntermediateShadow { - offset_x: Au(0), - offset_y: Au(0), - blur_radius: Au(0), - spread_radius: Au(0), - color: IntermediateColor::transparent(), - inset: false, - }; - - let max_len = cmp::max(self.0.len(), other.0.len()); - - let mut result = Vec::with_capacity(max_len); - - for i in 0..max_len { - let shadow = match (self.0.get(i), other.0.get(i)) { - (Some(shadow), Some(other)) => { - shadow.add_weighted(other, self_portion, other_portion)? - } - (Some(shadow), None) => { - zero.inset = shadow.inset; - shadow.add_weighted(&zero, self_portion, other_portion).unwrap() - } - (None, Some(shadow)) => { - zero.inset = shadow.inset; - zero.add_weighted(&shadow, self_portion, other_portion).unwrap() - } - (None, None) => unreachable!(), - }; - result.push(shadow); - } - - Ok(IntermediateShadowList(result)) - } - - fn add(&self, other: &Self) -> Result { - let len = self.0.len() + other.0.len(); - - let mut result = Vec::with_capacity(len); - - result.extend(self.0.iter().cloned()); - result.extend(other.0.iter().cloned()); - - Ok(IntermediateShadowList(result)) - } -} - <% FILTER_FUNCTIONS = [ 'Blur', 'Brightness', 'Contrast', 'Grayscale', 'HueRotate', 'Invert', 'Opacity', 'Saturate', diff --git a/components/style/properties/longhand/effects.mako.rs b/components/style/properties/longhand/effects.mako.rs index 9c34c54efb59..e66d9324edac 100644 --- a/components/style/properties/longhand/effects.mako.rs +++ b/components/style/properties/longhand/effects.mako.rs @@ -14,24 +14,16 @@ ${helpers.predefined_type("opacity", flags="CREATES_STACKING_CONTEXT", spec="https://drafts.csswg.org/css-color/#opacity")} -<%helpers:vector_longhand name="box-shadow" allow_empty="True" - animation_value_type="IntermediateShadowList" - extra_prefixes="webkit" - ignored_when_colors_disabled="True" - spec="https://drafts.csswg.org/css-backgrounds/#box-shadow"> - pub type SpecifiedValue = specified::Shadow; - - pub mod computed_value { - use values::computed::Shadow; - - pub type T = Shadow; - } - - pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) - -> Result> { - specified::Shadow::parse(context, input, false) - } - +${helpers.predefined_type( + "box-shadow", + "BoxShadow", + None, + vector=True, + animation_value_type="AnimatedBoxShadowList", + extra_prefixes="webkit", + ignored_when_colors_disabled=True, + spec="https://drafts.csswg.org/css-backgrounds/#box-shadow", +)} ${helpers.predefined_type("clip", "ClipRectOrAuto", diff --git a/components/style/properties/longhand/inherited_text.mako.rs b/components/style/properties/longhand/inherited_text.mako.rs index 121f657e323d..26f7e374398f 100644 --- a/components/style/properties/longhand/inherited_text.mako.rs +++ b/components/style/properties/longhand/inherited_text.mako.rs @@ -407,21 +407,15 @@ ${helpers.predefined_type("word-spacing", % endif -<%helpers:vector_longhand name="text-shadow" allow_empty="True" - animation_value_type="IntermediateShadowList" - ignored_when_colors_disabled="True" - spec="https://drafts.csswg.org/css-backgrounds/#box-shadow"> - pub type SpecifiedValue = specified::Shadow; - pub mod computed_value { - use values::computed::Shadow; - pub type T = Shadow; - } - - pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) - -> Result> { - specified::Shadow::parse(context, input, true) - } - +${helpers.predefined_type( + "text-shadow", + "SimpleShadow", + None, + vector=True, + animation_value_type="AnimatedTextShadowList", + ignored_when_colors_disabled=True, + spec="https://drafts.csswg.org/css-text-decor-3/#text-shadow-property", +)} <%helpers:longhand name="text-emphasis-style" products="gecko" need_clone="True" boxed="True" animation_value_type="none" diff --git a/components/style/values/animated/effects.rs b/components/style/values/animated/effects.rs index 4c248e5d1ef3..3d5cb9ea1a78 100644 --- a/components/style/values/animated/effects.rs +++ b/components/style/values/animated/effects.rs @@ -4,18 +4,32 @@ //! Animated types for CSS values related to effects. +use app_units::Au; use properties::animated_properties::{Animatable, IntermediateColor}; +use properties::longhands::box_shadow::computed_value::T as ComputedBoxShadowList; use properties::longhands::filter::computed_value::T as ComputedFilterList; +use properties::longhands::text_shadow::computed_value::T as ComputedTextShadowList; +use std::cmp; #[cfg(not(feature = "gecko"))] use values::Impossible; use values::computed::{Angle, Number}; +use values::computed::effects::BoxShadow as ComputedBoxShadow; #[cfg(feature = "gecko")] use values::computed::effects::Filter as ComputedFilter; use values::computed::effects::SimpleShadow as ComputedSimpleShadow; use values::computed::length::Length; +use values::generics::effects::BoxShadow as GenericBoxShadow; use values::generics::effects::Filter as GenericFilter; use values::generics::effects::SimpleShadow as GenericSimpleShadow; +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, Debug, PartialEq)] +/// An animated value for the `box-shadow` property. +pub struct BoxShadowList(pub Vec); + +/// An animated value for a single `box-shadow`. +pub type BoxShadow = GenericBoxShadow; + /// An animated value for the `filter` property. #[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[derive(Clone, Debug, PartialEq)] @@ -29,9 +43,190 @@ pub type Filter = GenericFilter; #[cfg(not(feature = "gecko"))] pub type Filter = GenericFilter; +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, Debug, PartialEq)] +/// An animated value for the `box-shadow` property. +pub struct TextShadowList(pub Vec); + /// An animated value for the `drop-shadow()` filter. pub type SimpleShadow = GenericSimpleShadow; +impl From for ComputedBoxShadowList { + fn from(list: BoxShadowList) -> Self { + ComputedBoxShadowList(list.0.into_iter().map(|s| s.into()).collect()) + } +} + +impl From for BoxShadowList { + fn from(list: ComputedBoxShadowList) -> Self { + BoxShadowList(list.0.into_iter().map(|s| s.into()).collect()) + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list +impl Animatable for BoxShadowList { + #[inline] + fn add_weighted( + &self, + other: &Self, + self_portion: f64, + other_portion: f64, + ) -> Result { + // The inset value must change + let mut zero = BoxShadow { + base: SimpleShadow { + color: IntermediateColor::transparent(), + horizontal: Au(0), + vertical: Au(0), + blur: Au(0), + }, + spread: Au(0), + inset: false, + }; + + let max_len = cmp::max(self.0.len(), other.0.len()); + let mut shadows = Vec::with_capacity(max_len); + for i in 0..max_len { + shadows.push(match (self.0.get(i), other.0.get(i)) { + (Some(shadow), Some(other)) => { + shadow.add_weighted(other, self_portion, other_portion)? + }, + (Some(shadow), None) => { + zero.inset = shadow.inset; + shadow.add_weighted(&zero, self_portion, other_portion)? + }, + (None, Some(shadow)) => { + zero.inset = shadow.inset; + zero.add_weighted(&shadow, self_portion, other_portion)? + }, + (None, None) => unreachable!(), + }); + } + + Ok(BoxShadowList(shadows)) + } + + #[inline] + fn add(&self, other: &Self) -> Result { + Ok(BoxShadowList( + self.0.iter().cloned().chain(other.0.iter().cloned()).collect(), + )) + } +} + +impl From for ComputedTextShadowList { + fn from(list: TextShadowList) -> Self { + ComputedTextShadowList(list.0.into_iter().map(|s| s.into()).collect()) + } +} + +impl From for TextShadowList { + fn from(list: ComputedTextShadowList) -> Self { + TextShadowList(list.0.into_iter().map(|s| s.into()).collect()) + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list +impl Animatable for TextShadowList { + #[inline] + fn add_weighted( + &self, + other: &Self, + self_portion: f64, + other_portion: f64, + ) -> Result { + let zero = SimpleShadow { + color: IntermediateColor::transparent(), + horizontal: Au(0), + vertical: Au(0), + blur: Au(0), + }; + + let max_len = cmp::max(self.0.len(), other.0.len()); + let mut shadows = Vec::with_capacity(max_len); + for i in 0..max_len { + shadows.push(match (self.0.get(i), other.0.get(i)) { + (Some(shadow), Some(other)) => { + shadow.add_weighted(other, self_portion, other_portion)? + }, + (Some(shadow), None) => { + shadow.add_weighted(&zero, self_portion, other_portion)? + }, + (None, Some(shadow)) => { + zero.add_weighted(&shadow, self_portion, other_portion)? + }, + (None, None) => unreachable!(), + }); + } + + Ok(TextShadowList(shadows)) + } + + #[inline] + fn add(&self, other: &Self) -> Result { + Ok(TextShadowList( + self.0.iter().cloned().chain(other.0.iter().cloned()).collect(), + )) + } +} + +impl From for BoxShadow { + #[inline] + fn from(shadow: ComputedBoxShadow) -> Self { + BoxShadow { + base: shadow.base.into(), + spread: shadow.spread, + inset: shadow.inset, + } + } +} + +impl From for ComputedBoxShadow { + #[inline] + fn from(shadow: BoxShadow) -> Self { + ComputedBoxShadow { + base: shadow.base.into(), + spread: shadow.spread, + inset: shadow.inset, + } + } +} + +impl Animatable for BoxShadow { + #[inline] + fn add_weighted( + &self, + other: &Self, + self_portion: f64, + other_portion: f64, + ) -> Result { + if self.inset != other.inset { + return Err(()); + } + Ok(BoxShadow { + base: self.base.add_weighted(&other.base, self_portion, other_portion)?, + spread: self.spread.add_weighted(&other.spread, self_portion, other_portion)?, + inset: self.inset, + }) + } + + #[inline] + fn compute_distance(&self, other: &Self) -> Result { + self.compute_squared_distance(other).map(|sd| sd.sqrt()) + } + + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + if self.inset != other.inset { + return Err(()); + } + Ok( + self.base.compute_squared_distance(&other.base)? + + self.spread.compute_squared_distance(&other.spread)?, + ) + } +} + impl From for FilterList { #[cfg(not(feature = "gecko"))] #[inline] @@ -77,7 +272,6 @@ impl From for Filter { GenericFilter::DropShadow(shadow) => { GenericFilter::DropShadow(shadow.into()) }, - #[cfg(feature = "gecko")] GenericFilter::Url(url) => GenericFilter::Url(url), } } @@ -100,7 +294,6 @@ impl From for ComputedFilter { GenericFilter::DropShadow(shadow) => { GenericFilter::DropShadow(shadow.into()) }, - #[cfg(feature = "gecko")] GenericFilter::Url(url) => GenericFilter::Url(url.clone()) } } diff --git a/components/style/values/computed/effects.rs b/components/style/values/computed/effects.rs index 3a0abecc9ece..b718ea7f2652 100644 --- a/components/style/values/computed/effects.rs +++ b/components/style/values/computed/effects.rs @@ -9,9 +9,13 @@ use values::Impossible; use values::computed::{Angle, Number}; use values::computed::color::Color; use values::computed::length::Length; +use values::generics::effects::BoxShadow as GenericBoxShadow; use values::generics::effects::Filter as GenericFilter; use values::generics::effects::SimpleShadow as GenericSimpleShadow; +/// A computed value for a single shadow of the `box-shadow` property. +pub type BoxShadow = GenericBoxShadow; + /// A computed value for a single `filter`. #[cfg(feature = "gecko")] pub type Filter = GenericFilter; diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index c1571e319847..cb0b68b10805 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -9,7 +9,6 @@ use context::QuirksMode; use euclid::Size2D; use font_metrics::FontMetricsProvider; use media_queries::Device; -use num_traits::Zero; #[cfg(feature = "gecko")] use properties; use properties::{ComputedValues, StyleBuilder}; @@ -28,7 +27,7 @@ pub use self::background::BackgroundSize; pub use self::border::{BorderImageSlice, BorderImageWidth, BorderImageSideWidth}; pub use self::border::{BorderRadius, BorderCornerRadius}; pub use self::color::{Color, RGBAColor}; -pub use self::effects::Filter; +pub use self::effects::{BoxShadow, Filter, SimpleShadow}; pub use self::flex::FlexBasis; pub use self::image::{Gradient, GradientItem, ImageLayer, LineDirection, Image, ImageRect}; #[cfg(feature = "gecko")] @@ -412,38 +411,6 @@ impl ComputedValueAsSpecified for specified::AlignJustifyContent {} impl ComputedValueAsSpecified for specified::AlignJustifySelf {} impl ComputedValueAsSpecified for specified::BorderStyle {} -#[derive(Debug, PartialEq, Clone, Copy)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct Shadow { - pub offset_x: Au, - pub offset_y: Au, - pub blur_radius: Au, - pub spread_radius: Au, - pub color: Color, - pub inset: bool, -} - -impl ToCss for Shadow { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - if self.inset { - dest.write_str("inset ")?; - } - self.offset_x.to_css(dest)?; - dest.write_str(" ")?; - self.offset_y.to_css(dest)?; - dest.write_str(" ")?; - self.blur_radius.to_css(dest)?; - dest.write_str(" ")?; - if self.spread_radius != Au::zero() { - self.spread_radius.to_css(dest)?; - dest.write_str(" ")?; - } - self.color.to_css(dest)?; - Ok(()) - } -} - /// A `` value. pub type Number = CSSFloat; diff --git a/components/style/values/generics/effects.rs b/components/style/values/generics/effects.rs index 3331a9cf03e6..d4b9670ff391 100644 --- a/components/style/values/generics/effects.rs +++ b/components/style/values/generics/effects.rs @@ -4,9 +4,23 @@ //! Generic types for CSS values related to effects. +use std::fmt; +use style_traits::values::{SequenceWriter, ToCss}; #[cfg(feature = "gecko")] use values::specified::url::SpecifiedUrl; +/// A generic value for a single `box-shadow`. +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, Debug, HasViewportPercentage, PartialEq)] +pub struct BoxShadow { + /// The base shadow. + pub base: SimpleShadow, + /// The spread radius. + pub spread: ShapeLength, + /// Whether this is an inset box shadow. + pub inset: bool, +} + /// A generic value for a single `filter`. #[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))] #[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToComputedValue, ToCss)] @@ -62,3 +76,25 @@ pub struct SimpleShadow { /// Blur radius. pub blur: ShapeLength, } + +impl ToCss for BoxShadow +where + Color: ToCss, + SizeLength: ToCss, + ShapeLength: ToCss, +{ + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + { + let mut writer = SequenceWriter::new(&mut *dest, " "); + writer.item(&self.base)?; + writer.item(&self.spread)?; + } + if self.inset { + dest.write_str(" inset")?; + } + Ok(()) + } +} diff --git a/components/style/values/specified/effects.rs b/components/style/values/specified/effects.rs index 29cc79f8742f..0f0033e81b5c 100644 --- a/components/style/values/specified/effects.rs +++ b/components/style/values/specified/effects.rs @@ -6,11 +6,13 @@ use cssparser::{BasicParseError, Parser, Token}; use parser::{Parse, ParserContext}; -use style_traits::ParseError; +use style_traits::{ParseError, StyleParseError}; #[cfg(not(feature = "gecko"))] use values::Impossible; use values::computed::{Context, Number as ComputedNumber, ToComputedValue}; +use values::computed::effects::BoxShadow as ComputedBoxShadow; use values::computed::effects::SimpleShadow as ComputedSimpleShadow; +use values::generics::effects::BoxShadow as GenericBoxShadow; use values::generics::effects::Filter as GenericFilter; use values::generics::effects::SimpleShadow as GenericSimpleShadow; use values::specified::{Angle, Percentage}; @@ -19,6 +21,9 @@ use values::specified::length::Length; #[cfg(feature = "gecko")] use values::specified::url::SpecifiedUrl; +/// A specified value for a single shadow of the `box-shadow` property. +pub type BoxShadow = GenericBoxShadow, Length, Option>; + /// A specified value for a single `filter`. #[cfg(feature = "gecko")] pub type Filter = GenericFilter; @@ -42,6 +47,85 @@ pub enum Factor { /// A specified value for the `drop-shadow()` filter. pub type SimpleShadow = GenericSimpleShadow, Length, Option>; +impl Parse for BoxShadow { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let mut lengths = None; + let mut color = None; + let mut inset = false; + + loop { + if !inset { + if input.try(|input| input.expect_ident_matching("inset")).is_ok() { + inset = true; + continue; + } + } + if lengths.is_none() { + let value = input.try::<_, _, ParseError>(|i| { + let horizontal = Length::parse(context, i)?; + let vertical = Length::parse(context, i)?; + let (blur, spread) = match i.try::<_, _, ParseError>(|i| Length::parse_non_negative(context, i)) { + Ok(blur) => { + let spread = i.try(|i| Length::parse(context, i)).ok(); + (Some(blur), spread) + }, + Err(_) => (None, None), + }; + Ok((horizontal, vertical, blur, spread)) + }); + if let Ok(value) = value { + lengths = Some(value); + continue; + } + } + if color.is_none() { + if let Ok(value) = input.try(|i| Color::parse(context, i)) { + color = Some(value); + continue; + } + } + break; + } + + let lengths = lengths.ok_or(StyleParseError::UnspecifiedError)?; + Ok(BoxShadow { + base: SimpleShadow { + color: color, + horizontal: lengths.0, + vertical: lengths.1, + blur: lengths.2, + }, + spread: lengths.3, + inset: inset, + }) + } +} + +impl ToComputedValue for BoxShadow { + type ComputedValue = ComputedBoxShadow; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + ComputedBoxShadow { + base: self.base.to_computed_value(context), + spread: self.spread.as_ref().unwrap_or(&Length::zero()).to_computed_value(context), + inset: self.inset, + } + } + + #[inline] + fn from_computed_value(computed: &ComputedBoxShadow) -> Self { + BoxShadow { + base: ToComputedValue::from_computed_value(&computed.base), + spread: Some(ToComputedValue::from_computed_value(&computed.spread)), + inset: computed.inset, + } + } +} + impl Parse for Filter { #[inline] fn parse<'i, 't>( diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index a8be8583576c..7c1cac927e94 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -19,8 +19,7 @@ use std::fmt; use style_traits::{ToCss, ParseError, StyleParseError}; use style_traits::values::specified::AllowedNumericType; use super::{Auto, CSSFloat, CSSInteger, Either, None_}; -use super::computed::{self, Context}; -use super::computed::{Shadow as ComputedShadow, ToComputedValue}; +use super::computed::{self, Context, ToComputedValue}; use super::generics::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as GenericTrackSize}; use super::generics::grid::TrackList as GenericTrackList; use values::computed::ComputedValueAsSpecified; @@ -33,7 +32,7 @@ pub use self::background::BackgroundSize; pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth}; pub use self::border::{BorderImageSideWidth, BorderRadius, BorderSideWidth}; pub use self::color::{Color, RGBAColor}; -pub use self::effects::Filter; +pub use self::effects::{BoxShadow, Filter, SimpleShadow}; pub use self::flex::FlexBasis; #[cfg(feature = "gecko")] pub use self::gecko::ScrollSnapPoint; @@ -690,130 +689,6 @@ pub type TrackList = GenericTrackList; /// ` | none` pub type TrackListOrNone = Either; -#[derive(Clone, Debug, HasViewportPercentage, PartialEq)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct Shadow { - pub offset_x: Length, - pub offset_y: Length, - pub blur_radius: Length, - pub spread_radius: Length, - pub color: Option, - pub inset: bool, -} - -impl ToComputedValue for Shadow { - type ComputedValue = ComputedShadow; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - ComputedShadow { - offset_x: self.offset_x.to_computed_value(context), - offset_y: self.offset_y.to_computed_value(context), - blur_radius: self.blur_radius.to_computed_value(context), - spread_radius: self.spread_radius.to_computed_value(context), - color: self.color.as_ref().unwrap_or(&Color::CurrentColor) - .to_computed_value(context), - inset: self.inset, - } - } - - #[inline] - fn from_computed_value(computed: &ComputedShadow) -> Self { - Shadow { - offset_x: ToComputedValue::from_computed_value(&computed.offset_x), - offset_y: ToComputedValue::from_computed_value(&computed.offset_y), - blur_radius: ToComputedValue::from_computed_value(&computed.blur_radius), - spread_radius: ToComputedValue::from_computed_value(&computed.spread_radius), - color: Some(ToComputedValue::from_computed_value(&computed.color)), - inset: computed.inset, - } - } -} - -impl ToCss for Shadow { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - if self.inset { - dest.write_str("inset ")?; - } - self.offset_x.to_css(dest)?; - dest.write_str(" ")?; - self.offset_y.to_css(dest)?; - dest.write_str(" ")?; - self.blur_radius.to_css(dest)?; - if self.spread_radius != Length::zero() { - dest.write_str(" ")?; - self.spread_radius.to_css(dest)?; - } - if let Some(ref color) = self.color { - dest.write_str(" ")?; - color.to_css(dest)?; - } - Ok(()) - } -} - -impl Shadow { - // disable_spread_and_inset is for filter: drop-shadow(...) - #[allow(missing_docs)] - pub fn parse<'i, 't>(context: &ParserContext, - input: &mut Parser<'i, 't>, - disable_spread_and_inset: bool) - -> Result> { - let mut lengths = [Length::zero(), Length::zero(), Length::zero(), Length::zero()]; - let mut lengths_parsed = false; - let mut color = None; - let mut inset = false; - - loop { - if !inset && !disable_spread_and_inset { - if input.try(|input| input.expect_ident_matching("inset")).is_ok() { - inset = true; - continue - } - } - if !lengths_parsed { - if let Ok(value) = input.try(|i| Length::parse(context, i)) { - lengths[0] = value; - lengths[1] = Length::parse(context, input)?; - if let Ok(value) = input.try(|i| Length::parse_non_negative(context, i)) { - lengths[2] = value; - if !disable_spread_and_inset { - if let Ok(value) = input.try(|i| Length::parse(context, i)) { - lengths[3] = value; - } - } - } - lengths_parsed = true; - continue - } - } - if color.is_none() { - if let Ok(value) = input.try(|i| Color::parse(context, i)) { - color = Some(value); - continue - } - } - break - } - - // Lengths must be specified. - if !lengths_parsed { - return Err(StyleParseError::UnspecifiedError.into()) - } - - debug_assert!(!disable_spread_and_inset || lengths[3] == Length::zero()); - Ok(Shadow { - offset_x: lengths[0].take(), - offset_y: lengths[1].take(), - blur_radius: lengths[2].take(), - spread_radius: lengths[3].take(), - color: color, - inset: inset, - }) - } -} - no_viewport_percentage!(SVGPaint); /// Specified SVG Paint value diff --git a/tests/unit/style/properties/serialization.rs b/tests/unit/style/properties/serialization.rs index b5914c02749e..d0953061b5d4 100644 --- a/tests/unit/style/properties/serialization.rs +++ b/tests/unit/style/properties/serialization.rs @@ -1234,16 +1234,24 @@ mod shorthand_serialization { mod effects { pub use super::*; - pub use style::properties::longhands::box_shadow::SpecifiedValue as BoxShadow; - pub use style::values::specified::Shadow; + pub use style::properties::longhands::box_shadow::SpecifiedValue as BoxShadowList; + pub use style::values::specified::effects::{BoxShadow, SimpleShadow}; #[test] fn box_shadow_should_serialize_correctly() { let mut properties = Vec::new(); - let shadow_val = Shadow { offset_x: Length::from_px(1f32), offset_y: Length::from_px(2f32), - blur_radius: Length::from_px(3f32), spread_radius: Length::from_px(4f32), color: None, inset: false }; - let shadow_decl = BoxShadow(vec![shadow_val]); - properties.push(PropertyDeclaration:: BoxShadow(shadow_decl)); + let shadow_val = BoxShadow { + base: SimpleShadow { + color: None, + horizontal: Length::from_px(1f32), + vertical: Length::from_px(2f32), + blur: Some(Length::from_px(3f32)), + }, + spread: Some(Length::from_px(4f32)), + inset: false, + }; + let shadow_decl = BoxShadowList(vec![shadow_val]); + properties.push(PropertyDeclaration::BoxShadow(shadow_decl)); let shadow_css = "box-shadow: 1px 2px 3px 4px;"; let shadow = parse(|c, i| Ok(parse_property_declaration_list(c, i)), shadow_css).unwrap();