style/values/specified/
effects.rs

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