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