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 http://mozilla.org/MPL/2.0/. */
4 
5 //! Specified types for CSS values related to effects.
6 
7 use cssparser::{self, Parser, Token, BasicParseErrorKind};
8 use parser::{Parse, ParserContext};
9 use style_traits::{ParseError, StyleParseErrorKind, ValueParseErrorKind};
10 #[cfg(not(feature = "gecko"))]
11 use values::Impossible;
12 use values::computed::{Context, NonNegativeNumber as ComputedNonNegativeNumber, ToComputedValue};
13 use values::computed::effects::BoxShadow as ComputedBoxShadow;
14 use values::computed::effects::SimpleShadow as ComputedSimpleShadow;
15 use values::generics::NonNegative;
16 use values::generics::effects::BoxShadow as GenericBoxShadow;
17 use values::generics::effects::Filter as GenericFilter;
18 use values::generics::effects::SimpleShadow as GenericSimpleShadow;
19 use values::specified::{Angle, NumberOrPercentage};
20 use values::specified::color::RGBAColor;
21 use values::specified::length::{Length, NonNegativeLength};
22 #[cfg(feature = "gecko")]
23 use values::specified::url::SpecifiedUrl;
24 
25 /// A specified value for a single shadow of the `box-shadow` property.
26 pub type BoxShadow = GenericBoxShadow<Option<RGBAColor>, Length,
27                                       Option<NonNegativeLength>, Option<Length>>;
28 
29 /// A specified value for a single `filter`.
30 #[cfg(feature = "gecko")]
31 pub type Filter = GenericFilter<Angle, Factor, NonNegativeLength, SimpleShadow>;
32 
33 /// A specified value for a single `filter`.
34 #[cfg(not(feature = "gecko"))]
35 pub type Filter = GenericFilter<Angle, Factor, NonNegativeLength, Impossible>;
36 
37 /// A value for the `<factor>` parts in `Filter`.
38 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
39 pub struct Factor(NumberOrPercentage);
40 
41 impl Factor {
42     /// Parse this factor but clamp to one if the value is over 100%.
43     #[inline]
parse_with_clamping_to_one<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't> ) -> Result<Self, ParseError<'i>>44     pub fn parse_with_clamping_to_one<'i, 't>(
45         context: &ParserContext,
46         input: &mut Parser<'i, 't>
47     ) -> Result<Self, ParseError<'i>> {
48         Factor::parse(context, input).map(|v| v.clamp_to_one())
49     }
50 
51     /// Clamp the value to 1 if the value is over 100%.
52     #[inline]
clamp_to_one(self) -> Self53     fn clamp_to_one(self) -> Self {
54         match self.0 {
55             NumberOrPercentage::Percentage(percent) => {
56                 Factor(NumberOrPercentage::Percentage(percent.clamp_to_hundred()))
57             },
58             NumberOrPercentage::Number(number) => {
59                 Factor(NumberOrPercentage::Number(number.clamp_to_one()))
60             }
61         }
62     }
63 }
64 
65 impl Parse for Factor {
66     #[inline]
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't> ) -> Result<Self, ParseError<'i>>67     fn parse<'i, 't>(
68         context: &ParserContext,
69         input: &mut Parser<'i, 't>
70     ) -> Result<Self, ParseError<'i>> {
71         NumberOrPercentage::parse_non_negative(context, input).map(Factor)
72     }
73 }
74 
75 impl ToComputedValue for Factor {
76     type ComputedValue = ComputedNonNegativeNumber;
77 
78     #[inline]
to_computed_value(&self, context: &Context) -> Self::ComputedValue79     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
80         use values::computed::NumberOrPercentage;
81         match self.0.to_computed_value(context) {
82             NumberOrPercentage::Number(n) => n.into(),
83             NumberOrPercentage::Percentage(p) => p.0.into(),
84         }
85     }
86 
87     #[inline]
from_computed_value(computed: &Self::ComputedValue) -> Self88     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
89         Factor(NumberOrPercentage::Number(ToComputedValue::from_computed_value(&computed.0)))
90     }
91 }
92 
93 /// A specified value for the `drop-shadow()` filter.
94 pub type SimpleShadow = GenericSimpleShadow<Option<RGBAColor>, Length, Option<NonNegativeLength>>;
95 
96 impl Parse for BoxShadow {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>97     fn parse<'i, 't>(
98         context: &ParserContext,
99         input: &mut Parser<'i, 't>,
100     ) -> Result<Self, ParseError<'i>> {
101         let mut lengths = None;
102         let mut color = None;
103         let mut inset = false;
104 
105         loop {
106             if !inset {
107                 if input.try(|input| input.expect_ident_matching("inset")).is_ok() {
108                     inset = true;
109                     continue;
110                 }
111             }
112             if lengths.is_none() {
113                 let value = input.try::<_, _, ParseError>(|i| {
114                     let horizontal = Length::parse(context, i)?;
115                     let vertical = Length::parse(context, i)?;
116                     let (blur, spread) = match i.try::<_, _, ParseError>(|i| Length::parse_non_negative(context, i)) {
117                         Ok(blur) => {
118                             let spread = i.try(|i| Length::parse(context, i)).ok();
119                             (Some(blur.into()), spread)
120                         },
121                         Err(_) => (None, None),
122                     };
123                     Ok((horizontal, vertical, blur, spread))
124                 });
125                 if let Ok(value) = value {
126                     lengths = Some(value);
127                     continue;
128                 }
129             }
130             if color.is_none() {
131                 if let Ok(value) = input.try(|i| RGBAColor::parse(context, i)) {
132                     color = Some(value);
133                     continue;
134                 }
135             }
136             break;
137         }
138 
139         let lengths = lengths.ok_or(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))?;
140         Ok(BoxShadow {
141             base: SimpleShadow {
142                 color: color,
143                 horizontal: lengths.0,
144                 vertical: lengths.1,
145                 blur: lengths.2,
146             },
147             spread: lengths.3,
148             inset: inset,
149         })
150     }
151 }
152 
153 impl ToComputedValue for BoxShadow {
154     type ComputedValue = ComputedBoxShadow;
155 
156     #[inline]
to_computed_value(&self, context: &Context) -> Self::ComputedValue157     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
158         ComputedBoxShadow {
159             base: self.base.to_computed_value(context),
160             spread: self.spread.as_ref().unwrap_or(&Length::zero()).to_computed_value(context),
161             inset: self.inset,
162         }
163     }
164 
165     #[inline]
from_computed_value(computed: &ComputedBoxShadow) -> Self166     fn from_computed_value(computed: &ComputedBoxShadow) -> Self {
167         BoxShadow {
168             base: ToComputedValue::from_computed_value(&computed.base),
169             spread: Some(ToComputedValue::from_computed_value(&computed.spread)),
170             inset: computed.inset,
171         }
172     }
173 }
174 
175 impl Parse for Filter {
176     #[inline]
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't> ) -> Result<Self, ParseError<'i>>177     fn parse<'i, 't>(
178         context: &ParserContext,
179         input: &mut Parser<'i, 't>
180     ) -> Result<Self, ParseError<'i>> {
181         #[cfg(feature = "gecko")]
182         {
183             if let Ok(url) = input.try(|i| SpecifiedUrl::parse(context, i)) {
184                 return Ok(GenericFilter::Url(url));
185             }
186         }
187         let location = input.current_source_location();
188         let function = match input.expect_function() {
189             Ok(f) => f.clone(),
190             Err(cssparser::BasicParseError {
191                 kind: BasicParseErrorKind::UnexpectedToken(t),
192                 location,
193             }) => {
194                 return Err(location.new_custom_error(ValueParseErrorKind::InvalidFilter(t)))
195             }
196             Err(e) => return Err(e.into()),
197         };
198         input.parse_nested_block(|i| {
199             match_ignore_ascii_case! { &*function,
200                 "blur" => Ok(GenericFilter::Blur((Length::parse_non_negative(context, i)?).into())),
201                 "brightness" => Ok(GenericFilter::Brightness(Factor::parse(context, i)?)),
202                 "contrast" => Ok(GenericFilter::Contrast(Factor::parse(context, i)?)),
203                 "grayscale" => {
204                     // Values of amount over 100% are allowed but UAs must clamp the values to 1.
205                     // https://drafts.fxtf.org/filter-effects/#funcdef-filter-grayscale
206                     Ok(GenericFilter::Grayscale(Factor::parse_with_clamping_to_one(context, i)?))
207                 },
208                 "hue-rotate" => {
209                     // We allow unitless zero here, see:
210                     // https://github.com/w3c/fxtf-drafts/issues/228
211                     Ok(GenericFilter::HueRotate(Angle::parse_with_unitless(context, i)?))
212                 },
213                 "invert" => {
214                     // Values of amount over 100% are allowed but UAs must clamp the values to 1.
215                     // https://drafts.fxtf.org/filter-effects/#funcdef-filter-invert
216                     Ok(GenericFilter::Invert(Factor::parse_with_clamping_to_one(context, i)?))
217                 },
218                 "opacity" => {
219                     // Values of amount over 100% are allowed but UAs must clamp the values to 1.
220                     // https://drafts.fxtf.org/filter-effects/#funcdef-filter-opacity
221                     Ok(GenericFilter::Opacity(Factor::parse_with_clamping_to_one(context, i)?))
222                 },
223                 "saturate" => Ok(GenericFilter::Saturate(Factor::parse(context, i)?)),
224                 "sepia" => {
225                     // Values of amount over 100% are allowed but UAs must clamp the values to 1.
226                     // https://drafts.fxtf.org/filter-effects/#funcdef-filter-sepia
227                     Ok(GenericFilter::Sepia(Factor::parse_with_clamping_to_one(context, i)?))
228                 },
229                 "drop-shadow" => Ok(GenericFilter::DropShadow(Parse::parse(context, i)?)),
230                 _ => Err(location.new_custom_error(
231                     ValueParseErrorKind::InvalidFilter(Token::Function(function.clone()))
232                 )),
233             }
234         })
235     }
236 }
237 
238 impl Parse for SimpleShadow {
239     #[inline]
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't> ) -> Result<Self, ParseError<'i>>240     fn parse<'i, 't>(
241         context: &ParserContext,
242         input: &mut Parser<'i, 't>
243     ) -> Result<Self, ParseError<'i>> {
244         let color = input.try(|i| RGBAColor::parse(context, i)).ok();
245         let horizontal = Length::parse(context, input)?;
246         let vertical = Length::parse(context, input)?;
247         let blur = input.try(|i| Length::parse_non_negative(context, i)).ok();
248         let color = color.or_else(|| input.try(|i| RGBAColor::parse(context, i)).ok());
249         Ok(SimpleShadow {
250             color: color,
251             horizontal: horizontal,
252             vertical: vertical,
253             blur: blur.map(NonNegative::<Length>),
254         })
255     }
256 }
257 
258 impl ToComputedValue for SimpleShadow {
259     type ComputedValue = ComputedSimpleShadow;
260 
261     #[inline]
to_computed_value(&self, context: &Context) -> Self::ComputedValue262     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
263         ComputedSimpleShadow {
264             color: self.color.to_computed_value(context),
265             horizontal: self.horizontal.to_computed_value(context),
266             vertical: self.vertical.to_computed_value(context),
267             blur:
268                 self.blur.as_ref().unwrap_or(&NonNegativeLength::zero()).to_computed_value(context),
269         }
270     }
271 
272     #[inline]
from_computed_value(computed: &Self::ComputedValue) -> Self273     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
274         SimpleShadow {
275             color: ToComputedValue::from_computed_value(&computed.color),
276             horizontal: ToComputedValue::from_computed_value(&computed.horizontal),
277             vertical: ToComputedValue::from_computed_value(&computed.vertical),
278             blur: Some(ToComputedValue::from_computed_value(&computed.blur)),
279         }
280     }
281 }
282