1use crate::error::{ParserError, PrinterError};
4use crate::printer::Printer;
5use crate::targets::Browsers;
6use crate::traits::{FallbackValues, Parse, ToCss, Zero};
7use crate::values::color::ColorFallbackKind;
8use crate::values::{angle::Angle, color::CssColor, length::Length, percentage::NumberOrPercentage, url::Url};
9use cssparser::*;
10use smallvec::SmallVec;
11
12#[derive(Debug, Clone, PartialEq)]
14#[cfg_attr(
15 feature = "serde",
16 derive(serde::Serialize, serde::Deserialize),
17 serde(tag = "type", content = "value", rename_all = "kebab-case")
18)]
19pub enum Filter<'i> {
20 Blur(Length),
22 Brightness(NumberOrPercentage),
24 Contrast(NumberOrPercentage),
26 Grayscale(NumberOrPercentage),
28 HueRotate(Angle),
30 Invert(NumberOrPercentage),
32 Opacity(NumberOrPercentage),
34 Saturate(NumberOrPercentage),
36 Sepia(NumberOrPercentage),
38 DropShadow(DropShadow),
40 #[cfg_attr(feature = "serde", serde(borrow))]
42 Url(Url<'i>),
43}
44
45impl<'i> Parse<'i> for Filter<'i> {
46 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
47 if let Ok(url) = input.try_parse(Url::parse) {
48 return Ok(Filter::Url(url));
49 }
50
51 let location = input.current_source_location();
52 let function = input.expect_function()?;
53 match_ignore_ascii_case! { &function,
54 "blur" => {
55 input.parse_nested_block(|input| {
56 Ok(Filter::Blur(input.try_parse(Length::parse).unwrap_or(Length::zero())))
57 })
58 },
59 "brightness" => {
60 input.parse_nested_block(|input| {
61 Ok(Filter::Brightness(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))
62 })
63 },
64 "contrast" => {
65 input.parse_nested_block(|input| {
66 Ok(Filter::Contrast(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))
67 })
68 },
69 "grayscale" => {
70 input.parse_nested_block(|input| {
71 Ok(Filter::Grayscale(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))
72 })
73 },
74 "hue-rotate" => {
75 input.parse_nested_block(|input| {
76 Ok(Filter::HueRotate(input.try_parse(Angle::parse).unwrap_or(Angle::Deg(0.0))))
77 })
78 },
79 "invert" => {
80 input.parse_nested_block(|input| {
81 Ok(Filter::Invert(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))
82 })
83 },
84 "opacity" => {
85 input.parse_nested_block(|input| {
86 Ok(Filter::Opacity(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))
87 })
88 },
89 "saturate" => {
90 input.parse_nested_block(|input| {
91 Ok(Filter::Saturate(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))
92 })
93 },
94 "sepia" => {
95 input.parse_nested_block(|input| {
96 Ok(Filter::Sepia(input.try_parse(NumberOrPercentage::parse).unwrap_or(NumberOrPercentage::Number(1.0))))
97 })
98 },
99 "drop-shadow" => {
100 input.parse_nested_block(|input| {
101 Ok(Filter::DropShadow(DropShadow::parse(input)?))
102 })
103 },
104 _ => Err(location.new_unexpected_token_error(
105 cssparser::Token::Ident(function.clone())
106 ))
107 }
108 }
109}
110
111impl<'i> ToCss for Filter<'i> {
112 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
113 where
114 W: std::fmt::Write,
115 {
116 match self {
117 Filter::Blur(val) => {
118 dest.write_str("blur(")?;
119 if *val != Length::zero() {
120 val.to_css(dest)?;
121 }
122 dest.write_char(')')
123 }
124 Filter::Brightness(val) => {
125 dest.write_str("brightness(")?;
126 let v: f32 = val.into();
127 if v != 1.0 {
128 val.to_css(dest)?;
129 }
130 dest.write_char(')')
131 }
132 Filter::Contrast(val) => {
133 dest.write_str("contrast(")?;
134 let v: f32 = val.into();
135 if v != 1.0 {
136 val.to_css(dest)?;
137 }
138 dest.write_char(')')
139 }
140 Filter::Grayscale(val) => {
141 dest.write_str("grayscale(")?;
142 let v: f32 = val.into();
143 if v != 1.0 {
144 val.to_css(dest)?;
145 }
146 dest.write_char(')')
147 }
148 Filter::HueRotate(val) => {
149 dest.write_str("hue-rotate(")?;
150 if !val.is_zero() {
151 val.to_css(dest)?;
152 }
153 dest.write_char(')')
154 }
155 Filter::Invert(val) => {
156 dest.write_str("invert(")?;
157 let v: f32 = val.into();
158 if v != 1.0 {
159 val.to_css(dest)?;
160 }
161 dest.write_char(')')
162 }
163 Filter::Opacity(val) => {
164 dest.write_str("opacity(")?;
165 let v: f32 = val.into();
166 if v != 1.0 {
167 val.to_css(dest)?;
168 }
169 dest.write_char(')')
170 }
171 Filter::Saturate(val) => {
172 dest.write_str("saturate(")?;
173 let v: f32 = val.into();
174 if v != 1.0 {
175 val.to_css(dest)?;
176 }
177 dest.write_char(')')
178 }
179 Filter::Sepia(val) => {
180 dest.write_str("sepia(")?;
181 let v: f32 = val.into();
182 if v != 1.0 {
183 val.to_css(dest)?;
184 }
185 dest.write_char(')')
186 }
187 Filter::DropShadow(val) => {
188 dest.write_str("drop-shadow(")?;
189 val.to_css(dest)?;
190 dest.write_char(')')
191 }
192 Filter::Url(url) => url.to_css(dest),
193 }
194 }
195}
196
197impl<'i> Filter<'i> {
198 fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
199 match self {
200 Filter::DropShadow(shadow) => Filter::DropShadow(shadow.get_fallback(kind)),
201 _ => self.clone(),
202 }
203 }
204}
205
206#[derive(Debug, Clone, PartialEq)]
208#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
209pub struct DropShadow {
210 pub color: CssColor,
212 pub x_offset: Length,
214 pub y_offset: Length,
216 pub blur: Length,
218}
219
220impl<'i> Parse<'i> for DropShadow {
221 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
222 let mut color = None;
223 let mut lengths = None;
224
225 loop {
226 if lengths.is_none() {
227 let value = input.try_parse::<_, _, ParseError<ParserError<'i>>>(|input| {
228 let horizontal = Length::parse(input)?;
229 let vertical = Length::parse(input)?;
230 let blur = input.try_parse(Length::parse).unwrap_or(Length::zero());
231 Ok((horizontal, vertical, blur))
232 });
233
234 if let Ok(value) = value {
235 lengths = Some(value);
236 continue;
237 }
238 }
239
240 if color.is_none() {
241 if let Ok(value) = input.try_parse(CssColor::parse) {
242 color = Some(value);
243 continue;
244 }
245 }
246
247 break;
248 }
249
250 let lengths = lengths.ok_or(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))?;
251 Ok(DropShadow {
252 color: color.unwrap_or(CssColor::current_color()),
253 x_offset: lengths.0,
254 y_offset: lengths.1,
255 blur: lengths.2,
256 })
257 }
258}
259
260impl ToCss for DropShadow {
261 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
262 where
263 W: std::fmt::Write,
264 {
265 self.x_offset.to_css(dest)?;
266 dest.write_char(' ')?;
267 self.y_offset.to_css(dest)?;
268
269 if self.blur != Length::zero() {
270 dest.write_char(' ')?;
271 self.blur.to_css(dest)?;
272 }
273
274 if self.color != CssColor::current_color() {
275 dest.write_char(' ')?;
276 self.color.to_css(dest)?;
277 }
278
279 Ok(())
280 }
281}
282
283impl DropShadow {
284 fn get_fallback(&self, kind: ColorFallbackKind) -> DropShadow {
285 DropShadow {
286 color: self.color.get_fallback(kind),
287 ..self.clone()
288 }
289 }
290}
291
292#[derive(Debug, Clone, PartialEq)]
295#[cfg_attr(
296 feature = "serde",
297 derive(serde::Serialize, serde::Deserialize),
298 serde(tag = "type", content = "value", rename_all = "kebab-case")
299)]
300pub enum FilterList<'i> {
301 None,
303 #[cfg_attr(feature = "serde", serde(borrow))]
305 Filters(SmallVec<[Filter<'i>; 1]>),
306}
307
308impl<'i> Parse<'i> for FilterList<'i> {
309 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
310 if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
311 return Ok(FilterList::None);
312 }
313
314 let mut filters = SmallVec::new();
315 while let Ok(filter) = input.try_parse(Filter::parse) {
316 filters.push(filter);
317 }
318
319 Ok(FilterList::Filters(filters))
320 }
321}
322
323impl<'i> ToCss for FilterList<'i> {
324 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
325 where
326 W: std::fmt::Write,
327 {
328 match self {
329 FilterList::None => dest.write_str("none"),
330 FilterList::Filters(filters) => {
331 let mut first = true;
332 for filter in filters {
333 if first {
334 first = false;
335 } else {
336 dest.whitespace()?;
337 }
338 filter.to_css(dest)?;
339 }
340 Ok(())
341 }
342 }
343 }
344}
345
346impl<'i> FallbackValues for FilterList<'i> {
347 fn get_fallbacks(&mut self, targets: Browsers) -> Vec<Self> {
348 let mut res = Vec::new();
349 let mut fallbacks = ColorFallbackKind::empty();
350 if let FilterList::Filters(filters) = self {
351 for shadow in filters.iter() {
352 if let Filter::DropShadow(shadow) = &shadow {
353 fallbacks |= shadow.color.get_necessary_fallbacks(targets);
354 }
355 }
356
357 if fallbacks.contains(ColorFallbackKind::RGB) {
358 res.push(FilterList::Filters(
359 filters
360 .iter()
361 .map(|filter| filter.get_fallback(ColorFallbackKind::RGB))
362 .collect(),
363 ));
364 }
365
366 if fallbacks.contains(ColorFallbackKind::P3) {
367 res.push(FilterList::Filters(
368 filters
369 .iter()
370 .map(|filter| filter.get_fallback(ColorFallbackKind::P3))
371 .collect(),
372 ));
373 }
374
375 if fallbacks.contains(ColorFallbackKind::LAB) {
376 for filter in filters.iter_mut() {
377 *filter = filter.get_fallback(ColorFallbackKind::LAB);
378 }
379 }
380 }
381
382 res
383 }
384}