css_ast/functions/
gradient_functions.rs

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