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)?;
209 let g = channel_unit(c2)?;
210 let b = channel_unit(c3)?;
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() as f64 / 255.0,
359 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(red))) => red.value() as f64 / 100.0,
360 NoneOr::Some(_) => return None,
361 };
362 let green = match green {
363 NoneOr::None(_) => 0.0,
364 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(green))) => green.value() as f64 / 255.0,
365 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(green))) => green.value() as f64 / 100.0,
366 NoneOr::Some(_) => return None,
367 };
368 let blue = match blue {
369 NoneOr::None(_) => 0.0,
370 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(blue))) => blue.value() as f64 / 255.0,
371 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(blue))) => blue.value() as f64 / 100.0,
372 NoneOr::Some(_) => return None,
373 };
374 Some(chromashift::Color::Srgb(Srgb::new(red, green, blue, alpha)))
375 }
376}
377
378#[node]
397#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
398#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
399#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
400#[derive(csskit_derives::NodeWithMetadata)]
401pub struct HslFunction<'a> {
402 #[atom(CssAtomSet::Hsl)]
403 pub name: T![Function],
404 pub params: HslFunctionParams<'a>,
405 #[semantic_eq(skip)]
406 pub close: T![')'],
407}
408
409#[cfg(feature = "chromashift")]
410impl crate::ToChromashift for HslFunction<'_> {
411 fn to_chromashift(&self) -> Option<chromashift::Color> {
412 self.params.to_chromashift()
413 }
414}
415
416#[node]
417#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
418#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
419#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
420#[derive(csskit_derives::NodeWithMetadata)]
421pub struct HslaFunction<'a> {
422 #[atom(CssAtomSet::Hsla)]
423 pub name: T![Function],
424 pub params: HslFunctionParams<'a>,
425 #[semantic_eq(skip)]
426 pub close: T![')'],
427}
428
429#[cfg(feature = "chromashift")]
430impl crate::ToChromashift for HslaFunction<'_> {
431 fn to_chromashift(&self) -> Option<chromashift::Color> {
432 self.params.to_chromashift()
433 }
434}
435
436#[node]
437#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
438#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
439#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
440#[derive(csskit_derives::NodeWithMetadata)]
441pub struct HslFunctionParams<'a>(
442 pub NoneOr<NumericValue<'a, AngleOrNumber>>,
443 #[semantic_eq(skip)] pub Option<T![,]>,
444 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
445 #[semantic_eq(skip)] pub Option<T![,]>,
446 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
447 pub Option<CommaOrSlash>,
448 pub Option<NoneOr<NumericValue<'a, NumberOrPercentage>>>,
449);
450
451#[cfg(feature = "chromashift")]
452impl crate::ToChromashift for HslFunctionParams<'_> {
453 fn to_chromashift(&self) -> Option<chromashift::Color> {
454 use chromashift::Hsl;
455 let Self(hue, _, saturation, _, lightness, _, alpha) = &self;
456 let hue = match hue {
457 NoneOr::None(_) => 0.0,
458 NoneOr::Some(NumericValue::Literal(AngleOrNumber::Number(hue))) => hue.value(),
459 NoneOr::Some(NumericValue::Literal(AngleOrNumber::Angle(d))) => d.as_degrees(),
460 NoneOr::Some(_) => return None,
461 };
462 let saturation = match saturation {
463 NoneOr::None(_) => 0.0,
464 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
465 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value(),
466 NoneOr::Some(_) => return None,
467 };
468 let lightness = match lightness {
469 NoneOr::None(_) => 0.0,
470 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
471 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value(),
472 NoneOr::Some(_) => return None,
473 };
474 let alpha = match alpha {
475 Some(NoneOr::None(_)) => 0.0,
476 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(t)))) => t.value() * 100.0,
477 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(t)))) => t.value(),
478 Some(NoneOr::Some(_)) => return None,
479 None => 100.0,
480 };
481 Some(chromashift::Color::Hsl(Hsl::new(hue.into(), saturation.into(), lightness.into(), alpha)))
482 }
483}
484
485#[node]
495#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
496#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
497#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
498#[derive(csskit_derives::NodeWithMetadata)]
499pub struct HwbFunction<'a> {
500 #[atom(CssAtomSet::Hwb)]
501 pub name: T![Function],
502 pub params: HwbFunctionParams<'a>,
503 #[semantic_eq(skip)]
504 pub close: T![')'],
505}
506
507#[cfg(feature = "chromashift")]
508impl crate::ToChromashift for HwbFunction<'_> {
509 fn to_chromashift(&self) -> Option<chromashift::Color> {
510 use chromashift::Hwb;
511 let HwbFunctionParams(hue, whiteness, blackness, _, alpha) = &self.params;
512 let hue = match hue {
513 NoneOr::None(_) => 0.0,
514 NoneOr::Some(NumericValue::Literal(AngleOrNumber::Number(hue))) => hue.value(),
515 NoneOr::Some(NumericValue::Literal(AngleOrNumber::Angle(d))) => d.as_degrees(),
516 NoneOr::Some(_) => return None,
517 };
518 let whiteness = match whiteness {
519 NoneOr::None(_) => 0.0,
520 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
521 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value(),
522 NoneOr::Some(_) => return None,
523 };
524 let blackness = match blackness {
525 NoneOr::None(_) => 0.0,
526 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
527 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value(),
528 NoneOr::Some(_) => return None,
529 };
530 let alpha = match alpha {
531 Some(NoneOr::None(_)) => 0.0,
532 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(t)))) => t.value() * 100.0,
533 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(t)))) => t.value(),
534 Some(NoneOr::Some(_)) => return None,
535 None => 100.0,
536 };
537 Some(chromashift::Color::Hwb(Hwb::new(hue.into(), whiteness.into(), blackness.into(), alpha)))
538 }
539}
540
541#[node]
542#[derive(Parse, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
543#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
544#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
545#[derive(csskit_derives::NodeWithMetadata)]
546pub struct HwbFunctionParams<'a>(
547 pub NoneOr<NumericValue<'a, AngleOrNumber>>,
548 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
549 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
550 #[semantic_eq(skip)] pub Option<T![/]>,
551 pub Option<NoneOr<NumericValue<'a, NumberOrPercentage>>>,
552);
553
554#[node]
563#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
564#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
565#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
566#[derive(csskit_derives::NodeWithMetadata)]
567pub struct LabFunction<'a> {
568 #[atom(CssAtomSet::Lab)]
569 pub name: T![Function],
570 pub params: LabFunctionParams<'a>,
571 #[semantic_eq(skip)]
572 pub close: T![')'],
573}
574
575#[cfg(feature = "chromashift")]
576impl crate::ToChromashift for LabFunction<'_> {
577 fn to_chromashift(&self) -> Option<chromashift::Color> {
578 use chromashift::Lab;
579 let LabFunctionParams(l, a, b, _, alpha) = &self.params;
580 let l = match l {
581 NoneOr::None(_) => 0.0,
582 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
583 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value(),
584 NoneOr::Some(_) => return None,
585 } as f64;
586 let a = match a {
587 NoneOr::None(_) => 0.0,
588 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
589 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value() / 100.0 * 125.0,
590 NoneOr::Some(_) => return None,
591 } as f64;
592 let b = match b {
593 NoneOr::None(_) => 0.0,
594 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
595 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value() / 100.0 * 125.0,
596 NoneOr::Some(_) => return None,
597 } as f64;
598 let alpha = match alpha {
599 Some(NoneOr::None(_)) => 0.0,
600 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(t)))) => t.value() * 100.0,
601 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(t)))) => t.value(),
602 Some(NoneOr::Some(_)) => return None,
603 None => 100.0,
604 };
605 Some(chromashift::Color::Lab(Lab::new(l, a, b, alpha)))
606 }
607}
608
609#[node]
610#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
611#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
612#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
613#[derive(csskit_derives::NodeWithMetadata)]
614pub struct LabFunctionParams<'a>(
615 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
616 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
617 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
618 #[semantic_eq(skip)] pub Option<T![/]>,
619 pub Option<NoneOr<NumericValue<'a, NumberOrPercentage>>>,
620);
621
622#[node]
631#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
632#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
633#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
634#[derive(csskit_derives::NodeWithMetadata)]
635pub struct LchFunction<'a> {
636 #[atom(CssAtomSet::Lch)]
637 pub name: T![Function],
638 pub params: LchFunctionParams<'a>,
639 #[semantic_eq(skip)]
640 pub close: T![')'],
641}
642
643#[cfg(feature = "chromashift")]
644impl crate::ToChromashift for LchFunction<'_> {
645 fn to_chromashift(&self) -> Option<chromashift::Color> {
646 use chromashift::Lch;
647 let LchFunctionParams(lightness, chroma, hue, _, alpha) = &self.params;
648 let lightness = match lightness {
649 NoneOr::None(_) => 0.0,
650 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
651 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value(),
652 NoneOr::Some(_) => return None,
653 } as f64;
654 let chroma = match chroma {
655 NoneOr::None(_) => 0.0,
656 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
657 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value() / 100.0 * 150.0,
658 NoneOr::Some(_) => return None,
659 } as f64;
660 let hue = match hue {
661 NoneOr::None(_) => 0.0,
662 NoneOr::Some(NumericValue::Literal(AngleOrNumber::Number(hue))) => hue.value(),
663 NoneOr::Some(NumericValue::Literal(AngleOrNumber::Angle(d))) => d.as_degrees(),
664 NoneOr::Some(_) => return None,
665 } as f64;
666 let alpha = match alpha {
667 Some(NoneOr::None(_)) => 0.0,
668 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(t)))) => t.value() * 100.0,
669 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(t)))) => t.value(),
670 Some(NoneOr::Some(_)) => return None,
671 None => 100.0,
672 };
673 Some(chromashift::Color::Lch(Lch::new(lightness, chroma, hue, alpha)))
674 }
675}
676
677#[node]
678#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
679#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
680#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
681#[derive(csskit_derives::NodeWithMetadata)]
682pub struct LchFunctionParams<'a>(
683 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
684 pub NoneOr<NumericValue<'a, NumberOrPercentage>>,
685 pub NoneOr<NumericValue<'a, AngleOrNumber>>,
686 #[semantic_eq(skip)] pub Option<T![/]>,
687 pub Option<NoneOr<NumericValue<'a, NumberOrPercentage>>>,
688);
689
690#[node]
699#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
700#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
701#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
702#[derive(csskit_derives::NodeWithMetadata)]
703pub struct OklabFunction<'a> {
704 #[atom(CssAtomSet::Oklab)]
705 pub name: T![Function],
706 pub params: LabFunctionParams<'a>,
707 #[semantic_eq(skip)]
708 pub close: T![')'],
709}
710
711#[cfg(feature = "chromashift")]
712impl crate::ToChromashift for OklabFunction<'_> {
713 fn to_chromashift(&self) -> Option<chromashift::Color> {
714 use chromashift::Oklab;
715 let LabFunctionParams(l, a, b, _, alpha) = &self.params;
716 let alpha = match alpha {
717 Some(NoneOr::None(_)) => 0.0,
718 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(t)))) => t.value() * 100.0,
719 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(t)))) => t.value(),
720 Some(NoneOr::Some(_)) => return None,
721 None => 100.0,
722 };
723 let l = match l {
724 NoneOr::None(_) => 0.0,
725 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
726 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value() / 100.0,
727 NoneOr::Some(_) => return None,
728 } as f64;
729 let a = match a {
730 NoneOr::None(_) => 0.0,
731 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
732 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value() / 100.0 * 0.4,
733 NoneOr::Some(_) => return None,
734 } as f64;
735 let b = match b {
736 NoneOr::None(_) => 0.0,
737 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
738 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value() / 100.0 * 0.4,
739 NoneOr::Some(_) => return None,
740 } as f64;
741 Some(chromashift::Color::Oklab(Oklab::new(l, a, b, alpha)))
742 }
743}
744
745#[node]
754#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
755#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
756#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
757#[derive(csskit_derives::NodeWithMetadata)]
758pub struct OklchFunction<'a> {
759 #[atom(CssAtomSet::Oklch)]
760 pub name: T![Function],
761 pub params: LchFunctionParams<'a>,
762 #[semantic_eq(skip)]
763 pub close: T![')'],
764}
765
766#[cfg(feature = "chromashift")]
767impl crate::ToChromashift for OklchFunction<'_> {
768 fn to_chromashift(&self) -> Option<chromashift::Color> {
769 use chromashift::Oklch;
770 let LchFunctionParams(lightness, chroma, hue, _, alpha) = &self.params;
771 let lightness = match lightness {
772 NoneOr::None(_) => 0.0,
773 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
774 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value(),
775 NoneOr::Some(_) => return None,
776 } as f64;
777 let chroma = match chroma {
778 NoneOr::None(_) => 0.0,
779 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(n))) => n.value(),
780 NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(p))) => p.value() / 100.0 * 150.0,
781 NoneOr::Some(_) => return None,
782 } as f64;
783 let hue = match hue {
784 NoneOr::None(_) => 0.0,
785 NoneOr::Some(NumericValue::Literal(AngleOrNumber::Number(hue))) => hue.value(),
786 NoneOr::Some(NumericValue::Literal(AngleOrNumber::Angle(d))) => d.as_degrees(),
787 NoneOr::Some(_) => return None,
788 } as f64;
789 let alpha = match alpha {
790 Some(NoneOr::None(_)) => 0.0,
791 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Number(t)))) => t.value() * 100.0,
792 Some(NoneOr::Some(NumericValue::Literal(NumberOrPercentage::Percentage(t)))) => t.value(),
793 Some(NoneOr::Some(_)) => return None,
794 None => 100.0,
795 };
796 Some(chromashift::Color::Oklch(Oklch::new(lightness, chroma, hue, alpha)))
797 }
798}
799
800#[cfg(test)]
801mod tests {
802 use super::*;
803
804 #[test]
805 fn substitution_in_channels() {
806 use css_parse::assert_parse;
807 assert_parse!(CssAtomSet::ATOMS, RgbFunction, "rgb(calc(255/2) 0 0)");
809 assert_parse!(CssAtomSet::ATOMS, HslFunction, "hsl(calc(120deg) 50% 50%)");
810 assert_parse!(CssAtomSet::ATOMS, RgbFunction, "rgb(var(--r) 0 0)");
812 assert_parse!(CssAtomSet::ATOMS, HwbFunction, "hwb(90deg calc(10%) var(--b))");
813 assert_parse!(CssAtomSet::ATOMS, RgbFunction, "rgb(0 0 0/var(--a))");
815 assert_parse!(CssAtomSet::ATOMS, ColorFunctionColor, "color(srgb calc(0.5) var(--g) 0)");
817 }
818}