/* 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 https://mozilla.org/MPL/2.0/. */ //! Specified types for CSS values related to effects. use crate::parser::{Parse, ParserContext}; use crate::values::computed::effects::BoxShadow as ComputedBoxShadow; use crate::values::computed::effects::SimpleShadow as ComputedSimpleShadow; #[cfg(feature = "gecko")] use crate::values::computed::url::ComputedUrl; use crate::values::computed::Angle as ComputedAngle; use crate::values::computed::CSSPixelLength as ComputedCSSPixelLength; use crate::values::computed::Filter as ComputedFilter; use crate::values::computed::NonNegativeLength as ComputedNonNegativeLength; use crate::values::computed::NonNegativeNumber as ComputedNonNegativeNumber; use crate::values::computed::ZeroToOneNumber as ComputedZeroToOneNumber; use crate::values::computed::{Context, ToComputedValue}; use crate::values::generics::effects::BoxShadow as GenericBoxShadow; use crate::values::generics::effects::Filter as GenericFilter; use crate::values::generics::effects::SimpleShadow as GenericSimpleShadow; use crate::values::generics::NonNegative; use crate::values::specified::color::Color; use crate::values::specified::length::{Length, NonNegativeLength}; #[cfg(feature = "gecko")] use crate::values::specified::url::SpecifiedUrl; use crate::values::specified::{Angle, Number, NumberOrPercentage}; #[cfg(feature = "servo")] use crate::values::Impossible; use crate::Zero; use cssparser::{BasicParseErrorKind, Parser, Token}; use style_traits::{ParseError, StyleParseErrorKind, ValueParseErrorKind}; /// A specified value for a single shadow of the `box-shadow` property. pub type BoxShadow = GenericBoxShadow, Length, Option, Option>; /// A specified value for a single `filter`. #[cfg(feature = "gecko")] pub type SpecifiedFilter = GenericFilter< Angle, NonNegativeFactor, ZeroToOneFactor, NonNegativeLength, SimpleShadow, SpecifiedUrl, >; /// A specified value for a single `filter`. #[cfg(feature = "servo")] pub type SpecifiedFilter = GenericFilter< Angle, NonNegativeFactor, ZeroToOneFactor, NonNegativeLength, SimpleShadow, Impossible, >; pub use self::SpecifiedFilter as Filter; /// A value for the `` parts in `Filter`. #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] pub struct NonNegativeFactor(NumberOrPercentage); /// A value for the `` parts in `Filter` which clamps to one. #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] pub struct ZeroToOneFactor(NumberOrPercentage); /// Clamp the value to 1 if the value is over 100%. #[inline] fn clamp_to_one(number: NumberOrPercentage) -> NumberOrPercentage { match number { NumberOrPercentage::Percentage(percent) => { NumberOrPercentage::Percentage(percent.clamp_to_hundred()) }, NumberOrPercentage::Number(number) => NumberOrPercentage::Number(number.clamp_to_one()), } } macro_rules! factor_impl_common { ($ty:ty, $computed_ty:ty) => { impl $ty { #[inline] fn one() -> Self { Self(NumberOrPercentage::Number(Number::new(1.))) } } impl ToComputedValue for $ty { type ComputedValue = $computed_ty; #[inline] fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { use crate::values::computed::NumberOrPercentage; match self.0.to_computed_value(context) { NumberOrPercentage::Number(n) => n.into(), NumberOrPercentage::Percentage(p) => p.0.into(), } } #[inline] fn from_computed_value(computed: &Self::ComputedValue) -> Self { Self(NumberOrPercentage::Number( ToComputedValue::from_computed_value(&computed.0), )) } } }; } factor_impl_common!(NonNegativeFactor, ComputedNonNegativeNumber); factor_impl_common!(ZeroToOneFactor, ComputedZeroToOneNumber); impl Parse for NonNegativeFactor { #[inline] fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { NumberOrPercentage::parse_non_negative(context, input).map(Self) } } impl Parse for ZeroToOneFactor { #[inline] fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { NumberOrPercentage::parse_non_negative(context, input) .map(clamp_to_one) .map(Self) } } /// 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_parse(|input| input.expect_ident_matching("inset")) .is_ok() { inset = true; continue; } } if lengths.is_none() { let value = input.try_parse::<_, _, ParseError>(|i| { let horizontal = Length::parse(context, i)?; let vertical = Length::parse(context, i)?; let (blur, spread) = match i.try_parse(|i| Length::parse_non_negative(context, i)) { Ok(blur) => { let spread = i.try_parse(|i| Length::parse(context, i)).ok(); (Some(blur.into()), 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_parse(|i| Color::parse(context, i)) { color = Some(value); continue; } } break; } let lengths = lengths.ok_or(input.new_custom_error(StyleParseErrorKind::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, } } } // We need this for converting the specified Filter into computed Filter without Context (for // some FFIs in glue.rs). This can fail because in some circumstances, we still need Context to // determine the computed value. impl Filter { /// Generate the ComputedFilter without Context. pub fn to_computed_value_without_context(&self) -> Result { match *self { Filter::Blur(ref length) => Ok(ComputedFilter::Blur(ComputedNonNegativeLength::new( length.0.to_computed_pixel_length_without_context()?, ))), Filter::Brightness(ref factor) => Ok(ComputedFilter::Brightness( ComputedNonNegativeNumber::from(factor.0.to_number().get()), )), Filter::Contrast(ref factor) => Ok(ComputedFilter::Contrast( ComputedNonNegativeNumber::from(factor.0.to_number().get()), )), Filter::Grayscale(ref factor) => Ok(ComputedFilter::Grayscale( ComputedZeroToOneNumber::from(factor.0.to_number().get()), )), Filter::HueRotate(ref angle) => Ok(ComputedFilter::HueRotate( ComputedAngle::from_degrees(angle.degrees()), )), Filter::Invert(ref factor) => Ok(ComputedFilter::Invert( ComputedZeroToOneNumber::from(factor.0.to_number().get()), )), Filter::Opacity(ref factor) => Ok(ComputedFilter::Opacity( ComputedZeroToOneNumber::from(factor.0.to_number().get()), )), Filter::Saturate(ref factor) => Ok(ComputedFilter::Saturate( ComputedNonNegativeNumber::from(factor.0.to_number().get()), )), Filter::Sepia(ref factor) => Ok(ComputedFilter::Sepia(ComputedZeroToOneNumber::from( factor.0.to_number().get(), ))), Filter::DropShadow(ref shadow) => { if cfg!(feature = "gecko") { let color = match shadow .color .as_ref() .unwrap_or(&Color::currentcolor()) .to_computed_color(None) { Some(c) => c, None => return Err(()), }; let horizontal = ComputedCSSPixelLength::new( shadow .horizontal .to_computed_pixel_length_without_context()?, ); let vertical = ComputedCSSPixelLength::new( shadow.vertical.to_computed_pixel_length_without_context()?, ); let blur = ComputedNonNegativeLength::new( shadow .blur .as_ref() .unwrap_or(&NonNegativeLength::zero()) .0 .to_computed_pixel_length_without_context()?, ); Ok(ComputedFilter::DropShadow(ComputedSimpleShadow { color, horizontal, vertical, blur, })) } else { Err(()) } }, #[cfg(feature = "gecko")] Filter::Url(ref url) => Ok(ComputedFilter::Url(ComputedUrl(url.clone()))), #[cfg(feature = "servo")] Filter::Url(_) => Err(()), } } } 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_parse(|i| SpecifiedUrl::parse(context, i)) { return Ok(GenericFilter::Url(url)); } } let location = input.current_source_location(); let function = match input.expect_function() { Ok(f) => f.clone(), Err(cssparser::BasicParseError { kind: BasicParseErrorKind::UnexpectedToken(t), location, }) => return Err(location.new_custom_error(ValueParseErrorKind::InvalidFilter(t))), Err(e) => return Err(e.into()), }; input.parse_nested_block(|i| { match_ignore_ascii_case! { &*function, "blur" => Ok(GenericFilter::Blur( i.try_parse(|i| NonNegativeLength::parse(context, i)) .unwrap_or(Zero::zero()), )), "brightness" => Ok(GenericFilter::Brightness( i.try_parse(|i| NonNegativeFactor::parse(context, i)) .unwrap_or(NonNegativeFactor::one()), )), "contrast" => Ok(GenericFilter::Contrast( i.try_parse(|i| NonNegativeFactor::parse(context, i)) .unwrap_or(NonNegativeFactor::one()), )), "grayscale" => { // Values of amount over 100% are allowed but UAs must clamp the values to 1. // https://drafts.fxtf.org/filter-effects/#funcdef-filter-grayscale Ok(GenericFilter::Grayscale( i.try_parse(|i| ZeroToOneFactor::parse(context, i)) .unwrap_or(ZeroToOneFactor::one()), )) }, "hue-rotate" => { // We allow unitless zero here, see: // https://github.com/w3c/fxtf-drafts/issues/228 Ok(GenericFilter::HueRotate( i.try_parse(|i| Angle::parse_with_unitless(context, i)) .unwrap_or(Zero::zero()), )) }, "invert" => { // Values of amount over 100% are allowed but UAs must clamp the values to 1. // https://drafts.fxtf.org/filter-effects/#funcdef-filter-invert Ok(GenericFilter::Invert( i.try_parse(|i| ZeroToOneFactor::parse(context, i)) .unwrap_or(ZeroToOneFactor::one()), )) }, "opacity" => { // Values of amount over 100% are allowed but UAs must clamp the values to 1. // https://drafts.fxtf.org/filter-effects/#funcdef-filter-opacity Ok(GenericFilter::Opacity( i.try_parse(|i| ZeroToOneFactor::parse(context, i)) .unwrap_or(ZeroToOneFactor::one()), )) }, "saturate" => Ok(GenericFilter::Saturate( i.try_parse(|i| NonNegativeFactor::parse(context, i)) .unwrap_or(NonNegativeFactor::one()), )), "sepia" => { // Values of amount over 100% are allowed but UAs must clamp the values to 1. // https://drafts.fxtf.org/filter-effects/#funcdef-filter-sepia Ok(GenericFilter::Sepia( i.try_parse(|i| ZeroToOneFactor::parse(context, i)) .unwrap_or(ZeroToOneFactor::one()), )) }, "drop-shadow" => Ok(GenericFilter::DropShadow(Parse::parse(context, i)?)), _ => Err(location.new_custom_error( ValueParseErrorKind::InvalidFilter(Token::Function(function.clone())) )), } }) } } impl Parse for SimpleShadow { #[inline] fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { let color = input.try_parse(|i| Color::parse(context, i)).ok(); let horizontal = Length::parse(context, input)?; let vertical = Length::parse(context, input)?; let blur = input .try_parse(|i| Length::parse_non_negative(context, i)) .ok(); let blur = blur.map(NonNegative::); let color = color.or_else(|| input.try_parse(|i| Color::parse(context, i)).ok()); Ok(SimpleShadow { color, horizontal, vertical, blur, }) } } impl ToComputedValue for SimpleShadow { type ComputedValue = ComputedSimpleShadow; #[inline] fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { ComputedSimpleShadow { 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(&NonNegativeLength::zero()) .to_computed_value(context), } } #[inline] fn from_computed_value(computed: &Self::ComputedValue) -> Self { SimpleShadow { 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)), } } }