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, NumericValue};
6use css_parse::Box;
7
8#[node]
9#[derive(
10 Parse, Peek, IntoCursor, ToSpan, SemanticEq, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
11)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
13#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
14#[derive(csskit_derives::NodeWithMetadata)]
15pub enum ColorSpace {
16 #[atom(CssAtomSet::Srgb)]
17 Srgb(T![Ident]),
18 #[atom(CssAtomSet::SrgbLinear)]
19 SrgbLinear(T![Ident]),
20 #[atom(CssAtomSet::DisplayP3)]
21 DisplayP3(T![Ident]),
22 #[atom(CssAtomSet::A98Rgb)]
23 A98Rgb(T![Ident]),
24 #[atom(CssAtomSet::ProphotoRgb)]
25 ProphotoRgb(T![Ident]),
26 #[atom(CssAtomSet::Rec2020)]
27 Rec2020(T![Ident]),
28 #[atom(CssAtomSet::Xyz)]
29 Xyz(T![Ident]),
30 #[atom(CssAtomSet::XyzD50)]
31 XyzD50(T![Ident]),
32 #[atom(CssAtomSet::XyzD65)]
33 XyzD65(T![Ident]),
34}
35
36#[node]
37#[derive(IntoCursor, ToSpan, SemanticEq, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
39#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
40#[derive(csskit_derives::NodeWithMetadata)]
41pub struct CommaOrSlash(#[metadata(skip)] Cursor);
42
43impl<'a> Peek<'a> for CommaOrSlash {
44 const PEEK_KINDSET: KindSet = KindSet::new(&[Kind::Delim]);
45
46 #[inline(always)]
47 fn peek<I>(_: &Parser<'a, I>, c: Cursor) -> bool
48 where
49 I: Iterator<Item = Cursor> + Clone,
50 {
51 c == ',' || c == '/'
52 }
53}
54
55impl<'a> Parse<'a> for CommaOrSlash {
56 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
57 where
58 I: Iterator<Item = Cursor> + Clone,
59 {
60 if !p.peek::<Self>() {
61 Err(Diagnostic::new(p.next(), Diagnostic::unexpected))?
62 }
63 Ok(Self(p.next()))
64 }
65}
66
67#[node]
69#[derive(Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
70#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
71#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(all))]
72#[derive(csskit_derives::NodeWithMetadata)]
73pub enum ColorFunction<'a> {
74 Relative(Box<'a, RelativeColorFunction<'a>>),
75 Color(Box<'a, ColorFunctionColor<'a>>),
76 ColorMix(Box<'a, ColorMixFunction<'a>>),
77 LightDark(Box<'a, LightDarkFunction<'a>>),
78 Rgb(RgbFunction<'a>),
79 Rgba(RgbaFunction<'a>),
80 Hsl(HslFunction<'a>),
81 Hsla(HslaFunction<'a>),
82 Hwb(HwbFunction<'a>),
83 Lab(LabFunction<'a>),
84 Lch(LchFunction<'a>),
85 Oklab(OklabFunction<'a>),
86 Oklch(OklchFunction<'a>),
87}
88
89impl<'a> Parse<'a> for ColorFunction<'a> {
90 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
91 where
92 I: Iterator<Item = Cursor> + Clone,
93 {
94 if p.peek::<Box<RelativeColorFunction>>() {
96 return Ok(Self::Relative(p.parse()?));
97 }
98 if p.peek::<Box<ColorFunctionColor<'_>>>() {
99 return Ok(Self::Color(p.parse()?));
100 }
101 if p.peek::<Box<ColorMixFunction>>() {
102 return Ok(Self::ColorMix(p.parse()?));
103 }
104 if p.peek::<Box<LightDarkFunction>>() {
105 return Ok(Self::LightDark(p.parse()?));
106 }
107 if p.peek::<RgbFunction>() {
108 return Ok(Self::Rgb(p.parse()?));
109 }
110 if p.peek::<RgbaFunction>() {
111 return Ok(Self::Rgba(p.parse()?));
112 }
113 if p.peek::<HslFunction>() {
114 return Ok(Self::Hsl(p.parse()?));
115 }
116 if p.peek::<HslaFunction>() {
117 return Ok(Self::Hsla(p.parse()?));
118 }
119 if p.peek::<HwbFunction>() {
120 return Ok(Self::Hwb(p.parse()?));
121 }
122 if p.peek::<LabFunction>() {
123 return Ok(Self::Lab(p.parse()?));
124 }
125 if p.peek::<LchFunction>() {
126 return Ok(Self::Lch(p.parse()?));
127 }
128 if p.peek::<OklabFunction>() {
129 return Ok(Self::Oklab(p.parse()?));
130 }
131 Ok(Self::Oklch(p.parse()?))
132 }
133}
134
135#[cfg(feature = "chromashift")]
136impl crate::ToChromashift for ColorFunction<'_> {
137 fn to_chromashift(&self) -> Option<chromashift::Color> {
138 match self {
139 Self::Relative(r) => r.to_chromashift(),
140 Self::Color(c) => c.to_chromashift(),
141 Self::ColorMix(c) => c.to_chromashift(),
142 Self::LightDark(_c) => None,
143 Self::Rgb(c) => c.to_chromashift(),
144 Self::Rgba(c) => c.to_chromashift(),
145 Self::Hsl(c) => c.to_chromashift(),
146 Self::Hsla(c) => c.to_chromashift(),
147 Self::Hwb(c) => c.to_chromashift(),
148 Self::Lab(c) => c.to_chromashift(),
149 Self::Lch(c) => c.to_chromashift(),
150 Self::Oklab(c) => c.to_chromashift(),
151 Self::Oklch(c) => c.to_chromashift(),
152 }
153 }
154}
155
156#[node]
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 ColorFunctionColor<'a> {
172 #[atom(CssAtomSet::Color)]
173 pub name: T![Function],
174 pub params: ColorFunctionColorParams<'a>,
175 #[semantic_eq(skip)]
176 pub close: T![')'],
177}
178
179#[cfg(feature = "chromashift")]
180impl crate::ToChromashift for ColorFunctionColor<'_> {
181 fn to_chromashift(&self) -> Option<chromashift::Color> {
182 use chromashift::{A98Rgb, DisplayP3, LinearRgb, ProphotoRgb, Rec2020, Srgb, XyzD50, XyzD65};
183
184 let ColorFunctionColorParams(space, c1, c2, c3, _, alpha) = &self.params;
185
186 let alpha = match alpha {
187 Some(NoneOr::None(_)) => 0.0,
188 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(t)))) => t.value() * 100.0,
189 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(t)))) => t.value(),
190 Some(NoneOr::Some(_)) => return None,
191 None => 100.0,
192 };
193
194 let channel_unit = |c: &NoneOr<NumericValue<'_, NumberOrPercentage>>| -> Option<f64> {
196 match c {
197 NoneOr::None(_) => Some(0.0),
198 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => Some(n.value() as f64),
199 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => {
200 Some(p.value() as f64 / 100.0)
201 }
202 NoneOr::Some(_) => None,
203 }
204 };
205
206 match space {
207 ColorSpace::Srgb(_) => {
208 let r = (channel_unit(c1)? * 255.0).round() as u8;
209 let g = (channel_unit(c2)? * 255.0).round() as u8;
210 let b = (channel_unit(c3)? * 255.0).round() as u8;
211 Some(chromashift::Color::Srgb(Srgb::new(r, g, b, alpha)))
212 }
213 ColorSpace::SrgbLinear(_) => {
214 let r = channel_unit(c1)?;
215 let g = channel_unit(c2)?;
216 let b = channel_unit(c3)?;
217 Some(chromashift::Color::LinearRgb(LinearRgb::new(r, g, b, alpha)))
218 }
219 ColorSpace::DisplayP3(_) => {
220 let r = channel_unit(c1)?;
221 let g = channel_unit(c2)?;
222 let b = channel_unit(c3)?;
223 Some(chromashift::Color::DisplayP3(DisplayP3::new(r, g, b, alpha)))
224 }
225 ColorSpace::A98Rgb(_) => {
226 let r = channel_unit(c1)?;
227 let g = channel_unit(c2)?;
228 let b = channel_unit(c3)?;
229 Some(chromashift::Color::A98Rgb(A98Rgb::new(r, g, b, alpha)))
230 }
231 ColorSpace::ProphotoRgb(_) => {
232 let r = channel_unit(c1)?;
233 let g = channel_unit(c2)?;
234 let b = channel_unit(c3)?;
235 Some(chromashift::Color::ProphotoRgb(ProphotoRgb::new(r, g, b, alpha)))
236 }
237 ColorSpace::Rec2020(_) => {
238 let r = channel_unit(c1)?;
239 let g = channel_unit(c2)?;
240 let b = channel_unit(c3)?;
241 Some(chromashift::Color::Rec2020(Rec2020::new(r, g, b, alpha)))
242 }
243 ColorSpace::Xyz(_) | ColorSpace::XyzD65(_) => {
244 let x = channel_unit(c1)? * 100.0;
245 let y = channel_unit(c2)? * 100.0;
246 let z = channel_unit(c3)? * 100.0;
247 Some(chromashift::Color::XyzD65(XyzD65::new(x, y, z, alpha)))
248 }
249 ColorSpace::XyzD50(_) => {
250 let x = channel_unit(c1)? * 100.0;
251 let y = channel_unit(c2)? * 100.0;
252 let z = channel_unit(c3)? * 100.0;
253 Some(chromashift::Color::XyzD50(XyzD50::new(x, y, z, alpha)))
254 }
255 }
256 }
257}
258
259#[node]
260#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
261#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
262#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
263#[derive(csskit_derives::NodeWithMetadata)]
264pub struct ColorFunctionColorParams<'a>(
265 pub ColorSpace,
266 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
267 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
268 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
269 #[semantic_eq(skip)] pub Option<T![/]>,
270 pub Option<NoneOr<NumericValue<'a, NumberOrPercentage>>>,
271);
272
273#[node]
290#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
291#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
292#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
293#[derive(csskit_derives::NodeWithMetadata)]
294pub struct RgbFunction<'a> {
295 #[atom(CssAtomSet::Rgb)]
296 pub name: T![Function],
297 pub params: RgbFunctionParams<'a>,
298 #[semantic_eq(skip)]
299 pub close: T![')'],
300}
301
302#[cfg(feature = "chromashift")]
303impl crate::ToChromashift for RgbFunction<'_> {
304 fn to_chromashift(&self) -> Option<chromashift::Color> {
305 self.params.to_chromashift()
306 }
307}
308
309#[node]
310#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
311#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
312#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
313#[derive(csskit_derives::NodeWithMetadata)]
314pub struct RgbaFunction<'a> {
315 #[atom(CssAtomSet::Rgba)]
316 pub name: T![Function],
317 pub params: RgbFunctionParams<'a>,
318 #[semantic_eq(skip)]
319 pub close: T![')'],
320}
321
322#[cfg(feature = "chromashift")]
323impl crate::ToChromashift for RgbaFunction<'_> {
324 fn to_chromashift(&self) -> Option<chromashift::Color> {
325 self.params.to_chromashift()
326 }
327}
328
329#[node]
330#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
331#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
332#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
333#[derive(csskit_derives::NodeWithMetadata)]
334pub struct RgbFunctionParams<'a>(
335 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
336 #[semantic_eq(skip)] pub Option<T![,]>,
337 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
338 #[semantic_eq(skip)] pub Option<T![,]>,
339 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
340 pub Option<CommaOrSlash>,
341 pub Option<NoneOr<NumericValue<'a, NumberOrPercentage>>>,
342);
343
344#[cfg(feature = "chromashift")]
345impl crate::ToChromashift for RgbFunctionParams<'_> {
346 fn to_chromashift(&self) -> Option<chromashift::Color> {
347 use chromashift::Srgb;
348 let Self(red, _, green, _, blue, _, alpha) = &self;
349 let alpha = match alpha {
350 Some(NoneOr::None(_)) => 0.0,
351 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(t)))) => t.value() * 100.0,
352 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(t)))) => t.value(),
353 Some(NoneOr::Some(_)) => return None,
354 None => 100.0,
355 };
356 let red = (match red {
357 NoneOr::None(_) => 0.0,
358 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(red))) => red.value(),
359 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(red))) => red.value() / 100.0 * 255.0,
360 NoneOr::Some(_) => return None,
361 })
362 .round() as u8;
363 let green = (match green {
364 NoneOr::None(_) => 0.0,
365 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(green))) => green.value(),
366 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(green))) => green.value() / 100.0 * 255.0,
367 NoneOr::Some(_) => return None,
368 })
369 .round() as u8;
370 let blue = (match blue {
371 NoneOr::None(_) => 0.0,
372 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(blue))) => blue.value(),
373 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(blue))) => blue.value() / 100.0 * 255.0,
374 NoneOr::Some(_) => return None,
375 })
376 .round() as u8;
377 Some(chromashift::Color::Srgb(Srgb::new(red, green, blue, alpha)))
378 }
379}
380
381#[node]
400#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
401#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
402#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
403#[derive(csskit_derives::NodeWithMetadata)]
404pub struct HslFunction<'a> {
405 #[atom(CssAtomSet::Hsl)]
406 pub name: T![Function],
407 pub params: HslFunctionParams<'a>,
408 #[semantic_eq(skip)]
409 pub close: T![')'],
410}
411
412#[cfg(feature = "chromashift")]
413impl crate::ToChromashift for HslFunction<'_> {
414 fn to_chromashift(&self) -> Option<chromashift::Color> {
415 self.params.to_chromashift()
416 }
417}
418
419#[node]
420#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
421#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
422#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
423#[derive(csskit_derives::NodeWithMetadata)]
424pub struct HslaFunction<'a> {
425 #[atom(CssAtomSet::Hsla)]
426 pub name: T![Function],
427 pub params: HslFunctionParams<'a>,
428 #[semantic_eq(skip)]
429 pub close: T![')'],
430}
431
432#[cfg(feature = "chromashift")]
433impl crate::ToChromashift for HslaFunction<'_> {
434 fn to_chromashift(&self) -> Option<chromashift::Color> {
435 self.params.to_chromashift()
436 }
437}
438
439#[node]
440#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
441#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
442#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
443#[derive(csskit_derives::NodeWithMetadata)]
444pub struct HslFunctionParams<'a>(
445 pub NoneOr<NumericValue<'a, AngleOrNumber>>,
446 #[semantic_eq(skip)] pub Option<T![,]>,
447 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
448 #[semantic_eq(skip)] pub Option<T![,]>,
449 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
450 pub Option<CommaOrSlash>,
451 pub Option<NoneOr<NumericValue<'a, NumberOrPercentage>>>,
452);
453
454#[cfg(feature = "chromashift")]
455impl crate::ToChromashift for HslFunctionParams<'_> {
456 fn to_chromashift(&self) -> Option<chromashift::Color> {
457 use chromashift::Hsl;
458 let Self(hue, _, saturation, _, lightness, _, alpha) = &self;
459 let hue = match hue {
460 NoneOr::None(_) => 0.0,
461 NoneOr::Some(NumericValue::Literal(AngleOrNumber::Number(hue))) => hue.value(),
462 NoneOr::Some(NumericValue::Literal(AngleOrNumber::Angle(d))) => d.as_degrees(),
463 NoneOr::Some(_) => return None,
464 };
465 let saturation = match saturation {
466 NoneOr::None(_) => 0.0,
467 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
468 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value(),
469 NoneOr::Some(_) => return None,
470 };
471 let lightness = match lightness {
472 NoneOr::None(_) => 0.0,
473 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
474 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value(),
475 NoneOr::Some(_) => return None,
476 };
477 let alpha = match alpha {
478 Some(NoneOr::None(_)) => 0.0,
479 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(t)))) => t.value() * 100.0,
480 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(t)))) => t.value(),
481 Some(NoneOr::Some(_)) => return None,
482 None => 100.0,
483 };
484 Some(chromashift::Color::Hsl(Hsl::new(hue, saturation, lightness, alpha)))
485 }
486}
487
488#[node]
498#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
499#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
500#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
501#[derive(csskit_derives::NodeWithMetadata)]
502pub struct HwbFunction<'a> {
503 #[atom(CssAtomSet::Hwb)]
504 pub name: T![Function],
505 pub params: HwbFunctionParams<'a>,
506 #[semantic_eq(skip)]
507 pub close: T![')'],
508}
509
510#[cfg(feature = "chromashift")]
511impl crate::ToChromashift for HwbFunction<'_> {
512 fn to_chromashift(&self) -> Option<chromashift::Color> {
513 use chromashift::Hwb;
514 let HwbFunctionParams(hue, whiteness, blackness, _, alpha) = &self.params;
515 let hue = match hue {
516 NoneOr::None(_) => 0.0,
517 NoneOr::Some(NumericValue::Literal(AngleOrNumber::Number(hue))) => hue.value(),
518 NoneOr::Some(NumericValue::Literal(AngleOrNumber::Angle(d))) => d.as_degrees(),
519 NoneOr::Some(_) => return None,
520 };
521 let whiteness = match whiteness {
522 NoneOr::None(_) => 0.0,
523 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
524 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value(),
525 NoneOr::Some(_) => return None,
526 };
527 let blackness = match blackness {
528 NoneOr::None(_) => 0.0,
529 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
530 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value(),
531 NoneOr::Some(_) => return None,
532 };
533 let alpha = match alpha {
534 Some(NoneOr::None(_)) => 0.0,
535 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(t)))) => t.value() * 100.0,
536 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(t)))) => t.value(),
537 Some(NoneOr::Some(_)) => return None,
538 None => 100.0,
539 };
540 Some(chromashift::Color::Hwb(Hwb::new(hue, whiteness, blackness, alpha)))
541 }
542}
543
544#[node]
545#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
546#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
547#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
548#[derive(csskit_derives::NodeWithMetadata)]
549pub struct HwbFunctionParams<'a>(
550 pub NoneOr<NumericValue<'a, AngleOrNumber>>,
551 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
552 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
553 #[semantic_eq(skip)] pub Option<T![/]>,
554 pub Option<NoneOr<NumericValue<'a, NumberOrPercentage>>>,
555);
556
557#[node]
566#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
567#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
568#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
569#[derive(csskit_derives::NodeWithMetadata)]
570pub struct LabFunction<'a> {
571 #[atom(CssAtomSet::Lab)]
572 pub name: T![Function],
573 pub params: LabFunctionParams<'a>,
574 #[semantic_eq(skip)]
575 pub close: T![')'],
576}
577
578#[cfg(feature = "chromashift")]
579impl crate::ToChromashift for LabFunction<'_> {
580 fn to_chromashift(&self) -> Option<chromashift::Color> {
581 use chromashift::Lab;
582 let LabFunctionParams(l, a, b, _, alpha) = &self.params;
583 let l = match l {
584 NoneOr::None(_) => 0.0,
585 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
586 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value(),
587 NoneOr::Some(_) => return None,
588 } as f64;
589 let a = match a {
590 NoneOr::None(_) => 0.0,
591 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
592 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value() / 100.0 * 125.0,
593 NoneOr::Some(_) => return None,
594 } as f64;
595 let b = match b {
596 NoneOr::None(_) => 0.0,
597 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
598 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value() / 100.0 * 125.0,
599 NoneOr::Some(_) => return None,
600 } as f64;
601 let alpha = match alpha {
602 Some(NoneOr::None(_)) => 0.0,
603 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(t)))) => t.value() * 100.0,
604 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(t)))) => t.value(),
605 Some(NoneOr::Some(_)) => return None,
606 None => 100.0,
607 };
608 Some(chromashift::Color::Lab(Lab::new(l, a, b, alpha)))
609 }
610}
611
612#[node]
613#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
614#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
615#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
616#[derive(csskit_derives::NodeWithMetadata)]
617pub struct LabFunctionParams<'a>(
618 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
619 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
620 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
621 #[semantic_eq(skip)] pub Option<T![/]>,
622 pub Option<NoneOr<NumericValue<'a, NumberOrPercentage>>>,
623);
624
625#[node]
634#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
635#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
636#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
637#[derive(csskit_derives::NodeWithMetadata)]
638pub struct LchFunction<'a> {
639 #[atom(CssAtomSet::Lch)]
640 pub name: T![Function],
641 pub params: LchFunctionParams<'a>,
642 #[semantic_eq(skip)]
643 pub close: T![')'],
644}
645
646#[cfg(feature = "chromashift")]
647impl crate::ToChromashift for LchFunction<'_> {
648 fn to_chromashift(&self) -> Option<chromashift::Color> {
649 use chromashift::Lch;
650 let LchFunctionParams(lightness, chroma, hue, _, alpha) = &self.params;
651 let lightness = match lightness {
652 NoneOr::None(_) => 0.0,
653 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
654 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value(),
655 NoneOr::Some(_) => return None,
656 } as f64;
657 let chroma = match chroma {
658 NoneOr::None(_) => 0.0,
659 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
660 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value() / 100.0 * 150.0,
661 NoneOr::Some(_) => return None,
662 } as f64;
663 let hue = match hue {
664 NoneOr::None(_) => 0.0,
665 NoneOr::Some(NumericValue::Literal(AngleOrNumber::Number(hue))) => hue.value(),
666 NoneOr::Some(NumericValue::Literal(AngleOrNumber::Angle(d))) => d.as_degrees(),
667 NoneOr::Some(_) => return None,
668 } as f64;
669 let alpha = match alpha {
670 Some(NoneOr::None(_)) => 0.0,
671 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(t)))) => t.value() * 100.0,
672 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(t)))) => t.value(),
673 Some(NoneOr::Some(_)) => return None,
674 None => 100.0,
675 };
676 Some(chromashift::Color::Lch(Lch::new(lightness, chroma, hue, alpha)))
677 }
678}
679
680#[node]
681#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
682#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
683#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
684#[derive(csskit_derives::NodeWithMetadata)]
685pub struct LchFunctionParams<'a>(
686 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
687 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
688 pub NoneOr<NumericValue<'a, AngleOrNumber>>,
689 #[semantic_eq(skip)] pub Option<T![/]>,
690 pub Option<NoneOr<NumericValue<'a, NumberOrPercentage>>>,
691);
692
693#[node]
702#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
703#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
704#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
705#[derive(csskit_derives::NodeWithMetadata)]
706pub struct OklabFunction<'a> {
707 #[atom(CssAtomSet::Oklab)]
708 pub name: T![Function],
709 pub params: LabFunctionParams<'a>,
710 #[semantic_eq(skip)]
711 pub close: T![')'],
712}
713
714#[cfg(feature = "chromashift")]
715impl crate::ToChromashift for OklabFunction<'_> {
716 fn to_chromashift(&self) -> Option<chromashift::Color> {
717 use chromashift::Oklab;
718 let LabFunctionParams(l, a, b, _, alpha) = &self.params;
719 let alpha = match alpha {
720 Some(NoneOr::None(_)) => 0.0,
721 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(t)))) => t.value() * 100.0,
722 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(t)))) => t.value(),
723 Some(NoneOr::Some(_)) => return None,
724 None => 100.0,
725 };
726 let l = match l {
727 NoneOr::None(_) => 0.0,
728 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
729 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value() / 100.0,
730 NoneOr::Some(_) => return None,
731 } as f64;
732 let a = match a {
733 NoneOr::None(_) => 0.0,
734 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
735 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value() / 100.0 * 0.4,
736 NoneOr::Some(_) => return None,
737 } as f64;
738 let b = match b {
739 NoneOr::None(_) => 0.0,
740 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
741 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value() / 100.0 * 0.4,
742 NoneOr::Some(_) => return None,
743 } as f64;
744 Some(chromashift::Color::Oklab(Oklab::new(l, a, b, alpha)))
745 }
746}
747
748#[node]
757#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
758#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
759#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
760#[derive(csskit_derives::NodeWithMetadata)]
761pub struct OklchFunction<'a> {
762 #[atom(CssAtomSet::Oklch)]
763 pub name: T![Function],
764 pub params: LchFunctionParams<'a>,
765 #[semantic_eq(skip)]
766 pub close: T![')'],
767}
768
769#[cfg(feature = "chromashift")]
770impl crate::ToChromashift for OklchFunction<'_> {
771 fn to_chromashift(&self) -> Option<chromashift::Color> {
772 use chromashift::Oklch;
773 let LchFunctionParams(lightness, chroma, hue, _, alpha) = &self.params;
774 let lightness = match lightness {
775 NoneOr::None(_) => 0.0,
776 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
777 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value(),
778 NoneOr::Some(_) => return None,
779 } as f64;
780 let chroma = match chroma {
781 NoneOr::None(_) => 0.0,
782 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
783 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value() / 100.0 * 150.0,
784 NoneOr::Some(_) => return None,
785 } as f64;
786 let hue = match hue {
787 NoneOr::None(_) => 0.0,
788 NoneOr::Some(NumericValue::Literal(AngleOrNumber::Number(hue))) => hue.value(),
789 NoneOr::Some(NumericValue::Literal(AngleOrNumber::Angle(d))) => d.as_degrees(),
790 NoneOr::Some(_) => return None,
791 } as f64;
792 let alpha = match alpha {
793 Some(NoneOr::None(_)) => 0.0,
794 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(t)))) => t.value() * 100.0,
795 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(t)))) => t.value(),
796 Some(NoneOr::Some(_)) => return None,
797 None => 100.0,
798 };
799 Some(chromashift::Color::Oklch(Oklch::new(lightness, chroma, hue, alpha)))
800 }
801}
802
803#[cfg(test)]
804mod tests {
805 use super::*;
806
807 #[test]
808 fn substitution_in_channels() {
809 use css_parse::assert_parse;
810 assert_parse!(CssAtomSet::ATOMS, RgbFunction, "rgb(calc(255/2) 0 0)");
812 assert_parse!(CssAtomSet::ATOMS, HslFunction, "hsl(calc(120deg) 50% 50%)");
813 assert_parse!(CssAtomSet::ATOMS, RgbFunction, "rgb(var(--r) 0 0)");
815 assert_parse!(CssAtomSet::ATOMS, HwbFunction, "hwb(90deg calc(10%) var(--b))");
816 assert_parse!(CssAtomSet::ATOMS, RgbFunction, "rgb(0 0 0/var(--a))");
818 assert_parse!(CssAtomSet::ATOMS, ColorFunctionColor, "color(srgb calc(0.5) var(--g) 0)");
820 }
821}