Skip to main content

css_ast/functions/
color_function.rs

1use super::prelude::*;
2use crate::functions::color_mix_function::ColorMixFunction;
3use crate::functions::light_dark_function::LightDarkFunction;
4use crate::functions::relative_color::RelativeColorFunction;
5use crate::{AngleOrNumber, NoneOr, NumberOrPercentage};
6use css_parse::BumpBox;
7
8#[derive(
9	Parse, Peek, IntoCursor, ToSpan, SemanticEq, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
10)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
12#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
13#[derive(csskit_derives::NodeWithMetadata)]
14pub enum ColorSpace {
15	#[atom(CssAtomSet::Srgb)]
16	Srgb(T![Ident]),
17	#[atom(CssAtomSet::SrgbLinear)]
18	SrgbLinear(T![Ident]),
19	#[atom(CssAtomSet::DisplayP3)]
20	DisplayP3(T![Ident]),
21	#[atom(CssAtomSet::A98Rgb)]
22	A98Rgb(T![Ident]),
23	#[atom(CssAtomSet::ProphotoRgb)]
24	ProphotoRgb(T![Ident]),
25	#[atom(CssAtomSet::Rec2020)]
26	Rec2020(T![Ident]),
27	#[atom(CssAtomSet::Xyz)]
28	Xyz(T![Ident]),
29	#[atom(CssAtomSet::XyzD50)]
30	XyzD50(T![Ident]),
31	#[atom(CssAtomSet::XyzD65)]
32	XyzD65(T![Ident]),
33}
34
35#[derive(IntoCursor, ToSpan, SemanticEq, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
37#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
38#[derive(csskit_derives::NodeWithMetadata)]
39pub struct CommaOrSlash(Cursor);
40
41impl<'a> Peek<'a> for CommaOrSlash {
42	const PEEK_KINDSET: KindSet = KindSet::new(&[Kind::Delim]);
43
44	#[inline(always)]
45	fn peek<I>(_: &Parser<'a, I>, c: Cursor) -> bool
46	where
47		I: Iterator<Item = Cursor> + Clone,
48	{
49		c == ',' || c == '/'
50	}
51}
52
53impl<'a> Parse<'a> for CommaOrSlash {
54	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
55	where
56		I: Iterator<Item = Cursor> + Clone,
57	{
58		if !p.peek::<Self>() {
59			Err(Diagnostic::new(p.next(), Diagnostic::unexpected))?
60		}
61		Ok(Self(p.next()))
62	}
63}
64
65/// <https://drafts.csswg.org/css-color/#typedef-color-function>
66#[derive(Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
68#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(all))]
69#[derive(csskit_derives::NodeWithMetadata)]
70pub enum ColorFunction<'a> {
71	Relative(BumpBox<'a, RelativeColorFunction<'a>>),
72	Color(BumpBox<'a, ColorFunctionColor>),
73	ColorMix(BumpBox<'a, ColorMixFunction<'a>>),
74	LightDark(BumpBox<'a, LightDarkFunction<'a>>),
75	Rgb(RgbFunction),
76	Rgba(RgbaFunction),
77	Hsl(HslFunction),
78	Hsla(HslaFunction),
79	Hwb(HwbFunction),
80	Lab(LabFunction),
81	Lch(LchFunction),
82	Oklab(OklabFunction),
83	Oklch(OklchFunction),
84}
85
86impl<'a> Parse<'a> for ColorFunction<'a> {
87	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
88	where
89		I: Iterator<Item = Cursor> + Clone,
90	{
91		// Check for relative colour syntax first: any colour function with `from` as 2nd token.
92		if p.peek::<BumpBox<RelativeColorFunction>>() {
93			return Ok(Self::Relative(p.parse()?));
94		}
95		if p.peek::<BumpBox<ColorFunctionColor>>() {
96			return Ok(Self::Color(p.parse()?));
97		}
98		if p.peek::<BumpBox<ColorMixFunction>>() {
99			return Ok(Self::ColorMix(p.parse()?));
100		}
101		if p.peek::<BumpBox<LightDarkFunction>>() {
102			return Ok(Self::LightDark(p.parse()?));
103		}
104		if p.peek::<RgbFunction>() {
105			return Ok(Self::Rgb(p.parse()?));
106		}
107		if p.peek::<RgbaFunction>() {
108			return Ok(Self::Rgba(p.parse()?));
109		}
110		if p.peek::<HslFunction>() {
111			return Ok(Self::Hsl(p.parse()?));
112		}
113		if p.peek::<HslaFunction>() {
114			return Ok(Self::Hsla(p.parse()?));
115		}
116		if p.peek::<HwbFunction>() {
117			return Ok(Self::Hwb(p.parse()?));
118		}
119		if p.peek::<LabFunction>() {
120			return Ok(Self::Lab(p.parse()?));
121		}
122		if p.peek::<LchFunction>() {
123			return Ok(Self::Lch(p.parse()?));
124		}
125		if p.peek::<OklabFunction>() {
126			return Ok(Self::Oklab(p.parse()?));
127		}
128		Ok(Self::Oklch(p.parse()?))
129	}
130}
131
132#[cfg(feature = "chromashift")]
133impl crate::ToChromashift for ColorFunction<'_> {
134	fn to_chromashift(&self) -> Option<chromashift::Color> {
135		match self {
136			Self::Relative(r) => r.to_chromashift(),
137			Self::Color(c) => c.to_chromashift(),
138			Self::ColorMix(c) => c.to_chromashift(),
139			Self::LightDark(_c) => None,
140			Self::Rgb(c) => c.to_chromashift(),
141			Self::Rgba(c) => c.to_chromashift(),
142			Self::Hsl(c) => c.to_chromashift(),
143			Self::Hsla(c) => c.to_chromashift(),
144			Self::Hwb(c) => c.to_chromashift(),
145			Self::Lab(c) => c.to_chromashift(),
146			Self::Lch(c) => c.to_chromashift(),
147			Self::Oklab(c) => c.to_chromashift(),
148			Self::Oklch(c) => c.to_chromashift(),
149		}
150	}
151}
152
153/// <https://drafts.csswg.org/css-color/#funcdef-color>
154///
155/// ```text,ignore
156/// color() = color( <colorspace-params> [ / [ <alpha-value> | none ] ]? )
157/// <colorspace-params> = [ <predefined-rgb-params> | <xyz-params>]
158/// <predefined-rgb-params> = <predefined-rgb> [ <number> | <percentage> | none ]{3}
159/// <predefined-rgb> = srgb | srgb-linear | display-p3 | a98-rgb | prophoto-rgb | rec2020
160/// <xyz-params> = <xyz-space> [ <number> | <percentage> | none ]{3}
161/// <xyz-space> = xyz | xyz-d50 | xyz-d65
162/// ```
163#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
164#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
165#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
166#[derive(csskit_derives::NodeWithMetadata)]
167pub struct ColorFunctionColor {
168	#[atom(CssAtomSet::Color)]
169	pub name: T![Function],
170	pub params: ColorFunctionColorParams,
171	pub close: T![')'],
172}
173
174#[cfg(feature = "chromashift")]
175impl crate::ToChromashift for ColorFunctionColor {
176	fn to_chromashift(&self) -> Option<chromashift::Color> {
177		use chromashift::{A98Rgb, DisplayP3, LinearRgb, ProphotoRgb, Rec2020, Srgb, XyzD50, XyzD65};
178
179		let ColorFunctionColorParams(space, c1, c2, c3, _, alpha) = &self.params;
180
181		let alpha = match alpha {
182			Some(NoneOr::None(_)) => 0.0,
183			Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
184			Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
185			None => 100.0,
186		};
187
188		// Helper to extract a channel as f64 in 0.0-1.0 range
189		let channel_unit = |c: &NoneOr<NumberOrPercentage>| -> f64 {
190			match c {
191				NoneOr::None(_) => 0.0,
192				NoneOr::Some(NumberOrPercentage::Number(n)) => n.value() as f64,
193				NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() as f64 / 100.0,
194			}
195		};
196
197		match space {
198			ColorSpace::Srgb(_) => {
199				let r = (channel_unit(c1) * 255.0).round() as u8;
200				let g = (channel_unit(c2) * 255.0).round() as u8;
201				let b = (channel_unit(c3) * 255.0).round() as u8;
202				Some(chromashift::Color::Srgb(Srgb::new(r, g, b, alpha)))
203			}
204			ColorSpace::SrgbLinear(_) => {
205				let r = channel_unit(c1);
206				let g = channel_unit(c2);
207				let b = channel_unit(c3);
208				Some(chromashift::Color::LinearRgb(LinearRgb::new(r, g, b, alpha)))
209			}
210			ColorSpace::DisplayP3(_) => {
211				let r = channel_unit(c1);
212				let g = channel_unit(c2);
213				let b = channel_unit(c3);
214				Some(chromashift::Color::DisplayP3(DisplayP3::new(r, g, b, alpha)))
215			}
216			ColorSpace::A98Rgb(_) => {
217				let r = channel_unit(c1);
218				let g = channel_unit(c2);
219				let b = channel_unit(c3);
220				Some(chromashift::Color::A98Rgb(A98Rgb::new(r, g, b, alpha)))
221			}
222			ColorSpace::ProphotoRgb(_) => {
223				let r = channel_unit(c1);
224				let g = channel_unit(c2);
225				let b = channel_unit(c3);
226				Some(chromashift::Color::ProphotoRgb(ProphotoRgb::new(r, g, b, alpha)))
227			}
228			ColorSpace::Rec2020(_) => {
229				let r = channel_unit(c1);
230				let g = channel_unit(c2);
231				let b = channel_unit(c3);
232				Some(chromashift::Color::Rec2020(Rec2020::new(r, g, b, alpha)))
233			}
234			ColorSpace::Xyz(_) | ColorSpace::XyzD65(_) => {
235				let x = channel_unit(c1) * 100.0;
236				let y = channel_unit(c2) * 100.0;
237				let z = channel_unit(c3) * 100.0;
238				Some(chromashift::Color::XyzD65(XyzD65::new(x, y, z, alpha)))
239			}
240			ColorSpace::XyzD50(_) => {
241				let x = channel_unit(c1) * 100.0;
242				let y = channel_unit(c2) * 100.0;
243				let z = channel_unit(c3) * 100.0;
244				Some(chromashift::Color::XyzD50(XyzD50::new(x, y, z, alpha)))
245			}
246		}
247	}
248}
249
250#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
251#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
252#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
253#[derive(csskit_derives::NodeWithMetadata)]
254pub struct ColorFunctionColorParams(
255	pub ColorSpace,
256	pub NoneOr<NumberOrPercentage>,
257	pub NoneOr<NumberOrPercentage>,
258	pub NoneOr<NumberOrPercentage>,
259	pub Option<T![/]>,
260	pub Option<NoneOr<NumberOrPercentage>>,
261);
262
263/// <https://drafts.csswg.org/css-color/#funcdef-rgb>
264///
265/// ```text,ignore
266/// rgb() = [ <legacy-rgb-syntax> | <modern-rgb-syntax> ]
267/// rgba() = [ <legacy-rgba-syntax> | <modern-rgba-syntax> ]
268/// <legacy-rgb-syntax> =   rgb( <percentage>#{3} , <alpha-value>? ) |
269///                   rgb( <number>#{3} , <alpha-value>? )
270/// <legacy-rgba-syntax> = rgba( <percentage>#{3} , <alpha-value>? ) |
271///                   rgba( <number>#{3} , <alpha-value>? )
272/// <modern-rgb-syntax> = rgb(
273///   [ <number> | <percentage> | none]{3}
274///   [ / [<alpha-value> | none] ]?  )
275/// <modern-rgba-syntax> = rgba(
276///   [ <number> | <percentage> | none]{3}
277///   [ / [<alpha-value> | none] ]?  )
278/// ```
279#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
280#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
281#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
282#[derive(csskit_derives::NodeWithMetadata)]
283pub struct RgbFunction {
284	#[atom(CssAtomSet::Rgb)]
285	pub name: T![Function],
286	pub params: RgbFunctionParams,
287	pub close: T![')'],
288}
289
290#[cfg(feature = "chromashift")]
291impl crate::ToChromashift for RgbFunction {
292	fn to_chromashift(&self) -> Option<chromashift::Color> {
293		self.params.to_chromashift()
294	}
295}
296
297#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
298#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
299#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
300#[derive(csskit_derives::NodeWithMetadata)]
301pub struct RgbaFunction {
302	#[atom(CssAtomSet::Rgba)]
303	pub name: T![Function],
304	pub params: RgbFunctionParams,
305	pub close: T![')'],
306}
307
308#[cfg(feature = "chromashift")]
309impl crate::ToChromashift for RgbaFunction {
310	fn to_chromashift(&self) -> Option<chromashift::Color> {
311		self.params.to_chromashift()
312	}
313}
314
315#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
316#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
317#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
318#[derive(csskit_derives::NodeWithMetadata)]
319pub struct RgbFunctionParams(
320	pub NoneOr<NumberOrPercentage>,
321	pub Option<T![,]>,
322	pub NoneOr<NumberOrPercentage>,
323	pub Option<T![,]>,
324	pub NoneOr<NumberOrPercentage>,
325	pub Option<CommaOrSlash>,
326	pub Option<NoneOr<NumberOrPercentage>>,
327);
328
329#[cfg(feature = "chromashift")]
330impl crate::ToChromashift for RgbFunctionParams {
331	fn to_chromashift(&self) -> Option<chromashift::Color> {
332		use chromashift::Srgb;
333		let Self(red, _, green, _, blue, _, alpha) = &self;
334		let alpha = match alpha {
335			Some(NoneOr::None(_)) => 0.0,
336			Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
337			Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
338			None => 100.0,
339		};
340		let red = (match red {
341			NoneOr::None(_) => 0.0,
342			NoneOr::Some(NumberOrPercentage::Number(red)) => red.value(),
343			NoneOr::Some(NumberOrPercentage::Percentage(red)) => red.value() / 100.0 * 255.0,
344		})
345		.round() as u8;
346		let green = (match green {
347			NoneOr::None(_) => 0.0,
348			NoneOr::Some(NumberOrPercentage::Number(green)) => green.value(),
349			NoneOr::Some(NumberOrPercentage::Percentage(green)) => green.value() / 100.0 * 255.0,
350		})
351		.round() as u8;
352		let blue = (match blue {
353			NoneOr::None(_) => 0.0,
354			NoneOr::Some(NumberOrPercentage::Number(blue)) => blue.value(),
355			NoneOr::Some(NumberOrPercentage::Percentage(blue)) => blue.value() / 100.0 * 255.0,
356		})
357		.round() as u8;
358		Some(chromashift::Color::Srgb(Srgb::new(red, green, blue, alpha)))
359	}
360}
361
362/// <https://drafts.csswg.org/css-color/#funcdef-hsl>
363///
364/// ```text,ignore
365/// hsl() = [ <legacy-hsl-syntax> | <modern-hsl-syntax> ]
366/// hsla() = [ <legacy-hsla-syntax> | <modern-hsla-syntax> ]
367/// <modern-hsl-syntax> = hsl(
368///     [<hue> | none]
369///     [<percentage> | <number> | none]
370///     [<percentage> | <number> | none]
371///     [ / [<alpha-value> | none] ]? )
372/// <modern-hsla-syntax> = hsla(
373///     [<hue> | none]
374///     [<percentage> | <number> | none]
375///     [<percentage> | <number> | none]
376///     [ / [<alpha-value> | none] ]? )
377/// <legacy-hsl-syntax> = hsl( <hue>, <percentage>, <percentage>, <alpha-value>? )
378/// <legacy-hsla-syntax> = hsla( <hue>, <percentage>, <percentage>, <alpha-value>? )
379/// ```
380#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
381#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
382#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
383#[derive(csskit_derives::NodeWithMetadata)]
384pub struct HslFunction {
385	#[atom(CssAtomSet::Hsl)]
386	pub name: T![Function],
387	pub params: HslFunctionParams,
388	pub close: T![')'],
389}
390
391#[cfg(feature = "chromashift")]
392impl crate::ToChromashift for HslFunction {
393	fn to_chromashift(&self) -> Option<chromashift::Color> {
394		self.params.to_chromashift()
395	}
396}
397
398#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
399#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
400#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
401#[derive(csskit_derives::NodeWithMetadata)]
402pub struct HslaFunction {
403	#[atom(CssAtomSet::Hsla)]
404	pub name: T![Function],
405	pub params: HslFunctionParams,
406	pub close: T![')'],
407}
408
409#[cfg(feature = "chromashift")]
410impl crate::ToChromashift for HslaFunction {
411	fn to_chromashift(&self) -> Option<chromashift::Color> {
412		self.params.to_chromashift()
413	}
414}
415
416#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
417#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
418#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
419#[derive(csskit_derives::NodeWithMetadata)]
420pub struct HslFunctionParams(
421	pub NoneOr<AngleOrNumber>,
422	pub Option<T![,]>,
423	pub NoneOr<NumberOrPercentage>,
424	pub Option<T![,]>,
425	pub NoneOr<NumberOrPercentage>,
426	pub Option<CommaOrSlash>,
427	pub Option<NoneOr<NumberOrPercentage>>,
428);
429
430#[cfg(feature = "chromashift")]
431impl crate::ToChromashift for HslFunctionParams {
432	fn to_chromashift(&self) -> Option<chromashift::Color> {
433		use chromashift::Hsl;
434		let Self(hue, _, saturation, _, lightness, _, alpha) = &self;
435		let hue = match hue {
436			NoneOr::None(_) => 0.0,
437			NoneOr::Some(AngleOrNumber::Number(hue)) => hue.value(),
438			NoneOr::Some(AngleOrNumber::Angle(d)) => d.as_degrees(),
439		};
440		let saturation = match saturation {
441			NoneOr::None(_) => 0.0,
442			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
443			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
444		};
445		let lightness = match lightness {
446			NoneOr::None(_) => 0.0,
447			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
448			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
449		};
450		let alpha = match alpha {
451			Some(NoneOr::None(_)) => 0.0,
452			Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
453			Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
454			None => 100.0,
455		};
456		Some(chromashift::Color::Hsl(Hsl::new(hue, saturation, lightness, alpha)))
457	}
458}
459
460/// <https://drafts.csswg.org/css-color/#funcdef-hwb>
461///
462/// ```text,ignore
463/// hwb() = hwb(
464///  [<hue> | none]
465///  [<percentage> | <number> | none]
466///  [<percentage> | <number> | none]
467///  [ / [<alpha-value> | none] ]? )
468/// ```
469#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
470#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
471#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
472#[derive(csskit_derives::NodeWithMetadata)]
473pub struct HwbFunction {
474	#[atom(CssAtomSet::Hwb)]
475	pub name: T![Function],
476	pub params: HwbFunctionParams,
477	pub close: T![')'],
478}
479
480#[cfg(feature = "chromashift")]
481impl crate::ToChromashift for HwbFunction {
482	fn to_chromashift(&self) -> Option<chromashift::Color> {
483		use chromashift::Hwb;
484		let HwbFunctionParams(hue, whiteness, blackness, _, alpha) = &self.params;
485		let hue = match hue {
486			NoneOr::None(_) => 0.0,
487			NoneOr::Some(AngleOrNumber::Number(hue)) => hue.value(),
488			NoneOr::Some(AngleOrNumber::Angle(d)) => d.as_degrees(),
489		};
490		let whiteness = match whiteness {
491			NoneOr::None(_) => 0.0,
492			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
493			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
494		};
495		let blackness = match blackness {
496			NoneOr::None(_) => 0.0,
497			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
498			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
499		};
500		let alpha = match alpha {
501			Some(NoneOr::None(_)) => 0.0,
502			Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
503			Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
504			None => 100.0,
505		};
506		Some(chromashift::Color::Hwb(Hwb::new(hue, whiteness, blackness, alpha)))
507	}
508}
509
510#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
511#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
512#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
513#[derive(csskit_derives::NodeWithMetadata)]
514pub struct HwbFunctionParams(
515	pub NoneOr<AngleOrNumber>,
516	pub NoneOr<NumberOrPercentage>,
517	pub NoneOr<NumberOrPercentage>,
518	pub Option<T![/]>,
519	pub Option<NoneOr<NumberOrPercentage>>,
520);
521
522/// <https://drafts.csswg.org/css-color/#funcdef-lab>
523///
524/// ```text,ignore
525/// lab() = lab( [<percentage> | <number> | none]
526///  [ <percentage> | <number> | none]
527///  [ <percentage> | <number> | none]
528///  [ / [<alpha-value> | none] ]? )
529/// ```
530#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
531#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
532#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
533#[derive(csskit_derives::NodeWithMetadata)]
534pub struct LabFunction {
535	#[atom(CssAtomSet::Lab)]
536	pub name: T![Function],
537	pub params: LabFunctionParams,
538	pub close: T![')'],
539}
540
541#[cfg(feature = "chromashift")]
542impl crate::ToChromashift for LabFunction {
543	fn to_chromashift(&self) -> Option<chromashift::Color> {
544		use chromashift::Lab;
545		let LabFunctionParams(l, a, b, _, alpha) = &self.params;
546		let l = match l {
547			NoneOr::None(_) => 0.0,
548			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
549			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
550		} as f64;
551		let a = match a {
552			NoneOr::None(_) => 0.0,
553			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
554			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 125.0,
555		} as f64;
556		let b = match b {
557			NoneOr::None(_) => 0.0,
558			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
559			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 125.0,
560		} as f64;
561		let alpha = match alpha {
562			Some(NoneOr::None(_)) => 0.0,
563			Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
564			Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
565			None => 100.0,
566		};
567		Some(chromashift::Color::Lab(Lab::new(l, a, b, alpha)))
568	}
569}
570
571#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
572#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
573#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
574#[derive(csskit_derives::NodeWithMetadata)]
575pub struct LabFunctionParams(
576	pub NoneOr<NumberOrPercentage>,
577	pub NoneOr<NumberOrPercentage>,
578	pub NoneOr<NumberOrPercentage>,
579	pub Option<T![/]>,
580	pub Option<NoneOr<NumberOrPercentage>>,
581);
582
583/// <https://drafts.csswg.org/css-color/#funcdef-lch>
584///
585/// ```text,ignore
586/// lch() = lch( [<percentage> | <number> | none]
587///  [ <percentage> | <number> | none]
588///  [ <hue> | none]
589///  [ / [<alpha-value> | none] ]? )
590/// ```
591#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
592#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
593#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
594#[derive(csskit_derives::NodeWithMetadata)]
595pub struct LchFunction {
596	#[atom(CssAtomSet::Lch)]
597	pub name: T![Function],
598	pub params: LchFunctionParams,
599	pub close: T![')'],
600}
601
602#[cfg(feature = "chromashift")]
603impl crate::ToChromashift for LchFunction {
604	fn to_chromashift(&self) -> Option<chromashift::Color> {
605		use chromashift::Lch;
606		let LchFunctionParams(lightness, chroma, hue, _, alpha) = &self.params;
607		let lightness = match lightness {
608			NoneOr::None(_) => 0.0,
609			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
610			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
611		} as f64;
612		let chroma = match chroma {
613			NoneOr::None(_) => 0.0,
614			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
615			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 150.0,
616		} as f64;
617		let hue = match hue {
618			NoneOr::None(_) => 0.0,
619			NoneOr::Some(AngleOrNumber::Number(hue)) => hue.value(),
620			NoneOr::Some(AngleOrNumber::Angle(d)) => d.as_degrees(),
621		} as f64;
622		let alpha = match alpha {
623			Some(NoneOr::None(_)) => 0.0,
624			Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
625			Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
626			None => 100.0,
627		};
628		Some(chromashift::Color::Lch(Lch::new(lightness, chroma, hue, alpha)))
629	}
630}
631
632#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
633#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
634#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
635#[derive(csskit_derives::NodeWithMetadata)]
636pub struct LchFunctionParams(
637	pub NoneOr<NumberOrPercentage>,
638	pub NoneOr<NumberOrPercentage>,
639	pub NoneOr<AngleOrNumber>,
640	pub Option<T![/]>,
641	pub Option<NoneOr<NumberOrPercentage>>,
642);
643
644/// <https://drafts.csswg.org/css-color/#funcdef-oklab>
645///
646/// ```text,ignore
647/// oklab() = oklab( [ <percentage> | <number> | none]
648///  [ <percentage> | <number> | none]
649///  [ <percentage> | <number> | none]
650///  [ / [<alpha-value> | none] ]? )
651///  ```
652#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
653#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
654#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
655#[derive(csskit_derives::NodeWithMetadata)]
656pub struct OklabFunction {
657	#[atom(CssAtomSet::Oklab)]
658	pub name: T![Function],
659	pub params: LabFunctionParams,
660	pub close: T![')'],
661}
662
663#[cfg(feature = "chromashift")]
664impl crate::ToChromashift for OklabFunction {
665	fn to_chromashift(&self) -> Option<chromashift::Color> {
666		use chromashift::Oklab;
667		let LabFunctionParams(l, a, b, _, alpha) = &self.params;
668		let alpha = match alpha {
669			Some(NoneOr::None(_)) => 0.0,
670			Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
671			Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
672			None => 100.0,
673		};
674		let l = match l {
675			NoneOr::None(_) => 0.0,
676			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
677			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0,
678		} as f64;
679		let a = match a {
680			NoneOr::None(_) => 0.0,
681			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
682			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 0.4,
683		} as f64;
684		let b = match b {
685			NoneOr::None(_) => 0.0,
686			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
687			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 0.4,
688		} as f64;
689		Some(chromashift::Color::Oklab(Oklab::new(l, a, b, alpha)))
690	}
691}
692
693/// <https://drafts.csswg.org/css-color/#funcdef-oklch>
694///
695/// ```text,ignore
696/// oklab() = oklab( [ <percentage> | <number> | none]
697///  [ <percentage> | <number> | none]
698///  [ <percentage> | <number> | none]
699///  [ / [<alpha-value> | none] ]? )
700///  ```
701#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
702#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
703#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
704#[derive(csskit_derives::NodeWithMetadata)]
705pub struct OklchFunction {
706	#[atom(CssAtomSet::Oklch)]
707	pub name: T![Function],
708	pub params: LchFunctionParams,
709	pub close: T![')'],
710}
711
712#[cfg(feature = "chromashift")]
713impl crate::ToChromashift for OklchFunction {
714	fn to_chromashift(&self) -> Option<chromashift::Color> {
715		use chromashift::Oklch;
716		let LchFunctionParams(lightness, chroma, hue, _, alpha) = &self.params;
717		let lightness = match lightness {
718			NoneOr::None(_) => 0.0,
719			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
720			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
721		} as f64;
722		let chroma = match chroma {
723			NoneOr::None(_) => 0.0,
724			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
725			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 150.0,
726		} as f64;
727		let hue = match hue {
728			NoneOr::None(_) => 0.0,
729			NoneOr::Some(AngleOrNumber::Number(hue)) => hue.value(),
730			NoneOr::Some(AngleOrNumber::Angle(d)) => d.as_degrees(),
731		} as f64;
732		let alpha = match alpha {
733			Some(NoneOr::None(_)) => 0.0,
734			Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
735			Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
736			None => 100.0,
737		};
738		Some(chromashift::Color::Oklch(Oklch::new(lightness, chroma, hue, alpha)))
739	}
740}
741
742#[cfg(test)]
743mod tests {
744	use super::*;
745
746	#[test]
747	fn size_test() {
748		assert_eq!(std::mem::size_of::<ColorFunction<'_>>(), 144);
749		assert_eq!(std::mem::size_of::<ColorFunctionColor>(), 120);
750		assert_eq!(std::mem::size_of::<RgbFunction>(), 136);
751		assert_eq!(std::mem::size_of::<RgbaFunction>(), 136);
752		assert_eq!(std::mem::size_of::<HslFunction>(), 136);
753		assert_eq!(std::mem::size_of::<HslaFunction>(), 136);
754		assert_eq!(std::mem::size_of::<HwbFunction>(), 104);
755		assert_eq!(std::mem::size_of::<LabFunction>(), 104);
756		assert_eq!(std::mem::size_of::<LchFunction>(), 104);
757		assert_eq!(std::mem::size_of::<OklabFunction>(), 104);
758		assert_eq!(std::mem::size_of::<OklchFunction>(), 104);
759	}
760}