1#![allow(non_upper_case_globals)]
4
5use super::angle::Angle;
6use super::calc::Calc;
7use super::number::CSSNumber;
8use super::percentage::Percentage;
9use crate::compat::Feature;
10use crate::error::{ParserError, PrinterError};
11use crate::macros::enum_property;
12use crate::printer::Printer;
13use crate::properties::PropertyId;
14use crate::rules::supports::SupportsCondition;
15use crate::targets::{should_compile, Browsers, Features, Targets};
16use crate::traits::{FallbackValues, IsCompatible, Parse, ToCss};
17#[cfg(feature = "visitor")]
18use crate::visitor::{Visit, VisitTypes, Visitor};
19use bitflags::bitflags;
20use cssparser::color::{parse_hash_color, parse_named_color};
21use cssparser::*;
22use cssparser_color::{hsl_to_rgb, AngleOrNumber, ColorParser, NumberOrPercentage};
23use std::any::TypeId;
24use std::f32::consts::PI;
25use std::fmt::Write;
26
27#[derive(Debug, Clone, PartialEq)]
37#[cfg_attr(feature = "visitor", derive(Visit))]
38#[cfg_attr(feature = "visitor", visit(visit_color, COLORS))]
39#[cfg_attr(
40 feature = "serde",
41 derive(serde::Serialize, serde::Deserialize),
42 serde(untagged, rename_all = "lowercase")
43)]
44#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
45#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
46pub enum CssColor {
47 #[cfg_attr(feature = "serde", serde(with = "CurrentColor"))]
49 CurrentColor,
50 #[cfg_attr(
52 feature = "serde",
53 serde(serialize_with = "serialize_rgba", deserialize_with = "deserialize_rgba")
54 )]
55 #[cfg_attr(feature = "jsonschema", schemars(with = "RGBColor"))]
56 RGBA(RGBA),
57 LAB(Box<LABColor>),
59 Predefined(Box<PredefinedColor>),
61 Float(Box<FloatColor>),
63 #[cfg_attr(feature = "visitor", skip_type)]
65 #[cfg_attr(feature = "serde", serde(with = "LightDark"))]
66 LightDark(Box<CssColor>, Box<CssColor>),
67 System(SystemColor),
69}
70
71#[cfg(feature = "serde")]
72#[derive(serde::Serialize, serde::Deserialize)]
73#[serde(tag = "type", rename_all = "lowercase")]
74#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
75enum CurrentColor {
76 CurrentColor,
77}
78
79#[cfg(feature = "serde")]
80impl CurrentColor {
81 fn serialize<S>(serializer: S) -> Result<S::Ok, S::Error>
82 where
83 S: serde::Serializer,
84 {
85 serde::Serialize::serialize(&CurrentColor::CurrentColor, serializer)
86 }
87
88 fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error>
89 where
90 D: serde::Deserializer<'de>,
91 {
92 use serde::Deserialize;
93 let _: CurrentColor = Deserialize::deserialize(deserializer)?;
94 Ok(())
95 }
96}
97
98#[cfg(feature = "serde")]
100#[derive(serde::Serialize, serde::Deserialize)]
101#[serde(tag = "type", rename_all = "lowercase")]
102#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
103enum RGBColor {
104 RGB(RGB),
105}
106
107#[cfg(feature = "serde")]
108fn serialize_rgba<S>(rgba: &RGBA, serializer: S) -> Result<S::Ok, S::Error>
109where
110 S: serde::Serializer,
111{
112 use serde::Serialize;
113 RGBColor::RGB(rgba.into()).serialize(serializer)
114}
115
116#[cfg(feature = "serde")]
117fn deserialize_rgba<'de, D>(deserializer: D) -> Result<RGBA, D::Error>
118where
119 D: serde::Deserializer<'de>,
120{
121 use serde::Deserialize;
122 match RGBColor::deserialize(deserializer)? {
123 RGBColor::RGB(srgb) => Ok(srgb.into()),
124 }
125}
126
127#[cfg(feature = "serde")]
129#[derive(serde::Serialize, serde::Deserialize)]
130#[serde(tag = "type", rename_all = "kebab-case")]
131#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
132enum LightDark {
133 LightDark { light: CssColor, dark: CssColor },
134}
135
136#[cfg(feature = "serde")]
137impl<'de> LightDark {
138 pub fn serialize<S>(light: &Box<CssColor>, dark: &Box<CssColor>, serializer: S) -> Result<S::Ok, S::Error>
139 where
140 S: serde::Serializer,
141 {
142 let wrapper = LightDark::LightDark {
143 light: (**light).clone(),
144 dark: (**dark).clone(),
145 };
146 serde::Serialize::serialize(&wrapper, serializer)
147 }
148
149 pub fn deserialize<D>(deserializer: D) -> Result<(Box<CssColor>, Box<CssColor>), D::Error>
150 where
151 D: serde::Deserializer<'de>,
152 {
153 let v: LightDark = serde::Deserialize::deserialize(deserializer)?;
154 match v {
155 LightDark::LightDark { light, dark } => Ok((Box::new(light), Box::new(dark))),
156 }
157 }
158}
159
160#[derive(Debug, Clone, Copy, PartialEq)]
162#[cfg_attr(feature = "visitor", derive(Visit))]
163#[cfg_attr(
164 feature = "serde",
165 derive(serde::Serialize, serde::Deserialize),
166 serde(tag = "type", rename_all = "lowercase")
167)]
168#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
169pub enum LABColor {
170 LAB(LAB),
172 LCH(LCH),
174 OKLAB(OKLAB),
176 OKLCH(OKLCH),
178}
179
180#[derive(Debug, Clone, Copy, PartialEq)]
182#[cfg_attr(feature = "visitor", derive(Visit))]
183#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type"))]
184#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
185pub enum PredefinedColor {
186 #[cfg_attr(feature = "serde", serde(rename = "srgb"))]
188 SRGB(SRGB),
189 #[cfg_attr(feature = "serde", serde(rename = "srgb-linear"))]
191 SRGBLinear(SRGBLinear),
192 #[cfg_attr(feature = "serde", serde(rename = "display-p3"))]
194 DisplayP3(P3),
195 #[cfg_attr(feature = "serde", serde(rename = "a98-rgb"))]
197 A98(A98),
198 #[cfg_attr(feature = "serde", serde(rename = "prophoto-rgb"))]
200 ProPhoto(ProPhoto),
201 #[cfg_attr(feature = "serde", serde(rename = "rec2020"))]
203 Rec2020(Rec2020),
204 #[cfg_attr(feature = "serde", serde(rename = "xyz-d50"))]
206 XYZd50(XYZd50),
207 #[cfg_attr(feature = "serde", serde(rename = "xyz-d65"))]
209 XYZd65(XYZd65),
210}
211
212#[derive(Debug, Clone, Copy, PartialEq)]
216#[cfg_attr(feature = "visitor", derive(Visit))]
217#[cfg_attr(
218 feature = "serde",
219 derive(serde::Serialize, serde::Deserialize),
220 serde(tag = "type", rename_all = "lowercase")
221)]
222#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
223pub enum FloatColor {
224 RGB(RGB),
226 HSL(HSL),
228 HWB(HWB),
230}
231
232bitflags! {
233 #[derive(PartialEq, Eq, Clone, Copy)]
235 pub struct ColorFallbackKind: u8 {
236 const RGB = 0b01;
238 const P3 = 0b10;
240 const LAB = 0b100;
242 const OKLAB = 0b1000;
244 }
245}
246
247enum_property! {
248 enum ColorSpaceName {
251 "srgb": SRGB,
252 "srgb-linear": SRGBLinear,
253 "lab": LAB,
254 "oklab": OKLAB,
255 "xyz": XYZ,
256 "xyz-d50": XYZd50,
257 "xyz-d65": XYZd65,
258 "hsl": Hsl,
259 "hwb": Hwb,
260 "lch": LCH,
261 "oklch": OKLCH,
262 }
263}
264
265enum_property! {
266 pub enum HueInterpolationMethod {
269 Shorter,
271 Longer,
273 Increasing,
275 Decreasing,
277 Specified,
279 }
280}
281
282impl ColorFallbackKind {
283 pub(crate) fn lowest(&self) -> ColorFallbackKind {
284 *self & ColorFallbackKind::from_bits_truncate(self.bits().wrapping_neg())
286 }
287
288 pub(crate) fn highest(&self) -> ColorFallbackKind {
289 if self.is_empty() {
291 return ColorFallbackKind::empty();
292 }
293
294 let zeros = 7 - self.bits().leading_zeros();
295 ColorFallbackKind::from_bits_truncate(1 << zeros)
296 }
297
298 pub(crate) fn and_below(&self) -> ColorFallbackKind {
299 if self.is_empty() {
300 return ColorFallbackKind::empty();
301 }
302
303 *self | ColorFallbackKind::from_bits_truncate(self.bits() - 1)
304 }
305
306 pub(crate) fn supports_condition<'i>(&self) -> SupportsCondition<'i> {
307 let s = match *self {
308 ColorFallbackKind::P3 => "color(display-p3 0 0 0)",
309 ColorFallbackKind::LAB => "lab(0% 0 0)",
310 _ => unreachable!(),
311 };
312
313 SupportsCondition::Declaration {
314 property_id: PropertyId::Color,
315 value: s.into(),
316 }
317 }
318}
319
320impl CssColor {
321 pub fn current_color() -> CssColor {
323 CssColor::CurrentColor
324 }
325
326 pub fn transparent() -> CssColor {
328 CssColor::RGBA(RGBA::transparent())
329 }
330
331 pub fn to_rgb(&self) -> Result<CssColor, ()> {
333 match self {
334 CssColor::LightDark(light, dark) => {
335 Ok(CssColor::LightDark(Box::new(light.to_rgb()?), Box::new(dark.to_rgb()?)))
336 }
337 _ => Ok(RGBA::try_from(self)?.into()),
338 }
339 }
340
341 pub fn to_lab(&self) -> Result<CssColor, ()> {
343 match self {
344 CssColor::LightDark(light, dark) => {
345 Ok(CssColor::LightDark(Box::new(light.to_lab()?), Box::new(dark.to_lab()?)))
346 }
347 _ => Ok(LAB::try_from(self)?.into()),
348 }
349 }
350
351 pub fn to_p3(&self) -> Result<CssColor, ()> {
353 match self {
354 CssColor::LightDark(light, dark) => {
355 Ok(CssColor::LightDark(Box::new(light.to_p3()?), Box::new(dark.to_p3()?)))
356 }
357 _ => Ok(P3::try_from(self)?.into()),
358 }
359 }
360
361 pub(crate) fn get_possible_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
362 let mut fallbacks = match self {
366 CssColor::CurrentColor | CssColor::RGBA(_) | CssColor::Float(..) | CssColor::System(..) => {
367 return ColorFallbackKind::empty()
368 }
369 CssColor::LAB(lab) => match &**lab {
370 LABColor::LAB(..) | LABColor::LCH(..) if should_compile!(targets, LabColors) => {
371 ColorFallbackKind::LAB.and_below()
372 }
373 LABColor::OKLAB(..) | LABColor::OKLCH(..) if should_compile!(targets, OklabColors) => {
374 ColorFallbackKind::OKLAB.and_below()
375 }
376 _ => return ColorFallbackKind::empty(),
377 },
378 CssColor::Predefined(predefined) => match &**predefined {
379 PredefinedColor::DisplayP3(..) if should_compile!(targets, P3Colors) => ColorFallbackKind::P3.and_below(),
380 _ if should_compile!(targets, ColorFunction) => ColorFallbackKind::LAB.and_below(),
381 _ => return ColorFallbackKind::empty(),
382 },
383 CssColor::LightDark(light, dark) => {
384 return light.get_possible_fallbacks(targets) | dark.get_possible_fallbacks(targets);
385 }
386 };
387
388 if fallbacks.contains(ColorFallbackKind::OKLAB) {
389 if !should_compile!(targets, OklabColors) {
390 fallbacks.remove(ColorFallbackKind::LAB.and_below());
391 }
392 }
393
394 if fallbacks.contains(ColorFallbackKind::LAB) {
395 if !should_compile!(targets, LabColors) {
396 fallbacks.remove(ColorFallbackKind::P3.and_below());
397 } else if targets
398 .browsers
399 .map(|targets| Feature::LabColors.is_partially_compatible(targets))
400 .unwrap_or(false)
401 {
402 fallbacks.remove(ColorFallbackKind::P3);
405 }
406 }
407
408 if fallbacks.contains(ColorFallbackKind::P3) {
409 if !should_compile!(targets, P3Colors) {
410 fallbacks.remove(ColorFallbackKind::RGB);
411 } else if fallbacks.highest() != ColorFallbackKind::P3
412 && !targets
413 .browsers
414 .map(|targets| Feature::P3Colors.is_partially_compatible(targets))
415 .unwrap_or(false)
416 {
417 fallbacks.remove(ColorFallbackKind::P3);
420 }
421 }
422
423 fallbacks
424 }
425
426 pub fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
428 let fallbacks = self.get_possible_fallbacks(targets);
431 fallbacks - fallbacks.highest()
432 }
433
434 pub fn get_fallback(&self, kind: ColorFallbackKind) -> CssColor {
436 if matches!(self, CssColor::RGBA(_)) {
437 return self.clone();
438 }
439
440 match kind {
441 ColorFallbackKind::RGB => self.to_rgb().unwrap(),
442 ColorFallbackKind::P3 => self.to_p3().unwrap(),
443 ColorFallbackKind::LAB => self.to_lab().unwrap(),
444 _ => unreachable!(),
445 }
446 }
447
448 pub(crate) fn get_features(&self) -> Features {
449 let mut features = Features::empty();
450 match self {
451 CssColor::LAB(labcolor) => match &**labcolor {
452 LABColor::LAB(_) | LABColor::LCH(_) => {
453 features |= Features::LabColors;
454 }
455 LABColor::OKLAB(_) | LABColor::OKLCH(_) => {
456 features |= Features::OklabColors;
457 }
458 },
459 CssColor::Predefined(predefined_color) => {
460 features |= Features::ColorFunction;
461 match &**predefined_color {
462 PredefinedColor::DisplayP3(_) => {
463 features |= Features::P3Colors;
464 }
465 _ => {}
466 }
467 }
468 CssColor::Float(_) => {
469 features |= Features::SpaceSeparatedColorNotation;
470 }
471 CssColor::LightDark(light, dark) => {
472 features |= Features::LightDark;
473 features |= light.get_features();
474 features |= dark.get_features();
475 }
476 _ => {}
477 }
478
479 features
480 }
481}
482
483impl IsCompatible for CssColor {
484 fn is_compatible(&self, browsers: Browsers) -> bool {
485 match self {
486 CssColor::CurrentColor | CssColor::RGBA(_) | CssColor::Float(..) => true,
487 CssColor::LAB(lab) => match &**lab {
488 LABColor::LAB(..) | LABColor::LCH(..) => Feature::LabColors.is_compatible(browsers),
489 LABColor::OKLAB(..) | LABColor::OKLCH(..) => Feature::OklabColors.is_compatible(browsers),
490 },
491 CssColor::Predefined(predefined) => match &**predefined {
492 PredefinedColor::DisplayP3(..) => Feature::P3Colors.is_compatible(browsers),
493 _ => Feature::ColorFunction.is_compatible(browsers),
494 },
495 CssColor::LightDark(light, dark) => {
496 Feature::LightDark.is_compatible(browsers) && light.is_compatible(browsers) && dark.is_compatible(browsers)
497 }
498 CssColor::System(system) => system.is_compatible(browsers),
499 }
500 }
501}
502
503impl FallbackValues for CssColor {
504 fn get_fallbacks(&mut self, targets: Targets) -> Vec<CssColor> {
505 let fallbacks = self.get_necessary_fallbacks(targets);
506
507 let mut res = Vec::new();
508 if fallbacks.contains(ColorFallbackKind::RGB) {
509 res.push(self.to_rgb().unwrap());
510 }
511
512 if fallbacks.contains(ColorFallbackKind::P3) {
513 res.push(self.to_p3().unwrap());
514 }
515
516 if fallbacks.contains(ColorFallbackKind::LAB) {
517 *self = self.to_lab().unwrap();
518 }
519
520 res
521 }
522}
523
524impl Default for CssColor {
525 fn default() -> CssColor {
526 CssColor::transparent()
527 }
528}
529
530impl<'i> Parse<'i> for CssColor {
531 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
532 let location = input.current_source_location();
533 let token = input.next()?;
534 match *token {
535 Token::Hash(ref value) | Token::IDHash(ref value) => parse_hash_color(value.as_bytes())
536 .map(|(r, g, b, a)| CssColor::RGBA(RGBA::new(r, g, b, a)))
537 .map_err(|_| location.new_unexpected_token_error(token.clone())),
538 Token::Ident(ref value) => Ok(match_ignore_ascii_case! { value,
539 "currentcolor" => CssColor::CurrentColor,
540 "transparent" => CssColor::RGBA(RGBA::transparent()),
541 _ => {
542 if let Ok((r, g, b)) = parse_named_color(value) {
543 CssColor::RGBA(RGBA { red: r, green: g, blue: b, alpha: 255 })
544 } else if let Ok(system_color) = SystemColor::parse_string(&value) {
545 CssColor::System(system_color)
546 } else {
547 return Err(location.new_unexpected_token_error(token.clone()))
548 }
549 }
550 }),
551 Token::Function(ref name) => parse_color_function(location, name.clone(), input),
552 _ => Err(location.new_unexpected_token_error(token.clone())),
553 }
554 }
555}
556
557impl ToCss for CssColor {
558 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
559 where
560 W: std::fmt::Write,
561 {
562 match self {
563 CssColor::CurrentColor => dest.write_str("currentColor"),
564 CssColor::RGBA(color) => {
565 if color.alpha == 255 {
566 let hex: u32 = ((color.red as u32) << 16) | ((color.green as u32) << 8) | (color.blue as u32);
567 if let Some(name) = short_color_name(hex) {
568 return dest.write_str(name);
569 }
570
571 let compact = compact_hex(hex);
572 if hex == expand_hex(compact) {
573 write!(dest, "#{:03x}", compact)?;
574 } else {
575 write!(dest, "#{:06x}", hex)?;
576 }
577 } else {
578 if should_compile!(dest.targets.current, HexAlphaColors) {
580 if dest.minify && color.red == 0 && color.green == 0 && color.blue == 0 && color.alpha == 0 {
583 return dest.write_str("transparent");
584 } else {
585 dest.write_str("rgba(")?;
586 write!(dest, "{}", color.red)?;
587 dest.delim(',', false)?;
588 write!(dest, "{}", color.green)?;
589 dest.delim(',', false)?;
590 write!(dest, "{}", color.blue)?;
591 dest.delim(',', false)?;
592
593 let mut rounded_alpha = (color.alpha_f32() * 100.0).round() / 100.0;
595 let clamped = (rounded_alpha * 255.0).round().max(0.).min(255.0) as u8;
596 if clamped != color.alpha {
597 rounded_alpha = (color.alpha_f32() * 1000.).round() / 1000.;
598 }
599
600 rounded_alpha.to_css(dest)?;
601 dest.write_char(')')?;
602 return Ok(());
603 }
604 }
605
606 let hex: u32 = ((color.red as u32) << 24)
607 | ((color.green as u32) << 16)
608 | ((color.blue as u32) << 8)
609 | (color.alpha as u32);
610 let compact = compact_hex(hex);
611 if hex == expand_hex(compact) {
612 write!(dest, "#{:04x}", compact)?;
613 } else {
614 write!(dest, "#{:08x}", hex)?;
615 }
616 }
617 Ok(())
618 }
619 CssColor::LAB(lab) => match &**lab {
620 LABColor::LAB(lab) => write_components("lab", lab.l / 100.0, lab.a, lab.b, lab.alpha, dest),
621 LABColor::LCH(lch) => write_components("lch", lch.l / 100.0, lch.c, lch.h, lch.alpha, dest),
622 LABColor::OKLAB(lab) => write_components("oklab", lab.l, lab.a, lab.b, lab.alpha, dest),
623 LABColor::OKLCH(lch) => write_components("oklch", lch.l, lch.c, lch.h, lch.alpha, dest),
624 },
625 CssColor::Predefined(predefined) => write_predefined(predefined, dest),
626 CssColor::Float(float) => {
627 let rgb = RGB::from(**float);
629 CssColor::from(rgb).to_css(dest)
630 }
631 CssColor::LightDark(light, dark) => {
632 if should_compile!(dest.targets.current, LightDark) {
633 dest.write_str("var(--lightningcss-light")?;
634 dest.delim(',', false)?;
635 light.to_css(dest)?;
636 dest.write_char(')')?;
637 dest.whitespace()?;
638 dest.write_str("var(--lightningcss-dark")?;
639 dest.delim(',', false)?;
640 dark.to_css(dest)?;
641 return dest.write_char(')');
642 }
643
644 dest.write_str("light-dark(")?;
645 light.to_css(dest)?;
646 dest.delim(',', false)?;
647 dark.to_css(dest)?;
648 dest.write_char(')')
649 }
650 CssColor::System(system) => system.to_css(dest),
651 }
652 }
653}
654
655fn compact_hex(v: u32) -> u32 {
658 return ((v & 0x0FF00000) >> 12) | ((v & 0x00000FF0) >> 4);
659}
660
661fn expand_hex(v: u32) -> u32 {
663 return ((v & 0xF000) << 16) | ((v & 0xFF00) << 12) | ((v & 0x0FF0) << 8) | ((v & 0x00FF) << 4) | (v & 0x000F);
664}
665
666fn short_color_name(v: u32) -> Option<&'static str> {
667 let s = match v {
669 0x000080 => "navy",
670 0x008000 => "green",
671 0x008080 => "teal",
672 0x4b0082 => "indigo",
673 0x800000 => "maroon",
674 0x800080 => "purple",
675 0x808000 => "olive",
676 0x808080 => "gray",
677 0xa0522d => "sienna",
678 0xa52a2a => "brown",
679 0xc0c0c0 => "silver",
680 0xcd853f => "peru",
681 0xd2b48c => "tan",
682 0xda70d6 => "orchid",
683 0xdda0dd => "plum",
684 0xee82ee => "violet",
685 0xf0e68c => "khaki",
686 0xf0ffff => "azure",
687 0xf5deb3 => "wheat",
688 0xf5f5dc => "beige",
689 0xfa8072 => "salmon",
690 0xfaf0e6 => "linen",
691 0xff0000 => "red",
692 0xff6347 => "tomato",
693 0xff7f50 => "coral",
694 0xffa500 => "orange",
695 0xffc0cb => "pink",
696 0xffd700 => "gold",
697 0xffe4c4 => "bisque",
698 0xfffafa => "snow",
699 0xfffff0 => "ivory",
700 _ => return None,
701 };
702
703 Some(s)
704}
705
706struct RelativeComponentParser {
707 names: (&'static str, &'static str, &'static str),
708 components: (f32, f32, f32, f32),
709 types: (ChannelType, ChannelType, ChannelType),
710}
711
712impl RelativeComponentParser {
713 fn new<T: ColorSpace>(color: &T) -> Self {
714 Self {
715 names: color.channels(),
716 components: color.components(),
717 types: color.types(),
718 }
719 }
720
721 fn get_ident(&self, ident: &str, allowed_types: ChannelType) -> Option<(f32, ChannelType)> {
722 if ident.eq_ignore_ascii_case(self.names.0) && allowed_types.intersects(self.types.0) {
723 return Some((self.components.0, self.types.0));
724 }
725
726 if ident.eq_ignore_ascii_case(self.names.1) && allowed_types.intersects(self.types.1) {
727 return Some((self.components.1, self.types.1));
728 }
729
730 if ident.eq_ignore_ascii_case(self.names.2) && allowed_types.intersects(self.types.2) {
731 return Some((self.components.2, self.types.2));
732 }
733
734 if ident.eq_ignore_ascii_case("alpha")
735 && allowed_types.intersects(ChannelType::Number | ChannelType::Percentage)
736 {
737 return Some((self.components.3, ChannelType::Number));
738 }
739
740 None
741 }
742
743 fn parse_ident<'i, 't>(
744 &self,
745 input: &mut Parser<'i, 't>,
746 allowed_types: ChannelType,
747 ) -> Result<(f32, ChannelType), ParseError<'i, ParserError<'i>>> {
748 match self.get_ident(input.expect_ident()?.as_ref(), allowed_types) {
749 Some(v) => Ok(v),
750 None => Err(input.new_error_for_next_token()),
751 }
752 }
753}
754
755impl<'i> ColorParser<'i> for RelativeComponentParser {
756 type Output = cssparser_color::Color;
757 type Error = ParserError<'i>;
758
759 fn parse_angle_or_number<'t>(
760 &self,
761 input: &mut Parser<'i, 't>,
762 ) -> Result<AngleOrNumber, ParseError<'i, Self::Error>> {
763 if let Ok((value, ty)) =
764 input.try_parse(|input| self.parse_ident(input, ChannelType::Angle | ChannelType::Number))
765 {
766 return Ok(match ty {
767 ChannelType::Angle => AngleOrNumber::Angle { degrees: value },
768 ChannelType::Number => AngleOrNumber::Number { value },
769 _ => unreachable!(),
770 });
771 }
772
773 if let Ok(value) = input.try_parse(|input| -> Result<AngleOrNumber, ParseError<'i, ParserError<'i>>> {
774 match Calc::parse_with(input, |ident| {
775 self
776 .get_ident(ident, ChannelType::Angle | ChannelType::Number)
777 .map(|(value, ty)| match ty {
778 ChannelType::Angle => Calc::Value(Box::new(Angle::Deg(value))),
779 ChannelType::Number => Calc::Number(value),
780 _ => unreachable!(),
781 })
782 }) {
783 Ok(Calc::Value(v)) => Ok(AngleOrNumber::Angle {
784 degrees: v.to_degrees(),
785 }),
786 Ok(Calc::Number(v)) => Ok(AngleOrNumber::Number { value: v }),
787 _ => Err(input.new_custom_error(ParserError::InvalidValue)),
788 }
789 }) {
790 return Ok(value);
791 }
792
793 Err(input.new_error_for_next_token())
794 }
795
796 fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {
797 if let Ok((value, _)) = input.try_parse(|input| self.parse_ident(input, ChannelType::Number)) {
798 return Ok(value);
799 }
800
801 match Calc::parse_with(input, |ident| {
802 self.get_ident(ident, ChannelType::Number).map(|(v, _)| Calc::Number(v))
803 }) {
804 Ok(Calc::Value(v)) => Ok(*v),
805 Ok(Calc::Number(n)) => Ok(n),
806 _ => Err(input.new_error_for_next_token()),
807 }
808 }
809
810 fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {
811 if let Ok((value, _)) = input.try_parse(|input| self.parse_ident(input, ChannelType::Percentage)) {
812 return Ok(value);
813 }
814
815 if let Ok(value) = input.try_parse(|input| -> Result<Percentage, ParseError<'i, ParserError<'i>>> {
816 match Calc::parse_with(input, |ident| {
817 self
818 .get_ident(ident, ChannelType::Percentage)
819 .map(|(v, _)| Calc::Value(Box::new(Percentage(v))))
820 }) {
821 Ok(Calc::Value(v)) => Ok(*v),
822 _ => Err(input.new_custom_error(ParserError::InvalidValue)),
823 }
824 }) {
825 return Ok(value.0);
826 }
827
828 Err(input.new_error_for_next_token())
829 }
830
831 fn parse_number_or_percentage<'t>(
832 &self,
833 input: &mut Parser<'i, 't>,
834 ) -> Result<NumberOrPercentage, ParseError<'i, Self::Error>> {
835 if let Ok((value, ty)) =
836 input.try_parse(|input| self.parse_ident(input, ChannelType::Percentage | ChannelType::Number))
837 {
838 return Ok(match ty {
839 ChannelType::Percentage => NumberOrPercentage::Percentage { unit_value: value },
840 ChannelType::Number => NumberOrPercentage::Number { value },
841 _ => unreachable!(),
842 });
843 }
844
845 if let Ok(value) = input.try_parse(|input| -> Result<NumberOrPercentage, ParseError<'i, ParserError<'i>>> {
846 match Calc::parse_with(input, |ident| {
847 self
848 .get_ident(ident, ChannelType::Percentage | ChannelType::Number)
849 .map(|(value, ty)| match ty {
850 ChannelType::Percentage => Calc::Value(Box::new(Percentage(value))),
851 ChannelType::Number => Calc::Number(value),
852 _ => unreachable!(),
853 })
854 }) {
855 Ok(Calc::Value(v)) => Ok(NumberOrPercentage::Percentage { unit_value: v.0 }),
856 Ok(Calc::Number(v)) => Ok(NumberOrPercentage::Number { value: v }),
857 _ => Err(input.new_custom_error(ParserError::InvalidValue)),
858 }
859 }) {
860 return Ok(value);
861 }
862
863 Err(input.new_error_for_next_token())
864 }
865}
866
867pub(crate) trait LightDarkColor {
868 fn light_dark(light: Self, dark: Self) -> Self;
869}
870
871impl LightDarkColor for CssColor {
872 #[inline]
873 fn light_dark(light: Self, dark: Self) -> Self {
874 CssColor::LightDark(Box::new(light), Box::new(dark))
875 }
876}
877
878pub(crate) struct ComponentParser {
879 pub allow_none: bool,
880 from: Option<RelativeComponentParser>,
881}
882
883impl ComponentParser {
884 pub fn new(allow_none: bool) -> Self {
885 Self { allow_none, from: None }
886 }
887
888 pub fn parse_relative<
889 'i,
890 't,
891 T: TryFrom<CssColor> + ColorSpace,
892 C: LightDarkColor,
893 P: Fn(&mut Parser<'i, 't>, &mut Self) -> Result<C, ParseError<'i, ParserError<'i>>>,
894 >(
895 &mut self,
896 input: &mut Parser<'i, 't>,
897 parse: P,
898 ) -> Result<C, ParseError<'i, ParserError<'i>>> {
899 if input.try_parse(|input| input.expect_ident_matching("from")).is_ok() {
900 let from = CssColor::parse(input)?;
901 return self.parse_from::<T, C, P>(from, input, &parse);
902 }
903
904 parse(input, self)
905 }
906
907 fn parse_from<
908 'i,
909 't,
910 T: TryFrom<CssColor> + ColorSpace,
911 C: LightDarkColor,
912 P: Fn(&mut Parser<'i, 't>, &mut Self) -> Result<C, ParseError<'i, ParserError<'i>>>,
913 >(
914 &mut self,
915 from: CssColor,
916 input: &mut Parser<'i, 't>,
917 parse: &P,
918 ) -> Result<C, ParseError<'i, ParserError<'i>>> {
919 if let CssColor::LightDark(light, dark) = from {
920 let state = input.state();
921 let light = self.parse_from::<T, C, P>(*light, input, parse)?;
922 input.reset(&state);
923 let dark = self.parse_from::<T, C, P>(*dark, input, parse)?;
924 return Ok(C::light_dark(light, dark));
925 }
926
927 let from = T::try_from(from)
928 .map_err(|_| input.new_custom_error(ParserError::InvalidValue))?
929 .resolve();
930 self.from = Some(RelativeComponentParser::new(&from));
931
932 parse(input, self)
933 }
934}
935
936impl<'i> ColorParser<'i> for ComponentParser {
937 type Output = cssparser_color::Color;
938 type Error = ParserError<'i>;
939
940 fn parse_angle_or_number<'t>(
941 &self,
942 input: &mut Parser<'i, 't>,
943 ) -> Result<AngleOrNumber, ParseError<'i, Self::Error>> {
944 if let Some(from) = &self.from {
945 if let Ok(res) = input.try_parse(|input| from.parse_angle_or_number(input)) {
946 return Ok(res);
947 }
948 }
949
950 if let Ok(angle) = input.try_parse(Angle::parse) {
951 Ok(AngleOrNumber::Angle {
952 degrees: angle.to_degrees(),
953 })
954 } else if let Ok(value) = input.try_parse(CSSNumber::parse) {
955 Ok(AngleOrNumber::Number { value })
956 } else if self.allow_none {
957 input.expect_ident_matching("none")?;
958 Ok(AngleOrNumber::Number { value: f32::NAN })
959 } else {
960 Err(input.new_custom_error(ParserError::InvalidValue))
961 }
962 }
963
964 fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {
965 if let Some(from) = &self.from {
966 if let Ok(res) = input.try_parse(|input| from.parse_number(input)) {
967 return Ok(res);
968 }
969 }
970
971 if let Ok(val) = input.try_parse(CSSNumber::parse) {
972 return Ok(val);
973 } else if self.allow_none {
974 input.expect_ident_matching("none")?;
975 Ok(f32::NAN)
976 } else {
977 Err(input.new_custom_error(ParserError::InvalidValue))
978 }
979 }
980
981 fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {
982 if let Some(from) = &self.from {
983 if let Ok(res) = input.try_parse(|input| from.parse_percentage(input)) {
984 return Ok(res);
985 }
986 }
987
988 if let Ok(val) = input.try_parse(Percentage::parse) {
989 return Ok(val.0);
990 } else if self.allow_none {
991 input.expect_ident_matching("none")?;
992 Ok(f32::NAN)
993 } else {
994 Err(input.new_custom_error(ParserError::InvalidValue))
995 }
996 }
997
998 fn parse_number_or_percentage<'t>(
999 &self,
1000 input: &mut Parser<'i, 't>,
1001 ) -> Result<NumberOrPercentage, ParseError<'i, Self::Error>> {
1002 if let Some(from) = &self.from {
1003 if let Ok(res) = input.try_parse(|input| from.parse_number_or_percentage(input)) {
1004 return Ok(res);
1005 }
1006 }
1007
1008 if let Ok(value) = input.try_parse(CSSNumber::parse) {
1009 Ok(NumberOrPercentage::Number { value })
1010 } else if let Ok(value) = input.try_parse(Percentage::parse) {
1011 Ok(NumberOrPercentage::Percentage { unit_value: value.0 })
1012 } else if self.allow_none {
1013 input.expect_ident_matching("none")?;
1014 Ok(NumberOrPercentage::Number { value: f32::NAN })
1015 } else {
1016 Err(input.new_custom_error(ParserError::InvalidValue))
1017 }
1018 }
1019}
1020
1021fn parse_color_function<'i, 't>(
1023 location: SourceLocation,
1024 function: CowRcStr<'i>,
1025 input: &mut Parser<'i, 't>,
1026) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1027 let mut parser = ComponentParser::new(true);
1028
1029 match_ignore_ascii_case! {&*function,
1030 "lab" => {
1031 parse_lab::<LAB, _>(input, &mut parser, 100.0, 125.0, |l, a, b, alpha| {
1032 LABColor::LAB(LAB { l, a, b, alpha })
1033 })
1034 },
1035 "oklab" => {
1036 parse_lab::<OKLAB, _>(input, &mut parser, 1.0, 0.4, |l, a, b, alpha| {
1037 LABColor::OKLAB(OKLAB { l, a, b, alpha })
1038 })
1039 },
1040 "lch" => {
1041 parse_lch::<LCH, _>(input, &mut parser, 100.0, 150.0, |l, c, h, alpha| {
1042 LABColor::LCH(LCH { l, c, h, alpha })
1043 })
1044 },
1045 "oklch" => {
1046 parse_lch::<OKLCH, _>(input, &mut parser, 1.0, 0.4, |l, c, h, alpha| {
1047 LABColor::OKLCH(OKLCH { l, c, h, alpha })
1048 })
1049 },
1050 "color" => {
1051 let predefined = parse_predefined(input, &mut parser)?;
1052 Ok(predefined)
1053 },
1054 "hsl" | "hsla" => {
1055 parse_hsl_hwb::<HSL, _>(input, &mut parser, true, |h, s, l, a| {
1056 let hsl = HSL { h, s, l, alpha: a };
1057 if !h.is_nan() && !s.is_nan() && !l.is_nan() && !a.is_nan() {
1058 CssColor::RGBA(hsl.into())
1059 } else {
1060 CssColor::Float(Box::new(FloatColor::HSL(hsl)))
1061 }
1062 })
1063 },
1064 "hwb" => {
1065 parse_hsl_hwb::<HWB, _>(input, &mut parser, false, |h, w, b, a| {
1066 let hwb = HWB { h, w, b, alpha: a };
1067 if !h.is_nan() && !w.is_nan() && !b.is_nan() && !a.is_nan() {
1068 CssColor::RGBA(hwb.into())
1069 } else {
1070 CssColor::Float(Box::new(FloatColor::HWB(hwb)))
1071 }
1072 })
1073 },
1074 "rgb" | "rgba" => {
1075 parse_rgb(input, &mut parser)
1076 },
1077 "color-mix" => {
1078 input.parse_nested_block(parse_color_mix)
1079 },
1080 "light-dark" => {
1081 input.parse_nested_block(|input| {
1082 let light = match CssColor::parse(input)? {
1083 CssColor::LightDark(light, _) => light,
1084 light => Box::new(light)
1085 };
1086 input.expect_comma()?;
1087 let dark = match CssColor::parse(input)? {
1088 CssColor::LightDark(_, dark) => dark,
1089 dark => Box::new(dark)
1090 };
1091 Ok(CssColor::LightDark(light, dark))
1092 })
1093 },
1094 _ => Err(location.new_unexpected_token_error(
1095 cssparser::Token::Ident(function.clone())
1096 ))
1097 }
1098}
1099
1100#[inline]
1102fn parse_lab<'i, 't, T: TryFrom<CssColor> + ColorSpace, F: Fn(f32, f32, f32, f32) -> LABColor>(
1103 input: &mut Parser<'i, 't>,
1104 parser: &mut ComponentParser,
1105 l_basis: f32,
1106 ab_basis: f32,
1107 f: F,
1108) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1109 input.parse_nested_block(|input| {
1111 parser.parse_relative::<T, _, _>(input, |input, parser| {
1112 let l = parse_number_or_percentage(input, parser, l_basis)?.clamp(0.0, f32::MAX);
1114 let a = parse_number_or_percentage(input, parser, ab_basis)?;
1115 let b = parse_number_or_percentage(input, parser, ab_basis)?;
1116 let alpha = parse_alpha(input, parser)?;
1117 let lab = f(l, a, b, alpha);
1118
1119 Ok(CssColor::LAB(Box::new(lab)))
1120 })
1121 })
1122}
1123
1124#[inline]
1126fn parse_lch<'i, 't, T: TryFrom<CssColor> + ColorSpace, F: Fn(f32, f32, f32, f32) -> LABColor>(
1127 input: &mut Parser<'i, 't>,
1128 parser: &mut ComponentParser,
1129 l_basis: f32,
1130 c_basis: f32,
1131 f: F,
1132) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1133 input.parse_nested_block(|input| {
1135 parser.parse_relative::<T, _, _>(input, |input, parser| {
1136 if let Some(from) = &mut parser.from {
1137 from.components.2 %= 360.0;
1140 if from.components.2 < 0.0 {
1141 from.components.2 += 360.0;
1142 }
1143 }
1144
1145 let l = parse_number_or_percentage(input, parser, l_basis)?.clamp(0.0, f32::MAX);
1146 let c = parse_number_or_percentage(input, parser, c_basis)?.clamp(0.0, f32::MAX);
1147 let h = parse_angle_or_number(input, parser)?;
1148 let alpha = parse_alpha(input, parser)?;
1149 let lab = f(l, c, h, alpha);
1150
1151 Ok(CssColor::LAB(Box::new(lab)))
1152 })
1153 })
1154}
1155
1156#[inline]
1157fn parse_predefined<'i, 't>(
1158 input: &mut Parser<'i, 't>,
1159 parser: &mut ComponentParser,
1160) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1161 let res = input.parse_nested_block(|input| {
1163 let from = if input.try_parse(|input| input.expect_ident_matching("from")).is_ok() {
1164 Some(CssColor::parse(input)?)
1165 } else {
1166 None
1167 };
1168
1169 let colorspace = input.expect_ident_cloned()?;
1170
1171 if let Some(CssColor::LightDark(light, dark)) = from {
1172 let state = input.state();
1173 let light = parse_predefined_relative(input, parser, &colorspace, Some(&*light))?;
1174 input.reset(&state);
1175 let dark = parse_predefined_relative(input, parser, &colorspace, Some(&*dark))?;
1176 return Ok(CssColor::LightDark(Box::new(light), Box::new(dark)));
1177 }
1178
1179 parse_predefined_relative(input, parser, &colorspace, from.as_ref())
1180 })?;
1181
1182 Ok(res)
1183}
1184
1185#[inline]
1186fn parse_predefined_relative<'i, 't>(
1187 input: &mut Parser<'i, 't>,
1188 parser: &mut ComponentParser,
1189 colorspace: &CowRcStr<'i>,
1190 from: Option<&CssColor>,
1191) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1192 let location = input.current_source_location();
1193 if let Some(from) = from {
1194 let handle_error = |_| input.new_custom_error(ParserError::InvalidValue);
1195 parser.from = Some(match_ignore_ascii_case! { &*&colorspace,
1196 "srgb" => RelativeComponentParser::new(&SRGB::try_from(from).map_err(handle_error)?.resolve_missing()),
1197 "srgb-linear" => RelativeComponentParser::new(&SRGBLinear::try_from(from).map_err(handle_error)?.resolve_missing()),
1198 "display-p3" => RelativeComponentParser::new(&P3::try_from(from).map_err(handle_error)?.resolve_missing()),
1199 "a98-rgb" => RelativeComponentParser::new(&A98::try_from(from).map_err(handle_error)?.resolve_missing()),
1200 "prophoto-rgb" => RelativeComponentParser::new(&ProPhoto::try_from(from).map_err(handle_error)?.resolve_missing()),
1201 "rec2020" => RelativeComponentParser::new(&Rec2020::try_from(from).map_err(handle_error)?.resolve_missing()),
1202 "xyz-d50" => RelativeComponentParser::new(&XYZd50::try_from(from).map_err(handle_error)?.resolve_missing()),
1203 "xyz" | "xyz-d65" => RelativeComponentParser::new(&XYZd65::try_from(from).map_err(handle_error)?.resolve_missing()),
1204 _ => return Err(location.new_unexpected_token_error(
1205 cssparser::Token::Ident(colorspace.clone())
1206 ))
1207 });
1208 }
1209
1210 let a = input.try_parse(|input| parse_number_or_percentage(input, parser, 1.0))?;
1213 let b = input.try_parse(|input| parse_number_or_percentage(input, parser, 1.0))?;
1214 let c = input.try_parse(|input| parse_number_or_percentage(input, parser, 1.0))?;
1215 let alpha = parse_alpha(input, parser)?;
1216
1217 let res = match_ignore_ascii_case! { &*&colorspace,
1218 "srgb" => PredefinedColor::SRGB(SRGB { r: a, g: b, b: c, alpha }),
1219 "srgb-linear" => PredefinedColor::SRGBLinear(SRGBLinear { r: a, g: b, b: c, alpha }),
1220 "display-p3" => PredefinedColor::DisplayP3(P3 { r: a, g: b, b: c, alpha }),
1221 "a98-rgb" => PredefinedColor::A98(A98 { r: a, g: b, b: c, alpha }),
1222 "prophoto-rgb" => PredefinedColor::ProPhoto(ProPhoto { r: a, g: b, b: c, alpha }),
1223 "rec2020" => PredefinedColor::Rec2020(Rec2020 { r: a, g: b, b: c, alpha }),
1224 "xyz-d50" => PredefinedColor::XYZd50(XYZd50 { x: a, y: b, z: c, alpha}),
1225 "xyz" | "xyz-d65" => PredefinedColor::XYZd65(XYZd65 { x: a, y: b, z: c, alpha }),
1226 _ => return Err(location.new_unexpected_token_error(
1227 cssparser::Token::Ident(colorspace.clone())
1228 ))
1229 };
1230
1231 Ok(CssColor::Predefined(Box::new(res)))
1232}
1233
1234#[inline]
1237fn parse_hsl_hwb<'i, 't, T: TryFrom<CssColor> + ColorSpace, F: Fn(f32, f32, f32, f32) -> CssColor>(
1238 input: &mut Parser<'i, 't>,
1239 parser: &mut ComponentParser,
1240 allows_legacy: bool,
1241 f: F,
1242) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1243 input.parse_nested_block(|input| {
1245 parser.parse_relative::<T, _, _>(input, |input, parser| {
1246 let (h, a, b, is_legacy) = parse_hsl_hwb_components::<T>(input, parser, allows_legacy)?;
1247 let alpha = if is_legacy {
1248 parse_legacy_alpha(input, parser)?
1249 } else {
1250 parse_alpha(input, parser)?
1251 };
1252
1253 Ok(f(h, a, b, alpha))
1254 })
1255 })
1256}
1257
1258#[inline]
1259pub(crate) fn parse_hsl_hwb_components<'i, 't, T: TryFrom<CssColor> + ColorSpace>(
1260 input: &mut Parser<'i, 't>,
1261 parser: &mut ComponentParser,
1262 allows_legacy: bool,
1263) -> Result<(f32, f32, f32, bool), ParseError<'i, ParserError<'i>>> {
1264 let h = parse_angle_or_number(input, parser)?;
1265 let is_legacy_syntax =
1266 allows_legacy && parser.from.is_none() && !h.is_nan() && input.try_parse(|p| p.expect_comma()).is_ok();
1267 let a = parse_number_or_percentage(input, parser, 100.0)?.clamp(0.0, 100.0);
1268 if is_legacy_syntax {
1269 input.expect_comma()?;
1270 }
1271 let b = parse_number_or_percentage(input, parser, 100.0)?.clamp(0.0, 100.0);
1272 if is_legacy_syntax && (a.is_nan() || b.is_nan()) {
1273 return Err(input.new_custom_error(ParserError::InvalidValue));
1274 }
1275 Ok((h, a, b, is_legacy_syntax))
1276}
1277
1278#[inline]
1279fn parse_rgb<'i, 't>(
1280 input: &mut Parser<'i, 't>,
1281 parser: &mut ComponentParser,
1282) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1283 input.parse_nested_block(|input| {
1285 parser.parse_relative::<RGB, _, _>(input, |input, parser| {
1286 let (r, g, b, is_legacy) = parse_rgb_components(input, parser)?;
1287 let alpha = if is_legacy {
1288 parse_legacy_alpha(input, parser)?
1289 } else {
1290 parse_alpha(input, parser)?
1291 };
1292
1293 if !r.is_nan() && !g.is_nan() && !b.is_nan() && !alpha.is_nan() {
1294 if is_legacy {
1295 Ok(CssColor::RGBA(RGBA::new(r as u8, g as u8, b as u8, alpha)))
1296 } else {
1297 Ok(CssColor::RGBA(RGBA::from_floats(
1298 r / 255.0,
1299 g / 255.0,
1300 b / 255.0,
1301 alpha,
1302 )))
1303 }
1304 } else {
1305 Ok(CssColor::Float(Box::new(FloatColor::RGB(RGB { r, g, b, alpha }))))
1306 }
1307 })
1308 })
1309}
1310
1311#[inline]
1312pub(crate) fn parse_rgb_components<'i, 't>(
1313 input: &mut Parser<'i, 't>,
1314 parser: &mut ComponentParser,
1315) -> Result<(f32, f32, f32, bool), ParseError<'i, ParserError<'i>>> {
1316 let red = parser.parse_number_or_percentage(input)?;
1317 let is_legacy_syntax =
1318 parser.from.is_none() && !red.unit_value().is_nan() && input.try_parse(|p| p.expect_comma()).is_ok();
1319 let (r, g, b) = if is_legacy_syntax {
1320 match red {
1321 NumberOrPercentage::Number { value } => {
1322 let r = value.round().clamp(0.0, 255.0);
1323 let g = parser.parse_number(input)?.round().clamp(0.0, 255.0);
1324 input.expect_comma()?;
1325 let b = parser.parse_number(input)?.round().clamp(0.0, 255.0);
1326 (r, g, b)
1327 }
1328 NumberOrPercentage::Percentage { unit_value } => {
1329 let r = (unit_value * 255.0).round().clamp(0.0, 255.0);
1330 let g = (parser.parse_percentage(input)? * 255.0).round().clamp(0.0, 255.0);
1331 input.expect_comma()?;
1332 let b = (parser.parse_percentage(input)? * 255.0).round().clamp(0.0, 255.0);
1333 (r, g, b)
1334 }
1335 }
1336 } else {
1337 #[inline]
1338 fn get_component<'i, 't>(value: NumberOrPercentage) -> f32 {
1339 match value {
1340 NumberOrPercentage::Number { value } if value.is_nan() => value,
1341 NumberOrPercentage::Number { value } => value.round().clamp(0.0, 255.0),
1342 NumberOrPercentage::Percentage { unit_value } => (unit_value * 255.0).round().clamp(0.0, 255.0),
1343 }
1344 }
1345
1346 let r = get_component(red);
1347 let g = get_component(parser.parse_number_or_percentage(input)?);
1348 let b = get_component(parser.parse_number_or_percentage(input)?);
1349 (r, g, b)
1350 };
1351
1352 if is_legacy_syntax && (g.is_nan() || b.is_nan()) {
1353 return Err(input.new_custom_error(ParserError::InvalidValue));
1354 }
1355 Ok((r, g, b, is_legacy_syntax))
1356}
1357
1358#[inline]
1359fn parse_angle_or_number<'i, 't>(
1360 input: &mut Parser<'i, 't>,
1361 parser: &ComponentParser,
1362) -> Result<f32, ParseError<'i, ParserError<'i>>> {
1363 Ok(match parser.parse_angle_or_number(input)? {
1364 AngleOrNumber::Number { value } => value,
1365 AngleOrNumber::Angle { degrees } => degrees,
1366 })
1367}
1368
1369#[inline]
1370fn parse_number_or_percentage<'i, 't>(
1371 input: &mut Parser<'i, 't>,
1372 parser: &ComponentParser,
1373 percent_basis: f32,
1374) -> Result<f32, ParseError<'i, ParserError<'i>>> {
1375 Ok(match parser.parse_number_or_percentage(input)? {
1376 NumberOrPercentage::Number { value } => value,
1377 NumberOrPercentage::Percentage { unit_value } => unit_value * percent_basis,
1378 })
1379}
1380
1381#[inline]
1382fn parse_alpha<'i, 't>(
1383 input: &mut Parser<'i, 't>,
1384 parser: &ComponentParser,
1385) -> Result<f32, ParseError<'i, ParserError<'i>>> {
1386 let res = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1387 parse_number_or_percentage(input, parser, 1.0)?.clamp(0.0, 1.0)
1388 } else {
1389 1.0
1390 };
1391 Ok(res)
1392}
1393
1394#[inline]
1395fn parse_legacy_alpha<'i, 't>(
1396 input: &mut Parser<'i, 't>,
1397 parser: &ComponentParser,
1398) -> Result<f32, ParseError<'i, ParserError<'i>>> {
1399 Ok(if !input.is_exhausted() {
1400 input.expect_comma()?;
1401 parse_number_or_percentage(input, parser, 1.0)?.clamp(0.0, 1.0)
1402 } else {
1403 1.0
1404 })
1405}
1406
1407#[inline]
1408fn write_components<W>(
1409 name: &str,
1410 a: f32,
1411 b: f32,
1412 c: f32,
1413 alpha: f32,
1414 dest: &mut Printer<W>,
1415) -> Result<(), PrinterError>
1416where
1417 W: std::fmt::Write,
1418{
1419 dest.write_str(name)?;
1420 dest.write_char('(')?;
1421 if a.is_nan() {
1422 dest.write_str("none")?;
1423 } else {
1424 Percentage(a).to_css(dest)?;
1425 }
1426 dest.write_char(' ')?;
1427 write_component(b, dest)?;
1428 dest.write_char(' ')?;
1429 write_component(c, dest)?;
1430 if alpha.is_nan() || (alpha - 1.0).abs() > f32::EPSILON {
1431 dest.delim('/', true)?;
1432 write_component(alpha, dest)?;
1433 }
1434
1435 dest.write_char(')')
1436}
1437
1438#[inline]
1439fn write_component<W>(c: f32, dest: &mut Printer<W>) -> Result<(), PrinterError>
1440where
1441 W: std::fmt::Write,
1442{
1443 if c.is_nan() {
1444 dest.write_str("none")?;
1445 } else {
1446 c.to_css(dest)?;
1447 }
1448 Ok(())
1449}
1450
1451#[inline]
1452fn write_predefined<W>(predefined: &PredefinedColor, dest: &mut Printer<W>) -> Result<(), PrinterError>
1453where
1454 W: std::fmt::Write,
1455{
1456 use PredefinedColor::*;
1457
1458 let (name, a, b, c, alpha) = match predefined {
1459 SRGB(rgb) => ("srgb", rgb.r, rgb.g, rgb.b, rgb.alpha),
1460 SRGBLinear(rgb) => ("srgb-linear", rgb.r, rgb.g, rgb.b, rgb.alpha),
1461 DisplayP3(rgb) => ("display-p3", rgb.r, rgb.g, rgb.b, rgb.alpha),
1462 A98(rgb) => ("a98-rgb", rgb.r, rgb.g, rgb.b, rgb.alpha),
1463 ProPhoto(rgb) => ("prophoto-rgb", rgb.r, rgb.g, rgb.b, rgb.alpha),
1464 Rec2020(rgb) => ("rec2020", rgb.r, rgb.g, rgb.b, rgb.alpha),
1465 XYZd50(xyz) => ("xyz-d50", xyz.x, xyz.y, xyz.z, xyz.alpha),
1466 XYZd65(xyz) => ("xyz", xyz.x, xyz.y, xyz.z, xyz.alpha),
1468 };
1469
1470 dest.write_str("color(")?;
1471 dest.write_str(name)?;
1472 dest.write_char(' ')?;
1473 write_component(a, dest)?;
1474 dest.write_char(' ')?;
1475 write_component(b, dest)?;
1476 dest.write_char(' ')?;
1477 write_component(c, dest)?;
1478
1479 if alpha.is_nan() || (alpha - 1.0).abs() > f32::EPSILON {
1480 dest.delim('/', true)?;
1481 write_component(alpha, dest)?;
1482 }
1483
1484 dest.write_char(')')
1485}
1486
1487bitflags! {
1488 #[derive(PartialEq, Eq, Clone, Copy)]
1490 pub struct ChannelType: u8 {
1491 const Percentage = 0b001;
1493 const Angle = 0b010;
1495 const Number = 0b100;
1497 }
1498}
1499
1500pub trait ColorSpace {
1502 fn components(&self) -> (f32, f32, f32, f32);
1504 fn channels(&self) -> (&'static str, &'static str, &'static str);
1506 fn types(&self) -> (ChannelType, ChannelType, ChannelType);
1508 fn resolve_missing(&self) -> Self;
1510 fn resolve(&self) -> Self;
1513}
1514
1515macro_rules! define_colorspace {
1516 (
1517 $(#[$outer:meta])*
1518 $vis:vis struct $name:ident {
1519 $(#[$a_meta: meta])*
1520 $a: ident: $at: ident,
1521 $(#[$b_meta: meta])*
1522 $b: ident: $bt: ident,
1523 $(#[$c_meta: meta])*
1524 $c: ident: $ct: ident
1525 }
1526 ) => {
1527 $(#[$outer])*
1528 #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))]
1529 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1530 #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1531 pub struct $name {
1532 $(#[$a_meta])*
1533 pub $a: f32,
1534 $(#[$b_meta])*
1535 pub $b: f32,
1536 $(#[$c_meta])*
1537 pub $c: f32,
1538 pub alpha: f32,
1540 }
1541
1542 impl ColorSpace for $name {
1543 fn components(&self) -> (f32, f32, f32, f32) {
1544 (self.$a, self.$b, self.$c, self.alpha)
1545 }
1546
1547 fn channels(&self) -> (&'static str, &'static str, &'static str) {
1548 (stringify!($a), stringify!($b), stringify!($c))
1549 }
1550
1551 fn types(&self) -> (ChannelType, ChannelType, ChannelType) {
1552 (ChannelType::$at, ChannelType::$bt, ChannelType::$ct)
1553 }
1554
1555 #[inline]
1556 fn resolve_missing(&self) -> Self {
1557 Self {
1558 $a: if self.$a.is_nan() { 0.0 } else { self.$a },
1559 $b: if self.$b.is_nan() { 0.0 } else { self.$b },
1560 $c: if self.$c.is_nan() { 0.0 } else { self.$c },
1561 alpha: if self.alpha.is_nan() { 0.0 } else { self.alpha },
1562 }
1563 }
1564
1565 #[inline]
1566 fn resolve(&self) -> Self {
1567 let mut resolved = self.resolve_missing();
1568 if !resolved.in_gamut() {
1569 resolved = map_gamut(resolved);
1570 }
1571 resolved
1572 }
1573 }
1574 };
1575}
1576
1577define_colorspace! {
1578 pub struct SRGB {
1580 r: Number,
1582 g: Number,
1584 b: Number
1586 }
1587}
1588
1589#[derive(Clone, Copy, PartialEq, Debug)]
1592pub struct RGBA {
1593 pub red: u8,
1595 pub green: u8,
1597 pub blue: u8,
1599 pub alpha: u8,
1601}
1602
1603impl RGBA {
1604 #[inline]
1608 pub fn from_floats(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
1609 Self::new(clamp_unit_f32(red), clamp_unit_f32(green), clamp_unit_f32(blue), alpha)
1610 }
1611
1612 #[inline]
1614 pub fn transparent() -> Self {
1615 Self::new(0, 0, 0, 0.0)
1616 }
1617
1618 #[inline]
1620 pub fn new(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
1621 RGBA {
1622 red,
1623 green,
1624 blue,
1625 alpha: clamp_unit_f32(alpha),
1626 }
1627 }
1628
1629 #[inline]
1631 pub fn red_f32(&self) -> f32 {
1632 self.red as f32 / 255.0
1633 }
1634
1635 #[inline]
1637 pub fn green_f32(&self) -> f32 {
1638 self.green as f32 / 255.0
1639 }
1640
1641 #[inline]
1643 pub fn blue_f32(&self) -> f32 {
1644 self.blue as f32 / 255.0
1645 }
1646
1647 #[inline]
1649 pub fn alpha_f32(&self) -> f32 {
1650 self.alpha as f32 / 255.0
1651 }
1652}
1653
1654fn clamp_unit_f32(val: f32) -> u8 {
1655 clamp_floor_256_f32(val * 255.)
1670}
1671
1672fn clamp_floor_256_f32(val: f32) -> u8 {
1673 val.round().max(0.).min(255.) as u8
1674}
1675
1676define_colorspace! {
1677 pub struct RGB {
1680 r: Number,
1682 g: Number,
1684 b: Number
1686 }
1687}
1688
1689define_colorspace! {
1690 pub struct SRGBLinear {
1692 r: Number,
1694 g: Number,
1696 b: Number
1698 }
1699}
1700
1701define_colorspace! {
1702 pub struct P3 {
1704 r: Number,
1706 g: Number,
1708 b: Number
1710 }
1711}
1712
1713define_colorspace! {
1714 pub struct A98 {
1716 r: Number,
1718 g: Number,
1720 b: Number
1722 }
1723}
1724
1725define_colorspace! {
1726 pub struct ProPhoto {
1728 r: Number,
1730 g: Number,
1732 b: Number
1734 }
1735}
1736
1737define_colorspace! {
1738 pub struct Rec2020 {
1740 r: Number,
1742 g: Number,
1744 b: Number
1746 }
1747}
1748
1749define_colorspace! {
1750 pub struct LAB {
1752 l: Number,
1754 a: Number,
1756 b: Number
1758 }
1759}
1760
1761define_colorspace! {
1762 pub struct LCH {
1764 l: Number,
1766 c: Number,
1768 h: Angle
1770 }
1771}
1772
1773define_colorspace! {
1774 pub struct OKLAB {
1776 l: Number,
1778 a: Number,
1780 b: Number
1782 }
1783}
1784
1785define_colorspace! {
1786 pub struct OKLCH {
1788 l: Number,
1790 c: Number,
1792 h: Angle
1794 }
1795}
1796
1797define_colorspace! {
1798 pub struct XYZd50 {
1800 x: Number,
1802 y: Number,
1804 z: Number
1806 }
1807}
1808
1809define_colorspace! {
1810 pub struct XYZd65 {
1812 x: Number,
1814 y: Number,
1816 z: Number
1818 }
1819}
1820
1821define_colorspace! {
1822 pub struct HSL {
1824 h: Angle,
1826 s: Number,
1828 l: Number
1830 }
1831}
1832
1833define_colorspace! {
1834 pub struct HWB {
1836 h: Angle,
1838 w: Number,
1840 b: Number
1842 }
1843}
1844
1845macro_rules! via {
1846 ($t: ident -> $u: ident -> $v: ident) => {
1847 impl From<$t> for $v {
1848 #[inline]
1849 fn from(t: $t) -> $v {
1850 let xyz: $u = t.into();
1851 xyz.into()
1852 }
1853 }
1854
1855 impl From<$v> for $t {
1856 #[inline]
1857 fn from(t: $v) -> $t {
1858 let xyz: $u = t.into();
1859 xyz.into()
1860 }
1861 }
1862 };
1863}
1864
1865#[inline]
1866fn rectangular_to_polar(l: f32, a: f32, b: f32) -> (f32, f32, f32) {
1867 let mut h = b.atan2(a) * 180.0 / PI;
1869 if h < 0.0 {
1870 h += 360.0;
1871 }
1872 let c = (a.powi(2) + b.powi(2)).sqrt();
1873 h = h % 360.0;
1874 (l, c, h)
1875}
1876
1877#[inline]
1878fn polar_to_rectangular(l: f32, c: f32, h: f32) -> (f32, f32, f32) {
1879 let a = c * (h * PI / 180.0).cos();
1881 let b = c * (h * PI / 180.0).sin();
1882 (l, a, b)
1883}
1884
1885impl From<LCH> for LAB {
1886 fn from(lch: LCH) -> LAB {
1887 let lch = lch.resolve_missing();
1888 let (l, a, b) = polar_to_rectangular(lch.l, lch.c, lch.h);
1889 LAB {
1890 l,
1891 a,
1892 b,
1893 alpha: lch.alpha,
1894 }
1895 }
1896}
1897
1898impl From<LAB> for LCH {
1899 fn from(lab: LAB) -> LCH {
1900 let lab = lab.resolve_missing();
1901 let (l, c, h) = rectangular_to_polar(lab.l, lab.a, lab.b);
1902 LCH {
1903 l,
1904 c,
1905 h,
1906 alpha: lab.alpha,
1907 }
1908 }
1909}
1910
1911impl From<OKLCH> for OKLAB {
1912 fn from(lch: OKLCH) -> OKLAB {
1913 let lch = lch.resolve_missing();
1914 let (l, a, b) = polar_to_rectangular(lch.l, lch.c, lch.h);
1915 OKLAB {
1916 l,
1917 a,
1918 b,
1919 alpha: lch.alpha,
1920 }
1921 }
1922}
1923
1924impl From<OKLAB> for OKLCH {
1925 fn from(lab: OKLAB) -> OKLCH {
1926 let lab = lab.resolve_missing();
1927 let (l, c, h) = rectangular_to_polar(lab.l, lab.a, lab.b);
1928 OKLCH {
1929 l,
1930 c,
1931 h,
1932 alpha: lab.alpha,
1933 }
1934 }
1935}
1936
1937const D50: &[f32] = &[0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585];
1938
1939impl From<LAB> for XYZd50 {
1940 fn from(lab: LAB) -> XYZd50 {
1941 const K: f32 = 24389.0 / 27.0; const E: f32 = 216.0 / 24389.0; let lab = lab.resolve_missing();
1946 let l = lab.l;
1947 let a = lab.a;
1948 let b = lab.b;
1949
1950 let f1 = (l + 16.0) / 116.0;
1952 let f0 = a / 500.0 + f1;
1953 let f2 = f1 - b / 200.0;
1954
1955 let x = if f0.powi(3) > E {
1957 f0.powi(3)
1958 } else {
1959 (116.0 * f0 - 16.0) / K
1960 };
1961
1962 let y = if l > K * E { ((l + 16.0) / 116.0).powi(3) } else { l / K };
1963
1964 let z = if f2.powi(3) > E {
1965 f2.powi(3)
1966 } else {
1967 (116.0 * f2 - 16.0) / K
1968 };
1969
1970 XYZd50 {
1972 x: x * D50[0],
1973 y: y * D50[1],
1974 z: z * D50[2],
1975 alpha: lab.alpha,
1976 }
1977 }
1978}
1979
1980impl From<XYZd50> for XYZd65 {
1981 fn from(xyz: XYZd50) -> XYZd65 {
1982 const MATRIX: &[f32] = &[
1984 0.9554734527042182,
1985 -0.023098536874261423,
1986 0.0632593086610217,
1987 -0.028369706963208136,
1988 1.0099954580058226,
1989 0.021041398966943008,
1990 0.012314001688319899,
1991 -0.020507696433477912,
1992 1.3303659366080753,
1993 ];
1994
1995 let xyz = xyz.resolve_missing();
1996 let (x, y, z) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
1997 XYZd65 {
1998 x,
1999 y,
2000 z,
2001 alpha: xyz.alpha,
2002 }
2003 }
2004}
2005
2006impl From<XYZd65> for XYZd50 {
2007 fn from(xyz: XYZd65) -> XYZd50 {
2008 const MATRIX: &[f32] = &[
2010 1.0479298208405488,
2011 0.022946793341019088,
2012 -0.05019222954313557,
2013 0.029627815688159344,
2014 0.990434484573249,
2015 -0.01707382502938514,
2016 -0.009243058152591178,
2017 0.015055144896577895,
2018 0.7518742899580008,
2019 ];
2020
2021 let xyz = xyz.resolve_missing();
2022 let (x, y, z) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2023 XYZd50 {
2024 x,
2025 y,
2026 z,
2027 alpha: xyz.alpha,
2028 }
2029 }
2030}
2031
2032impl From<XYZd65> for SRGBLinear {
2033 fn from(xyz: XYZd65) -> SRGBLinear {
2034 const MATRIX: &[f32] = &[
2036 3.2409699419045226,
2037 -1.537383177570094,
2038 -0.4986107602930034,
2039 -0.9692436362808796,
2040 1.8759675015077202,
2041 0.04155505740717559,
2042 0.05563007969699366,
2043 -0.20397695888897652,
2044 1.0569715142428786,
2045 ];
2046
2047 let xyz = xyz.resolve_missing();
2048 let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2049 SRGBLinear {
2050 r,
2051 g,
2052 b,
2053 alpha: xyz.alpha,
2054 }
2055 }
2056}
2057
2058#[inline]
2059fn multiply_matrix(m: &[f32], x: f32, y: f32, z: f32) -> (f32, f32, f32) {
2060 let a = m[0] * x + m[1] * y + m[2] * z;
2061 let b = m[3] * x + m[4] * y + m[5] * z;
2062 let c = m[6] * x + m[7] * y + m[8] * z;
2063 (a, b, c)
2064}
2065
2066impl From<SRGBLinear> for SRGB {
2067 #[inline]
2068 fn from(rgb: SRGBLinear) -> SRGB {
2069 let rgb = rgb.resolve_missing();
2070 let (r, g, b) = gam_srgb(rgb.r, rgb.g, rgb.b);
2071 SRGB {
2072 r,
2073 g,
2074 b,
2075 alpha: rgb.alpha,
2076 }
2077 }
2078}
2079
2080fn gam_srgb(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
2081 #[inline]
2090 fn gam_srgb_component(c: f32) -> f32 {
2091 let abs = c.abs();
2092 if abs > 0.0031308 {
2093 let sign = if c < 0.0 { -1.0 } else { 1.0 };
2094 return sign * (1.055 * abs.powf(1.0 / 2.4) - 0.055);
2095 }
2096
2097 return 12.92 * c;
2098 }
2099
2100 let r = gam_srgb_component(r);
2101 let g = gam_srgb_component(g);
2102 let b = gam_srgb_component(b);
2103 (r, g, b)
2104}
2105
2106impl From<OKLAB> for XYZd65 {
2107 fn from(lab: OKLAB) -> XYZd65 {
2108 const LMS_TO_XYZ: &[f32] = &[
2110 1.2268798733741557,
2111 -0.5578149965554813,
2112 0.28139105017721583,
2113 -0.04057576262431372,
2114 1.1122868293970594,
2115 -0.07171106666151701,
2116 -0.07637294974672142,
2117 -0.4214933239627914,
2118 1.5869240244272418,
2119 ];
2120
2121 const OKLAB_TO_LMS: &[f32] = &[
2122 0.99999999845051981432,
2123 0.39633779217376785678,
2124 0.21580375806075880339,
2125 1.0000000088817607767,
2126 -0.1055613423236563494,
2127 -0.063854174771705903402,
2128 1.0000000546724109177,
2129 -0.089484182094965759684,
2130 -1.2914855378640917399,
2131 ];
2132
2133 let lab = lab.resolve_missing();
2134 let (a, b, c) = multiply_matrix(OKLAB_TO_LMS, lab.l, lab.a, lab.b);
2135 let (x, y, z) = multiply_matrix(LMS_TO_XYZ, a.powi(3), b.powi(3), c.powi(3));
2136 XYZd65 {
2137 x,
2138 y,
2139 z,
2140 alpha: lab.alpha,
2141 }
2142 }
2143}
2144
2145impl From<XYZd65> for OKLAB {
2146 fn from(xyz: XYZd65) -> OKLAB {
2147 const XYZ_TO_LMS: &[f32] = &[
2149 0.8190224432164319,
2150 0.3619062562801221,
2151 -0.12887378261216414,
2152 0.0329836671980271,
2153 0.9292868468965546,
2154 0.03614466816999844,
2155 0.048177199566046255,
2156 0.26423952494422764,
2157 0.6335478258136937,
2158 ];
2159
2160 const LMS_TO_OKLAB: &[f32] = &[
2161 0.2104542553,
2162 0.7936177850,
2163 -0.0040720468,
2164 1.9779984951,
2165 -2.4285922050,
2166 0.4505937099,
2167 0.0259040371,
2168 0.7827717662,
2169 -0.8086757660,
2170 ];
2171
2172 let xyz = xyz.resolve_missing();
2173 let (a, b, c) = multiply_matrix(XYZ_TO_LMS, xyz.x, xyz.y, xyz.z);
2174 let (l, a, b) = multiply_matrix(LMS_TO_OKLAB, a.cbrt(), b.cbrt(), c.cbrt());
2175 OKLAB {
2176 l,
2177 a,
2178 b,
2179 alpha: xyz.alpha,
2180 }
2181 }
2182}
2183
2184impl From<XYZd50> for LAB {
2185 fn from(xyz: XYZd50) -> LAB {
2186 const E: f32 = 216.0 / 24389.0; const K: f32 = 24389.0 / 27.0; let xyz = xyz.resolve_missing();
2194 let x = xyz.x / D50[0];
2195 let y = xyz.y / D50[1];
2196 let z = xyz.z / D50[2];
2197
2198 let f0 = if x > E { x.cbrt() } else { (K * x + 16.0) / 116.0 };
2200
2201 let f1 = if y > E { y.cbrt() } else { (K * y + 16.0) / 116.0 };
2202
2203 let f2 = if z > E { z.cbrt() } else { (K * z + 16.0) / 116.0 };
2204
2205 let l = (116.0 * f1) - 16.0;
2206 let a = 500.0 * (f0 - f1);
2207 let b = 200.0 * (f1 - f2);
2208 LAB {
2209 l,
2210 a,
2211 b,
2212 alpha: xyz.alpha,
2213 }
2214 }
2215}
2216
2217impl From<SRGB> for SRGBLinear {
2218 fn from(rgb: SRGB) -> SRGBLinear {
2219 let rgb = rgb.resolve_missing();
2220 let (r, g, b) = lin_srgb(rgb.r, rgb.g, rgb.b);
2221 SRGBLinear {
2222 r,
2223 g,
2224 b,
2225 alpha: rgb.alpha,
2226 }
2227 }
2228}
2229
2230fn lin_srgb(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
2231 #[inline]
2240 fn lin_srgb_component(c: f32) -> f32 {
2241 let abs = c.abs();
2242 if abs < 0.04045 {
2243 return c / 12.92;
2244 }
2245
2246 let sign = if c < 0.0 { -1.0 } else { 1.0 };
2247 sign * ((abs + 0.055) / 1.055).powf(2.4)
2248 }
2249
2250 let r = lin_srgb_component(r);
2251 let g = lin_srgb_component(g);
2252 let b = lin_srgb_component(b);
2253 (r, g, b)
2254}
2255
2256impl From<SRGBLinear> for XYZd65 {
2257 fn from(rgb: SRGBLinear) -> XYZd65 {
2258 const MATRIX: &[f32] = &[
2262 0.41239079926595934,
2263 0.357584339383878,
2264 0.1804807884018343,
2265 0.21263900587151027,
2266 0.715168678767756,
2267 0.07219231536073371,
2268 0.01933081871559182,
2269 0.11919477979462598,
2270 0.9505321522496607,
2271 ];
2272
2273 let rgb = rgb.resolve_missing();
2274 let (x, y, z) = multiply_matrix(MATRIX, rgb.r, rgb.g, rgb.b);
2275 XYZd65 {
2276 x,
2277 y,
2278 z,
2279 alpha: rgb.alpha,
2280 }
2281 }
2282}
2283
2284impl From<XYZd65> for P3 {
2285 fn from(xyz: XYZd65) -> P3 {
2286 const MATRIX: &[f32] = &[
2288 2.493496911941425,
2289 -0.9313836179191239,
2290 -0.40271078445071684,
2291 -0.8294889695615747,
2292 1.7626640603183463,
2293 0.023624685841943577,
2294 0.03584583024378447,
2295 -0.07617238926804182,
2296 0.9568845240076872,
2297 ];
2298
2299 let xyz = xyz.resolve_missing();
2300 let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2301 let (r, g, b) = gam_srgb(r, g, b); P3 {
2303 r,
2304 g,
2305 b,
2306 alpha: xyz.alpha,
2307 }
2308 }
2309}
2310
2311impl From<P3> for XYZd65 {
2312 fn from(p3: P3) -> XYZd65 {
2313 const MATRIX: &[f32] = &[
2318 0.4865709486482162,
2319 0.26566769316909306,
2320 0.1982172852343625,
2321 0.2289745640697488,
2322 0.6917385218365064,
2323 0.079286914093745,
2324 0.0000000000000000,
2325 0.04511338185890264,
2326 1.043944368900976,
2327 ];
2328
2329 let p3 = p3.resolve_missing();
2330 let (r, g, b) = lin_srgb(p3.r, p3.g, p3.b);
2331 let (x, y, z) = multiply_matrix(MATRIX, r, g, b);
2332 XYZd65 {
2333 x,
2334 y,
2335 z,
2336 alpha: p3.alpha,
2337 }
2338 }
2339}
2340
2341impl From<A98> for XYZd65 {
2342 fn from(a98: A98) -> XYZd65 {
2343 #[inline]
2345 fn lin_a98rgb_component(c: f32) -> f32 {
2346 let sign = if c < 0.0 { -1.0 } else { 1.0 };
2347 sign * c.abs().powf(563.0 / 256.0)
2348 }
2349
2350 let a98 = a98.resolve_missing();
2354 let r = lin_a98rgb_component(a98.r);
2355 let g = lin_a98rgb_component(a98.g);
2356 let b = lin_a98rgb_component(a98.b);
2357
2358 const MATRIX: &[f32] = &[
2366 0.5766690429101305,
2367 0.1855582379065463,
2368 0.1882286462349947,
2369 0.29734497525053605,
2370 0.6273635662554661,
2371 0.07529145849399788,
2372 0.02703136138641234,
2373 0.07068885253582723,
2374 0.9913375368376388,
2375 ];
2376
2377 let (x, y, z) = multiply_matrix(MATRIX, r, g, b);
2378 XYZd65 {
2379 x,
2380 y,
2381 z,
2382 alpha: a98.alpha,
2383 }
2384 }
2385}
2386
2387impl From<XYZd65> for A98 {
2388 fn from(xyz: XYZd65) -> A98 {
2389 const MATRIX: &[f32] = &[
2392 2.0415879038107465,
2393 -0.5650069742788596,
2394 -0.34473135077832956,
2395 -0.9692436362808795,
2396 1.8759675015077202,
2397 0.04155505740717557,
2398 0.013444280632031142,
2399 -0.11836239223101838,
2400 1.0151749943912054,
2401 ];
2402
2403 #[inline]
2404 fn gam_a98_component(c: f32) -> f32 {
2405 let sign = if c < 0.0 { -1.0 } else { 1.0 };
2410 sign * c.abs().powf(256.0 / 563.0)
2411 }
2412
2413 let xyz = xyz.resolve_missing();
2414 let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2415 let r = gam_a98_component(r);
2416 let g = gam_a98_component(g);
2417 let b = gam_a98_component(b);
2418 A98 {
2419 r,
2420 g,
2421 b,
2422 alpha: xyz.alpha,
2423 }
2424 }
2425}
2426
2427impl From<ProPhoto> for XYZd50 {
2428 fn from(prophoto: ProPhoto) -> XYZd50 {
2429 #[inline]
2437 fn lin_prophoto_component(c: f32) -> f32 {
2438 const ET2: f32 = 16.0 / 512.0;
2439 let abs = c.abs();
2440 if abs <= ET2 {
2441 return c / 16.0;
2442 }
2443
2444 let sign = if c < 0.0 { -1.0 } else { 1.0 };
2445 sign * c.powf(1.8)
2446 }
2447
2448 let prophoto = prophoto.resolve_missing();
2449 let r = lin_prophoto_component(prophoto.r);
2450 let g = lin_prophoto_component(prophoto.g);
2451 let b = lin_prophoto_component(prophoto.b);
2452
2453 const MATRIX: &[f32] = &[
2458 0.7977604896723027,
2459 0.13518583717574031,
2460 0.0313493495815248,
2461 0.2880711282292934,
2462 0.7118432178101014,
2463 0.00008565396060525902,
2464 0.0,
2465 0.0,
2466 0.8251046025104601,
2467 ];
2468
2469 let (x, y, z) = multiply_matrix(MATRIX, r, g, b);
2470 XYZd50 {
2471 x,
2472 y,
2473 z,
2474 alpha: prophoto.alpha,
2475 }
2476 }
2477}
2478
2479impl From<XYZd50> for ProPhoto {
2480 fn from(xyz: XYZd50) -> ProPhoto {
2481 const MATRIX: &[f32] = &[
2483 1.3457989731028281,
2484 -0.25558010007997534,
2485 -0.05110628506753401,
2486 -0.5446224939028347,
2487 1.5082327413132781,
2488 0.02053603239147973,
2489 0.0,
2490 0.0,
2491 1.2119675456389454,
2492 ];
2493
2494 #[inline]
2495 fn gam_prophoto_component(c: f32) -> f32 {
2496 const ET: f32 = 1.0 / 512.0;
2502 let abs = c.abs();
2503 if abs >= ET {
2504 let sign = if c < 0.0 { -1.0 } else { 1.0 };
2505 return sign * abs.powf(1.0 / 1.8);
2506 }
2507
2508 16.0 * c
2509 }
2510
2511 let xyz = xyz.resolve_missing();
2512 let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2513 let r = gam_prophoto_component(r);
2514 let g = gam_prophoto_component(g);
2515 let b = gam_prophoto_component(b);
2516 ProPhoto {
2517 r,
2518 g,
2519 b,
2520 alpha: xyz.alpha,
2521 }
2522 }
2523}
2524
2525impl From<Rec2020> for XYZd65 {
2526 fn from(rec2020: Rec2020) -> XYZd65 {
2527 #[inline]
2533 fn lin_rec2020_component(c: f32) -> f32 {
2534 const A: f32 = 1.09929682680944;
2535 const B: f32 = 0.018053968510807;
2536
2537 let abs = c.abs();
2538 if abs < B * 4.5 {
2539 return c / 4.5;
2540 }
2541
2542 let sign = if c < 0.0 { -1.0 } else { 1.0 };
2543 sign * ((abs + A - 1.0) / A).powf(1.0 / 0.45)
2544 }
2545
2546 let rec2020 = rec2020.resolve_missing();
2547 let r = lin_rec2020_component(rec2020.r);
2548 let g = lin_rec2020_component(rec2020.g);
2549 let b = lin_rec2020_component(rec2020.b);
2550
2551 const MATRIX: &[f32] = &[
2556 0.6369580483012914,
2557 0.14461690358620832,
2558 0.1688809751641721,
2559 0.2627002120112671,
2560 0.6779980715188708,
2561 0.05930171646986196,
2562 0.000000000000000,
2563 0.028072693049087428,
2564 1.060985057710791,
2565 ];
2566
2567 let (x, y, z) = multiply_matrix(MATRIX, r, g, b);
2568 XYZd65 {
2569 x,
2570 y,
2571 z,
2572 alpha: rec2020.alpha,
2573 }
2574 }
2575}
2576
2577impl From<XYZd65> for Rec2020 {
2578 fn from(xyz: XYZd65) -> Rec2020 {
2579 const MATRIX: &[f32] = &[
2581 1.7166511879712674,
2582 -0.35567078377639233,
2583 -0.25336628137365974,
2584 -0.6666843518324892,
2585 1.6164812366349395,
2586 0.01576854581391113,
2587 0.017639857445310783,
2588 -0.042770613257808524,
2589 0.9421031212354738,
2590 ];
2591
2592 #[inline]
2593 fn gam_rec2020_component(c: f32) -> f32 {
2594 const A: f32 = 1.09929682680944;
2599 const B: f32 = 0.018053968510807;
2600
2601 let abs = c.abs();
2602 if abs > B {
2603 let sign = if c < 0.0 { -1.0 } else { 1.0 };
2604 return sign * (A * abs.powf(0.45) - (A - 1.0));
2605 }
2606
2607 4.5 * c
2608 }
2609
2610 let xyz = xyz.resolve_missing();
2611 let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2612 let r = gam_rec2020_component(r);
2613 let g = gam_rec2020_component(g);
2614 let b = gam_rec2020_component(b);
2615 Rec2020 {
2616 r,
2617 g,
2618 b,
2619 alpha: xyz.alpha,
2620 }
2621 }
2622}
2623
2624impl From<SRGB> for HSL {
2625 fn from(rgb: SRGB) -> HSL {
2626 let rgb = rgb.resolve();
2628 let r = rgb.r;
2629 let g = rgb.g;
2630 let b = rgb.b;
2631 let max = r.max(g).max(b);
2632 let min = r.min(g).min(b);
2633 let mut h = f32::NAN;
2634 let mut s: f32 = 0.0;
2635 let l = (min + max) / 2.0;
2636 let d = max - min;
2637
2638 if d != 0.0 {
2639 s = if l == 0.0 || l == 1.0 {
2640 0.0
2641 } else {
2642 (max - l) / l.min(1.0 - l)
2643 };
2644
2645 if max == r {
2646 h = (g - b) / d + (if g < b { 6.0 } else { 0.0 });
2647 } else if max == g {
2648 h = (b - r) / d + 2.0;
2649 } else if max == b {
2650 h = (r - g) / d + 4.0;
2651 }
2652
2653 h = h * 60.0;
2654 }
2655
2656 HSL {
2657 h,
2658 s: s * 100.0,
2659 l: l * 100.0,
2660 alpha: rgb.alpha,
2661 }
2662 }
2663}
2664
2665impl From<HSL> for SRGB {
2666 fn from(hsl: HSL) -> SRGB {
2667 let hsl = hsl.resolve_missing();
2669 let h = (hsl.h - 360.0 * (hsl.h / 360.0).floor()) / 360.0;
2670 let (r, g, b) = hsl_to_rgb(h, hsl.s / 100.0, hsl.l / 100.0);
2671 SRGB {
2672 r,
2673 g,
2674 b,
2675 alpha: hsl.alpha,
2676 }
2677 }
2678}
2679
2680impl From<SRGB> for HWB {
2681 fn from(rgb: SRGB) -> HWB {
2682 let rgb = rgb.resolve();
2683 let hsl = HSL::from(rgb);
2684 let r = rgb.r;
2685 let g = rgb.g;
2686 let b = rgb.b;
2687 let w = r.min(g).min(b);
2688 let b = 1.0 - r.max(g).max(b);
2689 HWB {
2690 h: hsl.h,
2691 w: w * 100.0,
2692 b: b * 100.0,
2693 alpha: rgb.alpha,
2694 }
2695 }
2696}
2697
2698impl From<HWB> for SRGB {
2699 fn from(hwb: HWB) -> SRGB {
2700 let hwb = hwb.resolve_missing();
2702 let h = hwb.h;
2703 let w = hwb.w / 100.0;
2704 let b = hwb.b / 100.0;
2705
2706 if w + b >= 1.0 {
2707 let gray = w / (w + b);
2708 return SRGB {
2709 r: gray,
2710 g: gray,
2711 b: gray,
2712 alpha: hwb.alpha,
2713 };
2714 }
2715
2716 let mut rgba = SRGB::from(HSL {
2717 h,
2718 s: 100.0,
2719 l: 50.0,
2720 alpha: hwb.alpha,
2721 });
2722 let x = 1.0 - w - b;
2723 rgba.r = rgba.r * x + w;
2724 rgba.g = rgba.g * x + w;
2725 rgba.b = rgba.b * x + w;
2726 rgba
2727 }
2728}
2729
2730impl From<RGBA> for SRGB {
2731 fn from(rgb: RGBA) -> SRGB {
2732 SRGB {
2733 r: rgb.red_f32(),
2734 g: rgb.green_f32(),
2735 b: rgb.blue_f32(),
2736 alpha: rgb.alpha_f32(),
2737 }
2738 }
2739}
2740
2741impl From<SRGB> for RGBA {
2742 fn from(rgb: SRGB) -> RGBA {
2743 let rgb = rgb.resolve();
2744 RGBA::from_floats(rgb.r, rgb.g, rgb.b, rgb.alpha)
2745 }
2746}
2747
2748impl From<SRGB> for RGB {
2749 fn from(rgb: SRGB) -> Self {
2750 RGB {
2751 r: rgb.r * 255.0,
2752 g: rgb.g * 255.0,
2753 b: rgb.b * 255.0,
2754 alpha: rgb.alpha,
2755 }
2756 }
2757}
2758
2759impl From<RGB> for SRGB {
2760 fn from(rgb: RGB) -> Self {
2761 SRGB {
2762 r: rgb.r / 255.0,
2763 g: rgb.g / 255.0,
2764 b: rgb.b / 255.0,
2765 alpha: rgb.alpha,
2766 }
2767 }
2768}
2769
2770impl From<RGBA> for RGB {
2771 fn from(rgb: RGBA) -> Self {
2772 RGB::from(&rgb)
2773 }
2774}
2775
2776impl From<&RGBA> for RGB {
2777 fn from(rgb: &RGBA) -> Self {
2778 RGB {
2779 r: rgb.red as f32,
2780 g: rgb.green as f32,
2781 b: rgb.blue as f32,
2782 alpha: rgb.alpha_f32(),
2783 }
2784 }
2785}
2786
2787impl From<RGB> for RGBA {
2788 fn from(rgb: RGB) -> Self {
2789 let rgb = rgb.resolve();
2790 RGBA::new(
2791 clamp_floor_256_f32(rgb.r),
2792 clamp_floor_256_f32(rgb.g),
2793 clamp_floor_256_f32(rgb.b),
2794 rgb.alpha,
2795 )
2796 }
2797}
2798
2799via!(LAB -> XYZd50 -> XYZd65);
2801via!(ProPhoto -> XYZd50 -> XYZd65);
2802via!(OKLCH -> OKLAB -> XYZd65);
2803
2804via!(LAB -> XYZd65 -> OKLAB);
2805via!(LAB -> XYZd65 -> OKLCH);
2806via!(LAB -> XYZd65 -> SRGB);
2807via!(LAB -> XYZd65 -> SRGBLinear);
2808via!(LAB -> XYZd65 -> P3);
2809via!(LAB -> XYZd65 -> A98);
2810via!(LAB -> XYZd65 -> ProPhoto);
2811via!(LAB -> XYZd65 -> Rec2020);
2812via!(LAB -> XYZd65 -> HSL);
2813via!(LAB -> XYZd65 -> HWB);
2814
2815via!(LCH -> LAB -> XYZd65);
2816via!(LCH -> XYZd65 -> OKLAB);
2817via!(LCH -> XYZd65 -> OKLCH);
2818via!(LCH -> XYZd65 -> SRGB);
2819via!(LCH -> XYZd65 -> SRGBLinear);
2820via!(LCH -> XYZd65 -> P3);
2821via!(LCH -> XYZd65 -> A98);
2822via!(LCH -> XYZd65 -> ProPhoto);
2823via!(LCH -> XYZd65 -> Rec2020);
2824via!(LCH -> XYZd65 -> XYZd50);
2825via!(LCH -> XYZd65 -> HSL);
2826via!(LCH -> XYZd65 -> HWB);
2827
2828via!(SRGB -> SRGBLinear -> XYZd65);
2829via!(SRGB -> XYZd65 -> OKLAB);
2830via!(SRGB -> XYZd65 -> OKLCH);
2831via!(SRGB -> XYZd65 -> P3);
2832via!(SRGB -> XYZd65 -> A98);
2833via!(SRGB -> XYZd65 -> ProPhoto);
2834via!(SRGB -> XYZd65 -> Rec2020);
2835via!(SRGB -> XYZd65 -> XYZd50);
2836
2837via!(P3 -> XYZd65 -> SRGBLinear);
2838via!(P3 -> XYZd65 -> OKLAB);
2839via!(P3 -> XYZd65 -> OKLCH);
2840via!(P3 -> XYZd65 -> A98);
2841via!(P3 -> XYZd65 -> ProPhoto);
2842via!(P3 -> XYZd65 -> Rec2020);
2843via!(P3 -> XYZd65 -> XYZd50);
2844via!(P3 -> XYZd65 -> HSL);
2845via!(P3 -> XYZd65 -> HWB);
2846
2847via!(SRGBLinear -> XYZd65 -> OKLAB);
2848via!(SRGBLinear -> XYZd65 -> OKLCH);
2849via!(SRGBLinear -> XYZd65 -> A98);
2850via!(SRGBLinear -> XYZd65 -> ProPhoto);
2851via!(SRGBLinear -> XYZd65 -> Rec2020);
2852via!(SRGBLinear -> XYZd65 -> XYZd50);
2853via!(SRGBLinear -> XYZd65 -> HSL);
2854via!(SRGBLinear -> XYZd65 -> HWB);
2855
2856via!(A98 -> XYZd65 -> OKLAB);
2857via!(A98 -> XYZd65 -> OKLCH);
2858via!(A98 -> XYZd65 -> ProPhoto);
2859via!(A98 -> XYZd65 -> Rec2020);
2860via!(A98 -> XYZd65 -> XYZd50);
2861via!(A98 -> XYZd65 -> HSL);
2862via!(A98 -> XYZd65 -> HWB);
2863
2864via!(ProPhoto -> XYZd65 -> OKLAB);
2865via!(ProPhoto -> XYZd65 -> OKLCH);
2866via!(ProPhoto -> XYZd65 -> Rec2020);
2867via!(ProPhoto -> XYZd65 -> HSL);
2868via!(ProPhoto -> XYZd65 -> HWB);
2869
2870via!(XYZd50 -> XYZd65 -> OKLAB);
2871via!(XYZd50 -> XYZd65 -> OKLCH);
2872via!(XYZd50 -> XYZd65 -> Rec2020);
2873via!(XYZd50 -> XYZd65 -> HSL);
2874via!(XYZd50 -> XYZd65 -> HWB);
2875
2876via!(Rec2020 -> XYZd65 -> OKLAB);
2877via!(Rec2020 -> XYZd65 -> OKLCH);
2878via!(Rec2020 -> XYZd65 -> HSL);
2879via!(Rec2020 -> XYZd65 -> HWB);
2880
2881via!(HSL -> XYZd65 -> OKLAB);
2882via!(HSL -> XYZd65 -> OKLCH);
2883via!(HSL -> SRGB -> XYZd65);
2884via!(HSL -> SRGB -> HWB);
2885
2886via!(HWB -> SRGB -> XYZd65);
2887via!(HWB -> XYZd65 -> OKLAB);
2888via!(HWB -> XYZd65 -> OKLCH);
2889
2890via!(RGB -> SRGB -> LAB);
2891via!(RGB -> SRGB -> LCH);
2892via!(RGB -> SRGB -> OKLAB);
2893via!(RGB -> SRGB -> OKLCH);
2894via!(RGB -> SRGB -> P3);
2895via!(RGB -> SRGB -> SRGBLinear);
2896via!(RGB -> SRGB -> A98);
2897via!(RGB -> SRGB -> ProPhoto);
2898via!(RGB -> SRGB -> XYZd50);
2899via!(RGB -> SRGB -> XYZd65);
2900via!(RGB -> SRGB -> Rec2020);
2901via!(RGB -> SRGB -> HSL);
2902via!(RGB -> SRGB -> HWB);
2903
2904via!(RGBA -> SRGB -> LAB);
2907via!(RGBA -> SRGB -> LCH);
2908via!(RGBA -> SRGB -> OKLAB);
2909via!(RGBA -> SRGB -> OKLCH);
2910via!(RGBA -> SRGB -> P3);
2911via!(RGBA -> SRGB -> SRGBLinear);
2912via!(RGBA -> SRGB -> A98);
2913via!(RGBA -> SRGB -> ProPhoto);
2914via!(RGBA -> SRGB -> XYZd50);
2915via!(RGBA -> SRGB -> XYZd65);
2916via!(RGBA -> SRGB -> Rec2020);
2917via!(RGBA -> SRGB -> HSL);
2918via!(RGBA -> SRGB -> HWB);
2919
2920macro_rules! color_space {
2921 ($space: ty) => {
2922 impl From<LABColor> for $space {
2923 fn from(color: LABColor) -> $space {
2924 use LABColor::*;
2925
2926 match color {
2927 LAB(v) => v.into(),
2928 LCH(v) => v.into(),
2929 OKLAB(v) => v.into(),
2930 OKLCH(v) => v.into(),
2931 }
2932 }
2933 }
2934
2935 impl From<PredefinedColor> for $space {
2936 fn from(color: PredefinedColor) -> $space {
2937 use PredefinedColor::*;
2938
2939 match color {
2940 SRGB(v) => v.into(),
2941 SRGBLinear(v) => v.into(),
2942 DisplayP3(v) => v.into(),
2943 A98(v) => v.into(),
2944 ProPhoto(v) => v.into(),
2945 Rec2020(v) => v.into(),
2946 XYZd50(v) => v.into(),
2947 XYZd65(v) => v.into(),
2948 }
2949 }
2950 }
2951
2952 impl From<FloatColor> for $space {
2953 fn from(color: FloatColor) -> $space {
2954 use FloatColor::*;
2955
2956 match color {
2957 RGB(v) => v.into(),
2958 HSL(v) => v.into(),
2959 HWB(v) => v.into(),
2960 }
2961 }
2962 }
2963
2964 impl TryFrom<&CssColor> for $space {
2965 type Error = ();
2966 fn try_from(color: &CssColor) -> Result<$space, ()> {
2967 Ok(match color {
2968 CssColor::RGBA(rgba) => (*rgba).into(),
2969 CssColor::LAB(lab) => (**lab).into(),
2970 CssColor::Predefined(predefined) => (**predefined).into(),
2971 CssColor::Float(float) => (**float).into(),
2972 CssColor::CurrentColor => return Err(()),
2973 CssColor::LightDark(..) => return Err(()),
2974 CssColor::System(..) => return Err(()),
2975 })
2976 }
2977 }
2978
2979 impl TryFrom<CssColor> for $space {
2980 type Error = ();
2981 fn try_from(color: CssColor) -> Result<$space, ()> {
2982 Ok(match color {
2983 CssColor::RGBA(rgba) => rgba.into(),
2984 CssColor::LAB(lab) => (*lab).into(),
2985 CssColor::Predefined(predefined) => (*predefined).into(),
2986 CssColor::Float(float) => (*float).into(),
2987 CssColor::CurrentColor => return Err(()),
2988 CssColor::LightDark(..) => return Err(()),
2989 CssColor::System(..) => return Err(()),
2990 })
2991 }
2992 }
2993 };
2994}
2995
2996color_space!(LAB);
2997color_space!(LCH);
2998color_space!(OKLAB);
2999color_space!(OKLCH);
3000color_space!(SRGB);
3001color_space!(SRGBLinear);
3002color_space!(XYZd50);
3003color_space!(XYZd65);
3004color_space!(P3);
3005color_space!(A98);
3006color_space!(ProPhoto);
3007color_space!(Rec2020);
3008color_space!(HSL);
3009color_space!(HWB);
3010color_space!(RGB);
3011color_space!(RGBA);
3012
3013macro_rules! predefined {
3014 ($key: ident, $t: ty) => {
3015 impl From<$t> for PredefinedColor {
3016 fn from(color: $t) -> PredefinedColor {
3017 PredefinedColor::$key(color)
3018 }
3019 }
3020
3021 impl From<$t> for CssColor {
3022 fn from(color: $t) -> CssColor {
3023 CssColor::Predefined(Box::new(PredefinedColor::$key(color)))
3024 }
3025 }
3026 };
3027}
3028
3029predefined!(SRGBLinear, SRGBLinear);
3030predefined!(XYZd50, XYZd50);
3031predefined!(XYZd65, XYZd65);
3032predefined!(DisplayP3, P3);
3033predefined!(A98, A98);
3034predefined!(ProPhoto, ProPhoto);
3035predefined!(Rec2020, Rec2020);
3036
3037macro_rules! lab {
3038 ($key: ident, $t: ty) => {
3039 impl From<$t> for LABColor {
3040 fn from(color: $t) -> LABColor {
3041 LABColor::$key(color)
3042 }
3043 }
3044
3045 impl From<$t> for CssColor {
3046 fn from(color: $t) -> CssColor {
3047 CssColor::LAB(Box::new(LABColor::$key(color)))
3048 }
3049 }
3050 };
3051}
3052
3053lab!(LAB, LAB);
3054lab!(LCH, LCH);
3055lab!(OKLAB, OKLAB);
3056lab!(OKLCH, OKLCH);
3057
3058macro_rules! rgb {
3059 ($t: ty) => {
3060 impl From<$t> for CssColor {
3061 fn from(color: $t) -> CssColor {
3062 CssColor::RGBA(color.into())
3065 }
3066 }
3067 };
3068}
3069
3070rgb!(SRGB);
3071rgb!(HSL);
3072rgb!(HWB);
3073rgb!(RGB);
3074
3075impl From<RGBA> for CssColor {
3076 fn from(color: RGBA) -> CssColor {
3077 CssColor::RGBA(color)
3078 }
3079}
3080
3081pub trait ColorGamut {
3083 fn in_gamut(&self) -> bool;
3085 fn clip(&self) -> Self;
3087}
3088
3089macro_rules! bounded_color_gamut {
3090 ($t: ty, $a: ident, $b: ident, $c: ident) => {
3091 impl ColorGamut for $t {
3092 #[inline]
3093 fn in_gamut(&self) -> bool {
3094 self.$a >= 0.0 && self.$a <= 1.0 && self.$b >= 0.0 && self.$b <= 1.0 && self.$c >= 0.0 && self.$c <= 1.0
3095 }
3096
3097 #[inline]
3098 fn clip(&self) -> Self {
3099 Self {
3100 $a: self.$a.clamp(0.0, 1.0),
3101 $b: self.$b.clamp(0.0, 1.0),
3102 $c: self.$c.clamp(0.0, 1.0),
3103 alpha: self.alpha.clamp(0.0, 1.0),
3104 }
3105 }
3106 }
3107 };
3108}
3109
3110macro_rules! unbounded_color_gamut {
3111 ($t: ty, $a: ident, $b: ident, $c: ident) => {
3112 impl ColorGamut for $t {
3113 #[inline]
3114 fn in_gamut(&self) -> bool {
3115 true
3116 }
3117
3118 #[inline]
3119 fn clip(&self) -> Self {
3120 *self
3121 }
3122 }
3123 };
3124}
3125
3126macro_rules! hsl_hwb_color_gamut {
3127 ($t: ty, $a: ident, $b: ident) => {
3128 impl ColorGamut for $t {
3129 #[inline]
3130 fn in_gamut(&self) -> bool {
3131 self.$a >= 0.0 && self.$a <= 100.0 && self.$b >= 0.0 && self.$b <= 100.0
3132 }
3133
3134 #[inline]
3135 fn clip(&self) -> Self {
3136 Self {
3137 h: self.h % 360.0,
3138 $a: self.$a.clamp(0.0, 100.0),
3139 $b: self.$b.clamp(0.0, 100.0),
3140 alpha: self.alpha.clamp(0.0, 1.0),
3141 }
3142 }
3143 }
3144 };
3145}
3146
3147bounded_color_gamut!(SRGB, r, g, b);
3148bounded_color_gamut!(SRGBLinear, r, g, b);
3149bounded_color_gamut!(P3, r, g, b);
3150bounded_color_gamut!(A98, r, g, b);
3151bounded_color_gamut!(ProPhoto, r, g, b);
3152bounded_color_gamut!(Rec2020, r, g, b);
3153unbounded_color_gamut!(LAB, l, a, b);
3154unbounded_color_gamut!(OKLAB, l, a, b);
3155unbounded_color_gamut!(XYZd50, x, y, z);
3156unbounded_color_gamut!(XYZd65, x, y, z);
3157unbounded_color_gamut!(LCH, l, c, h);
3158unbounded_color_gamut!(OKLCH, l, c, h);
3159hsl_hwb_color_gamut!(HSL, s, l);
3160hsl_hwb_color_gamut!(HWB, w, b);
3161
3162impl ColorGamut for RGB {
3163 #[inline]
3164 fn in_gamut(&self) -> bool {
3165 self.r >= 0.0 && self.r <= 255.0 && self.g >= 0.0 && self.g <= 255.0 && self.b >= 0.0 && self.b <= 255.0
3166 }
3167
3168 #[inline]
3169 fn clip(&self) -> Self {
3170 Self {
3171 r: self.r.clamp(0.0, 255.0),
3172 g: self.g.clamp(0.0, 255.0),
3173 b: self.b.clamp(0.0, 255.0),
3174 alpha: self.alpha.clamp(0.0, 1.0),
3175 }
3176 }
3177}
3178
3179fn delta_eok<T: Into<OKLAB>>(a: T, b: OKLCH) -> f32 {
3180 let a: OKLAB = a.into();
3182 let b: OKLAB = b.into();
3183 let delta_l = a.l - b.l;
3184 let delta_a = a.a - b.a;
3185 let delta_b = a.b - b.b;
3186
3187 (delta_l.powi(2) + delta_a.powi(2) + delta_b.powi(2)).sqrt()
3188}
3189
3190fn map_gamut<T>(color: T) -> T
3191where
3192 T: Into<OKLCH> + ColorGamut + Into<OKLAB> + From<OKLCH> + Copy,
3193{
3194 const JND: f32 = 0.02;
3195 const EPSILON: f32 = 0.00001;
3196
3197 let mut current: OKLCH = color.into();
3199
3200 if (current.l - 1.0).abs() < EPSILON || current.l > 1.0 {
3202 return OKLCH {
3203 l: 1.0,
3204 c: 0.0,
3205 h: 0.0,
3206 alpha: current.alpha,
3207 }
3208 .into();
3209 }
3210
3211 if current.l < EPSILON {
3213 return OKLCH {
3214 l: 0.0,
3215 c: 0.0,
3216 h: 0.0,
3217 alpha: current.alpha,
3218 }
3219 .into();
3220 }
3221
3222 let mut min = 0.0;
3223 let mut max = current.c;
3224
3225 while (max - min) > EPSILON {
3226 let chroma = (min + max) / 2.0;
3227 current.c = chroma;
3228
3229 let converted = T::from(current);
3230 if converted.in_gamut() {
3231 min = chroma;
3232 continue;
3233 }
3234
3235 let clipped = converted.clip();
3236 let delta_e = delta_eok(clipped, current);
3237 if delta_e < JND {
3238 return clipped;
3239 }
3240
3241 max = chroma;
3242 }
3243
3244 current.into()
3245}
3246
3247fn parse_color_mix<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
3248 input.expect_ident_matching("in")?;
3249 let method = ColorSpaceName::parse(input)?;
3250
3251 let hue_method = if matches!(
3252 method,
3253 ColorSpaceName::Hsl | ColorSpaceName::Hwb | ColorSpaceName::LCH | ColorSpaceName::OKLCH
3254 ) {
3255 let hue_method = input.try_parse(HueInterpolationMethod::parse);
3256 if hue_method.is_ok() {
3257 input.expect_ident_matching("hue")?;
3258 }
3259 hue_method
3260 } else {
3261 Ok(HueInterpolationMethod::Shorter)
3262 };
3263
3264 let hue_method = hue_method.unwrap_or(HueInterpolationMethod::Shorter);
3265 input.expect_comma()?;
3266
3267 let first_percent = input.try_parse(|input| input.expect_percentage());
3268 let first_color = CssColor::parse(input)?;
3269 let first_percent = first_percent
3270 .or_else(|_| input.try_parse(|input| input.expect_percentage()))
3271 .ok();
3272 input.expect_comma()?;
3273
3274 let second_percent = input.try_parse(|input| input.expect_percentage());
3275 let second_color = CssColor::parse(input)?;
3276 let second_percent = second_percent
3277 .or_else(|_| input.try_parse(|input| input.expect_percentage()))
3278 .ok();
3279
3280 let (p1, p2) = if first_percent.is_none() && second_percent.is_none() {
3282 (0.5, 0.5)
3283 } else {
3284 let p2 = second_percent.unwrap_or_else(|| 1.0 - first_percent.unwrap());
3285 let p1 = first_percent.unwrap_or_else(|| 1.0 - second_percent.unwrap());
3286 (p1, p2)
3287 };
3288
3289 if (p1 + p2) == 0.0 {
3290 return Err(input.new_custom_error(ParserError::InvalidValue));
3291 }
3292
3293 match method {
3294 ColorSpaceName::SRGB => first_color.interpolate::<SRGB>(p1, &second_color, p2, hue_method),
3295 ColorSpaceName::SRGBLinear => first_color.interpolate::<SRGBLinear>(p1, &second_color, p2, hue_method),
3296 ColorSpaceName::Hsl => first_color.interpolate::<HSL>(p1, &second_color, p2, hue_method),
3297 ColorSpaceName::Hwb => first_color.interpolate::<HWB>(p1, &second_color, p2, hue_method),
3298 ColorSpaceName::LAB => first_color.interpolate::<LAB>(p1, &second_color, p2, hue_method),
3299 ColorSpaceName::LCH => first_color.interpolate::<LCH>(p1, &second_color, p2, hue_method),
3300 ColorSpaceName::OKLAB => first_color.interpolate::<OKLAB>(p1, &second_color, p2, hue_method),
3301 ColorSpaceName::OKLCH => first_color.interpolate::<OKLCH>(p1, &second_color, p2, hue_method),
3302 ColorSpaceName::XYZ | ColorSpaceName::XYZd65 => {
3303 first_color.interpolate::<XYZd65>(p1, &second_color, p2, hue_method)
3304 }
3305 ColorSpaceName::XYZd50 => first_color.interpolate::<XYZd50>(p1, &second_color, p2, hue_method),
3306 }
3307 .map_err(|_| input.new_custom_error(ParserError::InvalidValue))
3308}
3309
3310impl CssColor {
3311 fn get_type_id(&self) -> TypeId {
3312 match self {
3313 CssColor::RGBA(..) => TypeId::of::<SRGB>(),
3314 CssColor::LAB(lab) => match &**lab {
3315 LABColor::LAB(..) => TypeId::of::<LAB>(),
3316 LABColor::LCH(..) => TypeId::of::<LCH>(),
3317 LABColor::OKLAB(..) => TypeId::of::<OKLAB>(),
3318 LABColor::OKLCH(..) => TypeId::of::<OKLCH>(),
3319 },
3320 CssColor::Predefined(predefined) => match &**predefined {
3321 PredefinedColor::SRGB(..) => TypeId::of::<SRGB>(),
3322 PredefinedColor::SRGBLinear(..) => TypeId::of::<SRGBLinear>(),
3323 PredefinedColor::DisplayP3(..) => TypeId::of::<P3>(),
3324 PredefinedColor::A98(..) => TypeId::of::<A98>(),
3325 PredefinedColor::ProPhoto(..) => TypeId::of::<ProPhoto>(),
3326 PredefinedColor::Rec2020(..) => TypeId::of::<Rec2020>(),
3327 PredefinedColor::XYZd50(..) => TypeId::of::<XYZd50>(),
3328 PredefinedColor::XYZd65(..) => TypeId::of::<XYZd65>(),
3329 },
3330 CssColor::Float(float) => match &**float {
3331 FloatColor::RGB(..) => TypeId::of::<SRGB>(),
3332 FloatColor::HSL(..) => TypeId::of::<HSL>(),
3333 FloatColor::HWB(..) => TypeId::of::<HWB>(),
3334 },
3335 _ => unreachable!(),
3336 }
3337 }
3338
3339 fn to_light_dark(&self) -> CssColor {
3340 match self {
3341 CssColor::LightDark(..) => self.clone(),
3342 _ => CssColor::LightDark(Box::new(self.clone()), Box::new(self.clone())),
3343 }
3344 }
3345
3346 pub fn interpolate<T>(
3349 &self,
3350 mut p1: f32,
3351 other: &CssColor,
3352 mut p2: f32,
3353 method: HueInterpolationMethod,
3354 ) -> Result<CssColor, ()>
3355 where
3356 for<'a> T: 'static
3357 + TryFrom<&'a CssColor>
3358 + Interpolate
3359 + Into<CssColor>
3360 + Into<OKLCH>
3361 + ColorGamut
3362 + Into<OKLAB>
3363 + From<OKLCH>
3364 + Copy,
3365 {
3366 if matches!(self, CssColor::CurrentColor | CssColor::System(..))
3367 || matches!(other, CssColor::CurrentColor | CssColor::System(..))
3368 {
3369 return Err(());
3370 }
3371
3372 if matches!(self, CssColor::LightDark(..)) || matches!(other, CssColor::LightDark(..)) {
3373 if let (CssColor::LightDark(al, ad), CssColor::LightDark(bl, bd)) =
3374 (self.to_light_dark(), other.to_light_dark())
3375 {
3376 return Ok(CssColor::LightDark(
3377 Box::new(al.interpolate::<T>(p1, &bl, p2, method)?),
3378 Box::new(ad.interpolate::<T>(p1, &bd, p2, method)?),
3379 ));
3380 }
3381 }
3382
3383 let type_id = TypeId::of::<T>();
3384 let converted_first = self.get_type_id() != type_id;
3385 let converted_second = other.get_type_id() != type_id;
3386
3387 let mut first_color = T::try_from(self).map_err(|_| ())?;
3389 let mut second_color = T::try_from(other).map_err(|_| ())?;
3390
3391 if converted_first && !first_color.in_gamut() {
3392 first_color = map_gamut(first_color);
3393 }
3394
3395 if converted_second && !second_color.in_gamut() {
3396 second_color = map_gamut(second_color);
3397 }
3398
3399 if converted_first {
3401 first_color.adjust_powerless_components();
3402 }
3403
3404 if converted_second {
3405 second_color.adjust_powerless_components();
3406 }
3407
3408 first_color.fill_missing_components(&second_color);
3410 second_color.fill_missing_components(&first_color);
3411
3412 first_color.adjust_hue(&mut second_color, method);
3414
3415 first_color.premultiply();
3417 second_color.premultiply();
3418
3419 let mut alpha_multiplier = p1 + p2;
3421 if alpha_multiplier != 1.0 {
3422 p1 = p1 / alpha_multiplier;
3423 p2 = p2 / alpha_multiplier;
3424 if alpha_multiplier > 1.0 {
3425 alpha_multiplier = 1.0;
3426 }
3427 }
3428
3429 let mut result_color = first_color.interpolate(p1, &second_color, p2);
3430 result_color.unpremultiply(alpha_multiplier);
3431
3432 Ok(result_color.into())
3433 }
3434}
3435
3436pub trait Interpolate {
3438 fn adjust_powerless_components(&mut self) {}
3440 fn fill_missing_components(&mut self, other: &Self);
3442 fn adjust_hue(&mut self, _: &mut Self, _: HueInterpolationMethod) {}
3444 fn premultiply(&mut self);
3446 fn unpremultiply(&mut self, alpha_multiplier: f32);
3448 fn interpolate(&self, p1: f32, other: &Self, p2: f32) -> Self;
3450}
3451
3452macro_rules! interpolate {
3453 ($a: ident, $b: ident, $c: ident) => {
3454 fn fill_missing_components(&mut self, other: &Self) {
3455 if self.$a.is_nan() {
3456 self.$a = other.$a;
3457 }
3458
3459 if self.$b.is_nan() {
3460 self.$b = other.$b;
3461 }
3462
3463 if self.$c.is_nan() {
3464 self.$c = other.$c;
3465 }
3466
3467 if self.alpha.is_nan() {
3468 self.alpha = other.alpha;
3469 }
3470 }
3471
3472 fn interpolate(&self, p1: f32, other: &Self, p2: f32) -> Self {
3473 Self {
3474 $a: self.$a * p1 + other.$a * p2,
3475 $b: self.$b * p1 + other.$b * p2,
3476 $c: self.$c * p1 + other.$c * p2,
3477 alpha: self.alpha * p1 + other.alpha * p2,
3478 }
3479 }
3480 };
3481}
3482
3483macro_rules! rectangular_premultiply {
3484 ($a: ident, $b: ident, $c: ident) => {
3485 fn premultiply(&mut self) {
3486 if !self.alpha.is_nan() {
3487 self.$a *= self.alpha;
3488 self.$b *= self.alpha;
3489 self.$c *= self.alpha;
3490 }
3491 }
3492
3493 fn unpremultiply(&mut self, alpha_multiplier: f32) {
3494 if !self.alpha.is_nan() && self.alpha != 0.0 {
3495 self.$a /= self.alpha;
3496 self.$b /= self.alpha;
3497 self.$c /= self.alpha;
3498 self.alpha *= alpha_multiplier;
3499 }
3500 }
3501 };
3502}
3503
3504macro_rules! polar_premultiply {
3505 ($a: ident, $b: ident) => {
3506 fn premultiply(&mut self) {
3507 if !self.alpha.is_nan() {
3508 self.$a *= self.alpha;
3509 self.$b *= self.alpha;
3510 }
3511 }
3512
3513 fn unpremultiply(&mut self, alpha_multiplier: f32) {
3514 self.h %= 360.0;
3515 if !self.alpha.is_nan() {
3516 self.$a /= self.alpha;
3517 self.$b /= self.alpha;
3518 self.alpha *= alpha_multiplier;
3519 }
3520 }
3521 };
3522}
3523
3524macro_rules! adjust_powerless_lab {
3525 () => {
3526 fn adjust_powerless_components(&mut self) {
3527 if self.l.abs() < f32::EPSILON {
3529 self.a = f32::NAN;
3530 self.b = f32::NAN;
3531 }
3532 }
3533 };
3534}
3535
3536macro_rules! adjust_powerless_lch {
3537 () => {
3538 fn adjust_powerless_components(&mut self) {
3539 if self.c.abs() < f32::EPSILON {
3542 self.h = f32::NAN;
3543 }
3544
3545 if self.l.abs() < f32::EPSILON {
3546 self.c = f32::NAN;
3547 self.h = f32::NAN;
3548 }
3549 }
3550
3551 fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {
3552 method.interpolate(&mut self.h, &mut other.h);
3553 }
3554 };
3555}
3556
3557impl Interpolate for SRGB {
3558 rectangular_premultiply!(r, g, b);
3559 interpolate!(r, g, b);
3560}
3561
3562impl Interpolate for SRGBLinear {
3563 rectangular_premultiply!(r, g, b);
3564 interpolate!(r, g, b);
3565}
3566
3567impl Interpolate for XYZd50 {
3568 rectangular_premultiply!(x, y, z);
3569 interpolate!(x, y, z);
3570}
3571
3572impl Interpolate for XYZd65 {
3573 rectangular_premultiply!(x, y, z);
3574 interpolate!(x, y, z);
3575}
3576
3577impl Interpolate for LAB {
3578 adjust_powerless_lab!();
3579 rectangular_premultiply!(l, a, b);
3580 interpolate!(l, a, b);
3581}
3582
3583impl Interpolate for OKLAB {
3584 adjust_powerless_lab!();
3585 rectangular_premultiply!(l, a, b);
3586 interpolate!(l, a, b);
3587}
3588
3589impl Interpolate for LCH {
3590 adjust_powerless_lch!();
3591 polar_premultiply!(l, c);
3592 interpolate!(l, c, h);
3593}
3594
3595impl Interpolate for OKLCH {
3596 adjust_powerless_lch!();
3597 polar_premultiply!(l, c);
3598 interpolate!(l, c, h);
3599}
3600
3601impl Interpolate for HSL {
3602 polar_premultiply!(s, l);
3603
3604 fn adjust_powerless_components(&mut self) {
3605 if self.s.abs() < f32::EPSILON {
3608 self.h = f32::NAN;
3609 }
3610
3611 if self.l.abs() < f32::EPSILON || (self.l - 100.0).abs() < f32::EPSILON {
3612 self.h = f32::NAN;
3613 self.s = f32::NAN;
3614 }
3615 }
3616
3617 fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {
3618 method.interpolate(&mut self.h, &mut other.h);
3619 }
3620
3621 interpolate!(h, s, l);
3622}
3623
3624impl Interpolate for HWB {
3625 polar_premultiply!(w, b);
3626
3627 fn adjust_powerless_components(&mut self) {
3628 if (self.w + self.b - 100.0).abs() < f32::EPSILON {
3631 self.h = f32::NAN;
3632 }
3633 }
3634
3635 fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {
3636 method.interpolate(&mut self.h, &mut other.h);
3637 }
3638
3639 interpolate!(h, w, b);
3640}
3641
3642impl HueInterpolationMethod {
3643 fn interpolate(&self, a: &mut f32, b: &mut f32) {
3644 if *self != HueInterpolationMethod::Specified {
3646 *a = ((*a % 360.0) + 360.0) % 360.0;
3647 *b = ((*b % 360.0) + 360.0) % 360.0;
3648 }
3649
3650 match self {
3651 HueInterpolationMethod::Shorter => {
3652 let delta = *b - *a;
3654 if delta > 180.0 {
3655 *a += 360.0;
3656 } else if delta < -180.0 {
3657 *b += 360.0;
3658 }
3659 }
3660 HueInterpolationMethod::Longer => {
3661 let delta = *b - *a;
3663 if 0.0 < delta && delta < 180.0 {
3664 *a += 360.0;
3665 } else if -180.0 < delta && delta < 0.0 {
3666 *b += 360.0;
3667 }
3668 }
3669 HueInterpolationMethod::Increasing => {
3670 if *b < *a {
3672 *b += 360.0;
3673 }
3674 }
3675 HueInterpolationMethod::Decreasing => {
3676 if *a < *b {
3678 *a += 360.0;
3679 }
3680 }
3681 HueInterpolationMethod::Specified => {}
3682 }
3683 }
3684}
3685
3686#[cfg(feature = "visitor")]
3687#[cfg_attr(docsrs, doc(cfg(feature = "visitor")))]
3688impl<'i, V: ?Sized + Visitor<'i, T>, T: Visit<'i, T, V>> Visit<'i, T, V> for RGBA {
3689 const CHILD_TYPES: VisitTypes = VisitTypes::empty();
3690 fn visit_children(&mut self, _: &mut V) -> Result<(), V::Error> {
3691 Ok(())
3692 }
3693}
3694
3695#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
3696#[css(case = lower)]
3697#[cfg_attr(feature = "visitor", derive(Visit))]
3698#[cfg_attr(
3699 feature = "serde",
3700 derive(serde::Serialize, serde::Deserialize),
3701 serde(rename_all = "lowercase")
3702)]
3703#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
3704#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
3705pub enum SystemColor {
3707 AccentColor,
3709 AccentColorText,
3711 ActiveText,
3713 ButtonBorder,
3715 ButtonFace,
3717 ButtonText,
3719 Canvas,
3721 CanvasText,
3723 Field,
3725 FieldText,
3727 GrayText,
3729 Highlight,
3731 HighlightText,
3733 LinkText,
3735 Mark,
3737 MarkText,
3739 SelectedItem,
3741 SelectedItemText,
3743 VisitedText,
3745
3746 ActiveBorder,
3749 ActiveCaption,
3751 AppWorkspace,
3753 Background,
3755 ButtonHighlight,
3757 ButtonShadow,
3759 CaptionText,
3761 InactiveBorder,
3763 InactiveCaption,
3765 InactiveCaptionText,
3767 InfoBackground,
3769 InfoText,
3771 Menu,
3773 MenuText,
3775 Scrollbar,
3777 ThreeDDarkShadow,
3779 ThreeDFace,
3781 ThreeDHighlight,
3783 ThreeDLightShadow,
3785 ThreeDShadow,
3787 Window,
3789 WindowFrame,
3791 WindowText,
3793}
3794
3795impl IsCompatible for SystemColor {
3796 fn is_compatible(&self, browsers: Browsers) -> bool {
3797 use SystemColor::*;
3798 match self {
3799 AccentColor | AccentColorText => Feature::AccentSystemColor.is_compatible(browsers),
3800 _ => true,
3801 }
3802 }
3803}