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::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::values::computed::effects::BoxShadow as ComputedBoxShadow;
10use crate::values::computed::effects::SimpleShadow as ComputedSimpleShadow;
11#[cfg(feature = "gecko")]
12use crate::values::computed::url::ComputedUrl;
13use crate::values::computed::Angle as ComputedAngle;
14use crate::values::computed::CSSPixelLength as ComputedCSSPixelLength;
15use crate::values::computed::Filter as ComputedFilter;
16use crate::values::computed::NonNegativeLength as ComputedNonNegativeLength;
17use crate::values::computed::NonNegativeNumber as ComputedNonNegativeNumber;
18use crate::values::computed::Number as ComputedNumber;
19use crate::values::computed::ZeroToOneNumber as ComputedZeroToOneNumber;
20use crate::values::computed::{Context, ToComputedValue};
21use crate::values::generics::effects::BoxShadow as GenericBoxShadow;
22use crate::values::generics::effects::Filter as GenericFilter;
23use crate::values::generics::effects::SimpleShadow as GenericSimpleShadow;
24use crate::values::generics::{NonNegative, ZeroToOne};
25use crate::values::specified::color::Color;
26use crate::values::specified::length::{Length, NonNegativeLength};
27#[cfg(feature = "gecko")]
28use crate::values::specified::url::SpecifiedUrl;
29use crate::values::specified::{Angle, NonNegativeNumberOrPercentage, Number, NumberOrPercentage};
30#[cfg(feature = "servo")]
31use crate::values::Impossible;
32use crate::Zero;
33use cssparser::{match_ignore_ascii_case, BasicParseErrorKind, Parser, Token};
34use style_traits::{ParseError, StyleParseErrorKind, ValueParseErrorKind};
35
36/// A specified value for a single shadow of the `box-shadow` property.
37pub type BoxShadow =
38    GenericBoxShadow<Option<Color>, Length, Option<NonNegativeLength>, Option<Length>>;
39
40/// A specified value for a single `filter`.
41#[cfg(feature = "gecko")]
42pub type SpecifiedFilter = GenericFilter<Angle, FilterFactor, Length, SimpleShadow, SpecifiedUrl>;
43
44/// A specified value for a single `filter`.
45#[cfg(feature = "servo")]
46pub type SpecifiedFilter = GenericFilter<Angle, FilterFactor, Length, SimpleShadow, Impossible>;
47
48pub use self::SpecifiedFilter as Filter;
49
50/// The factor for a filter function.
51#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
52pub struct FilterFactor(NumberOrPercentage);
53
54impl FilterFactor {
55    fn to_number(&self) -> Number {
56        self.0.to_number()
57    }
58}
59
60impl 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]
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
81type NonNegativeFactor = NonNegative<FilterFactor>;
82impl NonNegativeFactor {
83    fn one() -> Self {
84        Self(FilterFactor(NumberOrPercentage::Number(Number::new(1.))))
85    }
86}
87
88impl 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
99type ZeroToOneFactor = ZeroToOne<FilterFactor>;
100impl ZeroToOneFactor {
101    fn one() -> Self {
102        Self(FilterFactor(NumberOrPercentage::Number(Number::new(1.))))
103    }
104}
105
106impl 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.
119pub type SimpleShadow = GenericSimpleShadow<Option<Color>, Length, Option<NonNegativeLength>>;
120
121impl 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
183impl 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.
212impl 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
290impl 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
378impl 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
402impl 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}