Skip to main content

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