css_ast/functions/
color_function.rs

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