Skip to main content

lightningcss/values/
color.rs

1//! CSS color values.
2
3#![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/// A CSS [`<color>`](https://www.w3.org/TR/css-color-4/#color-type) value.
28///
29/// CSS supports many different color spaces to represent colors. The most common values
30/// are stored as RGBA using a single byte per component. Less common values are stored
31/// using a `Box` to reduce the amount of memory used per color.
32///
33/// Each color space is represented as a struct that implements the `From` and `Into` traits
34/// for all other color spaces, so it is possible to convert between color spaces easily.
35/// In addition, colors support [interpolation](#method.interpolate) as in the `color-mix()` function.
36#[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  /// The [`currentColor`](https://www.w3.org/TR/css-color-4/#currentcolor-color) keyword.
48  #[cfg_attr(feature = "serde", serde(with = "CurrentColor"))]
49  CurrentColor,
50  /// An value in the RGB color space, including values parsed as hex colors, or the `rgb()`, `hsl()`, and `hwb()` functions.
51  #[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  /// A value in a LAB color space, including the `lab()`, `lch()`, `oklab()`, and `oklch()` functions.
58  LAB(Box<LABColor>),
59  /// A value in a predefined color space, e.g. `display-p3`.
60  Predefined(Box<PredefinedColor>),
61  /// A floating point representation of an RGB, HSL, or HWB color when it contains `none` components.
62  Float(Box<FloatColor>),
63  /// The [`light-dark()`](https://drafts.csswg.org/css-color-5/#light-dark) function.
64  #[cfg_attr(feature = "visitor", skip_type)]
65  #[cfg_attr(feature = "serde", serde(with = "LightDark"))]
66  LightDark(Box<CssColor>, Box<CssColor>),
67  /// A [system color](https://drafts.csswg.org/css-color/#css-system-colors) keyword.
68  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// Convert RGBA to SRGB to serialize so we get a tagged struct.
99#[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// For AST serialization.
128#[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/// A color in a LAB color space, including the `lab()`, `lch()`, `oklab()`, and `oklch()` functions.
161#[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  /// A `lab()` color.
171  LAB(LAB),
172  /// An `lch()` color.
173  LCH(LCH),
174  /// An `oklab()` color.
175  OKLAB(OKLAB),
176  /// An `oklch()` color.
177  OKLCH(OKLCH),
178}
179
180/// A color in a predefined color space, e.g. `display-p3`.
181#[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  /// A color in the `srgb` color space.
187  #[cfg_attr(feature = "serde", serde(rename = "srgb"))]
188  SRGB(SRGB),
189  /// A color in the `srgb-linear` color space.
190  #[cfg_attr(feature = "serde", serde(rename = "srgb-linear"))]
191  SRGBLinear(SRGBLinear),
192  /// A color in the `display-p3` color space.
193  #[cfg_attr(feature = "serde", serde(rename = "display-p3"))]
194  DisplayP3(P3),
195  /// A color in the `a98-rgb` color space.
196  #[cfg_attr(feature = "serde", serde(rename = "a98-rgb"))]
197  A98(A98),
198  /// A color in the `prophoto-rgb` color space.
199  #[cfg_attr(feature = "serde", serde(rename = "prophoto-rgb"))]
200  ProPhoto(ProPhoto),
201  /// A color in the `rec2020` color space.
202  #[cfg_attr(feature = "serde", serde(rename = "rec2020"))]
203  Rec2020(Rec2020),
204  /// A color in the `xyz-d50` color space.
205  #[cfg_attr(feature = "serde", serde(rename = "xyz-d50"))]
206  XYZd50(XYZd50),
207  /// A color in the `xyz-d65` color space.
208  #[cfg_attr(feature = "serde", serde(rename = "xyz-d65"))]
209  XYZd65(XYZd65),
210}
211
212/// A floating point representation of color types that
213/// are usually stored as RGBA. These are used when there
214/// are any `none` components, which are represented as NaN.
215#[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  /// An RGB color.
225  RGB(RGB),
226  /// An HSL color.
227  HSL(HSL),
228  /// An HWB color.
229  HWB(HWB),
230}
231
232bitflags! {
233  /// A color type that is used as a fallback when compiling colors for older browsers.
234  #[derive(PartialEq, Eq, Clone, Copy)]
235  pub struct ColorFallbackKind: u8 {
236    /// An RGB color fallback.
237    const RGB    = 0b01;
238    /// A P3 color fallback.
239    const P3     = 0b10;
240    /// A LAB color fallback.
241    const LAB    = 0b100;
242    /// An OKLAB color fallback.
243    const OKLAB  = 0b1000;
244  }
245}
246
247enum_property! {
248  /// A [color space](https://www.w3.org/TR/css-color-4/#interpolation-space) keyword
249  /// used in interpolation functions such as `color-mix()`.
250  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  /// A hue [interpolation method](https://www.w3.org/TR/css-color-4/#typedef-hue-interpolation-method)
267  /// used in interpolation functions such as `color-mix()`.
268  pub enum HueInterpolationMethod {
269    /// Angles are adjusted so that θ₂ - θ₁ ∈ [-180, 180].
270    Shorter,
271    /// Angles are adjusted so that θ₂ - θ₁ ∈ {0, [180, 360)}.
272    Longer,
273    /// Angles are adjusted so that θ₂ - θ₁ ∈ [0, 360).
274    Increasing,
275    /// Angles are adjusted so that θ₂ - θ₁ ∈ (-360, 0].
276    Decreasing,
277    /// No fixup is performed. Angles are interpolated in the same way as every other component.
278    Specified,
279  }
280}
281
282impl ColorFallbackKind {
283  pub(crate) fn lowest(&self) -> ColorFallbackKind {
284    // This finds the lowest set bit.
285    *self & ColorFallbackKind::from_bits_truncate(self.bits().wrapping_neg())
286  }
287
288  pub(crate) fn highest(&self) -> ColorFallbackKind {
289    // This finds the highest set bit.
290    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  /// Returns the `currentColor` keyword.
322  pub fn current_color() -> CssColor {
323    CssColor::CurrentColor
324  }
325
326  /// Returns the `transparent` keyword.
327  pub fn transparent() -> CssColor {
328    CssColor::RGBA(RGBA::transparent())
329  }
330
331  /// Converts the color to RGBA.
332  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  /// Converts the color to the LAB color space.
342  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  /// Converts the color to the P3 color space.
352  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    // Fallbacks occur in levels: Oklab -> Lab -> P3 -> RGB. We start with all levels
363    // below and including the authored color space, and remove the ones that aren't
364    // compatible with our browser targets.
365    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        // We don't need P3 if Lab is supported by some of our targets.
403        // No browser implements Lab but not P3.
404        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        // Remove P3 if it isn't supported by any targets, and wasn't the
418        // original authored color.
419        fallbacks.remove(ColorFallbackKind::P3);
420      }
421    }
422
423    fallbacks
424  }
425
426  /// Returns the color fallback types needed for the given browser targets.
427  pub fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
428    // Get the full set of possible fallbacks, and remove the highest one, which
429    // will replace the original declaration. The remaining fallbacks need to be added.
430    let fallbacks = self.get_possible_fallbacks(targets);
431    fallbacks - fallbacks.highest()
432  }
433
434  /// Returns a fallback color for the given fallback type.
435  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 the #rrggbbaa syntax is not supported by the browser targets, output rgba()
579          if should_compile!(dest.targets.current, HexAlphaColors) {
580            // If the browser doesn't support `#rrggbbaa` color syntax, it is converted to `transparent` when compressed(minify = true).
581            // https://www.w3.org/TR/css-color-4/#transparent-black
582            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              // Try first with two decimal places, then with three.
594              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        // Serialize as hex.
628        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
655// From esbuild: https://github.com/evanw/esbuild/blob/18e13bdfdca5cd3c7a2fae1a8bd739f8f891572c/internal/css_parser/css_decls_color.go#L218
656// 0xAABBCCDD => 0xABCD
657fn compact_hex(v: u32) -> u32 {
658  return ((v & 0x0FF00000) >> 12) | ((v & 0x00000FF0) >> 4);
659}
660
661// 0xABCD => 0xAABBCCDD
662fn 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  // These names are shorter than their hex codes
668  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
1021// https://www.w3.org/TR/css-color-4/#lab-colors
1022fn 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/// Parses the lab() and oklab() functions.
1101#[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  // https://www.w3.org/TR/css-color-4/#funcdef-lab
1110  input.parse_nested_block(|input| {
1111    parser.parse_relative::<T, _, _>(input, |input, parser| {
1112      // f32::max() does not propagate NaN, so use clamp for now until f32::maximum() is stable.
1113      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/// Parses the lch() and oklch() functions.
1125#[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  // https://www.w3.org/TR/css-color-4/#funcdef-lch
1134  input.parse_nested_block(|input| {
1135    parser.parse_relative::<T, _, _>(input, |input, parser| {
1136      if let Some(from) = &mut parser.from {
1137        // Relative angles should be normalized.
1138        // https://www.w3.org/TR/css-color-5/#relative-LCH
1139        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  // https://www.w3.org/TR/css-color-4/#color-function
1162  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  // Out of gamut values should not be clamped, i.e. values < 0 or > 1 should be preserved.
1211  // The browser will gamut-map the color for the target device that it is rendered on.
1212  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/// Parses the hsl() and hwb() functions.
1235/// The results of this function are stored as floating point if there are any `none` components.
1236#[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  // https://drafts.csswg.org/css-color-4/#the-hsl-notation
1244  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  // https://drafts.csswg.org/css-color-4/#rgb-functions
1284  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    // "xyz" has better compatibility (Safari 15) than "xyz-d65", and it is shorter.
1467    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  /// A channel type for a color space.
1489  #[derive(PartialEq, Eq, Clone, Copy)]
1490  pub struct ChannelType: u8 {
1491    /// Channel represents a percentage.
1492    const Percentage = 0b001;
1493    /// Channel represents an angle.
1494    const Angle = 0b010;
1495    /// Channel represents a number.
1496    const Number = 0b100;
1497  }
1498}
1499
1500/// A trait for color spaces.
1501pub trait ColorSpace {
1502  /// Returns the raw color component values.
1503  fn components(&self) -> (f32, f32, f32, f32);
1504  /// Returns the channel names for this color space.
1505  fn channels(&self) -> (&'static str, &'static str, &'static str);
1506  /// Returns the channel types for this color space.
1507  fn types(&self) -> (ChannelType, ChannelType, ChannelType);
1508  /// Resolves missing color components (e.g. `none` keywords) in the color.
1509  fn resolve_missing(&self) -> Self;
1510  /// Returns a resolved color by replacing missing (i.e. `none`) components with zero,
1511  /// and performing gamut mapping to ensure the color can be represented within the color space.
1512  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      /// The alpha component.
1539      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  /// A color in the [`sRGB`](https://www.w3.org/TR/css-color-4/#predefined-sRGB) color space.
1579  pub struct SRGB {
1580    /// The red component.
1581    r: Number,
1582    /// The green component.
1583    g: Number,
1584    /// The blue component.
1585    b: Number
1586  }
1587}
1588
1589// Copied from an older version of cssparser.
1590/// A color with red, green, blue, and alpha components, in a byte each.
1591#[derive(Clone, Copy, PartialEq, Debug)]
1592pub struct RGBA {
1593  /// The red component.
1594  pub red: u8,
1595  /// The green component.
1596  pub green: u8,
1597  /// The blue component.
1598  pub blue: u8,
1599  /// The alpha component.
1600  pub alpha: u8,
1601}
1602
1603impl RGBA {
1604  /// Constructs a new RGBA value from float components. It expects the red,
1605  /// green, blue and alpha channels in that order, and all values will be
1606  /// clamped to the 0.0 ... 1.0 range.
1607  #[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  /// Returns a transparent color.
1613  #[inline]
1614  pub fn transparent() -> Self {
1615    Self::new(0, 0, 0, 0.0)
1616  }
1617
1618  /// Same thing, but with `u8` values instead of floats in the 0 to 1 range.
1619  #[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  /// Returns the red channel in a floating point number form, from 0 to 1.
1630  #[inline]
1631  pub fn red_f32(&self) -> f32 {
1632    self.red as f32 / 255.0
1633  }
1634
1635  /// Returns the green channel in a floating point number form, from 0 to 1.
1636  #[inline]
1637  pub fn green_f32(&self) -> f32 {
1638    self.green as f32 / 255.0
1639  }
1640
1641  /// Returns the blue channel in a floating point number form, from 0 to 1.
1642  #[inline]
1643  pub fn blue_f32(&self) -> f32 {
1644    self.blue as f32 / 255.0
1645  }
1646
1647  /// Returns the alpha channel in a floating point number form, from 0 to 1.
1648  #[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  // Whilst scaling by 256 and flooring would provide
1656  // an equal distribution of integers to percentage inputs,
1657  // this is not what Gecko does so we instead multiply by 255
1658  // and round (adding 0.5 and flooring is equivalent to rounding)
1659  //
1660  // Chrome does something similar for the alpha value, but not
1661  // the rgb values.
1662  //
1663  // See https://bugzilla.mozilla.org/show_bug.cgi?id=1340484
1664  //
1665  // Clamping to 256 and rounding after would let 1.0 map to 256, and
1666  // `256.0_f32 as u8` is undefined behavior:
1667  //
1668  // https://github.com/rust-lang/rust/issues/10184
1669  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  /// A color in the [`RGB`](https://w3c.github.io/csswg-drafts/css-color-4/#rgb-functions) color space.
1678  /// Components are in the 0-255 range.
1679  pub struct RGB {
1680    /// The red component.
1681    r: Number,
1682    /// The green component.
1683    g: Number,
1684    /// The blue component.
1685    b: Number
1686  }
1687}
1688
1689define_colorspace! {
1690  /// A color in the [`sRGB-linear`](https://www.w3.org/TR/css-color-4/#predefined-sRGB-linear) color space.
1691  pub struct SRGBLinear {
1692    /// The red component.
1693    r: Number,
1694    /// The green component.
1695    g: Number,
1696    /// The blue component.
1697    b: Number
1698  }
1699}
1700
1701define_colorspace! {
1702  /// A color in the [`display-p3`](https://www.w3.org/TR/css-color-4/#predefined-display-p3) color space.
1703  pub struct P3 {
1704    /// The red component.
1705    r: Number,
1706    /// The green component.
1707    g: Number,
1708    /// The blue component.
1709    b: Number
1710  }
1711}
1712
1713define_colorspace! {
1714  /// A color in the [`a98-rgb`](https://www.w3.org/TR/css-color-4/#predefined-a98-rgb) color space.
1715  pub struct A98 {
1716    /// The red component.
1717    r: Number,
1718    /// The green component.
1719    g: Number,
1720    /// The blue component.
1721    b: Number
1722  }
1723}
1724
1725define_colorspace! {
1726  /// A color in the [`prophoto-rgb`](https://www.w3.org/TR/css-color-4/#predefined-prophoto-rgb) color space.
1727  pub struct ProPhoto {
1728    /// The red component.
1729    r: Number,
1730    /// The green component.
1731    g: Number,
1732    /// The blue component.
1733    b: Number
1734  }
1735}
1736
1737define_colorspace! {
1738  /// A color in the [`rec2020`](https://www.w3.org/TR/css-color-4/#predefined-rec2020) color space.
1739  pub struct Rec2020 {
1740    /// The red component.
1741    r: Number,
1742    /// The green component.
1743    g: Number,
1744    /// The blue component.
1745    b: Number
1746  }
1747}
1748
1749define_colorspace! {
1750  /// A color in the [CIE Lab](https://www.w3.org/TR/css-color-4/#cie-lab) color space.
1751  pub struct LAB {
1752    /// The lightness component.
1753    l: Number,
1754    /// The a component.
1755    a: Number,
1756    /// The b component.
1757    b: Number
1758  }
1759}
1760
1761define_colorspace! {
1762  /// A color in the [CIE LCH](https://www.w3.org/TR/css-color-4/#cie-lab) color space.
1763  pub struct LCH {
1764    /// The lightness component.
1765    l: Number,
1766    /// The chroma component.
1767    c: Number,
1768    /// The hue component.
1769    h: Angle
1770  }
1771}
1772
1773define_colorspace! {
1774  /// A color in the [OKLab](https://www.w3.org/TR/css-color-4/#ok-lab) color space.
1775  pub struct OKLAB {
1776    /// The lightness component.
1777    l: Number,
1778    /// The a component.
1779    a: Number,
1780    /// The b component.
1781    b: Number
1782  }
1783}
1784
1785define_colorspace! {
1786  /// A color in the [OKLCH](https://www.w3.org/TR/css-color-4/#ok-lab) color space.
1787  pub struct OKLCH {
1788    /// The lightness component.
1789    l: Number,
1790    /// The chroma component.
1791    c: Number,
1792    /// The hue component.
1793    h: Angle
1794  }
1795}
1796
1797define_colorspace! {
1798  /// A color in the [`xyz-d50`](https://www.w3.org/TR/css-color-4/#predefined-xyz) color space.
1799  pub struct XYZd50 {
1800    /// The x component.
1801    x: Number,
1802    /// The y component.
1803    y: Number,
1804    /// The z component.
1805    z: Number
1806  }
1807}
1808
1809define_colorspace! {
1810  /// A color in the [`xyz-d65`](https://www.w3.org/TR/css-color-4/#predefined-xyz) color space.
1811  pub struct XYZd65 {
1812    /// The x component.
1813    x: Number,
1814    /// The y component.
1815    y: Number,
1816    /// The z component.
1817    z: Number
1818  }
1819}
1820
1821define_colorspace! {
1822  /// A color in the [`hsl`](https://www.w3.org/TR/css-color-4/#the-hsl-notation) color space.
1823  pub struct HSL {
1824    /// The hue component.
1825    h: Angle,
1826    /// The saturation component.
1827    s: Number,
1828    /// The lightness component.
1829    l: Number
1830  }
1831}
1832
1833define_colorspace! {
1834  /// A color in the [`hwb`](https://www.w3.org/TR/css-color-4/#the-hwb-notation) color space.
1835  pub struct HWB {
1836    /// The hue component.
1837    h: Angle,
1838    /// The whiteness component.
1839    w: Number,
1840    /// The blackness component.
1841    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  // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L375
1868  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  // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L385
1880  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    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L352
1942    const K: f32 = 24389.0 / 27.0; // 29^3/3^3
1943    const E: f32 = 216.0 / 24389.0; // 6^3/29^3
1944
1945    let lab = lab.resolve_missing();
1946    let l = lab.l;
1947    let a = lab.a;
1948    let b = lab.b;
1949
1950    // compute f, starting with the luminance-related term
1951    let f1 = (l + 16.0) / 116.0;
1952    let f0 = a / 500.0 + f1;
1953    let f2 = f1 - b / 200.0;
1954
1955    // compute xyz
1956    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    // Compute XYZ by scaling xyz by reference white
1971    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    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L319
1983    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    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L319
2009    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    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L62
2035    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  // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L31
2082  // convert an array of linear-light sRGB values in the range 0.0-1.0
2083  // to gamma corrected form
2084  // https://en.wikipedia.org/wiki/SRGB
2085  // Extended transfer function:
2086  // For negative values, linear portion extends on reflection
2087  // of axis, then uses reflected pow below that
2088
2089  #[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    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L418
2109    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    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L400
2148    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    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L332
2187    // Assuming XYZ is relative to D50, convert to CIE LAB
2188    // from CIE standard, which now defines these as a rational fraction
2189    const E: f32 = 216.0 / 24389.0; // 6^3/29^3
2190    const K: f32 = 24389.0 / 27.0; // 29^3/3^3
2191
2192    // compute xyz, which is XYZ scaled relative to reference white
2193    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    // now compute f
2199    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  // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L11
2232  // convert sRGB values where in-gamut values are in the range [0 - 1]
2233  // to linear light (un-companded) form.
2234  // https://en.wikipedia.org/wiki/SRGB
2235  // Extended transfer function:
2236  // for negative values, linear portion is extended on reflection of axis,
2237  // then reflected power function is used.
2238
2239  #[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    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L50
2259    // convert an array of linear-light sRGB values to CIE XYZ
2260    // using sRGB's own white, D65 (no chromatic adaptation)
2261    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    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L105
2287    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); // same as sRGB
2302    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    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L91
2314    // convert linear-light display-p3 values to CIE XYZ
2315    // using D65 (no chromatic adaptation)
2316    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
2317    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    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L181
2344    #[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    // convert an array of a98-rgb values in the range 0.0 - 1.0
2351    // to linear light (un-companded) form.
2352    // negative values are also now accepted
2353    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    // convert an array of linear-light a98-rgb values to CIE XYZ
2359    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
2360    // has greater numerical precision than section 4.3.5.3 of
2361    // https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
2362    // but the values below were calculated from first principles
2363    // from the chromaticity coordinates of R G B W
2364    // see matrixmaker.html
2365    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    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L222
2390    // convert XYZ to linear-light a98-rgb
2391    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      // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L193
2406      // convert linear-light a98-rgb  in the range 0.0-1.0
2407      // to gamma corrected form
2408      // negative values are also now accepted
2409      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    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L118
2430    // convert an array of prophoto-rgb values
2431    // where in-gamut colors are in the range [0.0 - 1.0]
2432    // to linear light (un-companded) form.
2433    // Transfer curve is gamma 1.8 with a small linear portion
2434    // Extended transfer function
2435
2436    #[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    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L155
2454    // convert an array of linear-light prophoto-rgb values to CIE XYZ
2455    // using  D50 (so no chromatic adaptation needed afterwards)
2456    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
2457    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    // convert XYZ to linear-light prophoto-rgb
2482    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      // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L137
2497      // convert linear-light prophoto-rgb  in the range 0.0-1.0
2498      // to gamma corrected form
2499      // Transfer curve is gamma 1.8 with a small linear portion
2500      // TODO for negative values, extend linear portion on reflection of axis, then add pow below that
2501      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    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L235
2528    // convert an array of rec2020 RGB values in the range 0.0 - 1.0
2529    // to linear light (un-companded) form.
2530    // ITU-R BT.2020-2 p.4
2531
2532    #[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    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L276
2552    // convert an array of linear-light rec2020 values to CIE XYZ
2553    // using  D65 (no chromatic adaptation)
2554    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
2555    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    // convert XYZ to linear-light rec2020
2580    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      // convert linear-light rec2020 RGB  in the range 0.0-1.0
2595      // to gamma corrected form
2596      // ITU-R BT.2020-2 p.4
2597
2598      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    // https://drafts.csswg.org/css-color/#rgb-to-hsl
2627    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    // https://drafts.csswg.org/css-color/#hsl-to-rgb
2668    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    // https://drafts.csswg.org/css-color/#hwb-to-rgb
2701    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
2799// Once Rust specialization is stable, this could be simplified.
2800via!(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
2904// RGBA is an 8-bit version. Convert to SRGB, which is a
2905// more accurate floating point representation for all operations.
2906via!(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        // TODO: should we serialize as color(srgb, ...)?
3063        // would be more precise than 8-bit color.
3064        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
3081/// A trait that colors implement to support [gamut mapping](https://www.w3.org/TR/css-color-4/#gamut-mapping).
3082pub trait ColorGamut {
3083  /// Returns whether the color is within the gamut of the color space.
3084  fn in_gamut(&self) -> bool;
3085  /// Clips the color so that it is within the gamut of the color space.
3086  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  // https://www.w3.org/TR/css-color-4/#color-difference-OK
3181  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  // https://www.w3.org/TR/css-color-4/#binsearch
3198  let mut current: OKLCH = color.into();
3199
3200  // If lightness is >= 100%, return pure white.
3201  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 lightness <= 0%, return pure black.
3212  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  // https://drafts.csswg.org/css-color-5/#color-mix-percent-norm
3281  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  /// Mixes this color with another color, including the specified amount of each.
3347  /// Implemented according to the [`color-mix()`](https://www.w3.org/TR/css-color-5/#color-mix) function.
3348  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    // https://drafts.csswg.org/css-color-5/#color-mix-result
3388    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    // https://www.w3.org/TR/css-color-4/#powerless
3400    if converted_first {
3401      first_color.adjust_powerless_components();
3402    }
3403
3404    if converted_second {
3405      second_color.adjust_powerless_components();
3406    }
3407
3408    // https://drafts.csswg.org/css-color-4/#interpolation-missing
3409    first_color.fill_missing_components(&second_color);
3410    second_color.fill_missing_components(&first_color);
3411
3412    // https://www.w3.org/TR/css-color-4/#hue-interpolation
3413    first_color.adjust_hue(&mut second_color, method);
3414
3415    // https://www.w3.org/TR/css-color-4/#interpolation-alpha
3416    first_color.premultiply();
3417    second_color.premultiply();
3418
3419    // https://drafts.csswg.org/css-color-5/#color-mix-percent-norm
3420    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
3436/// A trait that colors implement to support interpolation.
3437pub trait Interpolate {
3438  /// Adjusts components that are powerless to be NaN.
3439  fn adjust_powerless_components(&mut self) {}
3440  /// Fills missing components (represented as NaN) to match the other color to interpolate with.
3441  fn fill_missing_components(&mut self, other: &Self);
3442  /// Adjusts the color hue according to the given hue interpolation method.
3443  fn adjust_hue(&mut self, _: &mut Self, _: HueInterpolationMethod) {}
3444  /// Premultiplies the color by its alpha value.
3445  fn premultiply(&mut self);
3446  /// Un-premultiplies the color by the given alpha multiplier.
3447  fn unpremultiply(&mut self, alpha_multiplier: f32);
3448  /// Interpolates the color with another using the given amounts of each.
3449  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 the lightness of a LAB color is 0%, both the a and b components are powerless.
3528      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 the chroma of an LCH color is 0%, the hue component is powerless.
3540      // If the lightness of an LCH color is 0%, both the hue and chroma components are powerless.
3541      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 the saturation of an HSL color is 0%, then the hue component is powerless.
3606    // If the lightness of an HSL color is 0% or 100%, both the saturation and hue components are powerless.
3607    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 white+black is equal to 100% (after normalization), it defines an achromatic color,
3629    // i.e. some shade of gray, without any hint of the chosen hue. In this case, the hue component is powerless.
3630    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    // https://drafts.csswg.org/css-color/#hue-interpolation
3645    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        // https://www.w3.org/TR/css-color-4/#hue-shorter
3653        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        // https://www.w3.org/TR/css-color-4/#hue-longer
3662        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        // https://www.w3.org/TR/css-color-4/#hue-increasing
3671        if *b < *a {
3672          *b += 360.0;
3673        }
3674      }
3675      HueInterpolationMethod::Decreasing => {
3676        // https://www.w3.org/TR/css-color-4/#hue-decreasing
3677        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))]
3705/// A CSS [system color](https://drafts.csswg.org/css-color/#css-system-colors) keyword.
3706pub enum SystemColor {
3707  /// Background of accented user interface controls.
3708  AccentColor,
3709  /// Text of accented user interface controls.
3710  AccentColorText,
3711  /// Text in active links. For light backgrounds, traditionally red.
3712  ActiveText,
3713  /// The base border color for push buttons.
3714  ButtonBorder,
3715  /// The face background color for push buttons.
3716  ButtonFace,
3717  /// Text on push buttons.
3718  ButtonText,
3719  /// Background of application content or documents.
3720  Canvas,
3721  /// Text in application content or documents.
3722  CanvasText,
3723  /// Background of input fields.
3724  Field,
3725  /// Text in input fields.
3726  FieldText,
3727  /// Disabled text. (Often, but not necessarily, gray.)
3728  GrayText,
3729  /// Background of selected text, for example from ::selection.
3730  Highlight,
3731  /// Text of selected text.
3732  HighlightText,
3733  /// Text in non-active, non-visited links. For light backgrounds, traditionally blue.
3734  LinkText,
3735  /// Background of text that has been specially marked (such as by the HTML mark element).
3736  Mark,
3737  /// Text that has been specially marked (such as by the HTML mark element).
3738  MarkText,
3739  /// Background of selected items, for example a selected checkbox.
3740  SelectedItem,
3741  /// Text of selected items.
3742  SelectedItemText,
3743  /// Text in visited links. For light backgrounds, traditionally purple.
3744  VisitedText,
3745
3746  // Deprecated colors: https://drafts.csswg.org/css-color/#deprecated-system-colors
3747  /// Active window border. Same as ButtonBorder.
3748  ActiveBorder,
3749  /// Active window caption. Same as Canvas.
3750  ActiveCaption,
3751  /// Background color of multiple document interface. Same as Canvas.
3752  AppWorkspace,
3753  /// Desktop background. Same as Canvas.
3754  Background,
3755  /// The color of the border facing the light source for 3-D elements that appear 3-D due to one layer of surrounding border. Same as ButtonFace.
3756  ButtonHighlight,
3757  /// The color of the border away from the light source for 3-D elements that appear 3-D due to one layer of surrounding border. Same as ButtonFace.
3758  ButtonShadow,
3759  /// Text in caption, size box, and scrollbar arrow box. Same as CanvasText.
3760  CaptionText,
3761  /// Inactive window border. Same as ButtonBorder.
3762  InactiveBorder,
3763  /// Inactive window caption. Same as Canvas.
3764  InactiveCaption,
3765  /// Color of text in an inactive caption. Same as GrayText.
3766  InactiveCaptionText,
3767  /// Background color for tooltip controls. Same as Canvas.
3768  InfoBackground,
3769  /// Text color for tooltip controls. Same as CanvasText.
3770  InfoText,
3771  /// Menu background. Same as Canvas.
3772  Menu,
3773  /// Text in menus. Same as CanvasText.
3774  MenuText,
3775  /// Scroll bar gray area. Same as Canvas.
3776  Scrollbar,
3777  /// The color of the darker (generally outer) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border. Same as ButtonBorder.
3778  ThreeDDarkShadow,
3779  /// The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border. Same as ButtonFace.
3780  ThreeDFace,
3781  /// The color of the lighter (generally outer) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border. Same as ButtonBorder.
3782  ThreeDHighlight,
3783  /// The color of the darker (generally inner) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border. Same as ButtonBorder.
3784  ThreeDLightShadow,
3785  /// The color of the lighter (generally inner) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border. Same as ButtonBorder.
3786  ThreeDShadow,
3787  /// Window background. Same as Canvas.
3788  Window,
3789  /// Window frame. Same as ButtonBorder.
3790  WindowFrame,
3791  /// Text in windows. Same as CanvasText.
3792  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}