1use crate::{AngleOrNumber, NoneOr, NumberOrPercentage};
2use css_parse::{Build, Cursor, Function, Parser, Peek, T, function_set, keyword_set};
3use csskit_derives::{Parse, Peek, ToCursors, ToSpan, Visitable};
4
5keyword_set!(pub enum ColorSpace {
6 Srgb: "srgb",
7 SrgbLinear: "srgb-linear",
8 DisplayP3: "display-p3",
9 A98Rgb: "a98-rgb",
10 ProphotoRgb: "prophoto-rgb",
11 Rec2020: "rec2020",
12 Xyz: "xyz",
13 XyzD50: "xyz-d50",
14 XyzD65: "xyz-d65",
15});
16
17function_set!(pub struct ColorFunctionName "color");
18function_set!(pub struct RgbFunctionName "rgb");
19function_set!(pub struct RgbaFunctionName "rgba");
20function_set!(pub struct HslFunctionName "hsl");
21function_set!(pub struct HslaFunctionName "hsla");
22function_set!(pub struct HwbFunctionName "hwb");
23function_set!(pub struct LabFunctionName "lab");
24function_set!(pub struct LchFunctionName "lch");
25function_set!(pub struct OklabFunctionName "oklab");
26function_set!(pub struct OklchFunctionName "oklch");
27
28#[derive(ToCursors, ToSpan, Visitable, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
30#[visit(self)]
31pub struct CommaOrSlash(T![Delim]);
32
33impl<'a> Peek<'a> for CommaOrSlash {
34 fn peek(_: &Parser<'a>, c: Cursor) -> bool {
35 c == ',' || c == '/'
36 }
37}
38
39impl<'a> Build<'a> for CommaOrSlash {
40 fn build(p: &Parser<'a>, c: Cursor) -> Self {
41 debug_assert!(Self::peek(p, c));
42 Self(<T![Delim]>::build(p, c))
43 }
44}
45
46#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
49#[visit(self)]
50pub enum ColorFunction {
51 Color(ColorFunctionColor),
52 Rgb(RgbFunction),
53 Rgba(RgbaFunction),
54 Hsl(HslFunction),
55 Hsla(HslaFunction),
56 Hwb(HwbFunction),
57 Lab(LabFunction),
58 Lch(LchFunction),
59 Oklab(OklabFunction),
60 Oklch(OklchFunction),
61}
62
63#[cfg(feature = "chromashift")]
64impl crate::ToChromashift for ColorFunction {
65 fn to_chromashift(&self) -> Option<chromashift::Color> {
66 match self {
67 Self::Color(c) => c.to_chromashift(),
68 Self::Rgb(c) => c.to_chromashift(),
69 Self::Rgba(c) => c.to_chromashift(),
70 Self::Hsl(c) => c.to_chromashift(),
71 Self::Hsla(c) => c.to_chromashift(),
72 Self::Hwb(c) => c.to_chromashift(),
73 Self::Lab(c) => c.to_chromashift(),
74 Self::Lch(c) => c.to_chromashift(),
75 Self::Oklab(c) => c.to_chromashift(),
76 Self::Oklch(c) => c.to_chromashift(),
77 }
78 }
79}
80
81#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
93#[visit(self)]
94pub struct ColorFunctionColor(Function<ColorFunctionName, ColorFunctionColorParams>);
95
96#[cfg(feature = "chromashift")]
97impl crate::ToChromashift for ColorFunctionColor {
98 fn to_chromashift(&self) -> Option<chromashift::Color> {
99 todo!();
100 }
101}
102
103#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
104#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
105#[visit(self)]
106pub struct ColorFunctionColorParams(
107 pub ColorSpace,
108 pub NoneOr<NumberOrPercentage>,
109 pub NoneOr<NumberOrPercentage>,
110 pub NoneOr<NumberOrPercentage>,
111 pub Option<T![/]>,
112 pub Option<NoneOr<NumberOrPercentage>>,
113);
114
115#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
132#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
133#[visit(self)]
134pub struct RgbFunction(Function<RgbFunctionName, RgbFunctionParams>);
135
136#[cfg(feature = "chromashift")]
137impl crate::ToChromashift for RgbFunction {
138 fn to_chromashift(&self) -> Option<chromashift::Color> {
139 self.0.parameters.to_chromashift()
140 }
141}
142
143#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
144#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
145#[visit(self)]
146pub struct RgbaFunction(Function<RgbaFunctionName, RgbFunctionParams>);
147
148#[cfg(feature = "chromashift")]
149impl crate::ToChromashift for RgbaFunction {
150 fn to_chromashift(&self) -> Option<chromashift::Color> {
151 self.0.parameters.to_chromashift()
152 }
153}
154
155#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
156#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
157#[visit(self)]
158pub struct RgbFunctionParams(
159 pub NoneOr<NumberOrPercentage>,
160 pub Option<T![,]>,
161 pub NoneOr<NumberOrPercentage>,
162 pub Option<T![,]>,
163 pub NoneOr<NumberOrPercentage>,
164 pub Option<CommaOrSlash>,
165 pub Option<NoneOr<NumberOrPercentage>>,
166);
167
168#[cfg(feature = "chromashift")]
169impl crate::ToChromashift for RgbFunctionParams {
170 fn to_chromashift(&self) -> Option<chromashift::Color> {
171 use chromashift::Srgb;
172 let Self(red, _, green, _, blue, _, alpha) = &self;
173 let alpha = match alpha {
174 Some(NoneOr::None(_)) => 0.0,
175 Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
176 Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
177 None => 100.0,
178 };
179 let red = match red {
180 NoneOr::None(_) => {
181 return None;
182 }
183 NoneOr::Some(NumberOrPercentage::Number(red)) => red.value(),
184 NoneOr::Some(NumberOrPercentage::Percentage(red)) => red.value() / 100.0 * 255.0,
185 } as u8;
186 let green = match green {
187 NoneOr::None(_) => {
188 return None;
189 }
190 NoneOr::Some(NumberOrPercentage::Number(green)) => green.value(),
191 NoneOr::Some(NumberOrPercentage::Percentage(green)) => green.value() / 100.0 * 255.0,
192 } as u8;
193 let blue = match blue {
194 NoneOr::None(_) => {
195 return None;
196 }
197 NoneOr::Some(NumberOrPercentage::Number(blue)) => blue.value(),
198 NoneOr::Some(NumberOrPercentage::Percentage(blue)) => blue.value() / 100.0 * 255.0,
199 } as u8;
200 Some(chromashift::Color::Srgb(Srgb::new(red, green, blue, alpha)))
201 }
202}
203
204#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
223#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
224#[visit(self)]
225pub struct HslFunction(Function<HslFunctionName, HslFunctionParams>);
226
227#[cfg(feature = "chromashift")]
228impl crate::ToChromashift for HslFunction {
229 fn to_chromashift(&self) -> Option<chromashift::Color> {
230 self.0.parameters.to_chromashift()
231 }
232}
233
234#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
235#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
236#[visit(self)]
237pub struct HslaFunction(Function<HslaFunctionName, HslFunctionParams>);
238
239#[cfg(feature = "chromashift")]
240impl crate::ToChromashift for HslaFunction {
241 fn to_chromashift(&self) -> Option<chromashift::Color> {
242 self.0.parameters.to_chromashift()
243 }
244}
245
246#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
247#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
248#[visit(self)]
249pub struct HslFunctionParams(
250 pub NoneOr<AngleOrNumber>,
251 pub Option<T![,]>,
252 pub NoneOr<NumberOrPercentage>,
253 pub Option<T![,]>,
254 pub NoneOr<NumberOrPercentage>,
255 pub Option<CommaOrSlash>,
256 pub Option<NoneOr<NumberOrPercentage>>,
257);
258
259#[cfg(feature = "chromashift")]
260impl crate::ToChromashift for HslFunctionParams {
261 fn to_chromashift(&self) -> Option<chromashift::Color> {
262 use chromashift::Hsl;
263 let Self(hue, _, saturation, _, lightness, _, alpha) = &self;
264 let hue = match hue {
265 NoneOr::None(_) => {
266 return None;
267 }
268 NoneOr::Some(AngleOrNumber::Number(hue)) => hue.value(),
269 NoneOr::Some(AngleOrNumber::Angle(d)) => d.as_degrees(),
270 };
271 let saturation = match saturation {
272 NoneOr::None(_) => {
273 return None;
274 }
275 NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
276 NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
277 };
278 let lightness = match lightness {
279 NoneOr::None(_) => {
280 return None;
281 }
282 NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
283 NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
284 };
285 let alpha = match alpha {
286 Some(NoneOr::None(_)) => 0.0,
287 Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
288 Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
289 None => 100.0,
290 };
291 Some(chromashift::Color::Hsl(Hsl::new(hue, saturation, lightness, alpha)))
292 }
293}
294
295#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
302#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
303#[visit(self)]
304pub struct HwbFunction(Function<HwbFunctionName, HwbFunctionParams>);
305
306#[cfg(feature = "chromashift")]
307impl crate::ToChromashift for HwbFunction {
308 fn to_chromashift(&self) -> Option<chromashift::Color> {
309 use chromashift::Hwb;
310 let HwbFunctionParams(hue, whiteness, blackness, _, alpha) = &self.0.parameters;
311 let hue = match hue {
312 NoneOr::None(_) => {
313 return None;
314 }
315 NoneOr::Some(AngleOrNumber::Number(hue)) => hue.value(),
316 NoneOr::Some(AngleOrNumber::Angle(d)) => d.as_degrees(),
317 };
318 let whiteness = match whiteness {
319 NoneOr::None(_) => {
320 return None;
321 }
322 NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
323 NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
324 };
325 let blackness = match blackness {
326 NoneOr::None(_) => {
327 return None;
328 }
329 NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
330 NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
331 };
332 let alpha = match alpha {
333 Some(NoneOr::None(_)) => 0.0,
334 Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
335 Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
336 None => 100.0,
337 };
338 Some(chromashift::Color::Hwb(Hwb::new(hue, whiteness, blackness, alpha)))
339 }
340}
341
342#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
343#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
344#[visit(self)]
345pub struct HwbFunctionParams(
346 pub NoneOr<AngleOrNumber>,
347 pub NoneOr<NumberOrPercentage>,
348 pub NoneOr<NumberOrPercentage>,
349 pub Option<T![/]>,
350 pub Option<NoneOr<NumberOrPercentage>>,
351);
352
353#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
362#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
363#[visit(self)]
364pub struct LabFunction(Function<LabFunctionName, LabFunctionParams>);
365
366#[cfg(feature = "chromashift")]
367impl crate::ToChromashift for LabFunction {
368 fn to_chromashift(&self) -> Option<chromashift::Color> {
369 use chromashift::Lab;
370 let LabFunctionParams(l, a, b, _, alpha) = &self.0.parameters;
371 let l = match l {
372 NoneOr::None(_) => {
373 return None;
374 }
375 NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
376 NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
377 } as f64;
378 let a = match a {
379 NoneOr::None(_) => {
380 return None;
381 }
382 NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
383 NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 125.0,
384 } as f64;
385 let b = match b {
386 NoneOr::None(_) => {
387 return None;
388 }
389 NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
390 NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 125.0,
391 } as f64;
392 let alpha = match alpha {
393 Some(NoneOr::None(_)) => 0.0,
394 Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
395 Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
396 None => 100.0,
397 };
398 Some(chromashift::Color::Lab(Lab::new(l, a, b, alpha)))
399 }
400}
401
402#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
403#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
404#[visit(self)]
405pub struct LabFunctionParams(
406 pub NoneOr<NumberOrPercentage>,
407 pub NoneOr<NumberOrPercentage>,
408 pub NoneOr<NumberOrPercentage>,
409 pub Option<T![/]>,
410 pub Option<NoneOr<NumberOrPercentage>>,
411);
412
413#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
422#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
423#[visit(self)]
424pub struct LchFunction(Function<LchFunctionName, LchFunctionParams>);
425
426#[cfg(feature = "chromashift")]
427impl crate::ToChromashift for LchFunction {
428 fn to_chromashift(&self) -> Option<chromashift::Color> {
429 use chromashift::Lch;
430 let LchFunctionParams(lightness, chroma, hue, _, alpha) = &self.0.parameters;
431 let lightness = match lightness {
432 NoneOr::None(_) => {
433 return None;
434 }
435 NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
436 NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
437 } as f64;
438 let chroma = match chroma {
439 NoneOr::None(_) => {
440 return None;
441 }
442 NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
443 NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 150.0,
444 } as f64;
445 let hue = match hue {
446 NoneOr::None(_) => {
447 return None;
448 }
449 NoneOr::Some(AngleOrNumber::Number(hue)) => hue.value(),
450 NoneOr::Some(AngleOrNumber::Angle(d)) => d.as_degrees(),
451 } as f64;
452 let alpha = match alpha {
453 Some(NoneOr::None(_)) => 0.0,
454 Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
455 Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
456 None => 100.0,
457 };
458 Some(chromashift::Color::Lch(Lch::new(lightness, chroma, hue, alpha)))
459 }
460}
461
462#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
463#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
464#[visit(self)]
465pub struct LchFunctionParams(
466 pub NoneOr<NumberOrPercentage>,
467 pub NoneOr<NumberOrPercentage>,
468 pub NoneOr<AngleOrNumber>,
469 pub Option<T![/]>,
470 pub Option<NoneOr<NumberOrPercentage>>,
471);
472
473#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
482#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
483#[visit(self)]
484pub struct OklabFunction(Function<OklabFunctionName, LabFunctionParams>);
485
486#[cfg(feature = "chromashift")]
487impl crate::ToChromashift for OklabFunction {
488 fn to_chromashift(&self) -> Option<chromashift::Color> {
489 use chromashift::Oklab;
490 let LabFunctionParams(l, a, b, _, alpha) = &self.0.parameters;
491 let alpha = match alpha {
492 Some(NoneOr::None(_)) => 0.0,
493 Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
494 Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
495 None => 100.0,
496 };
497 let l = match l {
498 NoneOr::None(_) => {
499 return None;
500 }
501 NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
502 NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0,
503 } as f64;
504 let a = match a {
505 NoneOr::None(_) => {
506 return None;
507 }
508 NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
509 NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 0.4,
510 } as f64;
511 let b = match b {
512 NoneOr::None(_) => {
513 return None;
514 }
515 NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
516 NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 0.4,
517 } as f64;
518 Some(chromashift::Color::Oklab(Oklab::new(l, a, b, alpha)))
519 }
520}
521
522#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
531#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
532#[visit(self)]
533pub struct OklchFunction(Function<OklchFunctionName, LchFunctionParams>);
534
535#[cfg(feature = "chromashift")]
536impl crate::ToChromashift for OklchFunction {
537 fn to_chromashift(&self) -> Option<chromashift::Color> {
538 use chromashift::Oklch;
539 let LchFunctionParams(lightness, chroma, hue, _, alpha) = &self.0.parameters;
540 let lightness = match lightness {
541 NoneOr::None(_) => {
542 return None;
543 }
544 NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
545 NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value(),
546 } as f64;
547 let chroma = match chroma {
548 NoneOr::None(_) => {
549 return None;
550 }
551 NoneOr::Some(NumberOrPercentage::Number(n)) => n.value(),
552 NoneOr::Some(NumberOrPercentage::Percentage(p)) => p.value() / 100.0 * 150.0,
553 } as f64;
554 let hue = match hue {
555 NoneOr::None(_) => {
556 return None;
557 }
558 NoneOr::Some(AngleOrNumber::Number(hue)) => hue.value(),
559 NoneOr::Some(AngleOrNumber::Angle(d)) => d.as_degrees(),
560 } as f64;
561 let alpha = match alpha {
562 Some(NoneOr::None(_)) => 0.0,
563 Some(NoneOr::Some(NumberOrPercentage::Number(t))) => t.value() * 100.0,
564 Some(NoneOr::Some(NumberOrPercentage::Percentage(t))) => t.value(),
565 None => 100.0,
566 };
567 Some(chromashift::Color::Oklch(Oklch::new(lightness, chroma, hue, alpha)))
568 }
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574
575 #[test]
576 fn size_test() {
577 assert_eq!(std::mem::size_of::<ColorFunction>(), 144);
578 assert_eq!(std::mem::size_of::<ColorFunctionColor>(), 124);
579 assert_eq!(std::mem::size_of::<RgbFunction>(), 140);
580 assert_eq!(std::mem::size_of::<RgbaFunction>(), 140);
581 assert_eq!(std::mem::size_of::<HslFunction>(), 140);
582 assert_eq!(std::mem::size_of::<HslaFunction>(), 140);
583 assert_eq!(std::mem::size_of::<HwbFunction>(), 108);
584 assert_eq!(std::mem::size_of::<LabFunction>(), 108);
585 assert_eq!(std::mem::size_of::<LchFunction>(), 108);
586 assert_eq!(std::mem::size_of::<OklabFunction>(), 108);
587 assert_eq!(std::mem::size_of::<OklchFunction>(), 108);
588 }
589}