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// hwb() = hwb(
414//  [<hue> | none]
415//  [<percentage> | <number> | none]
416//  [<percentage> | <number> | none]
417//  [ / [<alpha-value> | none] ]? )
418#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
419#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
420#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
421#[derive(csskit_derives::NodeWithMetadata)]
422pub struct HwbFunction {
423	#[atom(CssAtomSet::Hwb)]
424	pub name: T![Function],
425	pub params: HwbFunctionParams,
426	pub close: T![')'],
427}
428
429#[cfg(feature = "chromashift")]
430impl crate::ToChromashift for HwbFunction {
431	fn to_chromashift(&self) -> Option<chromashift::Color> {
432		use chromashift::Hwb;
433		let HwbFunctionParams(hue, whiteness, blackness, _, alpha) = &self.params;
434		let hue = match hue {
435			NoneOr::None(_) => {
436				return None;
437			}
438			NoneOr::Some(AngleOrNumber::Number(hue)) => hue.value(),
439			NoneOr::Some(AngleOrNumber::Angle(d)) => d.as_degrees(),
440		};
441		let whiteness = match whiteness {
442			NoneOr::None(_) => {
443				return None;
444			}
445			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
446			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
447		};
448		let blackness = match blackness {
449			NoneOr::None(_) => {
450				return None;
451			}
452			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
453			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
454		};
455		let alpha = match alpha {
456			Some(NoneOr::None(_)) => 0.0,
457			Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
458			Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
459			None => 100.0,
460		};
461		Some(chromashift::Color::Hwb(Hwb::new(hue, whiteness, blackness, alpha)))
462	}
463}
464
465#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
466#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
467#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
468#[derive(csskit_derives::NodeWithMetadata)]
469pub struct HwbFunctionParams(
470	pub NoneOr<AngleOrNumber>,
471	pub NoneOr<NumberOrPercentage>,
472	pub NoneOr<NumberOrPercentage>,
473	pub Option<T![/]>,
474	pub Option<NoneOr<NumberOrPercentage>>,
475);
476
477/// <https://drafts.csswg.org/css-color/#funcdef-lab>
478///
479/// ```text,ignore
480/// lab() = lab( [<percentage> | <number> | none]
481///  [ <percentage> | <number> | none]
482///  [ <percentage> | <number> | none]
483///  [ / [<alpha-value> | none] ]? )
484/// ```
485#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
486#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
487#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
488#[derive(csskit_derives::NodeWithMetadata)]
489pub struct LabFunction {
490	#[atom(CssAtomSet::Lab)]
491	pub name: T![Function],
492	pub params: LabFunctionParams,
493	pub close: T![')'],
494}
495
496#[cfg(feature = "chromashift")]
497impl crate::ToChromashift for LabFunction {
498	fn to_chromashift(&self) -> Option<chromashift::Color> {
499		use chromashift::Lab;
500		let LabFunctionParams(l, a, b, _, alpha) = &self.params;
501		let l = match l {
502			NoneOr::None(_) => {
503				return None;
504			}
505			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
506			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
507		} as f64;
508		let a = match a {
509			NoneOr::None(_) => {
510				return None;
511			}
512			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
513			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 125.0,
514		} as f64;
515		let b = match b {
516			NoneOr::None(_) => {
517				return None;
518			}
519			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
520			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 125.0,
521		} as f64;
522		let alpha = match alpha {
523			Some(NoneOr::None(_)) => 0.0,
524			Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
525			Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
526			None => 100.0,
527		};
528		Some(chromashift::Color::Lab(Lab::new(l, a, b, alpha)))
529	}
530}
531
532#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
533#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
534#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
535#[derive(csskit_derives::NodeWithMetadata)]
536pub struct LabFunctionParams(
537	pub NoneOr<NumberOrPercentage>,
538	pub NoneOr<NumberOrPercentage>,
539	pub NoneOr<NumberOrPercentage>,
540	pub Option<T![/]>,
541	pub Option<NoneOr<NumberOrPercentage>>,
542);
543
544/// <https://drafts.csswg.org/css-color/#funcdef-lch>
545///
546/// ```text,ignore
547/// lch() = lch( [<percentage> | <number> | none]
548///  [ <percentage> | <number> | none]
549///  [ <hue> | none]
550///  [ / [<alpha-value> | none] ]? )
551/// ```
552#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
553#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
554#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
555#[derive(csskit_derives::NodeWithMetadata)]
556pub struct LchFunction {
557	#[atom(CssAtomSet::Lch)]
558	pub name: T![Function],
559	pub params: LchFunctionParams,
560	pub close: T![')'],
561}
562
563#[cfg(feature = "chromashift")]
564impl crate::ToChromashift for LchFunction {
565	fn to_chromashift(&self) -> Option<chromashift::Color> {
566		use chromashift::Lch;
567		let LchFunctionParams(lightness, chroma, hue, _, alpha) = &self.params;
568		let lightness = match lightness {
569			NoneOr::None(_) => {
570				return None;
571			}
572			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
573			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
574		} as f64;
575		let chroma = match chroma {
576			NoneOr::None(_) => {
577				return None;
578			}
579			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
580			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 150.0,
581		} as f64;
582		let hue = match hue {
583			NoneOr::None(_) => {
584				return None;
585			}
586			NoneOr::Some(AngleOrNumber::Number(hue)) => hue.value(),
587			NoneOr::Some(AngleOrNumber::Angle(d)) => d.as_degrees(),
588		} as f64;
589		let alpha = match alpha {
590			Some(NoneOr::None(_)) => 0.0,
591			Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
592			Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
593			None => 100.0,
594		};
595		Some(chromashift::Color::Lch(Lch::new(lightness, chroma, hue, alpha)))
596	}
597}
598
599#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
600#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
601#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
602#[derive(csskit_derives::NodeWithMetadata)]
603pub struct LchFunctionParams(
604	pub NoneOr<NumberOrPercentage>,
605	pub NoneOr<NumberOrPercentage>,
606	pub NoneOr<AngleOrNumber>,
607	pub Option<T![/]>,
608	pub Option<NoneOr<NumberOrPercentage>>,
609);
610
611/// <https://drafts.csswg.org/css-color/#funcdef-oklab>
612///
613/// ```text,ignore
614/// oklab() = oklab( [ <percentage> | <number> | none]
615///  [ <percentage> | <number> | none]
616///  [ <percentage> | <number> | none]
617///  [ / [<alpha-value> | none] ]? )
618///  ```
619#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
620#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
621#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
622#[derive(csskit_derives::NodeWithMetadata)]
623pub struct OklabFunction {
624	#[atom(CssAtomSet::Oklab)]
625	pub name: T![Function],
626	pub params: LabFunctionParams,
627	pub close: T![')'],
628}
629
630#[cfg(feature = "chromashift")]
631impl crate::ToChromashift for OklabFunction {
632	fn to_chromashift(&self) -> Option<chromashift::Color> {
633		use chromashift::Oklab;
634		let LabFunctionParams(l, a, b, _, alpha) = &self.params;
635		let alpha = match alpha {
636			Some(NoneOr::None(_)) => 0.0,
637			Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
638			Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
639			None => 100.0,
640		};
641		let l = match l {
642			NoneOr::None(_) => {
643				return None;
644			}
645			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
646			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0,
647		} as f64;
648		let a = match a {
649			NoneOr::None(_) => {
650				return None;
651			}
652			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
653			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 0.4,
654		} as f64;
655		let b = match b {
656			NoneOr::None(_) => {
657				return None;
658			}
659			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
660			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 0.4,
661		} as f64;
662		Some(chromashift::Color::Oklab(Oklab::new(l, a, b, alpha)))
663	}
664}
665
666/// <https://drafts.csswg.org/css-color/#funcdef-oklch>
667///
668/// ```text,ignore
669/// oklab() = oklab( [ <percentage> | <number> | none]
670///  [ <percentage> | <number> | none]
671///  [ <percentage> | <number> | none]
672///  [ / [<alpha-value> | none] ]? )
673///  ```
674#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
675#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
676#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
677#[derive(csskit_derives::NodeWithMetadata)]
678pub struct OklchFunction {
679	#[atom(CssAtomSet::Oklch)]
680	pub name: T![Function],
681	pub params: LchFunctionParams,
682	pub close: T![')'],
683}
684
685#[cfg(feature = "chromashift")]
686impl crate::ToChromashift for OklchFunction {
687	fn to_chromashift(&self) -> Option<chromashift::Color> {
688		use chromashift::Oklch;
689		let LchFunctionParams(lightness, chroma, hue, _, alpha) = &self.params;
690		let lightness = match lightness {
691			NoneOr::None(_) => {
692				return None;
693			}
694			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
695			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
696		} as f64;
697		let chroma = match chroma {
698			NoneOr::None(_) => {
699				return None;
700			}
701			NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
702			NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 150.0,
703		} as f64;
704		let hue = match hue {
705			NoneOr::None(_) => {
706				return None;
707			}
708			NoneOr::Some(AngleOrNumber::Number(hue)) => hue.value(),
709			NoneOr::Some(AngleOrNumber::Angle(d)) => d.as_degrees(),
710		} as f64;
711		let alpha = match alpha {
712			Some(NoneOr::None(_)) => 0.0,
713			Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
714			Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
715			None => 100.0,
716		};
717		Some(chromashift::Color::Oklch(Oklch::new(lightness, chroma, hue, alpha)))
718	}
719}
720
721#[cfg(test)]
722mod tests {
723	use super::*;
724
725	#[test]
726	fn size_test() {
727		assert_eq!(std::mem::size_of::<ColorFunction<'_>>(), 144);
728		assert_eq!(std::mem::size_of::<ColorFunctionColor>(), 120);
729		assert_eq!(std::mem::size_of::<RgbFunction>(), 136);
730		assert_eq!(std::mem::size_of::<RgbaFunction>(), 136);
731		assert_eq!(std::mem::size_of::<HslFunction>(), 136);
732		assert_eq!(std::mem::size_of::<HslaFunction>(), 136);
733		assert_eq!(std::mem::size_of::<HwbFunction>(), 104);
734		assert_eq!(std::mem::size_of::<LabFunction>(), 104);
735		assert_eq!(std::mem::size_of::<LchFunction>(), 104);
736		assert_eq!(std::mem::size_of::<OklabFunction>(), 104);
737		assert_eq!(std::mem::size_of::<OklchFunction>(), 104);
738	}
739}