Skip to main content

css_ast/functions/
color_function.rs

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