effects.rs (17010B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 4 5 //! Specified types for CSS values related to effects. 6 7 use crate::derives::*; 8 use crate::parser::{Parse, ParserContext}; 9 use crate::values::computed::effects::BoxShadow as ComputedBoxShadow; 10 use crate::values::computed::effects::SimpleShadow as ComputedSimpleShadow; 11 #[cfg(feature = "gecko")] 12 use crate::values::computed::url::ComputedUrl; 13 use crate::values::computed::Angle as ComputedAngle; 14 use crate::values::computed::CSSPixelLength as ComputedCSSPixelLength; 15 use crate::values::computed::Filter as ComputedFilter; 16 use crate::values::computed::NonNegativeLength as ComputedNonNegativeLength; 17 use crate::values::computed::NonNegativeNumber as ComputedNonNegativeNumber; 18 use crate::values::computed::Number as ComputedNumber; 19 use crate::values::computed::ZeroToOneNumber as ComputedZeroToOneNumber; 20 use crate::values::computed::{Context, ToComputedValue}; 21 use crate::values::generics::effects::BoxShadow as GenericBoxShadow; 22 use crate::values::generics::effects::Filter as GenericFilter; 23 use crate::values::generics::effects::SimpleShadow as GenericSimpleShadow; 24 use crate::values::generics::{NonNegative, ZeroToOne}; 25 use crate::values::specified::color::Color; 26 use crate::values::specified::length::{Length, NonNegativeLength}; 27 #[cfg(feature = "gecko")] 28 use crate::values::specified::url::SpecifiedUrl; 29 use crate::values::specified::{Angle, NonNegativeNumberOrPercentage, Number, NumberOrPercentage}; 30 #[cfg(feature = "servo")] 31 use crate::values::Impossible; 32 use crate::Zero; 33 use cssparser::{match_ignore_ascii_case, BasicParseErrorKind, Parser, Token}; 34 use style_traits::{ParseError, StyleParseErrorKind, ValueParseErrorKind}; 35 36 /// A specified value for a single shadow of the `box-shadow` property. 37 pub type BoxShadow = 38 GenericBoxShadow<Option<Color>, Length, Option<NonNegativeLength>, Option<Length>>; 39 40 /// A specified value for a single `filter`. 41 #[cfg(feature = "gecko")] 42 pub type SpecifiedFilter = GenericFilter<Angle, FilterFactor, Length, SimpleShadow, SpecifiedUrl>; 43 44 /// A specified value for a single `filter`. 45 #[cfg(feature = "servo")] 46 pub type SpecifiedFilter = GenericFilter<Angle, FilterFactor, Length, SimpleShadow, Impossible>; 47 48 pub use self::SpecifiedFilter as Filter; 49 50 /// The factor for a filter function. 51 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] 52 pub struct FilterFactor(NumberOrPercentage); 53 54 impl FilterFactor { 55 fn to_number(&self) -> Number { 56 self.0.to_number() 57 } 58 } 59 60 impl ToComputedValue for FilterFactor { 61 type ComputedValue = ComputedNumber; 62 fn to_computed_value(&self, _: &Context) -> Self::ComputedValue { 63 self.0.to_number().get() 64 } 65 fn from_computed_value(computed: &Self::ComputedValue) -> Self { 66 Self(NumberOrPercentage::Number(Number::new(*computed))) 67 } 68 } 69 70 /// Clamp the value to 1 if the value is over 100%. 71 #[inline] 72 fn clamp_to_one(number: NumberOrPercentage) -> NumberOrPercentage { 73 match number { 74 NumberOrPercentage::Percentage(percent) => { 75 NumberOrPercentage::Percentage(percent.clamp_to_hundred()) 76 }, 77 NumberOrPercentage::Number(number) => NumberOrPercentage::Number(number.clamp_to_one()), 78 } 79 } 80 81 type NonNegativeFactor = NonNegative<FilterFactor>; 82 impl NonNegativeFactor { 83 fn one() -> Self { 84 Self(FilterFactor(NumberOrPercentage::Number(Number::new(1.)))) 85 } 86 } 87 88 impl Parse for NonNegativeFactor { 89 fn parse<'i, 't>( 90 context: &ParserContext, 91 input: &mut Parser<'i, 't>, 92 ) -> Result<Self, ParseError<'i>> { 93 Ok(Self(FilterFactor( 94 NonNegativeNumberOrPercentage::parse(context, input)?.0, 95 ))) 96 } 97 } 98 99 type ZeroToOneFactor = ZeroToOne<FilterFactor>; 100 impl ZeroToOneFactor { 101 fn one() -> Self { 102 Self(FilterFactor(NumberOrPercentage::Number(Number::new(1.)))) 103 } 104 } 105 106 impl Parse for ZeroToOneFactor { 107 #[inline] 108 fn parse<'i, 't>( 109 context: &ParserContext, 110 input: &mut Parser<'i, 't>, 111 ) -> Result<Self, ParseError<'i>> { 112 Ok(Self(FilterFactor(clamp_to_one( 113 NumberOrPercentage::parse_non_negative(context, input)?, 114 )))) 115 } 116 } 117 118 /// A specified value for the `drop-shadow()` filter. 119 pub type SimpleShadow = GenericSimpleShadow<Option<Color>, Length, Option<NonNegativeLength>>; 120 121 impl Parse for BoxShadow { 122 fn parse<'i, 't>( 123 context: &ParserContext, 124 input: &mut Parser<'i, 't>, 125 ) -> Result<Self, ParseError<'i>> { 126 let mut lengths = None; 127 let mut color = None; 128 let mut inset = false; 129 130 loop { 131 if !inset { 132 if input 133 .try_parse(|input| input.expect_ident_matching("inset")) 134 .is_ok() 135 { 136 inset = true; 137 continue; 138 } 139 } 140 if lengths.is_none() { 141 let value = input.try_parse::<_, _, ParseError>(|i| { 142 let horizontal = Length::parse(context, i)?; 143 let vertical = Length::parse(context, i)?; 144 let (blur, spread) = 145 match i.try_parse(|i| Length::parse_non_negative(context, i)) { 146 Ok(blur) => { 147 let spread = i.try_parse(|i| Length::parse(context, i)).ok(); 148 (Some(blur.into()), spread) 149 }, 150 Err(_) => (None, None), 151 }; 152 Ok((horizontal, vertical, blur, spread)) 153 }); 154 if let Ok(value) = value { 155 lengths = Some(value); 156 continue; 157 } 158 } 159 if color.is_none() { 160 if let Ok(value) = input.try_parse(|i| Color::parse(context, i)) { 161 color = Some(value); 162 continue; 163 } 164 } 165 break; 166 } 167 168 let lengths = 169 lengths.ok_or(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))?; 170 Ok(BoxShadow { 171 base: SimpleShadow { 172 color: color, 173 horizontal: lengths.0, 174 vertical: lengths.1, 175 blur: lengths.2, 176 }, 177 spread: lengths.3, 178 inset: inset, 179 }) 180 } 181 } 182 183 impl ToComputedValue for BoxShadow { 184 type ComputedValue = ComputedBoxShadow; 185 186 #[inline] 187 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { 188 ComputedBoxShadow { 189 base: self.base.to_computed_value(context), 190 spread: self 191 .spread 192 .as_ref() 193 .unwrap_or(&Length::zero()) 194 .to_computed_value(context), 195 inset: self.inset, 196 } 197 } 198 199 #[inline] 200 fn from_computed_value(computed: &ComputedBoxShadow) -> Self { 201 BoxShadow { 202 base: ToComputedValue::from_computed_value(&computed.base), 203 spread: Some(ToComputedValue::from_computed_value(&computed.spread)), 204 inset: computed.inset, 205 } 206 } 207 } 208 209 // We need this for converting the specified Filter into computed Filter without Context (for 210 // some FFIs in glue.rs). This can fail because in some circumstances, we still need Context to 211 // determine the computed value. 212 impl Filter { 213 /// Generate the ComputedFilter without Context. 214 pub fn to_computed_value_without_context(&self) -> Result<ComputedFilter, ()> { 215 match *self { 216 Filter::Blur(ref length) => Ok(ComputedFilter::Blur(ComputedNonNegativeLength::new( 217 length.0.to_computed_pixel_length_without_context()?, 218 ))), 219 Filter::Brightness(ref factor) => Ok(ComputedFilter::Brightness( 220 ComputedNonNegativeNumber::from(factor.0.to_number().get()), 221 )), 222 Filter::Contrast(ref factor) => Ok(ComputedFilter::Contrast( 223 ComputedNonNegativeNumber::from(factor.0.to_number().get()), 224 )), 225 Filter::Grayscale(ref factor) => Ok(ComputedFilter::Grayscale( 226 ComputedZeroToOneNumber::from(factor.0.to_number().get()), 227 )), 228 Filter::HueRotate(ref angle) => Ok(ComputedFilter::HueRotate( 229 ComputedAngle::from_degrees(angle.degrees()), 230 )), 231 Filter::Invert(ref factor) => Ok(ComputedFilter::Invert( 232 ComputedZeroToOneNumber::from(factor.0.to_number().get()), 233 )), 234 Filter::Opacity(ref factor) => Ok(ComputedFilter::Opacity( 235 ComputedZeroToOneNumber::from(factor.0.to_number().get()), 236 )), 237 Filter::Saturate(ref factor) => Ok(ComputedFilter::Saturate( 238 ComputedNonNegativeNumber::from(factor.0.to_number().get()), 239 )), 240 Filter::Sepia(ref factor) => Ok(ComputedFilter::Sepia(ComputedZeroToOneNumber::from( 241 factor.0.to_number().get(), 242 ))), 243 Filter::DropShadow(ref shadow) => { 244 if cfg!(feature = "gecko") { 245 let color = match shadow 246 .color 247 .as_ref() 248 .unwrap_or(&Color::currentcolor()) 249 .to_computed_color(None) 250 { 251 Some(c) => c, 252 None => return Err(()), 253 }; 254 255 let horizontal = ComputedCSSPixelLength::new( 256 shadow 257 .horizontal 258 .to_computed_pixel_length_without_context()?, 259 ); 260 let vertical = ComputedCSSPixelLength::new( 261 shadow.vertical.to_computed_pixel_length_without_context()?, 262 ); 263 let blur = ComputedNonNegativeLength::new( 264 shadow 265 .blur 266 .as_ref() 267 .unwrap_or(&NonNegativeLength::zero()) 268 .0 269 .to_computed_pixel_length_without_context()?, 270 ); 271 272 Ok(ComputedFilter::DropShadow(ComputedSimpleShadow { 273 color, 274 horizontal, 275 vertical, 276 blur, 277 })) 278 } else { 279 Err(()) 280 } 281 }, 282 #[cfg(feature = "gecko")] 283 Filter::Url(ref url) => Ok(ComputedFilter::Url(ComputedUrl(url.clone()))), 284 #[cfg(feature = "servo")] 285 Filter::Url(_) => Err(()), 286 } 287 } 288 } 289 290 impl Parse for Filter { 291 #[inline] 292 fn parse<'i, 't>( 293 context: &ParserContext, 294 input: &mut Parser<'i, 't>, 295 ) -> Result<Self, ParseError<'i>> { 296 #[cfg(feature = "gecko")] 297 { 298 if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) { 299 return Ok(GenericFilter::Url(url)); 300 } 301 } 302 let location = input.current_source_location(); 303 let function = match input.expect_function() { 304 Ok(f) => f.clone(), 305 Err(cssparser::BasicParseError { 306 kind: BasicParseErrorKind::UnexpectedToken(t), 307 location, 308 }) => return Err(location.new_custom_error(ValueParseErrorKind::InvalidFilter(t))), 309 Err(e) => return Err(e.into()), 310 }; 311 input.parse_nested_block(|i| { 312 match_ignore_ascii_case! { &*function, 313 "blur" => Ok(GenericFilter::Blur( 314 i.try_parse(|i| NonNegativeLength::parse(context, i)) 315 .unwrap_or(Zero::zero()), 316 )), 317 "brightness" => Ok(GenericFilter::Brightness( 318 i.try_parse(|i| NonNegativeFactor::parse(context, i)) 319 .unwrap_or(NonNegativeFactor::one()), 320 )), 321 "contrast" => Ok(GenericFilter::Contrast( 322 i.try_parse(|i| NonNegativeFactor::parse(context, i)) 323 .unwrap_or(NonNegativeFactor::one()), 324 )), 325 "grayscale" => { 326 // Values of amount over 100% are allowed but UAs must clamp the values to 1. 327 // https://drafts.fxtf.org/filter-effects/#funcdef-filter-grayscale 328 Ok(GenericFilter::Grayscale( 329 i.try_parse(|i| ZeroToOneFactor::parse(context, i)) 330 .unwrap_or(ZeroToOneFactor::one()), 331 )) 332 }, 333 "hue-rotate" => { 334 // We allow unitless zero here, see: 335 // https://github.com/w3c/fxtf-drafts/issues/228 336 Ok(GenericFilter::HueRotate( 337 i.try_parse(|i| Angle::parse_with_unitless(context, i)) 338 .unwrap_or(Zero::zero()), 339 )) 340 }, 341 "invert" => { 342 // Values of amount over 100% are allowed but UAs must clamp the values to 1. 343 // https://drafts.fxtf.org/filter-effects/#funcdef-filter-invert 344 Ok(GenericFilter::Invert( 345 i.try_parse(|i| ZeroToOneFactor::parse(context, i)) 346 .unwrap_or(ZeroToOneFactor::one()), 347 )) 348 }, 349 "opacity" => { 350 // Values of amount over 100% are allowed but UAs must clamp the values to 1. 351 // https://drafts.fxtf.org/filter-effects/#funcdef-filter-opacity 352 Ok(GenericFilter::Opacity( 353 i.try_parse(|i| ZeroToOneFactor::parse(context, i)) 354 .unwrap_or(ZeroToOneFactor::one()), 355 )) 356 }, 357 "saturate" => Ok(GenericFilter::Saturate( 358 i.try_parse(|i| NonNegativeFactor::parse(context, i)) 359 .unwrap_or(NonNegativeFactor::one()), 360 )), 361 "sepia" => { 362 // Values of amount over 100% are allowed but UAs must clamp the values to 1. 363 // https://drafts.fxtf.org/filter-effects/#funcdef-filter-sepia 364 Ok(GenericFilter::Sepia( 365 i.try_parse(|i| ZeroToOneFactor::parse(context, i)) 366 .unwrap_or(ZeroToOneFactor::one()), 367 )) 368 }, 369 "drop-shadow" => Ok(GenericFilter::DropShadow(Parse::parse(context, i)?)), 370 _ => Err(location.new_custom_error( 371 ValueParseErrorKind::InvalidFilter(Token::Function(function.clone())) 372 )), 373 } 374 }) 375 } 376 } 377 378 impl Parse for SimpleShadow { 379 #[inline] 380 fn parse<'i, 't>( 381 context: &ParserContext, 382 input: &mut Parser<'i, 't>, 383 ) -> Result<Self, ParseError<'i>> { 384 let color = input.try_parse(|i| Color::parse(context, i)).ok(); 385 let horizontal = Length::parse(context, input)?; 386 let vertical = Length::parse(context, input)?; 387 let blur = input 388 .try_parse(|i| Length::parse_non_negative(context, i)) 389 .ok(); 390 let blur = blur.map(NonNegative::<Length>); 391 let color = color.or_else(|| input.try_parse(|i| Color::parse(context, i)).ok()); 392 393 Ok(SimpleShadow { 394 color, 395 horizontal, 396 vertical, 397 blur, 398 }) 399 } 400 } 401 402 impl ToComputedValue for SimpleShadow { 403 type ComputedValue = ComputedSimpleShadow; 404 405 #[inline] 406 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { 407 ComputedSimpleShadow { 408 color: self 409 .color 410 .as_ref() 411 .unwrap_or(&Color::currentcolor()) 412 .to_computed_value(context), 413 horizontal: self.horizontal.to_computed_value(context), 414 vertical: self.vertical.to_computed_value(context), 415 blur: self 416 .blur 417 .as_ref() 418 .unwrap_or(&NonNegativeLength::zero()) 419 .to_computed_value(context), 420 } 421 } 422 423 #[inline] 424 fn from_computed_value(computed: &Self::ComputedValue) -> Self { 425 SimpleShadow { 426 color: Some(ToComputedValue::from_computed_value(&computed.color)), 427 horizontal: ToComputedValue::from_computed_value(&computed.horizontal), 428 vertical: ToComputedValue::from_computed_value(&computed.vertical), 429 blur: Some(ToComputedValue::from_computed_value(&computed.blur)), 430 } 431 } 432 }