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, Parser};
35use style_traits::{ParseError, StyleParseErrorKind};
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(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
95        Ok(Self(FilterFactor(
96            NonNegativeNumberOrPercentage::parse(context, input)?.0,
97        )))
98    }
99}
100
101type ZeroToOneFactor = ZeroToOne<FilterFactor>;
102impl ZeroToOneFactor {
103    fn one() -> Self {
104        Self(FilterFactor(NumberOrPercentage::Number(Number::new(1.))))
105    }
106}
107
108impl Parse for ZeroToOneFactor {
109    #[inline]
110    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
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(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
122        let mut lengths = None;
123        let mut color = None;
124        let mut inset = false;
125
126        loop {
127            if !inset
128                && input
129                    .try_parse(|input| input.expect_ident_matching("inset"))
130                    .is_ok()
131            {
132                inset = true;
133                continue;
134            }
135            if lengths.is_none() {
136                let value = input.try_parse::<_, _, ParseError>(|i| {
137                    let horizontal = Length::parse(context, i)?;
138                    let vertical = Length::parse(context, i)?;
139                    let (blur, spread) =
140                        match i.try_parse(|i| Length::parse_non_negative(context, i)) {
141                            Ok(blur) => {
142                                let spread = i.try_parse(|i| Length::parse(context, i)).ok();
143                                (Some(blur.into()), spread)
144                            },
145                            Err(_) => (None, None),
146                        };
147                    Ok((horizontal, vertical, blur, spread))
148                });
149                if let Ok(value) = value {
150                    lengths = Some(value);
151                    continue;
152                }
153            }
154            if color.is_none() {
155                if let Ok(value) = input.try_parse(|i| Color::parse(context, i)) {
156                    color = Some(value);
157                    continue;
158                }
159            }
160            break;
161        }
162
163        let lengths = lengths.ok_or(ParseError::custom(StyleParseErrorKind::UnspecifiedError))?;
164        Ok(BoxShadow {
165            base: SimpleShadow {
166                color,
167                horizontal: lengths.0,
168                vertical: lengths.1,
169                blur: lengths.2,
170            },
171            spread: lengths.3,
172            inset,
173        })
174    }
175}
176
177impl ToComputedValue for BoxShadow {
178    type ComputedValue = ComputedBoxShadow;
179
180    #[inline]
181    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
182        ComputedBoxShadow {
183            base: self.base.to_computed_value(context),
184            spread: self
185                .spread
186                .as_ref()
187                .unwrap_or(&Length::zero())
188                .to_computed_value(context),
189            inset: self.inset,
190        }
191    }
192
193    #[inline]
194    fn from_computed_value(computed: &ComputedBoxShadow) -> Self {
195        BoxShadow {
196            base: ToComputedValue::from_computed_value(&computed.base),
197            spread: Some(ToComputedValue::from_computed_value(&computed.spread)),
198            inset: computed.inset,
199        }
200    }
201}
202
203// We need this for converting the specified Filter into computed Filter without Context (for
204// some FFIs in glue.rs). This can fail because in some circumstances, we still need Context to
205// determine the computed value.
206impl Filter {
207    /// Generate the ComputedFilter without Context.
208    pub fn to_computed_value_without_context(&self) -> Result<ComputedFilter, ()> {
209        match *self {
210            Filter::Blur(ref length) => Ok(ComputedFilter::Blur(ComputedNonNegativeLength::new(
211                length.0.to_computed_pixel_length_without_context()?,
212            ))),
213            Filter::Brightness(ref factor) => {
214                Ok(ComputedFilter::Brightness(ComputedNonNegativeNumber::from(
215                    factor.0.to_computed_value_without_context()?.value(),
216                )))
217            },
218            Filter::Contrast(ref factor) => {
219                Ok(ComputedFilter::Contrast(ComputedNonNegativeNumber::from(
220                    factor.0.to_computed_value_without_context()?.value(),
221                )))
222            },
223            Filter::Grayscale(ref factor) => {
224                Ok(ComputedFilter::Grayscale(ComputedZeroToOneNumber::from(
225                    factor.0.to_computed_value_without_context()?.value(),
226                )))
227            },
228            Filter::HueRotate(ref angle) => Ok(ComputedFilter::HueRotate(
229                ComputedAngle::from_degrees(angle.degrees().ok_or(())?),
230            )),
231            Filter::Invert(ref factor) => {
232                Ok(ComputedFilter::Invert(ComputedZeroToOneNumber::from(
233                    factor.0.to_computed_value_without_context()?.value(),
234                )))
235            },
236            Filter::Opacity(ref factor) => {
237                Ok(ComputedFilter::Opacity(ComputedZeroToOneNumber::from(
238                    factor.0.to_computed_value_without_context()?.value(),
239                )))
240            },
241            Filter::Saturate(ref factor) => {
242                Ok(ComputedFilter::Saturate(ComputedNonNegativeNumber::from(
243                    factor.0.to_computed_value_without_context()?.value(),
244                )))
245            },
246            Filter::Sepia(ref factor) => Ok(ComputedFilter::Sepia(ComputedZeroToOneNumber::from(
247                factor.0.to_computed_value_without_context()?.value(),
248            ))),
249            Filter::DropShadow(ref shadow) => {
250                if cfg!(feature = "gecko") {
251                    let color = shadow
252                        .color
253                        .as_ref()
254                        .unwrap_or(&Color::currentcolor())
255                        .to_computed_color(None)?;
256
257                    let horizontal = ComputedCSSPixelLength::new(
258                        shadow
259                            .horizontal
260                            .to_computed_pixel_length_without_context()?,
261                    );
262                    let vertical = ComputedCSSPixelLength::new(
263                        shadow.vertical.to_computed_pixel_length_without_context()?,
264                    );
265                    let blur = ComputedNonNegativeLength::new(
266                        shadow
267                            .blur
268                            .as_ref()
269                            .unwrap_or(&NonNegativeLength::zero())
270                            .0
271                            .to_computed_pixel_length_without_context()?,
272                    );
273
274                    Ok(ComputedFilter::DropShadow(ComputedSimpleShadow {
275                        color,
276                        horizontal,
277                        vertical,
278                        blur,
279                    }))
280                } else {
281                    Err(())
282                }
283            },
284            #[cfg(feature = "gecko")]
285            Filter::Url(ref url) => Ok(ComputedFilter::Url(ComputedUrl(url.clone()))),
286            #[cfg(feature = "servo")]
287            Filter::Url(_) => Err(()),
288        }
289    }
290}
291
292impl Parse for Filter {
293    #[inline]
294    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
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 function = match input.expect_function() {
302            Ok(f) => f.clone(),
303            Err(e) => return Err(e.into()),
304        };
305        input.parse_nested_block(|i| {
306            match_ignore_ascii_case! { &*function,
307                "blur" => Ok(GenericFilter::Blur(
308                    i.try_parse(|i| NonNegativeLength::parse(context, i))
309                     .unwrap_or(Zero::zero()),
310                )),
311                "brightness" => Ok(GenericFilter::Brightness(
312                    i.try_parse(|i| NonNegativeFactor::parse(context, i))
313                     .unwrap_or(NonNegativeFactor::one()),
314                )),
315                "contrast" => Ok(GenericFilter::Contrast(
316                    i.try_parse(|i| NonNegativeFactor::parse(context, i))
317                     .unwrap_or(NonNegativeFactor::one()),
318                )),
319                "grayscale" => {
320                    // Values of amount over 100% are allowed but UAs must clamp the values to 1.
321                    // https://drafts.fxtf.org/filter-effects/#funcdef-filter-grayscale
322                    Ok(GenericFilter::Grayscale(
323                        i.try_parse(|i| ZeroToOneFactor::parse(context, i))
324                         .unwrap_or(ZeroToOneFactor::one()),
325                    ))
326                },
327                "hue-rotate" => {
328                    // We allow unitless zero here, see:
329                    // https://github.com/w3c/fxtf-drafts/issues/228
330                    Ok(GenericFilter::HueRotate(
331                        i.try_parse(|i| Angle::parse_with_unitless(context, i))
332                         .unwrap_or(Zero::zero()),
333                    ))
334                },
335                "invert" => {
336                    // Values of amount over 100% are allowed but UAs must clamp the values to 1.
337                    // https://drafts.fxtf.org/filter-effects/#funcdef-filter-invert
338                    Ok(GenericFilter::Invert(
339                        i.try_parse(|i| ZeroToOneFactor::parse(context, i))
340                         .unwrap_or(ZeroToOneFactor::one()),
341                    ))
342                },
343                "opacity" => {
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-opacity
346                    Ok(GenericFilter::Opacity(
347                        i.try_parse(|i| ZeroToOneFactor::parse(context, i))
348                         .unwrap_or(ZeroToOneFactor::one()),
349                    ))
350                },
351                "saturate" => Ok(GenericFilter::Saturate(
352                    i.try_parse(|i| NonNegativeFactor::parse(context, i))
353                     .unwrap_or(NonNegativeFactor::one()),
354                )),
355                "sepia" => {
356                    // Values of amount over 100% are allowed but UAs must clamp the values to 1.
357                    // https://drafts.fxtf.org/filter-effects/#funcdef-filter-sepia
358                    Ok(GenericFilter::Sepia(
359                        i.try_parse(|i| ZeroToOneFactor::parse(context, i))
360                         .unwrap_or(ZeroToOneFactor::one()),
361                    ))
362                },
363                "drop-shadow" => Ok(GenericFilter::DropShadow(Parse::parse(context, i)?)),
364                _ => Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError)),
365            }
366        })
367    }
368}
369
370impl Parse for SimpleShadow {
371    #[inline]
372    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
373        let color = input.try_parse(|i| Color::parse(context, i)).ok();
374        let horizontal = Length::parse(context, input)?;
375        let vertical = Length::parse(context, input)?;
376        let blur = input
377            .try_parse(|i| Length::parse_non_negative(context, i))
378            .ok();
379        let blur = blur.map(NonNegative::<Length>);
380        let color = color.or_else(|| input.try_parse(|i| Color::parse(context, i)).ok());
381
382        Ok(SimpleShadow {
383            color,
384            horizontal,
385            vertical,
386            blur,
387        })
388    }
389}
390
391impl ToComputedValue for SimpleShadow {
392    type ComputedValue = ComputedSimpleShadow;
393
394    #[inline]
395    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
396        ComputedSimpleShadow {
397            color: self
398                .color
399                .as_ref()
400                .unwrap_or(&Color::currentcolor())
401                .to_computed_value(context),
402            horizontal: self.horizontal.to_computed_value(context),
403            vertical: self.vertical.to_computed_value(context),
404            blur: self
405                .blur
406                .as_ref()
407                .unwrap_or(&NonNegativeLength::zero())
408                .to_computed_value(context),
409        }
410    }
411
412    #[inline]
413    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
414        SimpleShadow {
415            color: Some(ToComputedValue::from_computed_value(&computed.color)),
416            horizontal: ToComputedValue::from_computed_value(&computed.horizontal),
417            vertical: ToComputedValue::from_computed_value(&computed.vertical),
418            blur: Some(ToComputedValue::from_computed_value(&computed.blur)),
419        }
420    }
421}