Skip to main content

css_ast/functions/
gradient_functions.rs

1use css_parse::Box;
2
3use super::prelude::*;
4use crate::{CalcableValue, LengthPercentage};
5
6/// <https://drafts.csswg.org/css-images-3/#typedef-gradient>
7/// ```text-ignore,
8/// <gradient> = <linear-gradient()> | <repeating-linear-gradient()> | <radial-gradient()> | <repeating-radial-gradient()>
9/// ```
10#[node]
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 Gradient<'a> {
16	#[atom(CssAtomSet::LinearGradient)]
17	LinearGradientFunction(Box<'a, LinearGradientFunction<'a>>),
18	#[atom(CssAtomSet::RepeatingLinearGradient)]
19	RepeatingLinearGradientFunction(Box<'a, RepeatingLinearGradientFunction<'a>>),
20	#[atom(CssAtomSet::RadialGradient)]
21	RadialGradientFunction(Box<'a, RadialGradientFunction<'a>>),
22	#[atom(CssAtomSet::RepeatingRadialGradient)]
23	RepeatingRadialGradientFunction(Box<'a, RepeatingRadialGradientFunction<'a>>),
24}
25
26/// <https://drafts.csswg.org/css-images-3/#funcdef-linear-gradient>
27/// ```text,ignore
28/// <linear-gradient()> = linear-gradient( [ <linear-gradient-syntax> ] )
29/// <linear-gradient-syntax> = [ <angle> | <zero> | to <side-or-corner> ]? , <color-stop-list>
30/// <side-or-corner> = [left | right] || [top | bottom]
31/// ```
32#[node]
33#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
35#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
36#[derive(csskit_derives::NodeWithMetadata)]
37pub struct LinearGradientFunction<'a> {
38	#[atom(CssAtomSet::LinearGradient)]
39	pub name: T![Function],
40	pub params: LinearGradientFunctionParams<'a>,
41	#[semantic_eq(skip)]
42	pub close: T![')'],
43}
44
45#[node]
46#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
48#[derive(csskit_derives::NodeWithMetadata)]
49pub struct LinearGradientFunctionParams<'a>(
50	Option<LinearDirection<'a>>,
51	#[semantic_eq(skip)] Option<T![,]>,
52	CommaSeparated<'a, ColorStopOrHint<'a>>,
53);
54
55/// <https://drafts.csswg.org/css-images-3/#funcdef-repeating-linear-gradient>
56/// ```text,ignore
57/// <repeating-linear-gradient()> = repeating-linear-gradient( [ <linear-gradient-syntax> ] )
58/// <linear-gradient-syntax> = [ <angle> | <zero> | to <side-or-corner> ]? , <color-stop-list>
59/// <side-or-corner> = [left | right] || [top | bottom]
60/// ```
61#[node]
62#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
64#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
65#[derive(csskit_derives::NodeWithMetadata)]
66pub struct RepeatingLinearGradientFunction<'a> {
67	#[atom(CssAtomSet::RepeatingLinearGradient)]
68	pub name: T![Function],
69	pub params: RepeatingLinearGradientFunctionParams<'a>,
70	#[semantic_eq(skip)]
71	pub close: T![')'],
72}
73
74#[node]
75#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
76#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
77#[derive(csskit_derives::NodeWithMetadata)]
78pub struct RepeatingLinearGradientFunctionParams<'a>(
79	Option<LinearDirection<'a>>,
80	#[semantic_eq(skip)] Option<T![,]>,
81	CommaSeparated<'a, ColorStopOrHint<'a>>,
82);
83
84/// <https://drafts.csswg.org/css-images-3/#funcdef-radial-gradient>
85/// ```text,ignore
86/// <radial-gradient()> = radial-gradient( [ <radial-gradient-syntax> ] )
87/// <radial-gradient-syntax> = [ <radial-shape> || <radial-size> ]? [ at <position> ]? , <color-stop-list>
88/// <radial-size> = <radial-extent> | <length [0,∞]> | <length-percentage [0,∞]>{2}
89/// <radial-extent> = closest-corner | closest-side | farthest-corner | farthest-side
90/// <radial-shape> = circle | ellipse
91/// ```
92#[node]
93#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
94#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
95#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
96#[derive(csskit_derives::NodeWithMetadata)]
97pub struct RadialGradientFunction<'a> {
98	#[atom(CssAtomSet::RadialGradient)]
99	pub name: T![Function],
100	pub params: RadialGradientFunctionParams<'a>,
101	#[semantic_eq(skip)]
102	pub close: T![')'],
103}
104
105#[node]
106#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
107#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
108#[derive(csskit_derives::NodeWithMetadata)]
109pub struct RadialGradientFunctionParams<'a>(
110	Option<RadialSize<'a>>,
111	Option<RadialShape>,
112	Option<T![Ident]>,
113	Option<Position<'a>>,
114	#[semantic_eq(skip)] Option<T![,]>,
115	CommaSeparated<'a, ColorStopOrHint<'a>>,
116);
117
118/// <https://drafts.csswg.org/css-images-3/#funcdef-repeating-radial-gradient>
119/// ```text,ignore
120/// <repeating-radial-gradient()> = repeating-radial-gradient( [ <radial-gradient-syntax> ] )
121/// <radial-gradient-syntax> = [ <radial-shape> || <radial-size> ]? [ at <position> ]? , <color-stop-list>
122/// <radial-size> = <radial-extent> | <length [0,∞]> | <length-percentage [0,∞]>{2}
123/// <radial-extent> = closest-corner | closest-side | farthest-corner | farthest-side
124/// <radial-shape> = circle | ellipse
125/// ```
126#[node]
127#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
128#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
129#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
130#[derive(csskit_derives::NodeWithMetadata)]
131pub struct RepeatingRadialGradientFunction<'a> {
132	#[atom(CssAtomSet::RepeatingRadialGradient)]
133	pub name: T![Function],
134	pub params: RepeatingRadialGradientFunctionParams<'a>,
135	#[semantic_eq(skip)]
136	pub close: T![')'],
137}
138
139#[node]
140#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
141#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
142#[derive(csskit_derives::NodeWithMetadata)]
143pub struct RepeatingRadialGradientFunctionParams<'a>(
144	Option<RadialSize<'a>>,
145	Option<RadialShape>,
146	Option<T![Ident]>,
147	Option<Position<'a>>,
148	#[semantic_eq(skip)] Option<T![,]>,
149	CommaSeparated<'a, ColorStopOrHint<'a>>,
150);
151
152#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
153#[node]
154#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
155#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
156#[cfg_attr(feature = "visitable", visit(skip))]
157#[derive(csskit_derives::NodeWithMetadata)]
158pub enum NamedDirection {
159	#[atom(CssAtomSet::Bottom)]
160	Bottom(T![Ident]),
161	#[atom(CssAtomSet::Top)]
162	Top(T![Ident]),
163	#[atom(CssAtomSet::Left)]
164	Left(T![Ident]),
165	#[atom(CssAtomSet::Right)]
166	Right(T![Ident]),
167}
168
169#[node]
170#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
171#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
172#[derive(csskit_derives::NodeWithMetadata)]
173pub enum LinearDirection<'a> {
174	Angle(CalcableValue<'a, Angle>),
175	Named(#[atom(CssAtomSet::To)] T![Ident], NamedDirection, Option<NamedDirection>),
176}
177
178/// <https://drafts.csswg.org/css-images-4/#typedef-radial-size>
179/// <https://drafts.csswg.org/css-images-4/#radial-size>
180///
181/// ```text,ignore
182/// <radial-size> = <radial-extent>{1,2} | <length-percentage [0,∞]>{1,2}
183/// <radial-extent> = closest-corner | closest-side | farthest-corner | farthest-side
184/// ```
185#[node]
186#[derive(Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
187#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
188#[derive(csskit_derives::NodeWithMetadata)]
189pub enum RadialSize<'a> {
190	Extent(RadialExtent, Option<RadialExtent>),
191	Circular(CalcableValue<'a, LengthPercentage>),
192	Elliptical(CalcableValue<'a, LengthPercentage>, CalcableValue<'a, LengthPercentage>),
193}
194
195/// <https://drafts.csswg.org/css-images-3/#typedef-radial-extent>
196///
197/// ```text,ignore
198/// <radial-extent> = closest-corner | closest-side | farthest-corner | farthest-side
199/// ```
200#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
201#[node]
202#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
203#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
204#[cfg_attr(feature = "visitable", visit(skip))]
205#[derive(csskit_derives::NodeWithMetadata)]
206pub enum RadialExtent {
207	#[atom(CssAtomSet::ClosestCorner)]
208	ClosestCorner(T![Ident]),
209	#[atom(CssAtomSet::ClosestSide)]
210	ClosestSide(T![Ident]),
211	#[atom(CssAtomSet::FarthestCorner)]
212	FarthestCorner(T![Ident]),
213	#[atom(CssAtomSet::FarthestSide)]
214	FarthestSide(T![Ident]),
215}
216
217impl<'a> Parse<'a> for RadialSize<'a> {
218	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
219	where
220		I: Iterator<Item = Cursor> + Clone,
221	{
222		if let Some(extent) = p.parse_if_peek::<RadialExtent>()? {
223			let second = p.parse_if_peek::<RadialExtent>()?;
224			return Ok(RadialSize::Extent(extent, second));
225		}
226		let first = p.parse::<CalcableValue<LengthPercentage>>()?;
227		if let Some(second) = p.parse_if_peek::<CalcableValue<LengthPercentage>>()? {
228			return Ok(Self::Elliptical(first, second));
229		}
230		Ok(Self::Circular(first))
231	}
232}
233
234/// <https://drafts.csswg.org/css-images-3/#typedef-radial-shape>
235///
236/// ```text,ignore
237/// <radial-shape> = circle | ellipse
238/// ```
239#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
240#[node]
241#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
242#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
243#[cfg_attr(feature = "visitable", visit(skip))]
244#[derive(csskit_derives::NodeWithMetadata)]
245pub enum RadialShape {
246	#[atom(CssAtomSet::Circle)]
247	Circle(T![Ident]),
248	#[atom(CssAtomSet::Ellipse)]
249	Ellipse(T![Ident]),
250}
251
252#[node]
253#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
254#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
255#[derive(csskit_derives::NodeWithMetadata)]
256pub enum ColorStopOrHint<'a> {
257	Hint(CalcableValue<'a, LengthPercentage>),
258	Stop(Color<'a>, Option<CalcableValue<'a, LengthPercentage>>),
259}
260
261#[cfg(test)]
262mod tests {
263	use super::*;
264	use crate::CssAtomSet;
265	use css_parse::assert_parse;
266
267	#[test]
268	fn test_writes() {
269		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(to bottom,yellow,blue)");
270		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(yellow,blue)");
271		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(to bottom,#fff,#fff 85%,#e6e6e6)");
272		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(45deg,#808080 25%,transparent 25%)");
273		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(to right,transparent,red 20%,red 80%,transparent)");
274		assert_parse!(
275			CssAtomSet::ATOMS,
276			Gradient,
277			"radial-gradient(closest-corner circle,rgba(1,65,255,0.4),rgba(1,65,255,0))"
278		);
279	}
280
281	#[test]
282	fn test_substitution() {
283		// calc()/var() permitted in angle, stop-position, and radial-size slots.
284		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(calc(45deg),red,blue)");
285		assert_parse!(CssAtomSet::ATOMS, Gradient, "linear-gradient(red calc(20% + 5px),blue)");
286		assert_parse!(CssAtomSet::ATOMS, Gradient, "radial-gradient(calc(50px) circle,red,blue)");
287	}
288}