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::Zero;
8use crate::derives::*;
9use crate::parser::{Parse, ParserContext};
10#[cfg(feature = "servo")]
11use crate::values::Impossible;
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::NumberOrPercentage as ComputedNumberOrPercentage;
19use crate::values::computed::ZeroToOneNumber as ComputedZeroToOneNumber;
20use crate::values::computed::effects::BoxShadow as ComputedBoxShadow;
21use crate::values::computed::effects::SimpleShadow as ComputedSimpleShadow;
22#[cfg(feature = "gecko")]
23use crate::values::computed::url::ComputedUrl;
24use crate::values::computed::{Context, ToComputedValue};
25use crate::values::generics::effects::BoxShadow as GenericBoxShadow;
26use crate::values::generics::effects::Filter as GenericFilter;
27use crate::values::generics::effects::SimpleShadow as GenericSimpleShadow;
28use crate::values::generics::{NonNegative, ZeroToOne};
29use crate::values::specified::color::Color;
30use crate::values::specified::length::{Length, NonNegativeLength};
31#[cfg(feature = "gecko")]
32use crate::values::specified::url::SpecifiedUrl;
33use crate::values::specified::{Angle, NonNegativeNumberOrPercentage, Number, NumberOrPercentage};
34use cssparser::{Parser, match_ignore_ascii_case};
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                && let Ok(value) = input.try_parse(|i| Color::parse(context, i))
156            {
157                color = Some(value);
158                continue;
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                let color = shadow
251                    .color
252                    .as_ref()
253                    .unwrap_or(&Color::currentcolor())
254                    .to_computed_color(None)?;
255
256                let horizontal = ComputedCSSPixelLength::new(
257                    shadow
258                        .horizontal
259                        .to_computed_pixel_length_without_context()?,
260                );
261                let vertical = ComputedCSSPixelLength::new(
262                    shadow.vertical.to_computed_pixel_length_without_context()?,
263                );
264                let blur = ComputedNonNegativeLength::new(
265                    shadow
266                        .blur
267                        .as_ref()
268                        .unwrap_or(&NonNegativeLength::zero())
269                        .0
270                        .to_computed_pixel_length_without_context()?,
271                );
272
273                Ok(ComputedFilter::DropShadow(ComputedSimpleShadow {
274                    color,
275                    horizontal,
276                    vertical,
277                    blur,
278                }))
279            },
280            #[cfg(feature = "gecko")]
281            Filter::Url(ref url) => Ok(ComputedFilter::Url(ComputedUrl(url.clone()))),
282            #[cfg(feature = "servo")]
283            Filter::Url(_) => Err(()),
284        }
285    }
286}
287
288impl Parse for Filter {
289    #[inline]
290    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
291        #[cfg(feature = "gecko")]
292        {
293            if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) {
294                return Ok(GenericFilter::Url(url));
295            }
296        }
297        let function = {
298            let f = input.expect_function()?;
299            f.clone()
300        };
301        input.parse_nested_block(|i| {
302            match_ignore_ascii_case! { &*function,
303                "blur" => Ok(GenericFilter::Blur(
304                    i.try_parse(|i| NonNegativeLength::parse(context, i))
305                     .unwrap_or(Zero::zero()),
306                )),
307                "brightness" => Ok(GenericFilter::Brightness(
308                    i.try_parse(|i| NonNegativeFactor::parse(context, i))
309                     .unwrap_or(NonNegativeFactor::one()),
310                )),
311                "contrast" => Ok(GenericFilter::Contrast(
312                    i.try_parse(|i| NonNegativeFactor::parse(context, i))
313                     .unwrap_or(NonNegativeFactor::one()),
314                )),
315                "grayscale" => {
316                    // Values of amount over 100% are allowed but UAs must clamp the values to 1.
317                    // https://drafts.fxtf.org/filter-effects/#funcdef-filter-grayscale
318                    Ok(GenericFilter::Grayscale(
319                        i.try_parse(|i| ZeroToOneFactor::parse(context, i))
320                         .unwrap_or(ZeroToOneFactor::one()),
321                    ))
322                },
323                "hue-rotate" => {
324                    // We allow unitless zero here, see:
325                    // https://github.com/w3c/fxtf-drafts/issues/228
326                    Ok(GenericFilter::HueRotate(
327                        i.try_parse(|i| Angle::parse_with_unitless(context, i))
328                         .unwrap_or(Zero::zero()),
329                    ))
330                },
331                "invert" => {
332                    // Values of amount over 100% are allowed but UAs must clamp the values to 1.
333                    // https://drafts.fxtf.org/filter-effects/#funcdef-filter-invert
334                    Ok(GenericFilter::Invert(
335                        i.try_parse(|i| ZeroToOneFactor::parse(context, i))
336                         .unwrap_or(ZeroToOneFactor::one()),
337                    ))
338                },
339                "opacity" => {
340                    // Values of amount over 100% are allowed but UAs must clamp the values to 1.
341                    // https://drafts.fxtf.org/filter-effects/#funcdef-filter-opacity
342                    Ok(GenericFilter::Opacity(
343                        i.try_parse(|i| ZeroToOneFactor::parse(context, i))
344                         .unwrap_or(ZeroToOneFactor::one()),
345                    ))
346                },
347                "saturate" => Ok(GenericFilter::Saturate(
348                    i.try_parse(|i| NonNegativeFactor::parse(context, i))
349                     .unwrap_or(NonNegativeFactor::one()),
350                )),
351                "sepia" => {
352                    // Values of amount over 100% are allowed but UAs must clamp the values to 1.
353                    // https://drafts.fxtf.org/filter-effects/#funcdef-filter-sepia
354                    Ok(GenericFilter::Sepia(
355                        i.try_parse(|i| ZeroToOneFactor::parse(context, i))
356                         .unwrap_or(ZeroToOneFactor::one()),
357                    ))
358                },
359                "drop-shadow" => Ok(GenericFilter::DropShadow(Parse::parse(context, i)?)),
360                _ => Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError)),
361            }
362        })
363    }
364}
365
366impl Parse for SimpleShadow {
367    #[inline]
368    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
369        let color = input.try_parse(|i| Color::parse(context, i)).ok();
370        let horizontal = Length::parse(context, input)?;
371        let vertical = Length::parse(context, input)?;
372        let blur = input
373            .try_parse(|i| Length::parse_non_negative(context, i))
374            .ok();
375        let blur = blur.map(NonNegative::<Length>);
376        let color = color.or_else(|| input.try_parse(|i| Color::parse(context, i)).ok());
377
378        Ok(SimpleShadow {
379            color,
380            horizontal,
381            vertical,
382            blur,
383        })
384    }
385}
386
387impl ToComputedValue for SimpleShadow {
388    type ComputedValue = ComputedSimpleShadow;
389
390    #[inline]
391    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
392        ComputedSimpleShadow {
393            color: self
394                .color
395                .as_ref()
396                .unwrap_or(&Color::currentcolor())
397                .to_computed_value(context),
398            horizontal: self.horizontal.to_computed_value(context),
399            vertical: self.vertical.to_computed_value(context),
400            blur: self
401                .blur
402                .as_ref()
403                .unwrap_or(&NonNegativeLength::zero())
404                .to_computed_value(context),
405        }
406    }
407
408    #[inline]
409    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
410        SimpleShadow {
411            color: Some(ToComputedValue::from_computed_value(&computed.color)),
412            horizontal: ToComputedValue::from_computed_value(&computed.horizontal),
413            vertical: ToComputedValue::from_computed_value(&computed.vertical),
414            blur: Some(ToComputedValue::from_computed_value(&computed.blur)),
415        }
416    }
417}
418
419/// https://drafts.fxtf.org/compositing/#propdef-mix-blend-mode
420#[allow(missing_docs)]
421#[derive(
422    Clone,
423    Copy,
424    Debug,
425    Deserialize,
426    Eq,
427    FromPrimitive,
428    Hash,
429    MallocSizeOf,
430    Parse,
431    PartialEq,
432    Serialize,
433    SpecifiedValueInfo,
434    ToComputedValue,
435    ToCss,
436    ToResolvedValue,
437    ToShmem,
438    ToTyped,
439)]
440#[repr(u8)]
441pub enum Blend {
442    Normal,
443    Multiply,
444    Screen,
445    Overlay,
446    Darken,
447    Lighten,
448    ColorDodge,
449    ColorBurn,
450    HardLight,
451    SoftLight,
452    Difference,
453    Exclusion,
454    Hue,
455    Saturation,
456    Color,
457    Luminosity,
458    PlusLighter,
459}