diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs index af17eb4d659..789aea4725c 100644 --- a/components/gfx/display_list/mod.rs +++ b/components/gfx/display_list/mod.rs @@ -479,7 +479,7 @@ impl StackingContext { &Rect::zero(), &Rect::zero(), 0, - filter::T::new(Vec::new()), + filter::T::none(), MixBlendMode::Normal, None, TransformStyle::Flat, diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index 129a479d1c9..97b26acaad4 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -50,7 +50,6 @@ use std::sync::Arc; use style::computed_values::{background_attachment, background_clip, background_origin}; use style::computed_values::{background_repeat, border_style, cursor}; use style::computed_values::{image_rendering, overflow_x, pointer_events, position, visibility}; -use style::computed_values::filter::Filter; use style::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode}; use style::properties::{self, ServoComputedValues}; use style::properties::longhands::border_image_repeat::computed_value::RepeatKeyword; @@ -61,6 +60,7 @@ use style::values::computed::{Gradient, GradientItem, LengthOrPercentage}; use style::values::computed::{LengthOrPercentageOrAuto, NumberOrPercentage, Position, Shadow}; use style::values::computed::image::{EndingShape, LineDirection}; use style::values::generics::background::BackgroundSize; +use style::values::generics::effects::Filter; use style::values::generics::image::{Circle, Ellipse, EndingShape as GenericEndingShape}; use style::values::generics::image::{GradientItem as GenericGradientItem, GradientKind}; use style::values::generics::image::{Image, ShapeExtent}; @@ -2006,7 +2006,7 @@ impl FragmentDisplayListBuilding for Fragment { // Create the filter pipeline. let effects = self.style().get_effects(); - let mut filters = effects.filter.clone(); + let mut filters = effects.filter.clone().0.into_vec(); if effects.opacity != 1.0 { filters.push(Filter::Opacity(effects.opacity)) } @@ -2022,7 +2022,7 @@ impl FragmentDisplayListBuilding for Fragment { &border_box, &overflow, self.effective_z_index(), - filters, + filters.into(), self.style().get_effects().mix_blend_mode.to_mix_blend_mode(), self.transform_matrix(&border_box), self.style().get_used_transform_style().to_transform_style(), diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 818717343ec..40293bb3780 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -2467,7 +2467,7 @@ impl Fragment { if self.style().get_effects().opacity != 1.0 { return true } - if !self.style().get_effects().filter.is_empty() { + if !self.style().get_effects().filter.0.is_empty() { return true } if self.style().get_effects().mix_blend_mode != mix_blend_mode::T::normal { diff --git a/components/layout/webrender_helpers.rs b/components/layout/webrender_helpers.rs index dc8bf7bc9a5..9110d95ec24 100644 --- a/components/layout/webrender_helpers.rs +++ b/components/layout/webrender_helpers.rs @@ -13,8 +13,9 @@ use gfx::display_list::{BorderDetails, BorderRadii, BoxShadowClipMode, ClippingR use gfx::display_list::{DisplayItem, DisplayList, DisplayListTraversal, StackingContextType}; use msg::constellation_msg::PipelineId; use style::computed_values::{image_rendering, mix_blend_mode, transform_style}; -use style::computed_values::filter::{self, Filter}; +use style::computed_values::filter; use style::values::computed::BorderStyle; +use style::values::generics::effects::Filter; use webrender_traits::{self, DisplayListBuilder, ExtendMode}; use webrender_traits::{LayoutTransform, ClipId, ClipRegionToken}; @@ -203,8 +204,8 @@ trait ToFilterOps { impl ToFilterOps for filter::T { fn to_filter_ops(&self) -> Vec { - let mut result = Vec::with_capacity(self.filters.len()); - for filter in self.filters.iter() { + let mut result = Vec::with_capacity(self.0.len()); + for filter in self.0.iter() { match *filter { Filter::Blur(radius) => result.push(webrender_traits::FilterOp::Blur(radius)), Filter::Brightness(amount) => result.push(webrender_traits::FilterOp::Brightness(amount)), @@ -215,6 +216,7 @@ impl ToFilterOps for filter::T { Filter::Opacity(amount) => result.push(webrender_traits::FilterOp::Opacity(amount.into())), Filter::Saturate(amount) => result.push(webrender_traits::FilterOp::Saturate(amount)), Filter::Sepia(amount) => result.push(webrender_traits::FilterOp::Sepia(amount)), + Filter::DropShadow(ref shadow) => match *shadow {}, } } result 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 fe818d71330..45072ba2eb2 100644 --- a/components/style/gecko_bindings/sugar/ns_css_shadow_item.rs +++ b/components/style/gecko_bindings/sugar/ns_css_shadow_item.rs @@ -8,6 +8,7 @@ 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::DropShadow; impl nsCSSShadowItem { /// Set this item to the given shadow value. @@ -39,4 +40,36 @@ impl nsCSSShadowItem { color: Color::rgba(convert_nscolor_to_rgba(self.mColor)), } } + + /// Sets this item from the given drop shadow. + #[inline] + pub fn set_from_drop_shadow(&mut self, shadow: DropShadow) { + self.mXOffset = shadow.horizontal.0; + self.mYOffset = shadow.vertical.0; + self.mRadius = shadow.blur.0; + self.mSpread = 0; + self.mInset = false; + if shadow.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(&shadow.color.color); + } + } + + /// Returns this item as a drop shadow. + #[inline] + pub fn to_drop_shadow(&self) -> DropShadow { + debug_assert_eq!(self.mSpread, 0); + debug_assert_eq!(self.mInset, false); + DropShadow { + color: Color::rgba(convert_nscolor_to_rgba(self.mColor)), + horizontal: Au(self.mXOffset), + vertical: Au(self.mYOffset), + blur: Au(self.mRadius), + } + } } diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index acd43354b06..d62499cdd27 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -3444,7 +3444,7 @@ fn static_assert() { %> pub fn set_filter(&mut self, v: longhands::filter::computed_value::T) { - use properties::longhands::filter::computed_value::Filter::*; + use values::generics::effects::Filter::*; use gecko_bindings::structs::nsCSSShadowArray; use gecko_bindings::structs::nsStyleFilter; use gecko_bindings::structs::NS_STYLE_FILTER_BLUR; @@ -3464,11 +3464,11 @@ fn static_assert() { } unsafe { - Gecko_ResetFilters(&mut self.gecko, v.filters.len()); + Gecko_ResetFilters(&mut self.gecko, v.0.len()); } - debug_assert!(v.filters.len() == self.gecko.mFilters.len()); + debug_assert!(v.0.len() == self.gecko.mFilters.len()); - for (servo, gecko_filter) in v.filters.into_iter().zip(self.gecko.mFilters.iter_mut()) { + for (servo, gecko_filter) in v.0.into_vec().into_iter().zip(self.gecko.mFilters.iter_mut()) { match servo { % for func in FILTER_FUNCTIONS: ${func}(factor) => fill_filter(NS_STYLE_FILTER_${func.upper()}, @@ -3497,7 +3497,7 @@ fn static_assert() { } let mut gecko_shadow = init_shadow(gecko_filter); - gecko_shadow.mArray[0].set_from_shadow(shadow); + gecko_shadow.mArray[0].set_from_drop_shadow(shadow); }, Url(ref url) => { unsafe { @@ -3515,7 +3515,7 @@ fn static_assert() { } pub fn clone_filter(&self) -> longhands::filter::computed_value::T { - use properties::longhands::filter::computed_value::Filter::*; + use values::generics::effects::{Filter, FilterList}; use values::specified::url::SpecifiedUrl; use gecko_bindings::structs::NS_STYLE_FILTER_BLUR; use gecko_bindings::structs::NS_STYLE_FILTER_BRIGHTNESS; @@ -3534,35 +3534,36 @@ fn static_assert() { match filter.mType { % for func in FILTER_FUNCTIONS: NS_STYLE_FILTER_${func.upper()} => { - filters.push(${func}( + filters.push(Filter::${func}( GeckoStyleCoordConvertible::from_gecko_style_coord( &filter.mFilterParameter).unwrap())); }, % endfor NS_STYLE_FILTER_BLUR => { - filters.push(Blur(Au::from_gecko_style_coord( + filters.push(Filter::Blur(Au::from_gecko_style_coord( &filter.mFilterParameter).unwrap())); }, NS_STYLE_FILTER_HUE_ROTATE => { - filters.push(HueRotate( + filters.push(Filter::HueRotate( GeckoStyleCoordConvertible::from_gecko_style_coord( &filter.mFilterParameter).unwrap())); }, NS_STYLE_FILTER_DROP_SHADOW => { filters.push(unsafe { - DropShadow((**filter.__bindgen_anon_1.mDropShadow.as_ref()).mArray[0].to_shadow()) + Filter::DropShadow((**filter.__bindgen_anon_1.mDropShadow.as_ref()).mArray[0].to_drop_shadow()) }); }, NS_STYLE_FILTER_URL => { filters.push(unsafe { - (Url(SpecifiedUrl::from_url_value_data( - &(**filter.__bindgen_anon_1.mURL.as_ref())._base).unwrap())) + Filter::Url( + SpecifiedUrl::from_url_value_data(&(**filter.__bindgen_anon_1.mURL.as_ref())._base).unwrap() + ) }); } _ => {}, } } - longhands::filter::computed_value::T::new(filters) + FilterList(filters.into_boxed_slice()) } diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index 0e7db04bbf7..8b5feb762c5 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -14,12 +14,9 @@ use euclid::{Point2D, Size2D}; #[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSPropertyID; #[cfg(feature = "gecko")] use gecko_bindings::sugar::ownership::{HasFFI, HasSimpleFFI}; #[cfg(feature = "gecko")] use gecko_string_cache::Atom; -#[cfg(feature = "gecko")] use gecko::url::SpecifiedUrl; use properties::{CSSWideKeyword, PropertyDeclaration}; use properties::longhands; use properties::longhands::background_size::computed_value::T as BackgroundSizeList; -use properties::longhands::filter::computed_value::Filter; -use properties::longhands::filter::computed_value::T as Filters; 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; @@ -37,12 +34,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::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::generics::{SVGPaint, SVGPaintKind}; use values::generics::border::BorderCornerRadius as GenericBorderCornerRadius; +use values::generics::effects::Filter; use values::generics::position as generic_position; use values::specified::length::Percentage; @@ -3196,135 +3195,61 @@ impl Animatable for IntermediateShadowList { } } -/// Intermediate type for filter property. -/// The difference from normal filter type is that this structure uses -/// IntermediateColor into DropShadow's value. -pub type IntermediateFilters = Vec; - -#[derive(Clone, PartialEq, Debug)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub enum IntermediateFilter { - Blur(Au), - Brightness(CSSFloat), - Contrast(CSSFloat), - Grayscale(CSSFloat), - HueRotate(Angle), - Invert(CSSFloat), - Opacity(CSSFloat), - Saturate(CSSFloat), - Sepia(CSSFloat), - % if product == "gecko": - DropShadow(IntermediateShadow), - Url(SpecifiedUrl), - % endif -} - -impl From for IntermediateFilters { - fn from(filters: Filters) -> IntermediateFilters { - filters.filters.into_iter().map(|f| f.into()).collect() - } -} - -impl From for Filters { - fn from(filters: IntermediateFilters) -> Filters { - Filters::new(filters.into_iter().map(|f| f.into()).collect()) - } -} - <% FILTER_FUNCTIONS = [ 'Blur', 'Brightness', 'Contrast', 'Grayscale', 'HueRotate', 'Invert', 'Opacity', 'Saturate', 'Sepia' ] %> -impl From for IntermediateFilter { - fn from(filter: Filter) -> IntermediateFilter { - use properties::longhands::filter::computed_value::Filter::*; - match filter { - % for func in FILTER_FUNCTIONS: - ${func}(val) => IntermediateFilter::${func}(val), - % endfor - % if product == "gecko": - DropShadow(shadow) => { - IntermediateFilter::DropShadow(shadow.into()) - }, - Url(ref url) => { - IntermediateFilter::Url(url.clone()) - }, - % endif - } - } -} - -impl From for Filter { - fn from(filter: IntermediateFilter) -> Filter { - match filter { - % for func in FILTER_FUNCTIONS: - IntermediateFilter::${func}(val) => Filter::${func}(val), - % endfor - % if product == "gecko": - IntermediateFilter::DropShadow(shadow) => { - Filter::DropShadow(shadow.into()) - }, - IntermediateFilter::Url(ref url) => { - Filter::Url(url.clone()) - }, - % endif - } - } -} - /// https://drafts.fxtf.org/filters/#animation-of-filters -fn add_weighted_filter_function_impl(from: &IntermediateFilter, - to: &IntermediateFilter, +fn add_weighted_filter_function_impl(from: &AnimatedFilter, + to: &AnimatedFilter, self_portion: f64, other_portion: f64) - -> Result { + -> Result { match (from, to) { % for func in [ 'Blur', 'HueRotate' ]: - (&IntermediateFilter::${func}(from_value), - &IntermediateFilter::${func}(to_value)) => { - Ok(IntermediateFilter::${func}( - try!(from_value.add_weighted(&to_value, - self_portion, - other_portion)))) + (&Filter::${func}(from_value), &Filter::${func}(to_value)) => { + Ok(Filter::${func}(from_value.add_weighted( + &to_value, + self_portion, + other_portion, + )?)) }, % endfor % for func in [ 'Grayscale', 'Invert', 'Sepia' ]: - (&IntermediateFilter::${func}(from_value), - &IntermediateFilter::${func}(to_value)) => { - Ok(IntermediateFilter::${func}(try!( - add_weighted_with_initial_val(&from_value, - &to_value, - self_portion, - other_portion, - &0.0)))) + (&Filter::${func}(from_value), &Filter::${func}(to_value)) => { + Ok(Filter::${func}(add_weighted_with_initial_val( + &from_value, + &to_value, + self_portion, + other_portion, + &0.0, + )?)) }, % endfor % for func in [ 'Brightness', 'Contrast', 'Opacity', 'Saturate' ]: - (&IntermediateFilter::${func}(from_value), - &IntermediateFilter::${func}(to_value)) => { - Ok(IntermediateFilter::${func}(try!( - add_weighted_with_initial_val(&from_value, - &to_value, - self_portion, - other_portion, - &1.0)))) + (&Filter::${func}(from_value), &Filter::${func}(to_value)) => { + Ok(Filter::${func}(add_weighted_with_initial_val( + &from_value, + &to_value, + self_portion, + other_portion, + &1.0, + )?)) }, % endfor % if product == "gecko": - (&IntermediateFilter::DropShadow(from_value), - &IntermediateFilter::DropShadow(to_value)) => { - Ok(IntermediateFilter::DropShadow(try!( - from_value.add_weighted(&to_value, - self_portion, - other_portion)))) - }, - (&IntermediateFilter::Url(_), - &IntermediateFilter::Url(_)) => { - Err(()) - }, + (&Filter::DropShadow(ref from_value), &Filter::DropShadow(ref to_value)) => { + Ok(Filter::DropShadow(from_value.add_weighted( + &to_value, + self_portion, + other_portion, + )?)) + }, + (&Filter::Url(_), &Filter::Url(_)) => { + Err(()) + }, % endif _ => { // If specified the different filter functions, @@ -3335,10 +3260,10 @@ fn add_weighted_filter_function_impl(from: &IntermediateFilter, } /// https://drafts.fxtf.org/filters/#animation-of-filters -fn add_weighted_filter_function(from: Option<<&IntermediateFilter>, - to: Option<<&IntermediateFilter>, +fn add_weighted_filter_function(from: Option<<&AnimatedFilter>, + to: Option<<&AnimatedFilter>, self_portion: f64, - other_portion: f64) -> Result { + other_portion: f64) -> Result { match (from, to) { (Some(f), Some(t)) => { add_weighted_filter_function_impl(f, t, self_portion, other_portion) @@ -3353,19 +3278,18 @@ fn add_weighted_filter_function(from: Option<<&IntermediateFilter>, } } -fn compute_filter_square_distance(from: &IntermediateFilter, - to: &IntermediateFilter) +fn compute_filter_square_distance(from: &AnimatedFilter, + to: &AnimatedFilter) -> Result { match (from, to) { % for func in FILTER_FUNCTIONS : - (&IntermediateFilter::${func}(f), - &IntermediateFilter::${func}(t)) => { + (&Filter::${func}(f), + &Filter::${func}(t)) => { Ok(try!(f.compute_squared_distance(&t))) }, % endfor % if product == "gecko": - (&IntermediateFilter::DropShadow(f), - &IntermediateFilter::DropShadow(t)) => { + (&Filter::DropShadow(ref f), &Filter::DropShadow(ref t)) => { Ok(try!(f.compute_squared_distance(&t))) }, % endif @@ -3375,38 +3299,34 @@ fn compute_filter_square_distance(from: &IntermediateFilter, } } -impl Animatable for IntermediateFilters { +impl Animatable for AnimatedFilterList { #[inline] fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result { - let mut filters: IntermediateFilters = Vec::new(); - let mut from_iter = self.iter(); - let mut to_iter = (&other).iter(); + let mut filters = vec![]; + let mut from_iter = self.0.iter(); + let mut to_iter = other.0.iter(); let mut from = from_iter.next(); let mut to = to_iter.next(); - while (from,to) != (None, None) { + while from.is_some() || to.is_some() { filters.push(try!(add_weighted_filter_function(from, to, self_portion, other_portion))); - if from != None { + if from.is_some() { from = from_iter.next(); } - if to != None { + if to.is_some() { to = to_iter.next(); } } - Ok(filters) + Ok(filters.into()) } fn add(&self, other: &Self) -> Result { - let from_list = &self; - let to_list = &other; - let filters: IntermediateFilters = - vec![&from_list[..], &to_list[..]].concat(); - Ok(filters) + Ok(self.0.iter().chain(other.0.iter()).cloned().collect::>().into()) } #[inline] @@ -3417,20 +3337,20 @@ impl Animatable for IntermediateFilters { #[inline] fn compute_squared_distance(&self, other: &Self) -> Result { let mut square_distance: f64 = 0.0; - let mut from_iter = self.iter(); - let mut to_iter = (&other).iter(); + let mut from_iter = self.0.iter(); + let mut to_iter = other.0.iter(); let mut from = from_iter.next(); let mut to = to_iter.next(); - while (from,to) != (None, None) { + while from.is_some() || to.is_some() { let current_square_distance: f64 ; - if from == None { + if from.is_none() { let none = try!(add_weighted_filter_function(to, to, 0.0, 0.0)); current_square_distance = compute_filter_square_distance(&none, &(to.unwrap())).unwrap(); to = to_iter.next(); - } else if to == None { + } else if to.is_none() { let none = try!(add_weighted_filter_function(from, from, 0.0, 0.0)); current_square_distance = compute_filter_square_distance(&none, &(from.unwrap())).unwrap(); diff --git a/components/style/properties/longhand/effects.mako.rs b/components/style/properties/longhand/effects.mako.rs index 5085731cd00..7522308a20d 100644 --- a/components/style/properties/longhand/effects.mako.rs +++ b/components/style/properties/longhand/effects.mako.rs @@ -41,339 +41,15 @@ ${helpers.predefined_type("clip", allow_quirks=True, spec="https://drafts.fxtf.org/css-masking/#clip-property")} -<%helpers:longhand name="filter" animation_value_type="IntermediateFilters" extra_prefixes="webkit" - flags="CREATES_STACKING_CONTEXT FIXPOS_CB" - spec="https://drafts.fxtf.org/filters/#propdef-filter"> - //pub use self::computed_value::T as SpecifiedValue; - use std::fmt; - use style_traits::{HasViewportPercentage, ToCss}; - use values::CSSFloat; - use values::specified::{Angle, Length}; - #[cfg(feature = "gecko")] - use values::specified::Shadow; - #[cfg(feature = "gecko")] - use values::specified::url::SpecifiedUrl; - - #[derive(Clone, Debug, HasViewportPercentage, PartialEq)] - #[cfg_attr(feature = "servo", derive(HeapSizeOf))] - pub struct SpecifiedValue(pub Vec); - - impl HasViewportPercentage for SpecifiedFilter { - fn has_viewport_percentage(&self) -> bool { - match *self { - SpecifiedFilter::Blur(ref length) => length.has_viewport_percentage(), - _ => false - } - } - } - - // TODO(pcwalton): `drop-shadow` - #[derive(Clone, PartialEq, Debug)] - #[cfg_attr(feature = "servo", derive(HeapSizeOf))] - pub enum SpecifiedFilter { - Blur(Length), - Brightness(CSSFloat), - Contrast(CSSFloat), - Grayscale(CSSFloat), - HueRotate(Angle), - Invert(CSSFloat), - Opacity(CSSFloat), - Saturate(CSSFloat), - Sepia(CSSFloat), - % if product == "gecko": - DropShadow(Shadow), - Url(SpecifiedUrl), - % endif - } - - pub mod computed_value { - use app_units::Au; - use values::CSSFloat; - #[cfg(feature = "gecko")] - use values::computed::Shadow; - use values::computed::Angle; - #[cfg(feature = "gecko")] - use values::specified::url::SpecifiedUrl; - - #[derive(Clone, PartialEq, Debug)] - #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))] - pub enum Filter { - Blur(Au), - Brightness(CSSFloat), - Contrast(CSSFloat), - Grayscale(CSSFloat), - HueRotate(Angle), - Invert(CSSFloat), - Opacity(CSSFloat), - Saturate(CSSFloat), - Sepia(CSSFloat), - % if product == "gecko": - DropShadow(Shadow), - Url(SpecifiedUrl), - % endif - } - - #[derive(Clone, PartialEq, Debug)] - #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))] - pub struct T { pub filters: Vec } - - impl T { - /// Creates a new filter pipeline. - #[inline] - pub fn new(filters: Vec) -> T { - T - { - filters: filters, - } - } - - /// Adds a new filter to the filter pipeline. - #[inline] - pub fn push(&mut self, filter: Filter) { - self.filters.push(filter) - } - - /// Returns true if this filter pipeline is empty and false otherwise. - #[inline] - pub fn is_empty(&self) -> bool { - self.filters.is_empty() - } - - /// Returns the resulting opacity of this filter pipeline. - #[inline] - pub fn opacity(&self) -> CSSFloat { - let mut opacity = 1.0; - - for filter in &self.filters { - if let Filter::Opacity(ref opacity_value) = *filter { - opacity *= *opacity_value - } - } - opacity - } - } - } - - impl ToCss for computed_value::T { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - let mut iter = self.filters.iter(); - if let Some(filter) = iter.next() { - filter.to_css(dest)?; - } else { - dest.write_str("none")?; - return Ok(()) - } - for filter in iter { - dest.write_str(" ")?; - filter.to_css(dest)?; - } - Ok(()) - } - } - - impl ToCss for SpecifiedValue { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - let mut iter = self.0.iter(); - if let Some(filter) = iter.next() { - filter.to_css(dest)?; - } else { - dest.write_str("none")?; - return Ok(()) - } - for filter in iter { - dest.write_str(" ")?; - filter.to_css(dest)?; - } - Ok(()) - } - } - - impl ToCss for computed_value::Filter { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - match *self { - computed_value::Filter::Blur(ref value) => { - dest.write_str("blur(")?; - value.to_css(dest)?; - dest.write_str(")")?; - } - computed_value::Filter::Brightness(value) => write!(dest, "brightness({})", value)?, - computed_value::Filter::Contrast(value) => write!(dest, "contrast({})", value)?, - computed_value::Filter::Grayscale(value) => write!(dest, "grayscale({})", value)?, - computed_value::Filter::HueRotate(value) => { - dest.write_str("hue-rotate(")?; - value.to_css(dest)?; - dest.write_str(")")?; - } - computed_value::Filter::Invert(value) => write!(dest, "invert({})", value)?, - computed_value::Filter::Opacity(value) => write!(dest, "opacity({})", value)?, - computed_value::Filter::Saturate(value) => write!(dest, "saturate({})", value)?, - computed_value::Filter::Sepia(value) => write!(dest, "sepia({})", value)?, - % if product == "gecko": - computed_value::Filter::DropShadow(shadow) => { - dest.write_str("drop-shadow(")?; - shadow.to_css(dest)?; - dest.write_str(")")?; - } - computed_value::Filter::Url(ref url) => { - url.to_css(dest)?; - } - % endif - } - Ok(()) - } - } - - impl ToCss for SpecifiedFilter { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - match *self { - SpecifiedFilter::Blur(ref value) => { - dest.write_str("blur(")?; - value.to_css(dest)?; - dest.write_str(")")?; - } - SpecifiedFilter::Brightness(value) => write!(dest, "brightness({})", value)?, - SpecifiedFilter::Contrast(value) => write!(dest, "contrast({})", value)?, - SpecifiedFilter::Grayscale(value) => write!(dest, "grayscale({})", value)?, - SpecifiedFilter::HueRotate(value) => { - dest.write_str("hue-rotate(")?; - value.to_css(dest)?; - dest.write_str(")")?; - } - SpecifiedFilter::Invert(value) => write!(dest, "invert({})", value)?, - SpecifiedFilter::Opacity(value) => write!(dest, "opacity({})", value)?, - SpecifiedFilter::Saturate(value) => write!(dest, "saturate({})", value)?, - SpecifiedFilter::Sepia(value) => write!(dest, "sepia({})", value)?, - % if product == "gecko": - SpecifiedFilter::DropShadow(ref shadow) => { - dest.write_str("drop-shadow(")?; - shadow.to_css(dest)?; - dest.write_str(")")?; - } - SpecifiedFilter::Url(ref url) => { - url.to_css(dest)?; - } - % endif - } - Ok(()) - } - } - - #[inline] - pub fn get_initial_value() -> computed_value::T { - computed_value::T::new(Vec::new()) - } - - pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) - -> Result> { - let mut filters = Vec::new(); - if input.try(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(SpecifiedValue(filters)) - } - loop { - % if product == "gecko": - if let Ok(url) = input.try(|i| SpecifiedUrl::parse(context, i)) { - filters.push(SpecifiedFilter::Url(url)); - } else - % endif - if let Ok(function_name) = input.try(|input| input.expect_function()) { - filters.push(input.parse_nested_block(|input| { - match_ignore_ascii_case! { &function_name, - "blur" => specified::Length::parse_non_negative(context, input).map(SpecifiedFilter::Blur), - "brightness" => parse_factor(input).map(SpecifiedFilter::Brightness), - "contrast" => parse_factor(input).map(SpecifiedFilter::Contrast), - "grayscale" => parse_factor(input).map(SpecifiedFilter::Grayscale), - "hue-rotate" => Angle::parse(context, input).map(SpecifiedFilter::HueRotate), - "invert" => parse_factor(input).map(SpecifiedFilter::Invert), - "opacity" => parse_factor(input).map(SpecifiedFilter::Opacity), - "saturate" => parse_factor(input).map(SpecifiedFilter::Saturate), - "sepia" => parse_factor(input).map(SpecifiedFilter::Sepia), - % if product == "gecko": - "drop-shadow" => specified::Shadow::parse(context, input, true) - .map(SpecifiedFilter::DropShadow), - % endif - _ => Err(StyleParseError::UnexpectedFunction(function_name.clone()).into()) - } - })?); - } else if filters.is_empty() { - return Err(StyleParseError::UnspecifiedError.into()) - } else { - return Ok(SpecifiedValue(filters)) - } - } - } - - fn parse_factor<'i, 't>(input: &mut Parser<'i, 't>) -> Result<::values::CSSFloat, ParseError<'i>> { - use cssparser::Token; - match input.next() { - Ok(Token::Number { value, .. }) if value.is_sign_positive() => Ok(value), - Ok(Token::Percentage { unit_value, .. }) if unit_value.is_sign_positive() => Ok(unit_value), - Ok(t) => Err(BasicParseError::UnexpectedToken(t).into()), - Err(e) => Err(e.into()) - } - } - - impl ToComputedValue for SpecifiedValue { - type ComputedValue = computed_value::T; - - fn to_computed_value(&self, context: &Context) -> computed_value::T { - computed_value::T{ filters: self.0.iter().map(|value| { - match *value { - SpecifiedFilter::Blur(ref factor) => - computed_value::Filter::Blur(factor.to_computed_value(context)), - SpecifiedFilter::Brightness(factor) => computed_value::Filter::Brightness(factor), - SpecifiedFilter::Contrast(factor) => computed_value::Filter::Contrast(factor), - SpecifiedFilter::Grayscale(factor) => computed_value::Filter::Grayscale(factor), - SpecifiedFilter::HueRotate(ref factor) => { - computed_value::Filter::HueRotate(factor.to_computed_value(context)) - }, - SpecifiedFilter::Invert(factor) => computed_value::Filter::Invert(factor), - SpecifiedFilter::Opacity(factor) => computed_value::Filter::Opacity(factor), - SpecifiedFilter::Saturate(factor) => computed_value::Filter::Saturate(factor), - SpecifiedFilter::Sepia(factor) => computed_value::Filter::Sepia(factor), - % if product == "gecko": - SpecifiedFilter::DropShadow(ref shadow) => { - computed_value::Filter::DropShadow(shadow.to_computed_value(context)) - }, - SpecifiedFilter::Url(ref url) => { - computed_value::Filter::Url(url.to_computed_value(context)) - } - % endif - } - }).collect() } - } - - fn from_computed_value(computed: &computed_value::T) -> Self { - SpecifiedValue(computed.filters.iter().map(|value| { - match *value { - computed_value::Filter::Blur(factor) => - SpecifiedFilter::Blur(ToComputedValue::from_computed_value(&factor)), - computed_value::Filter::Brightness(factor) => SpecifiedFilter::Brightness(factor), - computed_value::Filter::Contrast(factor) => SpecifiedFilter::Contrast(factor), - computed_value::Filter::Grayscale(factor) => SpecifiedFilter::Grayscale(factor), - computed_value::Filter::HueRotate(ref factor) => { - SpecifiedFilter::HueRotate(ToComputedValue::from_computed_value(factor)) - }, - computed_value::Filter::Invert(factor) => SpecifiedFilter::Invert(factor), - computed_value::Filter::Opacity(factor) => SpecifiedFilter::Opacity(factor), - computed_value::Filter::Saturate(factor) => SpecifiedFilter::Saturate(factor), - computed_value::Filter::Sepia(factor) => SpecifiedFilter::Sepia(factor), - % if product == "gecko": - computed_value::Filter::DropShadow(ref shadow) => { - SpecifiedFilter::DropShadow( - ToComputedValue::from_computed_value(shadow), - ) - } - computed_value::Filter::Url(ref url) => { - SpecifiedFilter::Url( - ToComputedValue::from_computed_value(url), - ) - } - % endif - } - }).collect()) - } - } - +${helpers.predefined_type( + "filter", + "FilterList", + "computed::FilterList::none()", + animation_value_type="AnimatedFilterList", + extra_prefixes="webkit", + flags="CREATES_STACKING_CONTEXT FIXPOS_CB", + spec="https://drafts.fxtf.org/filters/#propdef-filter", +)} ${helpers.single_keyword("mix-blend-mode", """normal multiply screen overlay darken lighten color-dodge diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 4e2a1a2039b..e2e8c15e3ca 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -2064,7 +2064,7 @@ impl ComputedValues { let effects = self.get_effects(); // TODO(gw): Add clip-path, isolation, mask-image, mask-border-source when supported. effects.opacity < 1.0 || - !effects.filter.is_empty() || + !effects.filter.0.is_empty() || !effects.clip.is_auto() || effects.mix_blend_mode != mix_blend_mode::T::normal } diff --git a/components/style/values/animated/effects.rs b/components/style/values/animated/effects.rs new file mode 100644 index 00000000000..f6de998ec3e --- /dev/null +++ b/components/style/values/animated/effects.rs @@ -0,0 +1,202 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Animated types for CSS values related to effects. + +use properties::animated_properties::Animatable; +#[cfg(feature = "gecko")] +use properties::animated_properties::IntermediateColor; +use values::computed::{Angle, Number}; +use values::computed::effects::DropShadow as ComputedDropShadow; +use values::computed::effects::Filter as ComputedFilter; +use values::computed::effects::FilterList as ComputedFilterList; +use values::computed::length::Length; +use values::generics::effects::Filter as GenericFilter; +use values::generics::effects::FilterList as GenericFilterList; + +/// An animated value for the `filter` property. +pub type FilterList = GenericFilterList; + +/// An animated value for a single `filter`. +pub type Filter = GenericFilter< + Angle, + // FIXME: Should be `NumberOrPercentage`. + Number, + Length, + DropShadow +>; + +/// An animated value for the `drop-shadow()` filter. +/// +/// Currently unsupported outside of Gecko. +#[cfg(not(feature = "gecko"))] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, Debug, PartialEq)] +pub enum DropShadow {} + +/// An animated value for the `drop-shadow()` filter. +/// +/// Contrary to the canonical order from the spec, the color is serialised +/// first, like in Gecko and Webkit. +#[cfg(feature = "gecko")] +#[derive(Clone, Debug, PartialEq)] +pub struct DropShadow { + /// Color. + pub color: IntermediateColor, + /// Horizontal radius. + pub horizontal: Length, + /// Vertical radius. + pub vertical: Length, + /// Blur radius. + pub blur: Length, +} + +impl From for FilterList { + #[inline] + fn from(filters: ComputedFilterList) -> Self { + filters.0.into_vec().into_iter().map(|f| f.into()).collect::>().into() + } +} + +impl From for ComputedFilterList { + #[inline] + fn from(filters: FilterList) -> Self { + filters.0.into_vec().into_iter().map(|f| f.into()).collect::>().into() + } +} + +impl From for Filter { + #[inline] + fn from(filter: ComputedFilter) -> Self { + match filter { + GenericFilter::Blur(angle) => GenericFilter::Blur(angle), + GenericFilter::Brightness(factor) => GenericFilter::Brightness(factor), + GenericFilter::Contrast(factor) => GenericFilter::Contrast(factor), + GenericFilter::Grayscale(factor) => GenericFilter::Grayscale(factor), + GenericFilter::HueRotate(factor) => GenericFilter::HueRotate(factor), + GenericFilter::Invert(factor) => GenericFilter::Invert(factor), + GenericFilter::Opacity(factor) => GenericFilter::Opacity(factor), + GenericFilter::Saturate(factor) => GenericFilter::Saturate(factor), + GenericFilter::Sepia(factor) => GenericFilter::Sepia(factor), + GenericFilter::DropShadow(shadow) => { + GenericFilter::DropShadow(shadow.into()) + }, + #[cfg(feature = "gecko")] + GenericFilter::Url(url) => GenericFilter::Url(url), + } + } +} + +impl From for ComputedFilter { + #[inline] + fn from(filter: Filter) -> Self { + match filter { + GenericFilter::Blur(angle) => GenericFilter::Blur(angle), + GenericFilter::Brightness(factor) => GenericFilter::Brightness(factor), + GenericFilter::Contrast(factor) => GenericFilter::Contrast(factor), + GenericFilter::Grayscale(factor) => GenericFilter::Grayscale(factor), + GenericFilter::HueRotate(factor) => GenericFilter::HueRotate(factor), + GenericFilter::Invert(factor) => GenericFilter::Invert(factor), + GenericFilter::Opacity(factor) => GenericFilter::Opacity(factor), + GenericFilter::Saturate(factor) => GenericFilter::Saturate(factor), + GenericFilter::Sepia(factor) => GenericFilter::Sepia(factor), + GenericFilter::DropShadow(shadow) => { + GenericFilter::DropShadow(shadow.into()) + }, + #[cfg(feature = "gecko")] + GenericFilter::Url(url) => GenericFilter::Url(url.clone()) + } + } +} + +impl From for DropShadow { + #[cfg(not(feature = "gecko"))] + #[inline] + fn from(shadow: ComputedDropShadow) -> Self { + match shadow {} + } + + #[cfg(feature = "gecko")] + #[inline] + fn from(shadow: ComputedDropShadow) -> Self { + DropShadow { + color: shadow.color.into(), + horizontal: shadow.horizontal, + vertical: shadow.vertical, + blur: shadow.blur, + } + } +} + +impl From for ComputedDropShadow { + #[cfg(not(feature = "gecko"))] + #[inline] + fn from(shadow: DropShadow) -> Self { + match shadow {} + } + + #[cfg(feature = "gecko")] + #[inline] + fn from(shadow: DropShadow) -> Self { + ComputedDropShadow { + color: shadow.color.into(), + horizontal: shadow.horizontal, + vertical: shadow.vertical, + blur: shadow.blur, + } + } +} + +impl Animatable for DropShadow { + #[cfg(not(feature = "gecko"))] + #[inline] + fn add_weighted(&self, _other: &Self, _self_portion: f64, _other_portion: f64) -> Result { + match *self {} + } + + #[cfg(feature = "gecko")] + #[inline] + fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result { + let color = self.color.add_weighted(&other.color, self_portion, other_portion)?; + let horizontal = self.horizontal.add_weighted(&other.horizontal, self_portion, other_portion)?; + let vertical = self.vertical.add_weighted(&other.vertical, self_portion, other_portion)?; + let blur = self.blur.add_weighted(&other.blur, self_portion, other_portion)?; + + Ok(DropShadow { + color: color, + horizontal: horizontal, + vertical: vertical, + blur: blur, + }) + } + + #[cfg(not(feature = "gecko"))] + #[inline] + fn compute_distance(&self, _other: &Self) -> Result { + match *self {} + } + + #[cfg(feature = "gecko")] + #[inline] + fn compute_distance(&self, other: &Self) -> Result { + self.compute_squared_distance(other).map(|sd| sd.sqrt()) + } + + #[cfg(not(feature = "gecko"))] + #[inline] + fn compute_squared_distance(&self, _other: &Self) -> Result { + match *self {} + } + + #[cfg(feature = "gecko")] + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result { + Ok( + self.color.compute_squared_distance(&other.color)? + + self.horizontal.compute_squared_distance(&other.horizontal)? + + self.vertical.compute_squared_distance(&other.vertical)? + + self.blur.compute_squared_distance(&other.blur)? + ) + } +} diff --git a/components/style/values/animated/mod.rs b/components/style/values/animated/mod.rs new file mode 100644 index 00000000000..944cc8c2520 --- /dev/null +++ b/components/style/values/animated/mod.rs @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Animated values. +//! +//! Some values, notably colors, cannot be interpolated directly with their +//! computed values and need yet another intermediate representation. This +//! module's raison d'ĂȘtre is to ultimately contain all these types. + +pub mod effects; diff --git a/components/style/values/computed/effects.rs b/components/style/values/computed/effects.rs new file mode 100644 index 00000000000..4fae8656bb1 --- /dev/null +++ b/components/style/values/computed/effects.rs @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Computed types for CSS values related to effects. + +use values::computed::{Angle, Number}; +#[cfg(feature = "gecko")] +use values::computed::color::Color; +use values::computed::length::Length; +use values::generics::effects::Filter as GenericFilter; +use values::generics::effects::FilterList as GenericFilterList; + +/// A computed value for the `filter` property. +pub type FilterList = GenericFilterList; + +/// A computed value for a single `filter`. +pub type Filter = GenericFilter< + Angle, + // FIXME: Should be `NumberOrPercentage`. + Number, + Length, + DropShadow, +>; + +/// A computed value for the `drop-shadow()` filter. +/// +/// Currently unsupported outside of Gecko. +#[cfg(not(feature = "gecko"))] +#[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))] +#[derive(Clone, Debug, PartialEq, ToCss)] +pub enum DropShadow {} + +/// A computed value for the `drop-shadow()` filter. +/// +/// Contrary to the canonical order from the spec, the color is serialised +/// first, like in Gecko and Webkit. +#[cfg(feature = "gecko")] +#[derive(Clone, Debug, PartialEq, ToCss)] +pub struct DropShadow { + /// Color. + pub color: Color, + /// Horizontal radius. + pub horizontal: Length, + /// Vertical radius. + pub vertical: Length, + /// Blur radius. + pub blur: Length, +} + +impl FilterList { + /// Returns the resulting opacity of this filter pipeline. + pub fn opacity(&self) -> Number { + let mut opacity = 0.; + for filter in &*self.0 { + if let GenericFilter::Opacity(factor) = *filter { + opacity *= factor + } + } + opacity + } +} diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 4930e25a050..0bd99f19470 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -28,6 +28,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::FilterList; pub use self::flex::FlexBasis; pub use self::image::{Gradient, GradientItem, ImageLayer, LineDirection, Image, ImageRect}; #[cfg(feature = "gecko")] @@ -49,6 +50,7 @@ pub mod background; pub mod basic_shape; pub mod border; pub mod color; +pub mod effects; pub mod flex; pub mod image; #[cfg(feature = "gecko")] @@ -249,6 +251,22 @@ impl ToComputedValue for Vec } } +impl ToComputedValue for Box<[T]> + where T: ToComputedValue +{ + type ComputedValue = Box<[::ComputedValue]>; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + self.iter().map(|item| item.to_computed_value(context)).collect::>().into_boxed_slice() + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + computed.iter().map(T::from_computed_value).collect::>().into_boxed_slice() + } +} + /// A marker trait to represent that the specified value is also the computed /// value. pub trait ComputedValueAsSpecified {} diff --git a/components/style/values/generics/counters.rs b/components/style/values/generics/counters.rs new file mode 100644 index 00000000000..180359af764 --- /dev/null +++ b/components/style/values/generics/counters.rs @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Generic types for counters-related CSS values. + +use std::fmt; +use style_traits::{HasViewportPercentage, ToCss}; +use values::CustomIdent; + +/// A generic value for the `counter-increment` property. +/// +/// Keyword `none` is represented by an empty slice. +#[derive(Clone, Debug, PartialEq, ToComputedValue)] +pub struct CounterIncrement(Box<[(CustomIdent, Integer)]); + +impl CounterIncrement { + /// Returns `none`. + #[inline] + pub fn none() -> Self { + CounterIncrement(vec![].into_boxed_slice()) + } +} + +impl HasViewportPercentage for CounterIncrement { + #[inline] fn has_viewport_percentage(&self) -> bool { false } +} + +impl ToCss for CounterIncrement +where + I: ToCss, +{ + #[inline] + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + if self.0.is_empty() { + return dest.write_str("none"); + } + for (&(ref name, ref value), i) in self.0.iter().enumerate() { + if i != 0 { + dest.write_str(" ")?; + } + name.to_css(dest)?; + dest.write_str(" ")?; + value.to_css(dest)?; + } + Ok(()) + } +} diff --git a/components/style/values/generics/effects.rs b/components/style/values/generics/effects.rs new file mode 100644 index 00000000000..504b8544e3d --- /dev/null +++ b/components/style/values/generics/effects.rs @@ -0,0 +1,92 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Generic types for CSS values related to effects. + +use std::fmt; +use style_traits::ToCss; +#[cfg(feature = "gecko")] +use values::specified::url::SpecifiedUrl; + +/// A generic value for the `filter` property. +/// +/// Keyword `none` is represented by an empty slice. +#[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))] +#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToComputedValue)] +pub struct FilterList(pub Box<[Filter]>); + +/// A generic value for a single `filter`. +#[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))] +#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToComputedValue, ToCss)] +pub enum Filter { + /// `blur()` + #[css(function)] + Blur(Length), + /// `brightness()` + #[css(function)] + Brightness(Factor), + /// `contrast()` + #[css(function)] + Contrast(Factor), + /// `grayscale()` + #[css(function)] + Grayscale(Factor), + /// `hue-rotate()` + #[css(function)] + HueRotate(Angle), + /// `invert()` + #[css(function)] + Invert(Factor), + /// `opacity()` + #[css(function)] + Opacity(Factor), + /// `saturate()` + #[css(function)] + Saturate(Factor), + /// `sepia()` + #[css(function)] + Sepia(Factor), + /// `drop-shadow(...)` + #[css(function)] + DropShadow(DropShadow), + /// `` + #[cfg(feature = "gecko")] + Url(SpecifiedUrl), +} + +impl FilterList { + /// Returns `none`. + #[inline] + pub fn none() -> Self { + FilterList(vec![].into_boxed_slice()) + } +} + +impl From> for FilterList { + #[inline] + fn from(vec: Vec) -> Self { + FilterList(vec.into_boxed_slice()) + } +} + +impl ToCss for FilterList +where + F: ToCss, +{ + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write + { + if let Some((first, rest)) = self.0.split_first() { + first.to_css(dest)?; + for filter in rest { + dest.write_str(" ")?; + filter.to_css(dest)?; + } + Ok(()) + } else { + dest.write_str("none") + } + } +} diff --git a/components/style/values/generics/mod.rs b/components/style/values/generics/mod.rs index 81cebbffb81..f468d61ff96 100644 --- a/components/style/values/generics/mod.rs +++ b/components/style/values/generics/mod.rs @@ -16,6 +16,7 @@ use values::specified::url::SpecifiedUrl; pub mod background; pub mod basic_shape; pub mod border; +pub mod effects; pub mod flex; #[cfg(feature = "gecko")] pub mod gecko; diff --git a/components/style/values/mod.rs b/components/style/values/mod.rs index 84cc853f2fb..65d6baa6362 100644 --- a/components/style/values/mod.rs +++ b/components/style/values/mod.rs @@ -18,6 +18,7 @@ use std::fmt::{self, Debug}; use std::hash; use style_traits::{ToCss, ParseError, StyleParseError}; +pub mod animated; pub mod computed; pub mod generics; pub mod specified; diff --git a/components/style/values/specified/effects.rs b/components/style/values/specified/effects.rs new file mode 100644 index 00000000000..9be883042f1 --- /dev/null +++ b/components/style/values/specified/effects.rs @@ -0,0 +1,244 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Specified types for CSS values related to effects. + +use cssparser::{BasicParseError, Parser, Token}; +use parser::{Parse, ParserContext}; +#[cfg(feature = "gecko")] +use std::fmt; +use style_traits::ParseError; +#[cfg(not(feature = "gecko"))] +use style_traits::StyleParseError; +#[cfg(feature = "gecko")] +use style_traits::ToCss; +use values::computed::{Context, Number as ComputedNumber, ToComputedValue}; +use values::computed::effects::DropShadow as ComputedDropShadow; +use values::generics::effects::Filter as GenericFilter; +use values::generics::effects::FilterList as GenericFilterList; +use values::specified::{Angle, Percentage}; +#[cfg(feature = "gecko")] +use values::specified::color::Color; +use values::specified::length::Length; +#[cfg(feature = "gecko")] +use values::specified::url::SpecifiedUrl; + +/// A specified value for the `filter` property. +pub type FilterList = GenericFilterList; + +/// A specified value for a single `filter`. +pub type Filter = GenericFilter; + +/// A value for the `` parts in `Filter`. +/// +/// FIXME: Should be `NumberOrPercentage`, but Gecko doesn't support that yet. +#[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))] +#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToCss)] +pub enum Factor { + /// Literal number. + Number(ComputedNumber), + /// Literal percentage. + Percentage(Percentage), +} + +/// A specified value for the `drop-shadow()` filter. +/// +/// Currently unsupported outside of Gecko. +#[cfg(not(feature = "gecko"))] +#[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))] +#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToCss)] +pub enum DropShadow {} + +/// A specified value for the `drop-shadow()` filter. +/// +/// Contrary to the canonical order from the spec, the color is serialised +/// first, like in Gecko's computed values and in all Webkit's values. +#[cfg(feature = "gecko")] +#[derive(Clone, Debug, HasViewportPercentage, PartialEq)] +pub struct DropShadow { + /// Color. + pub color: Option, + /// Horizontal radius. + pub horizontal: Length, + /// Vertical radius. + pub vertical: Length, + /// Blur radius. + pub blur: Option, +} + +impl Parse for FilterList { + #[inline] + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't> + ) -> Result> { + let mut filters = vec![]; + while let Ok(filter) = input.try(|i| Filter::parse(context, i)) { + filters.push(filter); + } + if filters.is_empty() { + input.expect_ident_matching("none")?; + } + Ok(GenericFilterList(filters.into_boxed_slice())) + } +} + +impl Parse for Filter { + #[inline] + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't> + ) -> Result> { + #[cfg(feature = "gecko")] + { + if let Ok(url) = input.try(|i| SpecifiedUrl::parse(context, i)) { + return Ok(GenericFilter::Url(url)); + } + } + let function = input.expect_function()?; + input.parse_nested_block(|i| { + try_match_ident_ignore_ascii_case! { function, + "blur" => Ok(GenericFilter::Blur(Length::parse_non_negative(context, i)?)), + "brightness" => Ok(GenericFilter::Brightness(Factor::parse(context, i)?)), + "contrast" => Ok(GenericFilter::Contrast(Factor::parse(context, i)?)), + "grayscale" => Ok(GenericFilter::Grayscale(Factor::parse(context, i)?)), + "hue-rotate" => Ok(GenericFilter::HueRotate(Angle::parse(context, i)?)), + "invert" => Ok(GenericFilter::Invert(Factor::parse(context, i)?)), + "opacity" => Ok(GenericFilter::Opacity(Factor::parse(context, i)?)), + "saturate" => Ok(GenericFilter::Saturate(Factor::parse(context, i)?)), + "sepia" => Ok(GenericFilter::Sepia(Factor::parse(context, i)?)), + "drop-shadow" => Ok(GenericFilter::DropShadow(DropShadow::parse(context, i)?)), + } + }) + } +} + +impl Parse for Factor { + #[inline] + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't> + ) -> Result> { + match input.next()? { + Token::Number { value, .. } if value.is_sign_positive() => { + Ok(Factor::Number(value)) + }, + Token::Percentage { unit_value, .. } if unit_value.is_sign_positive() => { + Ok(Factor::Percentage(Percentage(unit_value))) + }, + other => Err(BasicParseError::UnexpectedToken(other).into()), + } + } +} + +impl ToComputedValue for Factor { + /// This should actually be `ComputedNumberOrPercentage`, but layout uses + /// `computed::effects::FilterList` directly in `StackingContext`. + type ComputedValue = ComputedNumber; + + #[inline] + fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue { + match *self { + Factor::Number(number) => number, + Factor::Percentage(percentage) => percentage.0, + } + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + Factor::Number(*computed) + } +} + +impl Parse for DropShadow { + #[cfg(not(feature = "gecko"))] + #[inline] + fn parse<'i, 't>( + _context: &ParserContext, + _input: &mut Parser<'i, 't> + ) -> Result> { + Err(StyleParseError::UnspecifiedError.into()) + } + + #[cfg(feature = "gecko")] + #[inline] + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't> + ) -> Result> { + let color = input.try(|i| Color::parse(context, i)).ok(); + let horizontal = Length::parse(context, input)?; + let vertical = Length::parse(context, input)?; + let blur = input.try(|i| Length::parse_non_negative(context, i)).ok(); + let color = color.or_else(|| input.try(|i| Color::parse(context, i)).ok()); + Ok(DropShadow { + color: color, + horizontal: horizontal, + vertical: vertical, + blur: blur, + }) + } +} + +impl ToComputedValue for DropShadow { + type ComputedValue = ComputedDropShadow; + + #[cfg(not(feature = "gecko"))] + #[inline] + fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue { + match *self {} + } + + #[cfg(feature = "gecko")] + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + ComputedDropShadow { + color: + self.color.as_ref().unwrap_or(&Color::CurrentColor).to_computed_value(context), + horizontal: self.horizontal.to_computed_value(context), + vertical: self.vertical.to_computed_value(context), + blur: + self.blur.as_ref().unwrap_or(&Length::zero()).to_computed_value(context), + } + } + + #[cfg(not(feature = "gecko"))] + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + match *computed {} + } + + #[cfg(feature = "gecko")] + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + DropShadow { + color: Some(ToComputedValue::from_computed_value(&computed.color)), + horizontal: ToComputedValue::from_computed_value(&computed.horizontal), + vertical: ToComputedValue::from_computed_value(&computed.vertical), + blur: Some(ToComputedValue::from_computed_value(&computed.blur)), + } + } +} + +#[cfg(feature = "gecko")] +impl ToCss for DropShadow { + #[inline] + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + if let Some(ref color) = self.color { + color.to_css(dest)?; + dest.write_str(" ")?; + } + self.horizontal.to_css(dest)?; + dest.write_str(" ")?; + self.vertical.to_css(dest)?; + if let Some(ref blur) = self.blur { + dest.write_str(" ")?; + blur.to_css(dest)?; + } + Ok(()) + } +} diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index 66adc25b542..b29c8f47129 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -704,7 +704,7 @@ impl Either { /// This is not a regression, and that's a non-standard extension anyway, so I'm /// not implementing it for now. #[derive(Clone, Copy, Debug, Default, HasViewportPercentage, PartialEq)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))] pub struct Percentage(pub CSSFloat); impl ToCss for Percentage { diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index d429376c1f6..63063163f4b 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -33,6 +33,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::FilterList; pub use self::flex::FlexBasis; #[cfg(feature = "gecko")] pub use self::gecko::ScrollSnapPoint; @@ -56,6 +57,7 @@ pub mod basic_shape; pub mod border; pub mod calc; pub mod color; +pub mod effects; pub mod flex; #[cfg(feature = "gecko")] pub mod gecko; diff --git a/components/style_traits/viewport.rs b/components/style_traits/viewport.rs index 6a353f1370c..002aa16ab51 100644 --- a/components/style_traits/viewport.rs +++ b/components/style_traits/viewport.rs @@ -41,7 +41,10 @@ macro_rules! no_viewport_percentage { no_viewport_percentage!(bool, f32); -impl HasViewportPercentage for Box { +impl HasViewportPercentage for Box +where + T: ?Sized + HasViewportPercentage +{ #[inline] fn has_viewport_percentage(&self) -> bool { (**self).has_viewport_percentage() @@ -69,6 +72,13 @@ impl HasViewportPercentage for Vec { } } +impl HasViewportPercentage for [T] { + #[inline] + fn has_viewport_percentage(&self) -> bool { + self.iter().any(T::has_viewport_percentage) + } +} + /// A set of viewport descriptors: /// /// https://drafts.csswg.org/css-device-adapt/#viewport-desc