Skip to main content

css_ast/functions/
filter_functions.rs

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