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