Skip to main content

css_ast/functions/
filter_functions.rs

1use super::prelude::*;
2use crate::{AngleOrZero, Color, Length, NonNegative, NumberOrPercentage, Url};
3
4/// <https://drafts.csswg.org/filter-effects-1/#typedef-filter-function>
5///
6/// ```text,ignore
7/// <filter-function> = <blur()> | <brightness()> | <contrast()> | <drop-shadow()>
8///                   | <grayscale()> | <hue-rotate()> | <invert()> | <opacity()>
9///                   | <saturate()> | <sepia()>
10/// ```
11#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
13#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
14#[derive(csskit_derives::NodeWithMetadata)]
15pub enum FilterFunction<'a> {
16	#[atom(CssAtomSet::Blur)]
17	Blur(BlurFunction),
18	#[atom(CssAtomSet::Brightness)]
19	Brightness(BrightnessFunction),
20	#[atom(CssAtomSet::Contrast)]
21	Contrast(ContrastFunction),
22	#[atom(CssAtomSet::DropShadow)]
23	DropShadow(DropShadowFunction<'a>),
24	#[atom(CssAtomSet::Grayscale)]
25	Grayscale(GrayscaleFunction),
26	#[atom(CssAtomSet::HueRotate)]
27	HueRotate(HueRotateFunction),
28	#[atom(CssAtomSet::Invert)]
29	Invert(InvertFunction),
30	#[atom(CssAtomSet::Opacity)]
31	Opacity(OpacityFunction),
32	#[atom(CssAtomSet::Saturate)]
33	Saturate(SaturateFunction),
34	#[atom(CssAtomSet::Sepia)]
35	Sepia(SepiaFunction),
36}
37
38/// <https://drafts.csswg.org/filter-effects-1/#typedef-filter-value-list>
39///
40/// ```text,ignore
41/// <filter-value-list> = [ <filter-function> | <url> ]+
42/// ```
43#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
45#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
46#[derive(csskit_derives::NodeWithMetadata)]
47pub struct FilterValueList<'a>(pub Vec<'a, FilterValue<'a>>);
48
49/// A single item in a `<filter-value-list>`: either a filter function or a URL.
50#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
52#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
53#[derive(csskit_derives::NodeWithMetadata)]
54pub enum FilterValue<'a> {
55	FilterFunction(FilterFunction<'a>),
56	Url(Url),
57}
58
59/// `blur( <length [0,∞]>? )`
60#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
61#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
62#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
63#[derive(csskit_derives::NodeWithMetadata)]
64pub struct BlurFunction {
65	#[atom(CssAtomSet::Blur)]
66	pub name: T![Function],
67	pub radius: Option<NonNegative<Length>>,
68	pub close: T![')'],
69}
70
71/// `brightness( <number [0,∞]> | <percentage [0,∞]> )`
72#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
73#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
74#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
75#[derive(csskit_derives::NodeWithMetadata)]
76pub struct BrightnessFunction {
77	#[atom(CssAtomSet::Brightness)]
78	pub name: T![Function],
79	pub value: Option<NumberOrPercentage>,
80	pub close: T![')'],
81}
82
83/// `contrast( <number [0,∞]> | <percentage [0,∞]> )`
84#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
85#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
86#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
87#[derive(csskit_derives::NodeWithMetadata)]
88pub struct ContrastFunction {
89	#[atom(CssAtomSet::Contrast)]
90	pub name: T![Function],
91	pub value: Option<NumberOrPercentage>,
92	pub close: T![')'],
93}
94
95/// `drop-shadow( <color>? && <length>{2,3} )`
96///
97/// Note: we parse color first (before offsets) as a simplification of the `&&` grammar.
98#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
100#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
101#[derive(csskit_derives::NodeWithMetadata)]
102pub struct DropShadowFunction<'a> {
103	#[atom(CssAtomSet::DropShadow)]
104	pub name: T![Function],
105	pub color: Option<Color<'a>>,
106	pub offset_x: Length,
107	pub offset_y: Length,
108	pub blur_radius: Option<NonNegative<Length>>,
109	pub close: T![')'],
110}
111
112/// `grayscale( <number [0,1]> | <percentage [0,100]> )`
113#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
114#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
115#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
116#[derive(csskit_derives::NodeWithMetadata)]
117pub struct GrayscaleFunction {
118	#[atom(CssAtomSet::Grayscale)]
119	pub name: T![Function],
120	pub value: Option<NumberOrPercentage>,
121	pub close: T![')'],
122}
123
124/// `hue-rotate( <angle> | <zero> )`
125#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
126#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
127#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
128#[derive(csskit_derives::NodeWithMetadata)]
129pub struct HueRotateFunction {
130	#[atom(CssAtomSet::HueRotate)]
131	pub name: T![Function],
132	pub angle: Option<AngleOrZero>,
133	pub close: T![')'],
134}
135
136/// `invert( <number [0,1]> | <percentage [0,100]> )`
137#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
138#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
139#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
140#[derive(csskit_derives::NodeWithMetadata)]
141pub struct InvertFunction {
142	#[atom(CssAtomSet::Invert)]
143	pub name: T![Function],
144	pub value: Option<NumberOrPercentage>,
145	pub close: T![')'],
146}
147
148/// `opacity( <number [0,1]> | <percentage [0,100]> )`
149#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
150#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
151#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
152#[derive(csskit_derives::NodeWithMetadata)]
153pub struct OpacityFunction {
154	#[atom(CssAtomSet::Opacity)]
155	pub name: T![Function],
156	pub value: Option<NumberOrPercentage>,
157	pub close: T![')'],
158}
159
160/// `saturate( <number [0,∞]> | <percentage [0,∞]> )`
161#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
162#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
163#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
164#[derive(csskit_derives::NodeWithMetadata)]
165pub struct SaturateFunction {
166	#[atom(CssAtomSet::Saturate)]
167	pub name: T![Function],
168	pub value: Option<NumberOrPercentage>,
169	pub close: T![')'],
170}
171
172/// `sepia( <number [0,1]> | <percentage [0,100]> )`
173#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
174#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
175#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
176#[derive(csskit_derives::NodeWithMetadata)]
177pub struct SepiaFunction {
178	#[atom(CssAtomSet::Sepia)]
179	pub name: T![Function],
180	pub value: Option<NumberOrPercentage>,
181	pub close: T![')'],
182}
183
184#[cfg(test)]
185mod tests {
186	use super::*;
187	use crate::CssAtomSet;
188	use css_parse::{assert_parse, assert_parse_error};
189
190	#[test]
191	fn size_test() {
192		assert_eq!(std::mem::size_of::<FilterFunction>(), 96);
193	}
194
195	#[test]
196	fn test_filter_function_parses() {
197		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "blur()");
198		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "blur(5px)");
199		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "brightness(0.5)");
200		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "brightness(50%)");
201		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "contrast(2)");
202		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "contrast(200%)");
203		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "drop-shadow(2px 4px)");
204		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "drop-shadow(2px 4px 3px)");
205		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "drop-shadow(red 2px 4px)");
206		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "drop-shadow(red 2px 4px 5px)");
207		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "grayscale(1)");
208		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "grayscale(100%)");
209		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "hue-rotate(90deg)");
210		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "hue-rotate(0)");
211		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "invert(0.5)");
212		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "invert(50%)");
213		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "opacity(0.5)");
214		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "opacity(50%)");
215		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "saturate(2)");
216		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "saturate(200%)");
217		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "sepia(0.5)");
218		assert_parse!(CssAtomSet::ATOMS, FilterFunction, "sepia(50%)");
219	}
220
221	#[test]
222	fn test_filter_function_errors() {
223		assert_parse_error!(CssAtomSet::ATOMS, FilterFunction, "none");
224		assert_parse_error!(CssAtomSet::ATOMS, FilterFunction, "foo()");
225		assert_parse_error!(CssAtomSet::ATOMS, FilterFunction, "blur(-5px)");
226	}
227
228	#[test]
229	fn test_filter_value_list_parses() {
230		assert_parse!(CssAtomSet::ATOMS, FilterValueList, "blur(5px)");
231		assert_parse!(CssAtomSet::ATOMS, FilterValueList, "blur(5px)brightness(0.5)");
232		assert_parse!(CssAtomSet::ATOMS, FilterValueList, "blur(5px)contrast(200%)grayscale(0.5)");
233		assert_parse!(CssAtomSet::ATOMS, FilterValueList, "url(\"filter.svg\")");
234	}
235
236	#[test]
237	fn test_filter_value_list_errors() {
238		assert_parse_error!(CssAtomSet::ATOMS, FilterValueList, "none");
239	}
240}