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  fn alpha(&self) -> Result<f32, ()> {
332    Ok(match self {
333      CssColor::RGBA(rgba) => rgba.alpha_f32(),
334      CssColor::LAB(lab) => match &**lab {
335        LABColor::LAB(lab) => lab.alpha,
336        LABColor::LCH(lch) => lch.alpha,
337        LABColor::OKLAB(lab) => lab.alpha,
338        LABColor::OKLCH(lch) => lch.alpha,
339      },
340      CssColor::Predefined(predefined) => match &**predefined {
341        PredefinedColor::SRGB(rgb) => rgb.alpha,
342        PredefinedColor::SRGBLinear(rgb) => rgb.alpha,
343        PredefinedColor::DisplayP3(rgb) => rgb.alpha,
344        PredefinedColor::A98(rgb) => rgb.alpha,
345        PredefinedColor::ProPhoto(rgb) => rgb.alpha,
346        PredefinedColor::Rec2020(rgb) => rgb.alpha,
347        PredefinedColor::XYZd50(xyz) => xyz.alpha,
348        PredefinedColor::XYZd65(xyz) => xyz.alpha,
349      },
350      CssColor::Float(float) => match &**float {
351        FloatColor::RGB(rgb) => rgb.alpha,
352        FloatColor::HSL(hsl) => hsl.alpha,
353        FloatColor::HWB(hwb) => hwb.alpha,
354      },
355      CssColor::LightDark(..) | CssColor::CurrentColor | CssColor::System(..) => return Err(()),
356    })
357  }
358
359  fn with_alpha(self, alpha: f32) -> CssColor {
360    match self {
361      CssColor::RGBA(rgba) => {
362        // Store as Float to preserve the exact f32 alpha value through subsequent
363        // relative color computations. Going through u8 here would quantize 0.5 to
364        // 128/255 ≈ 0.50196 and accumulate error in nested alpha()/color-mix() calls.
365        let mut rgb = RGB::from(rgba);
366        rgb.alpha = alpha;
367        CssColor::Float(Box::new(FloatColor::RGB(rgb)))
368      }
369      CssColor::LAB(mut lab) => {
370        match &mut *lab {
371          LABColor::LAB(lab) => lab.alpha = alpha,
372          LABColor::LCH(lch) => lch.alpha = alpha,
373          LABColor::OKLAB(lab) => lab.alpha = alpha,
374          LABColor::OKLCH(lch) => lch.alpha = alpha,
375        }
376        CssColor::LAB(lab)
377      }
378      CssColor::Predefined(mut predefined) => {
379        match &mut *predefined {
380          PredefinedColor::SRGB(rgb) => rgb.alpha = alpha,
381          PredefinedColor::SRGBLinear(rgb) => rgb.alpha = alpha,
382          PredefinedColor::DisplayP3(rgb) => rgb.alpha = alpha,
383          PredefinedColor::A98(rgb) => rgb.alpha = alpha,
384          PredefinedColor::ProPhoto(rgb) => rgb.alpha = alpha,
385          PredefinedColor::Rec2020(rgb) => rgb.alpha = alpha,
386          PredefinedColor::XYZd50(xyz) => xyz.alpha = alpha,
387          PredefinedColor::XYZd65(xyz) => xyz.alpha = alpha,
388        }
389        CssColor::Predefined(predefined)
390      }
391      CssColor::Float(mut float) => {
392        match &mut *float {
393          FloatColor::RGB(rgb) => rgb.alpha = alpha,
394          FloatColor::HSL(hsl) => hsl.alpha = alpha,
395          FloatColor::HWB(hwb) => hwb.alpha = alpha,
396        }
397        CssColor::Float(float)
398      }
399      CssColor::LightDark(light, dark) => CssColor::LightDark(
400        Box::new((*light).with_alpha(alpha)),
401        Box::new((*dark).with_alpha(alpha)),
402      ),
403      color => color,
404    }
405  }
406
407  /// Converts the color to RGBA.
408  pub fn to_rgb(&self) -> Result<CssColor, ()> {
409    match self {
410      CssColor::LightDark(light, dark) => {
411        Ok(CssColor::LightDark(Box::new(light.to_rgb()?), Box::new(dark.to_rgb()?)))
412      }
413      _ => Ok(RGBA::try_from(self)?.into()),
414    }
415  }
416
417  /// Converts the color to the LAB color space.
418  pub fn to_lab(&self) -> Result<CssColor, ()> {
419    match self {
420      CssColor::LightDark(light, dark) => {
421        Ok(CssColor::LightDark(Box::new(light.to_lab()?), Box::new(dark.to_lab()?)))
422      }
423      _ => Ok(LAB::try_from(self)?.into()),
424    }
425  }
426
427  /// Converts the color to the P3 color space.
428  pub fn to_p3(&self) -> Result<CssColor, ()> {
429    match self {
430      CssColor::LightDark(light, dark) => {
431        Ok(CssColor::LightDark(Box::new(light.to_p3()?), Box::new(dark.to_p3()?)))
432      }
433      _ => Ok(P3::try_from(self)?.into()),
434    }
435  }
436
437  pub(crate) fn get_possible_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
438    // Fallbacks occur in levels: Oklab -> Lab -> P3 -> RGB. We start with all levels
439    // below and including the authored color space, and remove the ones that aren't
440    // compatible with our browser targets.
441    let mut fallbacks = match self {
442      CssColor::CurrentColor | CssColor::RGBA(_) | CssColor::Float(..) | CssColor::System(..) => {
443        return ColorFallbackKind::empty()
444      }
445      CssColor::LAB(lab) => match &**lab {
446        LABColor::LAB(..) | LABColor::LCH(..) if should_compile!(targets, LabColors) => {
447          ColorFallbackKind::LAB.and_below()
448        }
449        LABColor::OKLAB(..) | LABColor::OKLCH(..) if should_compile!(targets, OklabColors) => {
450          ColorFallbackKind::OKLAB.and_below()
451        }
452        _ => return ColorFallbackKind::empty(),
453      },
454      CssColor::Predefined(predefined) => match &**predefined {
455        PredefinedColor::DisplayP3(..) if should_compile!(targets, P3Colors) => ColorFallbackKind::P3.and_below(),
456        _ if should_compile!(targets, ColorFunction) => ColorFallbackKind::LAB.and_below(),
457        _ => return ColorFallbackKind::empty(),
458      },
459      CssColor::LightDark(light, dark) => {
460        return light.get_possible_fallbacks(targets) | dark.get_possible_fallbacks(targets);
461      }
462    };
463
464    if fallbacks.contains(ColorFallbackKind::OKLAB) {
465      if !should_compile!(targets, OklabColors) {
466        fallbacks.remove(ColorFallbackKind::LAB.and_below());
467      }
468    }
469
470    if fallbacks.contains(ColorFallbackKind::LAB) {
471      if !should_compile!(targets, LabColors) {
472        fallbacks.remove(ColorFallbackKind::P3.and_below());
473      } else if targets
474        .browsers
475        .map(|targets| Feature::LabColors.is_partially_compatible(targets))
476        .unwrap_or(false)
477      {
478        // We don't need P3 if Lab is supported by some of our targets.
479        // No browser implements Lab but not P3.
480        fallbacks.remove(ColorFallbackKind::P3);
481      }
482    }
483
484    if fallbacks.contains(ColorFallbackKind::P3) {
485      if !should_compile!(targets, P3Colors) {
486        fallbacks.remove(ColorFallbackKind::RGB);
487      } else if fallbacks.highest() != ColorFallbackKind::P3
488        && !targets
489          .browsers
490          .map(|targets| Feature::P3Colors.is_partially_compatible(targets))
491          .unwrap_or(false)
492      {
493        // Remove P3 if it isn't supported by any targets, and wasn't the
494        // original authored color.
495        fallbacks.remove(ColorFallbackKind::P3);
496      }
497    }
498
499    fallbacks
500  }
501
502  /// Returns the color fallback types needed for the given browser targets.
503  pub fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
504    // Get the full set of possible fallbacks, and remove the highest one, which
505    // will replace the original declaration. The remaining fallbacks need to be added.
506    let fallbacks = self.get_possible_fallbacks(targets);
507    fallbacks - fallbacks.highest()
508  }
509
510  /// Returns a fallback color for the given fallback type.
511  pub fn get_fallback(&self, kind: ColorFallbackKind) -> CssColor {
512    if matches!(self, CssColor::RGBA(_)) {
513      return self.clone();
514    }
515
516    match kind {
517      ColorFallbackKind::RGB => self.to_rgb().unwrap(),
518      ColorFallbackKind::P3 => self.to_p3().unwrap(),
519      ColorFallbackKind::LAB => self.to_lab().unwrap(),
520      _ => unreachable!(),
521    }
522  }
523
524  pub(crate) fn get_features(&self) -> Features {
525    let mut features = Features::empty();
526    match self {
527      CssColor::LAB(labcolor) => match &**labcolor {
528        LABColor::LAB(_) | LABColor::LCH(_) => {
529          features |= Features::LabColors;
530        }
531        LABColor::OKLAB(_) | LABColor::OKLCH(_) => {
532          features |= Features::OklabColors;
533        }
534      },
535      CssColor::Predefined(predefined_color) => {
536        features |= Features::ColorFunction;
537        match &**predefined_color {
538          PredefinedColor::DisplayP3(_) => {
539            features |= Features::P3Colors;
540          }
541          _ => {}
542        }
543      }
544      CssColor::Float(_) => {
545        features |= Features::SpaceSeparatedColorNotation;
546      }
547      CssColor::LightDark(light, dark) => {
548        features |= Features::LightDark;
549        features |= light.get_features();
550        features |= dark.get_features();
551      }
552      _ => {}
553    }
554
555    features
556  }
557}
558
559impl IsCompatible for CssColor {
560  fn is_compatible(&self, browsers: Browsers) -> bool {
561    match self {
562      CssColor::CurrentColor | CssColor::RGBA(_) | CssColor::Float(..) => true,
563      CssColor::LAB(lab) => match &**lab {
564        LABColor::LAB(..) | LABColor::LCH(..) => Feature::LabColors.is_compatible(browsers),
565        LABColor::OKLAB(..) | LABColor::OKLCH(..) => Feature::OklabColors.is_compatible(browsers),
566      },
567      CssColor::Predefined(predefined) => match &**predefined {
568        PredefinedColor::DisplayP3(..) => Feature::P3Colors.is_compatible(browsers),
569        _ => Feature::ColorFunction.is_compatible(browsers),
570      },
571      CssColor::LightDark(light, dark) => {
572        Feature::LightDark.is_compatible(browsers) && light.is_compatible(browsers) && dark.is_compatible(browsers)
573      }
574      CssColor::System(system) => system.is_compatible(browsers),
575    }
576  }
577}
578
579impl FallbackValues for CssColor {
580  fn get_fallbacks(&mut self, targets: Targets) -> Vec<CssColor> {
581    let fallbacks = self.get_necessary_fallbacks(targets);
582
583    let mut res = Vec::new();
584    if fallbacks.contains(ColorFallbackKind::RGB) {
585      res.push(self.to_rgb().unwrap());
586    }
587
588    if fallbacks.contains(ColorFallbackKind::P3) {
589      res.push(self.to_p3().unwrap());
590    }
591
592    if fallbacks.contains(ColorFallbackKind::LAB) {
593      *self = self.to_lab().unwrap();
594    }
595
596    res
597  }
598}
599
600impl Default for CssColor {
601  fn default() -> CssColor {
602    CssColor::transparent()
603  }
604}
605
606impl<'i> Parse<'i> for CssColor {
607  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
608    let location = input.current_source_location();
609    let token = input.next()?;
610    match *token {
611      Token::Hash(ref value) | Token::IDHash(ref value) => parse_hash_color(value.as_bytes())
612        .map(|(r, g, b, a)| CssColor::RGBA(RGBA::new(r, g, b, a)))
613        .map_err(|_| location.new_unexpected_token_error(token.clone())),
614      Token::Ident(ref value) => Ok(match_ignore_ascii_case! { value,
615        "currentcolor" => CssColor::CurrentColor,
616        "transparent" => CssColor::RGBA(RGBA::transparent()),
617        _ => {
618          if let Ok((r, g, b)) = parse_named_color(value) {
619            CssColor::RGBA(RGBA { red: r, green: g, blue: b, alpha: 255 })
620          } else if let Ok(system_color) = SystemColor::parse_string(&value) {
621            CssColor::System(system_color)
622          } else {
623            return Err(location.new_unexpected_token_error(token.clone()))
624          }
625        }
626      }),
627      Token::Function(ref name) => parse_color_function(location, name.clone(), input),
628      _ => Err(location.new_unexpected_token_error(token.clone())),
629    }
630  }
631}
632
633impl ToCss for CssColor {
634  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
635  where
636    W: std::fmt::Write,
637  {
638    match self {
639      CssColor::CurrentColor => dest.write_str("currentColor"),
640      CssColor::RGBA(color) => {
641        if color.alpha == 255 {
642          let hex: u32 = ((color.red as u32) << 16) | ((color.green as u32) << 8) | (color.blue as u32);
643          if let Some(name) = short_color_name(hex) {
644            return dest.write_str(name);
645          }
646
647          let compact = compact_hex(hex);
648          if hex == expand_hex(compact) {
649            write!(dest, "#{:03x}", compact)?;
650          } else {
651            write!(dest, "#{:06x}", hex)?;
652          }
653        } else {
654          // If the #rrggbbaa syntax is not supported by the browser targets, output rgba()
655          if should_compile!(dest.targets.current, HexAlphaColors) {
656            // If the browser doesn't support `#rrggbbaa` color syntax, it is converted to `transparent` when compressed(minify = true).
657            // https://www.w3.org/TR/css-color-4/#transparent-black
658            if dest.minify && color.red == 0 && color.green == 0 && color.blue == 0 && color.alpha == 0 {
659              return dest.write_str("transparent");
660            } else {
661              dest.write_str("rgba(")?;
662              write!(dest, "{}", color.red)?;
663              dest.delim(',', false)?;
664              write!(dest, "{}", color.green)?;
665              dest.delim(',', false)?;
666              write!(dest, "{}", color.blue)?;
667              dest.delim(',', false)?;
668
669              // Try first with two decimal places, then with three.
670              let mut rounded_alpha = (color.alpha_f32() * 100.0).round() / 100.0;
671              let clamped = (rounded_alpha * 255.0).round().max(0.).min(255.0) as u8;
672              if clamped != color.alpha {
673                rounded_alpha = (color.alpha_f32() * 1000.).round() / 1000.;
674              }
675
676              rounded_alpha.to_css(dest)?;
677              dest.write_char(')')?;
678              return Ok(());
679            }
680          }
681
682          let hex: u32 = ((color.red as u32) << 24)
683            | ((color.green as u32) << 16)
684            | ((color.blue as u32) << 8)
685            | (color.alpha as u32);
686          let compact = compact_hex(hex);
687          if hex == expand_hex(compact) {
688            write!(dest, "#{:04x}", compact)?;
689          } else {
690            write!(dest, "#{:08x}", hex)?;
691          }
692        }
693        Ok(())
694      }
695      CssColor::LAB(lab) => match &**lab {
696        LABColor::LAB(lab) => write_components("lab", lab.l / 100.0, lab.a, lab.b, lab.alpha, dest),
697        LABColor::LCH(lch) => write_components("lch", lch.l / 100.0, lch.c, lch.h, lch.alpha, dest),
698        LABColor::OKLAB(lab) => write_components("oklab", lab.l, lab.a, lab.b, lab.alpha, dest),
699        LABColor::OKLCH(lch) => write_components("oklch", lch.l, lch.c, lch.h, lch.alpha, dest),
700      },
701      CssColor::Predefined(predefined) => write_predefined(predefined, dest),
702      CssColor::Float(float) => {
703        // Serialize as hex.
704        let rgb = RGB::from(**float);
705        CssColor::from(rgb).to_css(dest)
706      }
707      CssColor::LightDark(light, dark) => {
708        if should_compile!(dest.targets.current, LightDark) {
709          dest.write_str("var(--lightningcss-light")?;
710          dest.delim(',', false)?;
711          light.to_css(dest)?;
712          dest.write_char(')')?;
713          dest.whitespace()?;
714          dest.write_str("var(--lightningcss-dark")?;
715          dest.delim(',', false)?;
716          dark.to_css(dest)?;
717          return dest.write_char(')');
718        }
719
720        dest.write_str("light-dark(")?;
721        light.to_css(dest)?;
722        dest.delim(',', false)?;
723        dark.to_css(dest)?;
724        dest.write_char(')')
725      }
726      CssColor::System(system) => system.to_css(dest),
727    }
728  }
729}
730
731// From esbuild: https://github.com/evanw/esbuild/blob/18e13bdfdca5cd3c7a2fae1a8bd739f8f891572c/internal/css_parser/css_decls_color.go#L218
732// 0xAABBCCDD => 0xABCD
733fn compact_hex(v: u32) -> u32 {
734  return ((v & 0x0FF00000) >> 12) | ((v & 0x00000FF0) >> 4);
735}
736
737// 0xABCD => 0xAABBCCDD
738fn expand_hex(v: u32) -> u32 {
739  return ((v & 0xF000) << 16) | ((v & 0xFF00) << 12) | ((v & 0x0FF0) << 8) | ((v & 0x00FF) << 4) | (v & 0x000F);
740}
741
742fn short_color_name(v: u32) -> Option<&'static str> {
743  // These names are shorter than their hex codes
744  let s = match v {
745    0x000080 => "navy",
746    0x008000 => "green",
747    0x008080 => "teal",
748    0x4b0082 => "indigo",
749    0x800000 => "maroon",
750    0x800080 => "purple",
751    0x808000 => "olive",
752    0x808080 => "gray",
753    0xa0522d => "sienna",
754    0xa52a2a => "brown",
755    0xc0c0c0 => "silver",
756    0xcd853f => "peru",
757    0xd2b48c => "tan",
758    0xda70d6 => "orchid",
759    0xdda0dd => "plum",
760    0xee82ee => "violet",
761    0xf0e68c => "khaki",
762    0xf0ffff => "azure",
763    0xf5deb3 => "wheat",
764    0xf5f5dc => "beige",
765    0xfa8072 => "salmon",
766    0xfaf0e6 => "linen",
767    0xff0000 => "red",
768    0xff6347 => "tomato",
769    0xff7f50 => "coral",
770    0xffa500 => "orange",
771    0xffc0cb => "pink",
772    0xffd700 => "gold",
773    0xffe4c4 => "bisque",
774    0xfffafa => "snow",
775    0xfffff0 => "ivory",
776    _ => return None,
777  };
778
779  Some(s)
780}
781
782struct RelativeComponentParser {
783  names: (&'static str, &'static str, &'static str),
784  components: (f32, f32, f32, f32),
785  types: (ChannelType, ChannelType, ChannelType),
786}
787
788impl RelativeComponentParser {
789  fn new<T: ColorSpace>(color: &T) -> Self {
790    Self {
791      names: color.channels(),
792      components: color.components(),
793      types: color.types(),
794    }
795  }
796
797  fn alpha_only(alpha: f32) -> Self {
798    Self {
799      names: ("", "", ""),
800      components: (0.0, 0.0, 0.0, alpha),
801      types: (ChannelType::empty(), ChannelType::empty(), ChannelType::empty()),
802    }
803  }
804
805  fn get_ident(&self, ident: &str, allowed_types: ChannelType) -> Option<(f32, ChannelType)> {
806    if ident.eq_ignore_ascii_case(self.names.0) && allowed_types.intersects(self.types.0) {
807      return Some((self.components.0, self.types.0));
808    }
809
810    if ident.eq_ignore_ascii_case(self.names.1) && allowed_types.intersects(self.types.1) {
811      return Some((self.components.1, self.types.1));
812    }
813
814    if ident.eq_ignore_ascii_case(self.names.2) && allowed_types.intersects(self.types.2) {
815      return Some((self.components.2, self.types.2));
816    }
817
818    if ident.eq_ignore_ascii_case("alpha")
819      && allowed_types.intersects(ChannelType::Number | ChannelType::Percentage)
820    {
821      return Some((self.components.3, ChannelType::Number));
822    }
823
824    None
825  }
826
827  fn parse_ident<'i, 't>(
828    &self,
829    input: &mut Parser<'i, 't>,
830    allowed_types: ChannelType,
831  ) -> Result<(f32, ChannelType), ParseError<'i, ParserError<'i>>> {
832    match self.get_ident(input.expect_ident()?.as_ref(), allowed_types) {
833      Some(v) => Ok(v),
834      None => Err(input.new_error_for_next_token()),
835    }
836  }
837}
838
839impl<'i> ColorParser<'i> for RelativeComponentParser {
840  type Output = cssparser_color::Color;
841  type Error = ParserError<'i>;
842
843  fn parse_angle_or_number<'t>(
844    &self,
845    input: &mut Parser<'i, 't>,
846  ) -> Result<AngleOrNumber, ParseError<'i, Self::Error>> {
847    if let Ok((value, ty)) =
848      input.try_parse(|input| self.parse_ident(input, ChannelType::Angle | ChannelType::Number))
849    {
850      return Ok(match ty {
851        ChannelType::Angle => AngleOrNumber::Angle { degrees: value },
852        ChannelType::Number => AngleOrNumber::Number { value },
853        _ => unreachable!(),
854      });
855    }
856
857    if let Ok(value) = input.try_parse(|input| -> Result<AngleOrNumber, ParseError<'i, ParserError<'i>>> {
858      match Calc::parse_with(input, |ident| {
859        self
860          .get_ident(ident, ChannelType::Angle | ChannelType::Number)
861          .map(|(value, ty)| match ty {
862            ChannelType::Angle => Calc::Value(Box::new(Angle::Deg(value))),
863            ChannelType::Number => Calc::Number(value),
864            _ => unreachable!(),
865          })
866      }) {
867        Ok(Calc::Value(v)) => Ok(AngleOrNumber::Angle {
868          degrees: v.to_degrees(),
869        }),
870        Ok(Calc::Number(v)) => Ok(AngleOrNumber::Number { value: v }),
871        _ => Err(input.new_custom_error(ParserError::InvalidValue)),
872      }
873    }) {
874      return Ok(value);
875    }
876
877    Err(input.new_error_for_next_token())
878  }
879
880  fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {
881    if let Ok((value, _)) = input.try_parse(|input| self.parse_ident(input, ChannelType::Number)) {
882      return Ok(value);
883    }
884
885    match Calc::parse_with(input, |ident| {
886      self.get_ident(ident, ChannelType::Number).map(|(v, _)| Calc::Number(v))
887    }) {
888      Ok(Calc::Value(v)) => Ok(*v),
889      Ok(Calc::Number(n)) => Ok(n),
890      _ => Err(input.new_error_for_next_token()),
891    }
892  }
893
894  fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {
895    if let Ok((value, _)) = input.try_parse(|input| self.parse_ident(input, ChannelType::Percentage)) {
896      return Ok(value);
897    }
898
899    if let Ok(value) = input.try_parse(|input| -> Result<Percentage, ParseError<'i, ParserError<'i>>> {
900      match Calc::parse_with(input, |ident| {
901        self
902          .get_ident(ident, ChannelType::Percentage)
903          .map(|(v, _)| Calc::Value(Box::new(Percentage(v))))
904      }) {
905        Ok(Calc::Value(v)) => Ok(*v),
906        _ => Err(input.new_custom_error(ParserError::InvalidValue)),
907      }
908    }) {
909      return Ok(value.0);
910    }
911
912    Err(input.new_error_for_next_token())
913  }
914
915  fn parse_number_or_percentage<'t>(
916    &self,
917    input: &mut Parser<'i, 't>,
918  ) -> Result<NumberOrPercentage, ParseError<'i, Self::Error>> {
919    if let Ok((value, ty)) =
920      input.try_parse(|input| self.parse_ident(input, ChannelType::Percentage | ChannelType::Number))
921    {
922      return Ok(match ty {
923        ChannelType::Percentage => NumberOrPercentage::Percentage { unit_value: value },
924        ChannelType::Number => NumberOrPercentage::Number { value },
925        _ => unreachable!(),
926      });
927    }
928
929    if let Ok(value) = input.try_parse(|input| -> Result<NumberOrPercentage, ParseError<'i, ParserError<'i>>> {
930      match Calc::parse_with(input, |ident| {
931        self
932          .get_ident(ident, ChannelType::Percentage | ChannelType::Number)
933          .map(|(value, ty)| match ty {
934            ChannelType::Percentage => Calc::Value(Box::new(Percentage(value))),
935            ChannelType::Number => Calc::Number(value),
936            _ => unreachable!(),
937          })
938      }) {
939        Ok(Calc::Value(v)) => Ok(NumberOrPercentage::Percentage { unit_value: v.0 }),
940        Ok(Calc::Number(v)) => Ok(NumberOrPercentage::Number { value: v }),
941        _ => Err(input.new_custom_error(ParserError::InvalidValue)),
942      }
943    }) {
944      return Ok(value);
945    }
946
947    Err(input.new_error_for_next_token())
948  }
949}
950
951pub(crate) trait LightDarkColor {
952  fn light_dark(light: Self, dark: Self) -> Self;
953}
954
955impl LightDarkColor for CssColor {
956  #[inline]
957  fn light_dark(light: Self, dark: Self) -> Self {
958    CssColor::LightDark(Box::new(light), Box::new(dark))
959  }
960}
961
962pub(crate) struct ComponentParser {
963  pub allow_none: bool,
964  from: Option<RelativeComponentParser>,
965}
966
967impl ComponentParser {
968  pub fn new(allow_none: bool) -> Self {
969    Self { allow_none, from: None }
970  }
971
972  pub fn parse_relative<
973    'i,
974    't,
975    T: TryFrom<CssColor> + ColorSpace,
976    C: LightDarkColor,
977    P: Fn(&mut Parser<'i, 't>, &mut Self) -> Result<C, ParseError<'i, ParserError<'i>>>,
978  >(
979    &mut self,
980    input: &mut Parser<'i, 't>,
981    parse: P,
982  ) -> Result<C, ParseError<'i, ParserError<'i>>> {
983    if input.try_parse(|input| input.expect_ident_matching("from")).is_ok() {
984      let from = CssColor::parse(input)?;
985      return self.parse_from::<T, C, P>(from, input, &parse);
986    }
987
988    parse(input, self)
989  }
990
991  fn parse_from<
992    'i,
993    't,
994    T: TryFrom<CssColor> + ColorSpace,
995    C: LightDarkColor,
996    P: Fn(&mut Parser<'i, 't>, &mut Self) -> Result<C, ParseError<'i, ParserError<'i>>>,
997  >(
998    &mut self,
999    from: CssColor,
1000    input: &mut Parser<'i, 't>,
1001    parse: &P,
1002  ) -> Result<C, ParseError<'i, ParserError<'i>>> {
1003    if let CssColor::LightDark(light, dark) = from {
1004      let state = input.state();
1005      let light = self.parse_from::<T, C, P>(*light, input, parse)?;
1006      input.reset(&state);
1007      let dark = self.parse_from::<T, C, P>(*dark, input, parse)?;
1008      return Ok(C::light_dark(light, dark));
1009    }
1010
1011    let from = T::try_from(from)
1012      .map_err(|_| input.new_custom_error(ParserError::InvalidValue))?
1013      .resolve();
1014    self.from = Some(RelativeComponentParser::new(&from));
1015
1016    parse(input, self)
1017  }
1018}
1019
1020impl<'i> ColorParser<'i> for ComponentParser {
1021  type Output = cssparser_color::Color;
1022  type Error = ParserError<'i>;
1023
1024  fn parse_angle_or_number<'t>(
1025    &self,
1026    input: &mut Parser<'i, 't>,
1027  ) -> Result<AngleOrNumber, ParseError<'i, Self::Error>> {
1028    if let Some(from) = &self.from {
1029      if let Ok(res) = input.try_parse(|input| from.parse_angle_or_number(input)) {
1030        return Ok(res);
1031      }
1032    }
1033
1034    if let Ok(angle) = input.try_parse(Angle::parse) {
1035      Ok(AngleOrNumber::Angle {
1036        degrees: angle.to_degrees(),
1037      })
1038    } else if let Ok(value) = input.try_parse(CSSNumber::parse) {
1039      Ok(AngleOrNumber::Number { value })
1040    } else if self.allow_none {
1041      input.expect_ident_matching("none")?;
1042      Ok(AngleOrNumber::Number { value: f32::NAN })
1043    } else {
1044      Err(input.new_custom_error(ParserError::InvalidValue))
1045    }
1046  }
1047
1048  fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {
1049    if let Some(from) = &self.from {
1050      if let Ok(res) = input.try_parse(|input| from.parse_number(input)) {
1051        return Ok(res);
1052      }
1053    }
1054
1055    if let Ok(val) = input.try_parse(CSSNumber::parse) {
1056      return Ok(val);
1057    } else if self.allow_none {
1058      input.expect_ident_matching("none")?;
1059      Ok(f32::NAN)
1060    } else {
1061      Err(input.new_custom_error(ParserError::InvalidValue))
1062    }
1063  }
1064
1065  fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i, Self::Error>> {
1066    if let Some(from) = &self.from {
1067      if let Ok(res) = input.try_parse(|input| from.parse_percentage(input)) {
1068        return Ok(res);
1069      }
1070    }
1071
1072    if let Ok(val) = input.try_parse(Percentage::parse) {
1073      return Ok(val.0);
1074    } else if self.allow_none {
1075      input.expect_ident_matching("none")?;
1076      Ok(f32::NAN)
1077    } else {
1078      Err(input.new_custom_error(ParserError::InvalidValue))
1079    }
1080  }
1081
1082  fn parse_number_or_percentage<'t>(
1083    &self,
1084    input: &mut Parser<'i, 't>,
1085  ) -> Result<NumberOrPercentage, ParseError<'i, Self::Error>> {
1086    if let Some(from) = &self.from {
1087      if let Ok(res) = input.try_parse(|input| from.parse_number_or_percentage(input)) {
1088        return Ok(res);
1089      }
1090    }
1091
1092    if let Ok(value) = input.try_parse(CSSNumber::parse) {
1093      Ok(NumberOrPercentage::Number { value })
1094    } else if let Ok(value) = input.try_parse(Percentage::parse) {
1095      Ok(NumberOrPercentage::Percentage { unit_value: value.0 })
1096    } else if self.allow_none {
1097      input.expect_ident_matching("none")?;
1098      Ok(NumberOrPercentage::Number { value: f32::NAN })
1099    } else {
1100      Err(input.new_custom_error(ParserError::InvalidValue))
1101    }
1102  }
1103}
1104
1105// https://www.w3.org/TR/css-color-4/#lab-colors
1106fn parse_color_function<'i, 't>(
1107  location: SourceLocation,
1108  function: CowRcStr<'i>,
1109  input: &mut Parser<'i, 't>,
1110) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1111  let mut parser = ComponentParser::new(true);
1112
1113  match_ignore_ascii_case! {&*function,
1114    "lab" => {
1115      parse_lab::<LAB, _>(input, &mut parser, 100.0, 125.0, |l, a, b, alpha| {
1116        LABColor::LAB(LAB { l, a, b, alpha })
1117      })
1118    },
1119    "oklab" => {
1120      parse_lab::<OKLAB, _>(input, &mut parser, 1.0, 0.4, |l, a, b, alpha| {
1121        LABColor::OKLAB(OKLAB { l, a, b, alpha })
1122      })
1123    },
1124    "lch" => {
1125      parse_lch::<LCH, _>(input, &mut parser, 100.0, 150.0, |l, c, h, alpha| {
1126        LABColor::LCH(LCH { l, c, h, alpha })
1127      })
1128    },
1129    "oklch" => {
1130      parse_lch::<OKLCH, _>(input, &mut parser, 1.0, 0.4, |l, c, h, alpha| {
1131        LABColor::OKLCH(OKLCH { l, c, h, alpha })
1132      })
1133    },
1134    "color" => {
1135      let predefined = parse_predefined(input, &mut parser)?;
1136      Ok(predefined)
1137    },
1138    "hsl" | "hsla" => {
1139      parse_hsl_hwb::<HSL, _>(input, &mut parser, true, |h, s, l, a| {
1140        let hsl = HSL { h, s, l, alpha: a };
1141        if !h.is_nan() && !s.is_nan() && !l.is_nan() && !a.is_nan() {
1142          CssColor::RGBA(hsl.into())
1143        } else {
1144          CssColor::Float(Box::new(FloatColor::HSL(hsl)))
1145        }
1146      })
1147    },
1148    "hwb" => {
1149      parse_hsl_hwb::<HWB, _>(input, &mut parser, false, |h, w, b, a| {
1150        let hwb = HWB { h, w, b, alpha: a };
1151        if !h.is_nan() && !w.is_nan() && !b.is_nan() && !a.is_nan() {
1152          CssColor::RGBA(hwb.into())
1153        } else {
1154          CssColor::Float(Box::new(FloatColor::HWB(hwb)))
1155        }
1156      })
1157    },
1158    "rgb" | "rgba" => {
1159       parse_rgb(input, &mut parser)
1160    },
1161    "alpha" => {
1162      parse_relative_alpha(input)
1163    },
1164    "color-mix" => {
1165      input.parse_nested_block(parse_color_mix)
1166    },
1167    "light-dark" => {
1168      input.parse_nested_block(|input| {
1169        let light = match CssColor::parse(input)? {
1170          CssColor::LightDark(light, _) => light,
1171          light => Box::new(light)
1172        };
1173        input.expect_comma()?;
1174        let dark = match CssColor::parse(input)? {
1175          CssColor::LightDark(_, dark) => dark,
1176          dark => Box::new(dark)
1177        };
1178
1179        if light==dark {
1180            return Ok(*light);
1181        }
1182
1183        Ok(CssColor::LightDark(light, dark))
1184      })
1185    },
1186    _ => Err(location.new_unexpected_token_error(
1187      cssparser::Token::Ident(function.clone())
1188    ))
1189  }
1190}
1191
1192/// Parses the lab() and oklab() functions.
1193#[inline]
1194fn parse_lab<'i, 't, T: TryFrom<CssColor> + ColorSpace, F: Fn(f32, f32, f32, f32) -> LABColor>(
1195  input: &mut Parser<'i, 't>,
1196  parser: &mut ComponentParser,
1197  l_basis: f32,
1198  ab_basis: f32,
1199  f: F,
1200) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1201  // https://www.w3.org/TR/css-color-4/#funcdef-lab
1202  input.parse_nested_block(|input| {
1203    parser.parse_relative::<T, _, _>(input, |input, parser| {
1204      // f32::max() does not propagate NaN, so use clamp for now until f32::maximum() is stable.
1205      let l = parse_number_or_percentage(input, parser, l_basis)?.clamp(0.0, f32::MAX);
1206      let a = parse_number_or_percentage(input, parser, ab_basis)?;
1207      let b = parse_number_or_percentage(input, parser, ab_basis)?;
1208      let alpha = parse_alpha(input, parser)?;
1209      let lab = f(l, a, b, alpha);
1210
1211      Ok(CssColor::LAB(Box::new(lab)))
1212    })
1213  })
1214}
1215
1216/// Parses the lch() and oklch() functions.
1217#[inline]
1218fn parse_lch<'i, 't, T: TryFrom<CssColor> + ColorSpace, F: Fn(f32, f32, f32, f32) -> LABColor>(
1219  input: &mut Parser<'i, 't>,
1220  parser: &mut ComponentParser,
1221  l_basis: f32,
1222  c_basis: f32,
1223  f: F,
1224) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1225  // https://www.w3.org/TR/css-color-4/#funcdef-lch
1226  input.parse_nested_block(|input| {
1227    parser.parse_relative::<T, _, _>(input, |input, parser| {
1228      if let Some(from) = &mut parser.from {
1229        // Relative angles should be normalized.
1230        // https://www.w3.org/TR/css-color-5/#relative-LCH
1231        from.components.2 %= 360.0;
1232        if from.components.2 < 0.0 {
1233          from.components.2 += 360.0;
1234        }
1235      }
1236
1237      let l = parse_number_or_percentage(input, parser, l_basis)?.clamp(0.0, f32::MAX);
1238      let c = parse_number_or_percentage(input, parser, c_basis)?.clamp(0.0, f32::MAX);
1239      let h = parse_angle_or_number(input, parser)?;
1240      let alpha = parse_alpha(input, parser)?;
1241      let lab = f(l, c, h, alpha);
1242
1243      Ok(CssColor::LAB(Box::new(lab)))
1244    })
1245  })
1246}
1247
1248/// Parses the alpha() function.
1249/// alpha() = alpha([from <color>] [ / [<alpha-value> | none] ]? )
1250#[inline]
1251fn parse_relative_alpha<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1252  // https://drafts.csswg.org/css-color-5/#relative-alpha
1253  input.parse_nested_block(|input| {
1254    input.expect_ident_matching("from")?;
1255    let from = CssColor::parse(input)?;
1256    let res = parse_relative_alpha_from(from, input)?;
1257    input.expect_exhausted()?;
1258    Ok(res)
1259  })
1260}
1261
1262#[inline]
1263fn parse_relative_alpha_from<'i, 't>(
1264  from: CssColor,
1265  input: &mut Parser<'i, 't>,
1266) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1267  if let CssColor::LightDark(light, dark) = from {
1268    let state = input.state();
1269    let light = parse_relative_alpha_from(*light, input)?;
1270    input.reset(&state);
1271    let dark = parse_relative_alpha_from(*dark, input)?;
1272    return Ok(CssColor::LightDark(Box::new(light), Box::new(dark)));
1273  }
1274
1275  let from_alpha = from.alpha().map_err(|_| input.new_custom_error(ParserError::InvalidValue))?;
1276  let alpha = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1277    let parser = ComponentParser {
1278      allow_none: true,
1279      from: Some(RelativeComponentParser::alpha_only(from_alpha)),
1280    };
1281    parse_number_or_percentage(input, &parser, 1.0)?.clamp(0.0, 1.0)
1282  } else {
1283    from_alpha
1284  };
1285
1286  Ok(from.with_alpha(alpha))
1287}
1288
1289#[inline]
1290fn parse_predefined<'i, 't>(
1291  input: &mut Parser<'i, 't>,
1292  parser: &mut ComponentParser,
1293) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1294  // https://www.w3.org/TR/css-color-4/#color-function
1295  let res = input.parse_nested_block(|input| {
1296    let from = if input.try_parse(|input| input.expect_ident_matching("from")).is_ok() {
1297      Some(CssColor::parse(input)?)
1298    } else {
1299      None
1300    };
1301
1302    let colorspace = input.expect_ident_cloned()?;
1303
1304    if let Some(CssColor::LightDark(light, dark)) = from {
1305      let state = input.state();
1306      let light = parse_predefined_relative(input, parser, &colorspace, Some(&*light))?;
1307      input.reset(&state);
1308      let dark = parse_predefined_relative(input, parser, &colorspace, Some(&*dark))?;
1309      return Ok(CssColor::LightDark(Box::new(light), Box::new(dark)));
1310    }
1311
1312    parse_predefined_relative(input, parser, &colorspace, from.as_ref())
1313  })?;
1314
1315  Ok(res)
1316}
1317
1318#[inline]
1319fn parse_predefined_relative<'i, 't>(
1320  input: &mut Parser<'i, 't>,
1321  parser: &mut ComponentParser,
1322  colorspace: &CowRcStr<'i>,
1323  from: Option<&CssColor>,
1324) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1325  let location = input.current_source_location();
1326  if let Some(from) = from {
1327    let handle_error = |_| input.new_custom_error(ParserError::InvalidValue);
1328    parser.from = Some(match_ignore_ascii_case! { &*&colorspace,
1329      "srgb" => RelativeComponentParser::new(&SRGB::try_from(from).map_err(handle_error)?.resolve_missing()),
1330      "srgb-linear" => RelativeComponentParser::new(&SRGBLinear::try_from(from).map_err(handle_error)?.resolve_missing()),
1331      "display-p3" => RelativeComponentParser::new(&P3::try_from(from).map_err(handle_error)?.resolve_missing()),
1332      "a98-rgb" => RelativeComponentParser::new(&A98::try_from(from).map_err(handle_error)?.resolve_missing()),
1333      "prophoto-rgb" => RelativeComponentParser::new(&ProPhoto::try_from(from).map_err(handle_error)?.resolve_missing()),
1334      "rec2020" => RelativeComponentParser::new(&Rec2020::try_from(from).map_err(handle_error)?.resolve_missing()),
1335      "xyz-d50" => RelativeComponentParser::new(&XYZd50::try_from(from).map_err(handle_error)?.resolve_missing()),
1336      "xyz" | "xyz-d65" => RelativeComponentParser::new(&XYZd65::try_from(from).map_err(handle_error)?.resolve_missing()),
1337      _ => return Err(location.new_unexpected_token_error(
1338        cssparser::Token::Ident(colorspace.clone())
1339      ))
1340    });
1341  }
1342
1343  // Out of gamut values should not be clamped, i.e. values < 0 or > 1 should be preserved.
1344  // The browser will gamut-map the color for the target device that it is rendered on.
1345  let a = input.try_parse(|input| parse_number_or_percentage(input, parser, 1.0))?;
1346  let b = input.try_parse(|input| parse_number_or_percentage(input, parser, 1.0))?;
1347  let c = input.try_parse(|input| parse_number_or_percentage(input, parser, 1.0))?;
1348  let alpha = parse_alpha(input, parser)?;
1349
1350  let res = match_ignore_ascii_case! { &*&colorspace,
1351    "srgb" => PredefinedColor::SRGB(SRGB { r: a, g: b, b: c, alpha }),
1352    "srgb-linear" => PredefinedColor::SRGBLinear(SRGBLinear { r: a, g: b, b: c, alpha }),
1353    "display-p3" => PredefinedColor::DisplayP3(P3 { r: a, g: b, b: c, alpha }),
1354    "a98-rgb" => PredefinedColor::A98(A98 { r: a, g: b, b: c, alpha }),
1355    "prophoto-rgb" => PredefinedColor::ProPhoto(ProPhoto { r: a, g: b, b: c, alpha }),
1356    "rec2020" => PredefinedColor::Rec2020(Rec2020 { r: a, g: b, b: c, alpha }),
1357    "xyz-d50" => PredefinedColor::XYZd50(XYZd50 { x: a, y: b, z: c, alpha}),
1358    "xyz" | "xyz-d65" => PredefinedColor::XYZd65(XYZd65 { x: a, y: b, z: c, alpha }),
1359    _ => return Err(location.new_unexpected_token_error(
1360      cssparser::Token::Ident(colorspace.clone())
1361    ))
1362  };
1363
1364  Ok(CssColor::Predefined(Box::new(res)))
1365}
1366
1367/// Parses the hsl() and hwb() functions.
1368/// The results of this function are stored as floating point if there are any `none` components.
1369#[inline]
1370fn parse_hsl_hwb<'i, 't, T: TryFrom<CssColor> + ColorSpace, F: Fn(f32, f32, f32, f32) -> CssColor>(
1371  input: &mut Parser<'i, 't>,
1372  parser: &mut ComponentParser,
1373  allows_legacy: bool,
1374  f: F,
1375) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1376  // https://drafts.csswg.org/css-color-4/#the-hsl-notation
1377  input.parse_nested_block(|input| {
1378    parser.parse_relative::<T, _, _>(input, |input, parser| {
1379      let (h, a, b, is_legacy) = parse_hsl_hwb_components::<T>(input, parser, allows_legacy)?;
1380      let alpha = if is_legacy {
1381        parse_legacy_alpha(input, parser)?
1382      } else {
1383        parse_alpha(input, parser)?
1384      };
1385
1386      Ok(f(h, a, b, alpha))
1387    })
1388  })
1389}
1390
1391#[inline]
1392pub(crate) fn parse_hsl_hwb_components<'i, 't, T: TryFrom<CssColor> + ColorSpace>(
1393  input: &mut Parser<'i, 't>,
1394  parser: &mut ComponentParser,
1395  allows_legacy: bool,
1396) -> Result<(f32, f32, f32, bool), ParseError<'i, ParserError<'i>>> {
1397  let h = parse_angle_or_number(input, parser)?;
1398  let is_legacy_syntax =
1399    allows_legacy && parser.from.is_none() && !h.is_nan() && input.try_parse(|p| p.expect_comma()).is_ok();
1400  let a = parse_number_or_percentage(input, parser, 100.0)?.clamp(0.0, 100.0);
1401  if is_legacy_syntax {
1402    input.expect_comma()?;
1403  }
1404  let b = parse_number_or_percentage(input, parser, 100.0)?.clamp(0.0, 100.0);
1405  if is_legacy_syntax && (a.is_nan() || b.is_nan()) {
1406    return Err(input.new_custom_error(ParserError::InvalidValue));
1407  }
1408  Ok((h, a, b, is_legacy_syntax))
1409}
1410
1411#[inline]
1412fn parse_rgb<'i, 't>(
1413  input: &mut Parser<'i, 't>,
1414  parser: &mut ComponentParser,
1415) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
1416  // https://drafts.csswg.org/css-color-4/#rgb-functions
1417  input.parse_nested_block(|input| {
1418    parser.parse_relative::<RGB, _, _>(input, |input, parser| {
1419      let (r, g, b, is_legacy) = parse_rgb_components(input, parser)?;
1420      let alpha = if is_legacy {
1421        parse_legacy_alpha(input, parser)?
1422      } else {
1423        parse_alpha(input, parser)?
1424      };
1425
1426      if !r.is_nan() && !g.is_nan() && !b.is_nan() && !alpha.is_nan() {
1427        if is_legacy {
1428          Ok(CssColor::RGBA(RGBA::new(r as u8, g as u8, b as u8, alpha)))
1429        } else {
1430          Ok(CssColor::RGBA(RGBA::from_floats(
1431            r / 255.0,
1432            g / 255.0,
1433            b / 255.0,
1434            alpha,
1435          )))
1436        }
1437      } else {
1438        Ok(CssColor::Float(Box::new(FloatColor::RGB(RGB { r, g, b, alpha }))))
1439      }
1440    })
1441  })
1442}
1443
1444#[inline]
1445pub(crate) fn parse_rgb_components<'i, 't>(
1446  input: &mut Parser<'i, 't>,
1447  parser: &mut ComponentParser,
1448) -> Result<(f32, f32, f32, bool), ParseError<'i, ParserError<'i>>> {
1449  let red = parser.parse_number_or_percentage(input)?;
1450  let is_legacy_syntax =
1451    parser.from.is_none() && !red.unit_value().is_nan() && input.try_parse(|p| p.expect_comma()).is_ok();
1452  let (r, g, b) = if is_legacy_syntax {
1453    match red {
1454      NumberOrPercentage::Number { value } => {
1455        let r = value.round().clamp(0.0, 255.0);
1456        let g = parser.parse_number(input)?.round().clamp(0.0, 255.0);
1457        input.expect_comma()?;
1458        let b = parser.parse_number(input)?.round().clamp(0.0, 255.0);
1459        (r, g, b)
1460      }
1461      NumberOrPercentage::Percentage { unit_value } => {
1462        let r = (unit_value * 255.0).round().clamp(0.0, 255.0);
1463        let g = (parser.parse_percentage(input)? * 255.0).round().clamp(0.0, 255.0);
1464        input.expect_comma()?;
1465        let b = (parser.parse_percentage(input)? * 255.0).round().clamp(0.0, 255.0);
1466        (r, g, b)
1467      }
1468    }
1469  } else {
1470    #[inline]
1471    fn get_component<'i, 't>(value: NumberOrPercentage) -> f32 {
1472      match value {
1473        NumberOrPercentage::Number { value } if value.is_nan() => value,
1474        NumberOrPercentage::Number { value } => value.round().clamp(0.0, 255.0),
1475        NumberOrPercentage::Percentage { unit_value } => (unit_value * 255.0).round().clamp(0.0, 255.0),
1476      }
1477    }
1478
1479    let r = get_component(red);
1480    let g = get_component(parser.parse_number_or_percentage(input)?);
1481    let b = get_component(parser.parse_number_or_percentage(input)?);
1482    (r, g, b)
1483  };
1484
1485  if is_legacy_syntax && (g.is_nan() || b.is_nan()) {
1486    return Err(input.new_custom_error(ParserError::InvalidValue));
1487  }
1488  Ok((r, g, b, is_legacy_syntax))
1489}
1490
1491#[inline]
1492fn parse_angle_or_number<'i, 't>(
1493  input: &mut Parser<'i, 't>,
1494  parser: &ComponentParser,
1495) -> Result<f32, ParseError<'i, ParserError<'i>>> {
1496  Ok(match parser.parse_angle_or_number(input)? {
1497    AngleOrNumber::Number { value } => value,
1498    AngleOrNumber::Angle { degrees } => degrees,
1499  })
1500}
1501
1502#[inline]
1503fn parse_number_or_percentage<'i, 't>(
1504  input: &mut Parser<'i, 't>,
1505  parser: &ComponentParser,
1506  percent_basis: f32,
1507) -> Result<f32, ParseError<'i, ParserError<'i>>> {
1508  Ok(match parser.parse_number_or_percentage(input)? {
1509    NumberOrPercentage::Number { value } => value,
1510    NumberOrPercentage::Percentage { unit_value } => unit_value * percent_basis,
1511  })
1512}
1513
1514#[inline]
1515fn parse_alpha<'i, 't>(
1516  input: &mut Parser<'i, 't>,
1517  parser: &ComponentParser,
1518) -> Result<f32, ParseError<'i, ParserError<'i>>> {
1519  let res = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
1520    parse_number_or_percentage(input, parser, 1.0)?.clamp(0.0, 1.0)
1521  } else {
1522    1.0
1523  };
1524  Ok(res)
1525}
1526
1527#[inline]
1528fn parse_legacy_alpha<'i, 't>(
1529  input: &mut Parser<'i, 't>,
1530  parser: &ComponentParser,
1531) -> Result<f32, ParseError<'i, ParserError<'i>>> {
1532  Ok(if !input.is_exhausted() {
1533    input.expect_comma()?;
1534    parse_number_or_percentage(input, parser, 1.0)?.clamp(0.0, 1.0)
1535  } else {
1536    1.0
1537  })
1538}
1539
1540#[inline]
1541fn write_components<W>(
1542  name: &str,
1543  a: f32,
1544  b: f32,
1545  c: f32,
1546  alpha: f32,
1547  dest: &mut Printer<W>,
1548) -> Result<(), PrinterError>
1549where
1550  W: std::fmt::Write,
1551{
1552  dest.write_str(name)?;
1553  dest.write_char('(')?;
1554  if a.is_nan() {
1555    dest.write_str("none")?;
1556  } else {
1557    Percentage(a).to_css(dest)?;
1558  }
1559  dest.write_char(' ')?;
1560  write_component(b, dest)?;
1561  dest.write_char(' ')?;
1562  write_component(c, dest)?;
1563  if alpha.is_nan() || (alpha - 1.0).abs() > f32::EPSILON {
1564    dest.delim('/', true)?;
1565    write_component(alpha, dest)?;
1566  }
1567
1568  dest.write_char(')')
1569}
1570
1571#[inline]
1572fn write_component<W>(c: f32, dest: &mut Printer<W>) -> Result<(), PrinterError>
1573where
1574  W: std::fmt::Write,
1575{
1576  if c.is_nan() {
1577    dest.write_str("none")?;
1578  } else {
1579    c.to_css(dest)?;
1580  }
1581  Ok(())
1582}
1583
1584#[inline]
1585fn write_predefined<W>(predefined: &PredefinedColor, dest: &mut Printer<W>) -> Result<(), PrinterError>
1586where
1587  W: std::fmt::Write,
1588{
1589  use PredefinedColor::*;
1590
1591  let (name, a, b, c, alpha) = match predefined {
1592    SRGB(rgb) => ("srgb", rgb.r, rgb.g, rgb.b, rgb.alpha),
1593    SRGBLinear(rgb) => ("srgb-linear", rgb.r, rgb.g, rgb.b, rgb.alpha),
1594    DisplayP3(rgb) => ("display-p3", rgb.r, rgb.g, rgb.b, rgb.alpha),
1595    A98(rgb) => ("a98-rgb", rgb.r, rgb.g, rgb.b, rgb.alpha),
1596    ProPhoto(rgb) => ("prophoto-rgb", rgb.r, rgb.g, rgb.b, rgb.alpha),
1597    Rec2020(rgb) => ("rec2020", rgb.r, rgb.g, rgb.b, rgb.alpha),
1598    XYZd50(xyz) => ("xyz-d50", xyz.x, xyz.y, xyz.z, xyz.alpha),
1599    // "xyz" has better compatibility (Safari 15) than "xyz-d65", and it is shorter.
1600    XYZd65(xyz) => ("xyz", xyz.x, xyz.y, xyz.z, xyz.alpha),
1601  };
1602
1603  dest.write_str("color(")?;
1604  dest.write_str(name)?;
1605  dest.write_char(' ')?;
1606  write_component(a, dest)?;
1607  dest.write_char(' ')?;
1608  write_component(b, dest)?;
1609  dest.write_char(' ')?;
1610  write_component(c, dest)?;
1611
1612  if alpha.is_nan() || (alpha - 1.0).abs() > f32::EPSILON {
1613    dest.delim('/', true)?;
1614    write_component(alpha, dest)?;
1615  }
1616
1617  dest.write_char(')')
1618}
1619
1620bitflags! {
1621  /// A channel type for a color space.
1622  #[derive(PartialEq, Eq, Clone, Copy)]
1623  pub struct ChannelType: u8 {
1624    /// Channel represents a percentage.
1625    const Percentage = 0b001;
1626    /// Channel represents an angle.
1627    const Angle = 0b010;
1628    /// Channel represents a number.
1629    const Number = 0b100;
1630  }
1631}
1632
1633/// A trait for color spaces.
1634pub trait ColorSpace {
1635  /// Returns the raw color component values.
1636  fn components(&self) -> (f32, f32, f32, f32);
1637  /// Returns the channel names for this color space.
1638  fn channels(&self) -> (&'static str, &'static str, &'static str);
1639  /// Returns the channel types for this color space.
1640  fn types(&self) -> (ChannelType, ChannelType, ChannelType);
1641  /// Resolves missing color components (e.g. `none` keywords) in the color.
1642  fn resolve_missing(&self) -> Self;
1643  /// Returns a resolved color by replacing missing (i.e. `none`) components with zero,
1644  /// and performing gamut mapping to ensure the color can be represented within the color space.
1645  fn resolve(&self) -> Self;
1646}
1647
1648macro_rules! define_colorspace {
1649  (
1650    $(#[$outer:meta])*
1651    $vis:vis struct $name:ident {
1652      $(#[$a_meta: meta])*
1653      $a: ident: $at: ident,
1654      $(#[$b_meta: meta])*
1655      $b: ident: $bt: ident,
1656      $(#[$c_meta: meta])*
1657      $c: ident: $ct: ident
1658    }
1659  ) => {
1660    $(#[$outer])*
1661    #[derive(Debug, Clone, Copy, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))]
1662    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1663    #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1664    pub struct $name {
1665      $(#[$a_meta])*
1666      pub $a: f32,
1667      $(#[$b_meta])*
1668      pub $b: f32,
1669      $(#[$c_meta])*
1670      pub $c: f32,
1671      /// The alpha component.
1672      pub alpha: f32,
1673    }
1674
1675    impl ColorSpace for $name {
1676      fn components(&self) -> (f32, f32, f32, f32) {
1677        (self.$a, self.$b, self.$c, self.alpha)
1678      }
1679
1680      fn channels(&self) -> (&'static str, &'static str, &'static str) {
1681        (stringify!($a), stringify!($b), stringify!($c))
1682      }
1683
1684      fn types(&self) -> (ChannelType, ChannelType, ChannelType) {
1685        (ChannelType::$at, ChannelType::$bt, ChannelType::$ct)
1686      }
1687
1688      #[inline]
1689      fn resolve_missing(&self) -> Self {
1690        Self {
1691          $a: if self.$a.is_nan() { 0.0 } else { self.$a },
1692          $b: if self.$b.is_nan() { 0.0 } else { self.$b },
1693          $c: if self.$c.is_nan() { 0.0 } else { self.$c },
1694          alpha: if self.alpha.is_nan() { 0.0 } else { self.alpha },
1695        }
1696      }
1697
1698      #[inline]
1699      fn resolve(&self) -> Self {
1700        let mut resolved = self.resolve_missing();
1701        if !resolved.in_gamut() {
1702          resolved = map_gamut(resolved);
1703        }
1704        resolved
1705      }
1706    }
1707  };
1708}
1709
1710define_colorspace! {
1711  /// A color in the [`sRGB`](https://www.w3.org/TR/css-color-4/#predefined-sRGB) color space.
1712  pub struct SRGB {
1713    /// The red component.
1714    r: Number,
1715    /// The green component.
1716    g: Number,
1717    /// The blue component.
1718    b: Number
1719  }
1720}
1721
1722// Copied from an older version of cssparser.
1723/// A color with red, green, blue, and alpha components, in a byte each.
1724#[derive(Clone, Copy, PartialEq, Debug)]
1725pub struct RGBA {
1726  /// The red component.
1727  pub red: u8,
1728  /// The green component.
1729  pub green: u8,
1730  /// The blue component.
1731  pub blue: u8,
1732  /// The alpha component.
1733  pub alpha: u8,
1734}
1735
1736impl RGBA {
1737  /// Constructs a new RGBA value from float components. It expects the red,
1738  /// green, blue and alpha channels in that order, and all values will be
1739  /// clamped to the 0.0 ... 1.0 range.
1740  #[inline]
1741  pub fn from_floats(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
1742    Self::new(clamp_unit_f32(red), clamp_unit_f32(green), clamp_unit_f32(blue), alpha)
1743  }
1744
1745  /// Returns a transparent color.
1746  #[inline]
1747  pub fn transparent() -> Self {
1748    Self::new(0, 0, 0, 0.0)
1749  }
1750
1751  /// Same thing, but with `u8` values instead of floats in the 0 to 1 range.
1752  #[inline]
1753  pub fn new(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
1754    RGBA {
1755      red,
1756      green,
1757      blue,
1758      alpha: clamp_unit_f32(alpha),
1759    }
1760  }
1761
1762  /// Returns the red channel in a floating point number form, from 0 to 1.
1763  #[inline]
1764  pub fn red_f32(&self) -> f32 {
1765    self.red as f32 / 255.0
1766  }
1767
1768  /// Returns the green channel in a floating point number form, from 0 to 1.
1769  #[inline]
1770  pub fn green_f32(&self) -> f32 {
1771    self.green as f32 / 255.0
1772  }
1773
1774  /// Returns the blue channel in a floating point number form, from 0 to 1.
1775  #[inline]
1776  pub fn blue_f32(&self) -> f32 {
1777    self.blue as f32 / 255.0
1778  }
1779
1780  /// Returns the alpha channel in a floating point number form, from 0 to 1.
1781  #[inline]
1782  pub fn alpha_f32(&self) -> f32 {
1783    self.alpha as f32 / 255.0
1784  }
1785}
1786
1787fn clamp_unit_f32(val: f32) -> u8 {
1788  // Whilst scaling by 256 and flooring would provide
1789  // an equal distribution of integers to percentage inputs,
1790  // this is not what Gecko does so we instead multiply by 255
1791  // and round (adding 0.5 and flooring is equivalent to rounding)
1792  //
1793  // Chrome does something similar for the alpha value, but not
1794  // the rgb values.
1795  //
1796  // See https://bugzilla.mozilla.org/show_bug.cgi?id=1340484
1797  //
1798  // Clamping to 256 and rounding after would let 1.0 map to 256, and
1799  // `256.0_f32 as u8` is undefined behavior:
1800  //
1801  // https://github.com/rust-lang/rust/issues/10184
1802  clamp_floor_256_f32(val * 255.)
1803}
1804
1805fn clamp_floor_256_f32(val: f32) -> u8 {
1806  val.round().max(0.).min(255.) as u8
1807}
1808
1809define_colorspace! {
1810  /// A color in the [`RGB`](https://w3c.github.io/csswg-drafts/css-color-4/#rgb-functions) color space.
1811  /// Components are in the 0-255 range.
1812  pub struct RGB {
1813    /// The red component.
1814    r: Number,
1815    /// The green component.
1816    g: Number,
1817    /// The blue component.
1818    b: Number
1819  }
1820}
1821
1822define_colorspace! {
1823  /// A color in the [`sRGB-linear`](https://www.w3.org/TR/css-color-4/#predefined-sRGB-linear) color space.
1824  pub struct SRGBLinear {
1825    /// The red component.
1826    r: Number,
1827    /// The green component.
1828    g: Number,
1829    /// The blue component.
1830    b: Number
1831  }
1832}
1833
1834define_colorspace! {
1835  /// A color in the [`display-p3`](https://www.w3.org/TR/css-color-4/#predefined-display-p3) color space.
1836  pub struct P3 {
1837    /// The red component.
1838    r: Number,
1839    /// The green component.
1840    g: Number,
1841    /// The blue component.
1842    b: Number
1843  }
1844}
1845
1846define_colorspace! {
1847  /// A color in the [`a98-rgb`](https://www.w3.org/TR/css-color-4/#predefined-a98-rgb) color space.
1848  pub struct A98 {
1849    /// The red component.
1850    r: Number,
1851    /// The green component.
1852    g: Number,
1853    /// The blue component.
1854    b: Number
1855  }
1856}
1857
1858define_colorspace! {
1859  /// A color in the [`prophoto-rgb`](https://www.w3.org/TR/css-color-4/#predefined-prophoto-rgb) color space.
1860  pub struct ProPhoto {
1861    /// The red component.
1862    r: Number,
1863    /// The green component.
1864    g: Number,
1865    /// The blue component.
1866    b: Number
1867  }
1868}
1869
1870define_colorspace! {
1871  /// A color in the [`rec2020`](https://www.w3.org/TR/css-color-4/#predefined-rec2020) color space.
1872  pub struct Rec2020 {
1873    /// The red component.
1874    r: Number,
1875    /// The green component.
1876    g: Number,
1877    /// The blue component.
1878    b: Number
1879  }
1880}
1881
1882define_colorspace! {
1883  /// A color in the [CIE Lab](https://www.w3.org/TR/css-color-4/#cie-lab) color space.
1884  pub struct LAB {
1885    /// The lightness component.
1886    l: Number,
1887    /// The a component.
1888    a: Number,
1889    /// The b component.
1890    b: Number
1891  }
1892}
1893
1894define_colorspace! {
1895  /// A color in the [CIE LCH](https://www.w3.org/TR/css-color-4/#cie-lab) color space.
1896  pub struct LCH {
1897    /// The lightness component.
1898    l: Number,
1899    /// The chroma component.
1900    c: Number,
1901    /// The hue component.
1902    h: Angle
1903  }
1904}
1905
1906define_colorspace! {
1907  /// A color in the [OKLab](https://www.w3.org/TR/css-color-4/#ok-lab) color space.
1908  pub struct OKLAB {
1909    /// The lightness component.
1910    l: Number,
1911    /// The a component.
1912    a: Number,
1913    /// The b component.
1914    b: Number
1915  }
1916}
1917
1918define_colorspace! {
1919  /// A color in the [OKLCH](https://www.w3.org/TR/css-color-4/#ok-lab) color space.
1920  pub struct OKLCH {
1921    /// The lightness component.
1922    l: Number,
1923    /// The chroma component.
1924    c: Number,
1925    /// The hue component.
1926    h: Angle
1927  }
1928}
1929
1930define_colorspace! {
1931  /// A color in the [`xyz-d50`](https://www.w3.org/TR/css-color-4/#predefined-xyz) color space.
1932  pub struct XYZd50 {
1933    /// The x component.
1934    x: Number,
1935    /// The y component.
1936    y: Number,
1937    /// The z component.
1938    z: Number
1939  }
1940}
1941
1942define_colorspace! {
1943  /// A color in the [`xyz-d65`](https://www.w3.org/TR/css-color-4/#predefined-xyz) color space.
1944  pub struct XYZd65 {
1945    /// The x component.
1946    x: Number,
1947    /// The y component.
1948    y: Number,
1949    /// The z component.
1950    z: Number
1951  }
1952}
1953
1954define_colorspace! {
1955  /// A color in the [`hsl`](https://www.w3.org/TR/css-color-4/#the-hsl-notation) color space.
1956  pub struct HSL {
1957    /// The hue component.
1958    h: Angle,
1959    /// The saturation component.
1960    s: Number,
1961    /// The lightness component.
1962    l: Number
1963  }
1964}
1965
1966define_colorspace! {
1967  /// A color in the [`hwb`](https://www.w3.org/TR/css-color-4/#the-hwb-notation) color space.
1968  pub struct HWB {
1969    /// The hue component.
1970    h: Angle,
1971    /// The whiteness component.
1972    w: Number,
1973    /// The blackness component.
1974    b: Number
1975  }
1976}
1977
1978macro_rules! via {
1979  ($t: ident -> $u: ident -> $v: ident) => {
1980    impl From<$t> for $v {
1981      #[inline]
1982      fn from(t: $t) -> $v {
1983        let xyz: $u = t.into();
1984        xyz.into()
1985      }
1986    }
1987
1988    impl From<$v> for $t {
1989      #[inline]
1990      fn from(t: $v) -> $t {
1991        let xyz: $u = t.into();
1992        xyz.into()
1993      }
1994    }
1995  };
1996}
1997
1998#[inline]
1999fn rectangular_to_polar(l: f32, a: f32, b: f32) -> (f32, f32, f32) {
2000  // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L375
2001  let mut h = b.atan2(a) * 180.0 / PI;
2002  if h < 0.0 {
2003    h += 360.0;
2004  }
2005  let c = (a.powi(2) + b.powi(2)).sqrt();
2006  h = h % 360.0;
2007  (l, c, h)
2008}
2009
2010#[inline]
2011fn polar_to_rectangular(l: f32, c: f32, h: f32) -> (f32, f32, f32) {
2012  // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L385
2013  let a = c * (h * PI / 180.0).cos();
2014  let b = c * (h * PI / 180.0).sin();
2015  (l, a, b)
2016}
2017
2018impl From<LCH> for LAB {
2019  fn from(lch: LCH) -> LAB {
2020    let lch = lch.resolve_missing();
2021    let (l, a, b) = polar_to_rectangular(lch.l, lch.c, lch.h);
2022    LAB {
2023      l,
2024      a,
2025      b,
2026      alpha: lch.alpha,
2027    }
2028  }
2029}
2030
2031impl From<LAB> for LCH {
2032  fn from(lab: LAB) -> LCH {
2033    let lab = lab.resolve_missing();
2034    let (l, c, h) = rectangular_to_polar(lab.l, lab.a, lab.b);
2035    LCH {
2036      l,
2037      c,
2038      h,
2039      alpha: lab.alpha,
2040    }
2041  }
2042}
2043
2044impl From<OKLCH> for OKLAB {
2045  fn from(lch: OKLCH) -> OKLAB {
2046    let lch = lch.resolve_missing();
2047    let (l, a, b) = polar_to_rectangular(lch.l, lch.c, lch.h);
2048    OKLAB {
2049      l,
2050      a,
2051      b,
2052      alpha: lch.alpha,
2053    }
2054  }
2055}
2056
2057impl From<OKLAB> for OKLCH {
2058  fn from(lab: OKLAB) -> OKLCH {
2059    let lab = lab.resolve_missing();
2060    let (l, c, h) = rectangular_to_polar(lab.l, lab.a, lab.b);
2061    OKLCH {
2062      l,
2063      c,
2064      h,
2065      alpha: lab.alpha,
2066    }
2067  }
2068}
2069
2070const D50: &[f32] = &[0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585];
2071
2072impl From<LAB> for XYZd50 {
2073  fn from(lab: LAB) -> XYZd50 {
2074    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L352
2075    const K: f32 = 24389.0 / 27.0; // 29^3/3^3
2076    const E: f32 = 216.0 / 24389.0; // 6^3/29^3
2077
2078    let lab = lab.resolve_missing();
2079    let l = lab.l;
2080    let a = lab.a;
2081    let b = lab.b;
2082
2083    // compute f, starting with the luminance-related term
2084    let f1 = (l + 16.0) / 116.0;
2085    let f0 = a / 500.0 + f1;
2086    let f2 = f1 - b / 200.0;
2087
2088    // compute xyz
2089    let x = if f0.powi(3) > E {
2090      f0.powi(3)
2091    } else {
2092      (116.0 * f0 - 16.0) / K
2093    };
2094
2095    let y = if l > K * E { ((l + 16.0) / 116.0).powi(3) } else { l / K };
2096
2097    let z = if f2.powi(3) > E {
2098      f2.powi(3)
2099    } else {
2100      (116.0 * f2 - 16.0) / K
2101    };
2102
2103    // Compute XYZ by scaling xyz by reference white
2104    XYZd50 {
2105      x: x * D50[0],
2106      y: y * D50[1],
2107      z: z * D50[2],
2108      alpha: lab.alpha,
2109    }
2110  }
2111}
2112
2113impl From<XYZd50> for XYZd65 {
2114  fn from(xyz: XYZd50) -> XYZd65 {
2115    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L319
2116    const MATRIX: &[f32] = &[
2117      0.9554734527042182,
2118      -0.023098536874261423,
2119      0.0632593086610217,
2120      -0.028369706963208136,
2121      1.0099954580058226,
2122      0.021041398966943008,
2123      0.012314001688319899,
2124      -0.020507696433477912,
2125      1.3303659366080753,
2126    ];
2127
2128    let xyz = xyz.resolve_missing();
2129    let (x, y, z) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2130    XYZd65 {
2131      x,
2132      y,
2133      z,
2134      alpha: xyz.alpha,
2135    }
2136  }
2137}
2138
2139impl From<XYZd65> for XYZd50 {
2140  fn from(xyz: XYZd65) -> XYZd50 {
2141    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L319
2142    const MATRIX: &[f32] = &[
2143      1.0479298208405488,
2144      0.022946793341019088,
2145      -0.05019222954313557,
2146      0.029627815688159344,
2147      0.990434484573249,
2148      -0.01707382502938514,
2149      -0.009243058152591178,
2150      0.015055144896577895,
2151      0.7518742899580008,
2152    ];
2153
2154    let xyz = xyz.resolve_missing();
2155    let (x, y, z) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2156    XYZd50 {
2157      x,
2158      y,
2159      z,
2160      alpha: xyz.alpha,
2161    }
2162  }
2163}
2164
2165impl From<XYZd65> for SRGBLinear {
2166  fn from(xyz: XYZd65) -> SRGBLinear {
2167    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L62
2168    const MATRIX: &[f32] = &[
2169      3.2409699419045226,
2170      -1.537383177570094,
2171      -0.4986107602930034,
2172      -0.9692436362808796,
2173      1.8759675015077202,
2174      0.04155505740717559,
2175      0.05563007969699366,
2176      -0.20397695888897652,
2177      1.0569715142428786,
2178    ];
2179
2180    let xyz = xyz.resolve_missing();
2181    let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2182    SRGBLinear {
2183      r,
2184      g,
2185      b,
2186      alpha: xyz.alpha,
2187    }
2188  }
2189}
2190
2191#[inline]
2192fn multiply_matrix(m: &[f32], x: f32, y: f32, z: f32) -> (f32, f32, f32) {
2193  let a = m[0] * x + m[1] * y + m[2] * z;
2194  let b = m[3] * x + m[4] * y + m[5] * z;
2195  let c = m[6] * x + m[7] * y + m[8] * z;
2196  (a, b, c)
2197}
2198
2199impl From<SRGBLinear> for SRGB {
2200  #[inline]
2201  fn from(rgb: SRGBLinear) -> SRGB {
2202    let rgb = rgb.resolve_missing();
2203    let (r, g, b) = gam_srgb(rgb.r, rgb.g, rgb.b);
2204    SRGB {
2205      r,
2206      g,
2207      b,
2208      alpha: rgb.alpha,
2209    }
2210  }
2211}
2212
2213fn gam_srgb(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
2214  // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L31
2215  // convert an array of linear-light sRGB values in the range 0.0-1.0
2216  // to gamma corrected form
2217  // https://en.wikipedia.org/wiki/SRGB
2218  // Extended transfer function:
2219  // For negative values, linear portion extends on reflection
2220  // of axis, then uses reflected pow below that
2221
2222  #[inline]
2223  fn gam_srgb_component(c: f32) -> f32 {
2224    let abs = c.abs();
2225    if abs > 0.0031308 {
2226      let sign = if c < 0.0 { -1.0 } else { 1.0 };
2227      return sign * (1.055 * abs.powf(1.0 / 2.4) - 0.055);
2228    }
2229
2230    return 12.92 * c;
2231  }
2232
2233  let r = gam_srgb_component(r);
2234  let g = gam_srgb_component(g);
2235  let b = gam_srgb_component(b);
2236  (r, g, b)
2237}
2238
2239impl From<OKLAB> for XYZd65 {
2240  fn from(lab: OKLAB) -> XYZd65 {
2241    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L418
2242    const LMS_TO_XYZ: &[f32] = &[
2243      1.2268798733741557,
2244      -0.5578149965554813,
2245      0.28139105017721583,
2246      -0.04057576262431372,
2247      1.1122868293970594,
2248      -0.07171106666151701,
2249      -0.07637294974672142,
2250      -0.4214933239627914,
2251      1.5869240244272418,
2252    ];
2253
2254    const OKLAB_TO_LMS: &[f32] = &[
2255      0.99999999845051981432,
2256      0.39633779217376785678,
2257      0.21580375806075880339,
2258      1.0000000088817607767,
2259      -0.1055613423236563494,
2260      -0.063854174771705903402,
2261      1.0000000546724109177,
2262      -0.089484182094965759684,
2263      -1.2914855378640917399,
2264    ];
2265
2266    let lab = lab.resolve_missing();
2267    let (a, b, c) = multiply_matrix(OKLAB_TO_LMS, lab.l, lab.a, lab.b);
2268    let (x, y, z) = multiply_matrix(LMS_TO_XYZ, a.powi(3), b.powi(3), c.powi(3));
2269    XYZd65 {
2270      x,
2271      y,
2272      z,
2273      alpha: lab.alpha,
2274    }
2275  }
2276}
2277
2278impl From<XYZd65> for OKLAB {
2279  fn from(xyz: XYZd65) -> OKLAB {
2280    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L400
2281    const XYZ_TO_LMS: &[f32] = &[
2282      0.8190224432164319,
2283      0.3619062562801221,
2284      -0.12887378261216414,
2285      0.0329836671980271,
2286      0.9292868468965546,
2287      0.03614466816999844,
2288      0.048177199566046255,
2289      0.26423952494422764,
2290      0.6335478258136937,
2291    ];
2292
2293    const LMS_TO_OKLAB: &[f32] = &[
2294      0.2104542553,
2295      0.7936177850,
2296      -0.0040720468,
2297      1.9779984951,
2298      -2.4285922050,
2299      0.4505937099,
2300      0.0259040371,
2301      0.7827717662,
2302      -0.8086757660,
2303    ];
2304
2305    let xyz = xyz.resolve_missing();
2306    let (a, b, c) = multiply_matrix(XYZ_TO_LMS, xyz.x, xyz.y, xyz.z);
2307    let (l, a, b) = multiply_matrix(LMS_TO_OKLAB, a.cbrt(), b.cbrt(), c.cbrt());
2308    OKLAB {
2309      l,
2310      a,
2311      b,
2312      alpha: xyz.alpha,
2313    }
2314  }
2315}
2316
2317impl From<XYZd50> for LAB {
2318  fn from(xyz: XYZd50) -> LAB {
2319    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L332
2320    // Assuming XYZ is relative to D50, convert to CIE LAB
2321    // from CIE standard, which now defines these as a rational fraction
2322    const E: f32 = 216.0 / 24389.0; // 6^3/29^3
2323    const K: f32 = 24389.0 / 27.0; // 29^3/3^3
2324
2325    // compute xyz, which is XYZ scaled relative to reference white
2326    let xyz = xyz.resolve_missing();
2327    let x = xyz.x / D50[0];
2328    let y = xyz.y / D50[1];
2329    let z = xyz.z / D50[2];
2330
2331    // now compute f
2332    let f0 = if x > E { x.cbrt() } else { (K * x + 16.0) / 116.0 };
2333
2334    let f1 = if y > E { y.cbrt() } else { (K * y + 16.0) / 116.0 };
2335
2336    let f2 = if z > E { z.cbrt() } else { (K * z + 16.0) / 116.0 };
2337
2338    let l = (116.0 * f1) - 16.0;
2339    let a = 500.0 * (f0 - f1);
2340    let b = 200.0 * (f1 - f2);
2341    LAB {
2342      l,
2343      a,
2344      b,
2345      alpha: xyz.alpha,
2346    }
2347  }
2348}
2349
2350impl From<SRGB> for SRGBLinear {
2351  fn from(rgb: SRGB) -> SRGBLinear {
2352    let rgb = rgb.resolve_missing();
2353    let (r, g, b) = lin_srgb(rgb.r, rgb.g, rgb.b);
2354    SRGBLinear {
2355      r,
2356      g,
2357      b,
2358      alpha: rgb.alpha,
2359    }
2360  }
2361}
2362
2363fn lin_srgb(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
2364  // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L11
2365  // convert sRGB values where in-gamut values are in the range [0 - 1]
2366  // to linear light (un-companded) form.
2367  // https://en.wikipedia.org/wiki/SRGB
2368  // Extended transfer function:
2369  // for negative values, linear portion is extended on reflection of axis,
2370  // then reflected power function is used.
2371
2372  #[inline]
2373  fn lin_srgb_component(c: f32) -> f32 {
2374    let abs = c.abs();
2375    if abs < 0.04045 {
2376      return c / 12.92;
2377    }
2378
2379    let sign = if c < 0.0 { -1.0 } else { 1.0 };
2380    sign * ((abs + 0.055) / 1.055).powf(2.4)
2381  }
2382
2383  let r = lin_srgb_component(r);
2384  let g = lin_srgb_component(g);
2385  let b = lin_srgb_component(b);
2386  (r, g, b)
2387}
2388
2389impl From<SRGBLinear> for XYZd65 {
2390  fn from(rgb: SRGBLinear) -> XYZd65 {
2391    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L50
2392    // convert an array of linear-light sRGB values to CIE XYZ
2393    // using sRGB's own white, D65 (no chromatic adaptation)
2394    const MATRIX: &[f32] = &[
2395      0.41239079926595934,
2396      0.357584339383878,
2397      0.1804807884018343,
2398      0.21263900587151027,
2399      0.715168678767756,
2400      0.07219231536073371,
2401      0.01933081871559182,
2402      0.11919477979462598,
2403      0.9505321522496607,
2404    ];
2405
2406    let rgb = rgb.resolve_missing();
2407    let (x, y, z) = multiply_matrix(MATRIX, rgb.r, rgb.g, rgb.b);
2408    XYZd65 {
2409      x,
2410      y,
2411      z,
2412      alpha: rgb.alpha,
2413    }
2414  }
2415}
2416
2417impl From<XYZd65> for P3 {
2418  fn from(xyz: XYZd65) -> P3 {
2419    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L105
2420    const MATRIX: &[f32] = &[
2421      2.493496911941425,
2422      -0.9313836179191239,
2423      -0.40271078445071684,
2424      -0.8294889695615747,
2425      1.7626640603183463,
2426      0.023624685841943577,
2427      0.03584583024378447,
2428      -0.07617238926804182,
2429      0.9568845240076872,
2430    ];
2431
2432    let xyz = xyz.resolve_missing();
2433    let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2434    let (r, g, b) = gam_srgb(r, g, b); // same as sRGB
2435    P3 {
2436      r,
2437      g,
2438      b,
2439      alpha: xyz.alpha,
2440    }
2441  }
2442}
2443
2444impl From<P3> for XYZd65 {
2445  fn from(p3: P3) -> XYZd65 {
2446    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L91
2447    // convert linear-light display-p3 values to CIE XYZ
2448    // using D65 (no chromatic adaptation)
2449    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
2450    const MATRIX: &[f32] = &[
2451      0.4865709486482162,
2452      0.26566769316909306,
2453      0.1982172852343625,
2454      0.2289745640697488,
2455      0.6917385218365064,
2456      0.079286914093745,
2457      0.0000000000000000,
2458      0.04511338185890264,
2459      1.043944368900976,
2460    ];
2461
2462    let p3 = p3.resolve_missing();
2463    let (r, g, b) = lin_srgb(p3.r, p3.g, p3.b);
2464    let (x, y, z) = multiply_matrix(MATRIX, r, g, b);
2465    XYZd65 {
2466      x,
2467      y,
2468      z,
2469      alpha: p3.alpha,
2470    }
2471  }
2472}
2473
2474impl From<A98> for XYZd65 {
2475  fn from(a98: A98) -> XYZd65 {
2476    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L181
2477    #[inline]
2478    fn lin_a98rgb_component(c: f32) -> f32 {
2479      let sign = if c < 0.0 { -1.0 } else { 1.0 };
2480      sign * c.abs().powf(563.0 / 256.0)
2481    }
2482
2483    // convert an array of a98-rgb values in the range 0.0 - 1.0
2484    // to linear light (un-companded) form.
2485    // negative values are also now accepted
2486    let a98 = a98.resolve_missing();
2487    let r = lin_a98rgb_component(a98.r);
2488    let g = lin_a98rgb_component(a98.g);
2489    let b = lin_a98rgb_component(a98.b);
2490
2491    // convert an array of linear-light a98-rgb values to CIE XYZ
2492    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
2493    // has greater numerical precision than section 4.3.5.3 of
2494    // https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
2495    // but the values below were calculated from first principles
2496    // from the chromaticity coordinates of R G B W
2497    // see matrixmaker.html
2498    const MATRIX: &[f32] = &[
2499      0.5766690429101305,
2500      0.1855582379065463,
2501      0.1882286462349947,
2502      0.29734497525053605,
2503      0.6273635662554661,
2504      0.07529145849399788,
2505      0.02703136138641234,
2506      0.07068885253582723,
2507      0.9913375368376388,
2508    ];
2509
2510    let (x, y, z) = multiply_matrix(MATRIX, r, g, b);
2511    XYZd65 {
2512      x,
2513      y,
2514      z,
2515      alpha: a98.alpha,
2516    }
2517  }
2518}
2519
2520impl From<XYZd65> for A98 {
2521  fn from(xyz: XYZd65) -> A98 {
2522    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L222
2523    // convert XYZ to linear-light a98-rgb
2524    const MATRIX: &[f32] = &[
2525      2.0415879038107465,
2526      -0.5650069742788596,
2527      -0.34473135077832956,
2528      -0.9692436362808795,
2529      1.8759675015077202,
2530      0.04155505740717557,
2531      0.013444280632031142,
2532      -0.11836239223101838,
2533      1.0151749943912054,
2534    ];
2535
2536    #[inline]
2537    fn gam_a98_component(c: f32) -> f32 {
2538      // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L193
2539      // convert linear-light a98-rgb  in the range 0.0-1.0
2540      // to gamma corrected form
2541      // negative values are also now accepted
2542      let sign = if c < 0.0 { -1.0 } else { 1.0 };
2543      sign * c.abs().powf(256.0 / 563.0)
2544    }
2545
2546    let xyz = xyz.resolve_missing();
2547    let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2548    let r = gam_a98_component(r);
2549    let g = gam_a98_component(g);
2550    let b = gam_a98_component(b);
2551    A98 {
2552      r,
2553      g,
2554      b,
2555      alpha: xyz.alpha,
2556    }
2557  }
2558}
2559
2560impl From<ProPhoto> for XYZd50 {
2561  fn from(prophoto: ProPhoto) -> XYZd50 {
2562    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L118
2563    // convert an array of prophoto-rgb values
2564    // where in-gamut colors are in the range [0.0 - 1.0]
2565    // to linear light (un-companded) form.
2566    // Transfer curve is gamma 1.8 with a small linear portion
2567    // Extended transfer function
2568
2569    #[inline]
2570    fn lin_prophoto_component(c: f32) -> f32 {
2571      const ET2: f32 = 16.0 / 512.0;
2572      let abs = c.abs();
2573      if abs <= ET2 {
2574        return c / 16.0;
2575      }
2576
2577      let sign = if c < 0.0 { -1.0 } else { 1.0 };
2578      sign * c.powf(1.8)
2579    }
2580
2581    let prophoto = prophoto.resolve_missing();
2582    let r = lin_prophoto_component(prophoto.r);
2583    let g = lin_prophoto_component(prophoto.g);
2584    let b = lin_prophoto_component(prophoto.b);
2585
2586    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L155
2587    // convert an array of linear-light prophoto-rgb values to CIE XYZ
2588    // using  D50 (so no chromatic adaptation needed afterwards)
2589    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
2590    const MATRIX: &[f32] = &[
2591      0.7977604896723027,
2592      0.13518583717574031,
2593      0.0313493495815248,
2594      0.2880711282292934,
2595      0.7118432178101014,
2596      0.00008565396060525902,
2597      0.0,
2598      0.0,
2599      0.8251046025104601,
2600    ];
2601
2602    let (x, y, z) = multiply_matrix(MATRIX, r, g, b);
2603    XYZd50 {
2604      x,
2605      y,
2606      z,
2607      alpha: prophoto.alpha,
2608    }
2609  }
2610}
2611
2612impl From<XYZd50> for ProPhoto {
2613  fn from(xyz: XYZd50) -> ProPhoto {
2614    // convert XYZ to linear-light prophoto-rgb
2615    const MATRIX: &[f32] = &[
2616      1.3457989731028281,
2617      -0.25558010007997534,
2618      -0.05110628506753401,
2619      -0.5446224939028347,
2620      1.5082327413132781,
2621      0.02053603239147973,
2622      0.0,
2623      0.0,
2624      1.2119675456389454,
2625    ];
2626
2627    #[inline]
2628    fn gam_prophoto_component(c: f32) -> f32 {
2629      // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L137
2630      // convert linear-light prophoto-rgb  in the range 0.0-1.0
2631      // to gamma corrected form
2632      // Transfer curve is gamma 1.8 with a small linear portion
2633      // TODO for negative values, extend linear portion on reflection of axis, then add pow below that
2634      const ET: f32 = 1.0 / 512.0;
2635      let abs = c.abs();
2636      if abs >= ET {
2637        let sign = if c < 0.0 { -1.0 } else { 1.0 };
2638        return sign * abs.powf(1.0 / 1.8);
2639      }
2640
2641      16.0 * c
2642    }
2643
2644    let xyz = xyz.resolve_missing();
2645    let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2646    let r = gam_prophoto_component(r);
2647    let g = gam_prophoto_component(g);
2648    let b = gam_prophoto_component(b);
2649    ProPhoto {
2650      r,
2651      g,
2652      b,
2653      alpha: xyz.alpha,
2654    }
2655  }
2656}
2657
2658impl From<Rec2020> for XYZd65 {
2659  fn from(rec2020: Rec2020) -> XYZd65 {
2660    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L235
2661    // convert an array of rec2020 RGB values in the range 0.0 - 1.0
2662    // to linear light (un-companded) form.
2663    // ITU-R BT.2020-2 p.4
2664
2665    #[inline]
2666    fn lin_rec2020_component(c: f32) -> f32 {
2667      const A: f32 = 1.09929682680944;
2668      const B: f32 = 0.018053968510807;
2669
2670      let abs = c.abs();
2671      if abs < B * 4.5 {
2672        return c / 4.5;
2673      }
2674
2675      let sign = if c < 0.0 { -1.0 } else { 1.0 };
2676      sign * ((abs + A - 1.0) / A).powf(1.0 / 0.45)
2677    }
2678
2679    let rec2020 = rec2020.resolve_missing();
2680    let r = lin_rec2020_component(rec2020.r);
2681    let g = lin_rec2020_component(rec2020.g);
2682    let b = lin_rec2020_component(rec2020.b);
2683
2684    // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L276
2685    // convert an array of linear-light rec2020 values to CIE XYZ
2686    // using  D65 (no chromatic adaptation)
2687    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
2688    const MATRIX: &[f32] = &[
2689      0.6369580483012914,
2690      0.14461690358620832,
2691      0.1688809751641721,
2692      0.2627002120112671,
2693      0.6779980715188708,
2694      0.05930171646986196,
2695      0.000000000000000,
2696      0.028072693049087428,
2697      1.060985057710791,
2698    ];
2699
2700    let (x, y, z) = multiply_matrix(MATRIX, r, g, b);
2701    XYZd65 {
2702      x,
2703      y,
2704      z,
2705      alpha: rec2020.alpha,
2706    }
2707  }
2708}
2709
2710impl From<XYZd65> for Rec2020 {
2711  fn from(xyz: XYZd65) -> Rec2020 {
2712    // convert XYZ to linear-light rec2020
2713    const MATRIX: &[f32] = &[
2714      1.7166511879712674,
2715      -0.35567078377639233,
2716      -0.25336628137365974,
2717      -0.6666843518324892,
2718      1.6164812366349395,
2719      0.01576854581391113,
2720      0.017639857445310783,
2721      -0.042770613257808524,
2722      0.9421031212354738,
2723    ];
2724
2725    #[inline]
2726    fn gam_rec2020_component(c: f32) -> f32 {
2727      // convert linear-light rec2020 RGB  in the range 0.0-1.0
2728      // to gamma corrected form
2729      // ITU-R BT.2020-2 p.4
2730
2731      const A: f32 = 1.09929682680944;
2732      const B: f32 = 0.018053968510807;
2733
2734      let abs = c.abs();
2735      if abs > B {
2736        let sign = if c < 0.0 { -1.0 } else { 1.0 };
2737        return sign * (A * abs.powf(0.45) - (A - 1.0));
2738      }
2739
2740      4.5 * c
2741    }
2742
2743    let xyz = xyz.resolve_missing();
2744    let (r, g, b) = multiply_matrix(MATRIX, xyz.x, xyz.y, xyz.z);
2745    let r = gam_rec2020_component(r);
2746    let g = gam_rec2020_component(g);
2747    let b = gam_rec2020_component(b);
2748    Rec2020 {
2749      r,
2750      g,
2751      b,
2752      alpha: xyz.alpha,
2753    }
2754  }
2755}
2756
2757impl From<SRGB> for HSL {
2758  fn from(rgb: SRGB) -> HSL {
2759    // https://drafts.csswg.org/css-color/#rgb-to-hsl
2760    let rgb = rgb.resolve();
2761    let r = rgb.r;
2762    let g = rgb.g;
2763    let b = rgb.b;
2764    let max = r.max(g).max(b);
2765    let min = r.min(g).min(b);
2766    let mut h = f32::NAN;
2767    let mut s: f32 = 0.0;
2768    let l = (min + max) / 2.0;
2769    let d = max - min;
2770
2771    if d != 0.0 {
2772      s = if l == 0.0 || l == 1.0 {
2773        0.0
2774      } else {
2775        (max - l) / l.min(1.0 - l)
2776      };
2777
2778      if max == r {
2779        h = (g - b) / d + (if g < b { 6.0 } else { 0.0 });
2780      } else if max == g {
2781        h = (b - r) / d + 2.0;
2782      } else if max == b {
2783        h = (r - g) / d + 4.0;
2784      }
2785
2786      h = h * 60.0;
2787    }
2788
2789    HSL {
2790      h,
2791      s: s * 100.0,
2792      l: l * 100.0,
2793      alpha: rgb.alpha,
2794    }
2795  }
2796}
2797
2798impl From<HSL> for SRGB {
2799  fn from(hsl: HSL) -> SRGB {
2800    // https://drafts.csswg.org/css-color/#hsl-to-rgb
2801    let hsl = hsl.resolve_missing();
2802    let h = (hsl.h - 360.0 * (hsl.h / 360.0).floor()) / 360.0;
2803    let (r, g, b) = hsl_to_rgb(h, hsl.s / 100.0, hsl.l / 100.0);
2804    SRGB {
2805      r,
2806      g,
2807      b,
2808      alpha: hsl.alpha,
2809    }
2810  }
2811}
2812
2813impl From<SRGB> for HWB {
2814  fn from(rgb: SRGB) -> HWB {
2815    let rgb = rgb.resolve();
2816    let hsl = HSL::from(rgb);
2817    let r = rgb.r;
2818    let g = rgb.g;
2819    let b = rgb.b;
2820    let w = r.min(g).min(b);
2821    let b = 1.0 - r.max(g).max(b);
2822    HWB {
2823      h: hsl.h,
2824      w: w * 100.0,
2825      b: b * 100.0,
2826      alpha: rgb.alpha,
2827    }
2828  }
2829}
2830
2831impl From<HWB> for SRGB {
2832  fn from(hwb: HWB) -> SRGB {
2833    // https://drafts.csswg.org/css-color/#hwb-to-rgb
2834    let hwb = hwb.resolve_missing();
2835    let h = hwb.h;
2836    let w = hwb.w / 100.0;
2837    let b = hwb.b / 100.0;
2838
2839    if w + b >= 1.0 {
2840      let gray = w / (w + b);
2841      return SRGB {
2842        r: gray,
2843        g: gray,
2844        b: gray,
2845        alpha: hwb.alpha,
2846      };
2847    }
2848
2849    let mut rgba = SRGB::from(HSL {
2850      h,
2851      s: 100.0,
2852      l: 50.0,
2853      alpha: hwb.alpha,
2854    });
2855    let x = 1.0 - w - b;
2856    rgba.r = rgba.r * x + w;
2857    rgba.g = rgba.g * x + w;
2858    rgba.b = rgba.b * x + w;
2859    rgba
2860  }
2861}
2862
2863impl From<RGBA> for SRGB {
2864  fn from(rgb: RGBA) -> SRGB {
2865    SRGB {
2866      r: rgb.red_f32(),
2867      g: rgb.green_f32(),
2868      b: rgb.blue_f32(),
2869      alpha: rgb.alpha_f32(),
2870    }
2871  }
2872}
2873
2874impl From<SRGB> for RGBA {
2875  fn from(rgb: SRGB) -> RGBA {
2876    let rgb = rgb.resolve();
2877    RGBA::from_floats(rgb.r, rgb.g, rgb.b, rgb.alpha)
2878  }
2879}
2880
2881impl From<SRGB> for RGB {
2882  fn from(rgb: SRGB) -> Self {
2883    RGB {
2884      r: rgb.r * 255.0,
2885      g: rgb.g * 255.0,
2886      b: rgb.b * 255.0,
2887      alpha: rgb.alpha,
2888    }
2889  }
2890}
2891
2892impl From<RGB> for SRGB {
2893  fn from(rgb: RGB) -> Self {
2894    SRGB {
2895      r: rgb.r / 255.0,
2896      g: rgb.g / 255.0,
2897      b: rgb.b / 255.0,
2898      alpha: rgb.alpha,
2899    }
2900  }
2901}
2902
2903impl From<RGBA> for RGB {
2904  fn from(rgb: RGBA) -> Self {
2905    RGB::from(&rgb)
2906  }
2907}
2908
2909impl From<&RGBA> for RGB {
2910  fn from(rgb: &RGBA) -> Self {
2911    RGB {
2912      r: rgb.red as f32,
2913      g: rgb.green as f32,
2914      b: rgb.blue as f32,
2915      alpha: rgb.alpha_f32(),
2916    }
2917  }
2918}
2919
2920impl From<RGB> for RGBA {
2921  fn from(rgb: RGB) -> Self {
2922    let rgb = rgb.resolve();
2923    RGBA::new(
2924      clamp_floor_256_f32(rgb.r),
2925      clamp_floor_256_f32(rgb.g),
2926      clamp_floor_256_f32(rgb.b),
2927      rgb.alpha,
2928    )
2929  }
2930}
2931
2932// Once Rust specialization is stable, this could be simplified.
2933via!(LAB -> XYZd50 -> XYZd65);
2934via!(ProPhoto -> XYZd50 -> XYZd65);
2935via!(OKLCH -> OKLAB -> XYZd65);
2936
2937via!(LAB -> XYZd65 -> OKLAB);
2938via!(LAB -> XYZd65 -> OKLCH);
2939via!(LAB -> XYZd65 -> SRGB);
2940via!(LAB -> XYZd65 -> SRGBLinear);
2941via!(LAB -> XYZd65 -> P3);
2942via!(LAB -> XYZd65 -> A98);
2943via!(LAB -> XYZd65 -> ProPhoto);
2944via!(LAB -> XYZd65 -> Rec2020);
2945via!(LAB -> XYZd65 -> HSL);
2946via!(LAB -> XYZd65 -> HWB);
2947
2948via!(LCH -> LAB -> XYZd65);
2949via!(LCH -> XYZd65 -> OKLAB);
2950via!(LCH -> XYZd65 -> OKLCH);
2951via!(LCH -> XYZd65 -> SRGB);
2952via!(LCH -> XYZd65 -> SRGBLinear);
2953via!(LCH -> XYZd65 -> P3);
2954via!(LCH -> XYZd65 -> A98);
2955via!(LCH -> XYZd65 -> ProPhoto);
2956via!(LCH -> XYZd65 -> Rec2020);
2957via!(LCH -> XYZd65 -> XYZd50);
2958via!(LCH -> XYZd65 -> HSL);
2959via!(LCH -> XYZd65 -> HWB);
2960
2961via!(SRGB -> SRGBLinear -> XYZd65);
2962via!(SRGB -> XYZd65 -> OKLAB);
2963via!(SRGB -> XYZd65 -> OKLCH);
2964via!(SRGB -> XYZd65 -> P3);
2965via!(SRGB -> XYZd65 -> A98);
2966via!(SRGB -> XYZd65 -> ProPhoto);
2967via!(SRGB -> XYZd65 -> Rec2020);
2968via!(SRGB -> XYZd65 -> XYZd50);
2969
2970via!(P3 -> XYZd65 -> SRGBLinear);
2971via!(P3 -> XYZd65 -> OKLAB);
2972via!(P3 -> XYZd65 -> OKLCH);
2973via!(P3 -> XYZd65 -> A98);
2974via!(P3 -> XYZd65 -> ProPhoto);
2975via!(P3 -> XYZd65 -> Rec2020);
2976via!(P3 -> XYZd65 -> XYZd50);
2977via!(P3 -> XYZd65 -> HSL);
2978via!(P3 -> XYZd65 -> HWB);
2979
2980via!(SRGBLinear -> XYZd65 -> OKLAB);
2981via!(SRGBLinear -> XYZd65 -> OKLCH);
2982via!(SRGBLinear -> XYZd65 -> A98);
2983via!(SRGBLinear -> XYZd65 -> ProPhoto);
2984via!(SRGBLinear -> XYZd65 -> Rec2020);
2985via!(SRGBLinear -> XYZd65 -> XYZd50);
2986via!(SRGBLinear -> XYZd65 -> HSL);
2987via!(SRGBLinear -> XYZd65 -> HWB);
2988
2989via!(A98 -> XYZd65 -> OKLAB);
2990via!(A98 -> XYZd65 -> OKLCH);
2991via!(A98 -> XYZd65 -> ProPhoto);
2992via!(A98 -> XYZd65 -> Rec2020);
2993via!(A98 -> XYZd65 -> XYZd50);
2994via!(A98 -> XYZd65 -> HSL);
2995via!(A98 -> XYZd65 -> HWB);
2996
2997via!(ProPhoto -> XYZd65 -> OKLAB);
2998via!(ProPhoto -> XYZd65 -> OKLCH);
2999via!(ProPhoto -> XYZd65 -> Rec2020);
3000via!(ProPhoto -> XYZd65 -> HSL);
3001via!(ProPhoto -> XYZd65 -> HWB);
3002
3003via!(XYZd50 -> XYZd65 -> OKLAB);
3004via!(XYZd50 -> XYZd65 -> OKLCH);
3005via!(XYZd50 -> XYZd65 -> Rec2020);
3006via!(XYZd50 -> XYZd65 -> HSL);
3007via!(XYZd50 -> XYZd65 -> HWB);
3008
3009via!(Rec2020 -> XYZd65 -> OKLAB);
3010via!(Rec2020 -> XYZd65 -> OKLCH);
3011via!(Rec2020 -> XYZd65 -> HSL);
3012via!(Rec2020 -> XYZd65 -> HWB);
3013
3014via!(HSL -> XYZd65 -> OKLAB);
3015via!(HSL -> XYZd65 -> OKLCH);
3016via!(HSL -> SRGB -> XYZd65);
3017via!(HSL -> SRGB -> HWB);
3018
3019via!(HWB -> SRGB -> XYZd65);
3020via!(HWB -> XYZd65 -> OKLAB);
3021via!(HWB -> XYZd65 -> OKLCH);
3022
3023via!(RGB -> SRGB -> LAB);
3024via!(RGB -> SRGB -> LCH);
3025via!(RGB -> SRGB -> OKLAB);
3026via!(RGB -> SRGB -> OKLCH);
3027via!(RGB -> SRGB -> P3);
3028via!(RGB -> SRGB -> SRGBLinear);
3029via!(RGB -> SRGB -> A98);
3030via!(RGB -> SRGB -> ProPhoto);
3031via!(RGB -> SRGB -> XYZd50);
3032via!(RGB -> SRGB -> XYZd65);
3033via!(RGB -> SRGB -> Rec2020);
3034via!(RGB -> SRGB -> HSL);
3035via!(RGB -> SRGB -> HWB);
3036
3037// RGBA is an 8-bit version. Convert to SRGB, which is a
3038// more accurate floating point representation for all operations.
3039via!(RGBA -> SRGB -> LAB);
3040via!(RGBA -> SRGB -> LCH);
3041via!(RGBA -> SRGB -> OKLAB);
3042via!(RGBA -> SRGB -> OKLCH);
3043via!(RGBA -> SRGB -> P3);
3044via!(RGBA -> SRGB -> SRGBLinear);
3045via!(RGBA -> SRGB -> A98);
3046via!(RGBA -> SRGB -> ProPhoto);
3047via!(RGBA -> SRGB -> XYZd50);
3048via!(RGBA -> SRGB -> XYZd65);
3049via!(RGBA -> SRGB -> Rec2020);
3050via!(RGBA -> SRGB -> HSL);
3051via!(RGBA -> SRGB -> HWB);
3052
3053macro_rules! color_space {
3054  ($space: ty) => {
3055    impl From<LABColor> for $space {
3056      fn from(color: LABColor) -> $space {
3057        use LABColor::*;
3058
3059        match color {
3060          LAB(v) => v.into(),
3061          LCH(v) => v.into(),
3062          OKLAB(v) => v.into(),
3063          OKLCH(v) => v.into(),
3064        }
3065      }
3066    }
3067
3068    impl From<PredefinedColor> for $space {
3069      fn from(color: PredefinedColor) -> $space {
3070        use PredefinedColor::*;
3071
3072        match color {
3073          SRGB(v) => v.into(),
3074          SRGBLinear(v) => v.into(),
3075          DisplayP3(v) => v.into(),
3076          A98(v) => v.into(),
3077          ProPhoto(v) => v.into(),
3078          Rec2020(v) => v.into(),
3079          XYZd50(v) => v.into(),
3080          XYZd65(v) => v.into(),
3081        }
3082      }
3083    }
3084
3085    impl From<FloatColor> for $space {
3086      fn from(color: FloatColor) -> $space {
3087        use FloatColor::*;
3088
3089        match color {
3090          RGB(v) => v.into(),
3091          HSL(v) => v.into(),
3092          HWB(v) => v.into(),
3093        }
3094      }
3095    }
3096
3097    impl TryFrom<&CssColor> for $space {
3098      type Error = ();
3099      fn try_from(color: &CssColor) -> Result<$space, ()> {
3100        Ok(match color {
3101          CssColor::RGBA(rgba) => (*rgba).into(),
3102          CssColor::LAB(lab) => (**lab).into(),
3103          CssColor::Predefined(predefined) => (**predefined).into(),
3104          CssColor::Float(float) => (**float).into(),
3105          CssColor::CurrentColor => return Err(()),
3106          CssColor::LightDark(..) => return Err(()),
3107          CssColor::System(..) => return Err(()),
3108        })
3109      }
3110    }
3111
3112    impl TryFrom<CssColor> for $space {
3113      type Error = ();
3114      fn try_from(color: CssColor) -> Result<$space, ()> {
3115        Ok(match color {
3116          CssColor::RGBA(rgba) => rgba.into(),
3117          CssColor::LAB(lab) => (*lab).into(),
3118          CssColor::Predefined(predefined) => (*predefined).into(),
3119          CssColor::Float(float) => (*float).into(),
3120          CssColor::CurrentColor => return Err(()),
3121          CssColor::LightDark(..) => return Err(()),
3122          CssColor::System(..) => return Err(()),
3123        })
3124      }
3125    }
3126  };
3127}
3128
3129color_space!(LAB);
3130color_space!(LCH);
3131color_space!(OKLAB);
3132color_space!(OKLCH);
3133color_space!(SRGB);
3134color_space!(SRGBLinear);
3135color_space!(XYZd50);
3136color_space!(XYZd65);
3137color_space!(P3);
3138color_space!(A98);
3139color_space!(ProPhoto);
3140color_space!(Rec2020);
3141color_space!(HSL);
3142color_space!(HWB);
3143color_space!(RGB);
3144color_space!(RGBA);
3145
3146macro_rules! predefined {
3147  ($key: ident, $t: ty) => {
3148    impl From<$t> for PredefinedColor {
3149      fn from(color: $t) -> PredefinedColor {
3150        PredefinedColor::$key(color)
3151      }
3152    }
3153
3154    impl From<$t> for CssColor {
3155      fn from(color: $t) -> CssColor {
3156        CssColor::Predefined(Box::new(PredefinedColor::$key(color)))
3157      }
3158    }
3159  };
3160}
3161
3162predefined!(SRGBLinear, SRGBLinear);
3163predefined!(XYZd50, XYZd50);
3164predefined!(XYZd65, XYZd65);
3165predefined!(DisplayP3, P3);
3166predefined!(A98, A98);
3167predefined!(ProPhoto, ProPhoto);
3168predefined!(Rec2020, Rec2020);
3169
3170macro_rules! lab {
3171  ($key: ident, $t: ty) => {
3172    impl From<$t> for LABColor {
3173      fn from(color: $t) -> LABColor {
3174        LABColor::$key(color)
3175      }
3176    }
3177
3178    impl From<$t> for CssColor {
3179      fn from(color: $t) -> CssColor {
3180        CssColor::LAB(Box::new(LABColor::$key(color)))
3181      }
3182    }
3183  };
3184}
3185
3186lab!(LAB, LAB);
3187lab!(LCH, LCH);
3188lab!(OKLAB, OKLAB);
3189lab!(OKLCH, OKLCH);
3190
3191macro_rules! rgb {
3192  ($t: ty) => {
3193    impl From<$t> for CssColor {
3194      fn from(color: $t) -> CssColor {
3195        // TODO: should we serialize as color(srgb, ...)?
3196        // would be more precise than 8-bit color.
3197        CssColor::RGBA(color.into())
3198      }
3199    }
3200  };
3201}
3202
3203rgb!(SRGB);
3204rgb!(HSL);
3205rgb!(HWB);
3206rgb!(RGB);
3207
3208impl From<RGBA> for CssColor {
3209  fn from(color: RGBA) -> CssColor {
3210    CssColor::RGBA(color)
3211  }
3212}
3213
3214/// A trait that colors implement to support [gamut mapping](https://www.w3.org/TR/css-color-4/#gamut-mapping).
3215pub trait ColorGamut {
3216  /// Returns whether the color is within the gamut of the color space.
3217  fn in_gamut(&self) -> bool;
3218  /// Clips the color so that it is within the gamut of the color space.
3219  fn clip(&self) -> Self;
3220}
3221
3222macro_rules! bounded_color_gamut {
3223  ($t: ty, $a: ident, $b: ident, $c: ident) => {
3224    impl ColorGamut for $t {
3225      #[inline]
3226      fn in_gamut(&self) -> bool {
3227        self.$a >= 0.0 && self.$a <= 1.0 && self.$b >= 0.0 && self.$b <= 1.0 && self.$c >= 0.0 && self.$c <= 1.0
3228      }
3229
3230      #[inline]
3231      fn clip(&self) -> Self {
3232        Self {
3233          $a: self.$a.clamp(0.0, 1.0),
3234          $b: self.$b.clamp(0.0, 1.0),
3235          $c: self.$c.clamp(0.0, 1.0),
3236          alpha: self.alpha.clamp(0.0, 1.0),
3237        }
3238      }
3239    }
3240  };
3241}
3242
3243macro_rules! unbounded_color_gamut {
3244  ($t: ty, $a: ident, $b: ident, $c: ident) => {
3245    impl ColorGamut for $t {
3246      #[inline]
3247      fn in_gamut(&self) -> bool {
3248        true
3249      }
3250
3251      #[inline]
3252      fn clip(&self) -> Self {
3253        *self
3254      }
3255    }
3256  };
3257}
3258
3259macro_rules! hsl_hwb_color_gamut {
3260  ($t: ty, $a: ident, $b: ident) => {
3261    impl ColorGamut for $t {
3262      #[inline]
3263      fn in_gamut(&self) -> bool {
3264        self.$a >= 0.0 && self.$a <= 100.0 && self.$b >= 0.0 && self.$b <= 100.0
3265      }
3266
3267      #[inline]
3268      fn clip(&self) -> Self {
3269        Self {
3270          h: self.h % 360.0,
3271          $a: self.$a.clamp(0.0, 100.0),
3272          $b: self.$b.clamp(0.0, 100.0),
3273          alpha: self.alpha.clamp(0.0, 1.0),
3274        }
3275      }
3276    }
3277  };
3278}
3279
3280bounded_color_gamut!(SRGB, r, g, b);
3281bounded_color_gamut!(SRGBLinear, r, g, b);
3282bounded_color_gamut!(P3, r, g, b);
3283bounded_color_gamut!(A98, r, g, b);
3284bounded_color_gamut!(ProPhoto, r, g, b);
3285bounded_color_gamut!(Rec2020, r, g, b);
3286unbounded_color_gamut!(LAB, l, a, b);
3287unbounded_color_gamut!(OKLAB, l, a, b);
3288unbounded_color_gamut!(XYZd50, x, y, z);
3289unbounded_color_gamut!(XYZd65, x, y, z);
3290unbounded_color_gamut!(LCH, l, c, h);
3291unbounded_color_gamut!(OKLCH, l, c, h);
3292hsl_hwb_color_gamut!(HSL, s, l);
3293hsl_hwb_color_gamut!(HWB, w, b);
3294
3295impl ColorGamut for RGB {
3296  #[inline]
3297  fn in_gamut(&self) -> bool {
3298    self.r >= 0.0 && self.r <= 255.0 && self.g >= 0.0 && self.g <= 255.0 && self.b >= 0.0 && self.b <= 255.0
3299  }
3300
3301  #[inline]
3302  fn clip(&self) -> Self {
3303    Self {
3304      r: self.r.clamp(0.0, 255.0),
3305      g: self.g.clamp(0.0, 255.0),
3306      b: self.b.clamp(0.0, 255.0),
3307      alpha: self.alpha.clamp(0.0, 1.0),
3308    }
3309  }
3310}
3311
3312fn delta_eok<T: Into<OKLAB>>(a: T, b: OKLCH) -> f32 {
3313  // https://www.w3.org/TR/css-color-4/#color-difference-OK
3314  let a: OKLAB = a.into();
3315  let b: OKLAB = b.into();
3316  let delta_l = a.l - b.l;
3317  let delta_a = a.a - b.a;
3318  let delta_b = a.b - b.b;
3319
3320  (delta_l.powi(2) + delta_a.powi(2) + delta_b.powi(2)).sqrt()
3321}
3322
3323fn map_gamut<T>(color: T) -> T
3324where
3325  T: Into<OKLCH> + ColorGamut + Into<OKLAB> + From<OKLCH> + Copy,
3326{
3327  const JND: f32 = 0.02;
3328  const EPSILON: f32 = 0.00001;
3329
3330  // https://www.w3.org/TR/css-color-4/#binsearch
3331  let mut current: OKLCH = color.into();
3332
3333  // If lightness is >= 100%, return pure white.
3334  if (current.l - 1.0).abs() < EPSILON || current.l > 1.0 {
3335    return OKLCH {
3336      l: 1.0,
3337      c: 0.0,
3338      h: 0.0,
3339      alpha: current.alpha,
3340    }
3341    .into();
3342  }
3343
3344  // If lightness <= 0%, return pure black.
3345  if current.l < EPSILON {
3346    return OKLCH {
3347      l: 0.0,
3348      c: 0.0,
3349      h: 0.0,
3350      alpha: current.alpha,
3351    }
3352    .into();
3353  }
3354
3355  let mut min = 0.0;
3356  let mut max = current.c;
3357
3358  while (max - min) > EPSILON {
3359    let chroma = (min + max) / 2.0;
3360    current.c = chroma;
3361
3362    let converted = T::from(current);
3363    if converted.in_gamut() {
3364      min = chroma;
3365      continue;
3366    }
3367
3368    let clipped = converted.clip();
3369    let delta_e = delta_eok(clipped, current);
3370    if delta_e < JND {
3371      return clipped;
3372    }
3373
3374    max = chroma;
3375  }
3376
3377  current.into()
3378}
3379
3380fn parse_color_mix<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CssColor, ParseError<'i, ParserError<'i>>> {
3381  input.expect_ident_matching("in")?;
3382  let method = ColorSpaceName::parse(input)?;
3383
3384  let hue_method = if matches!(
3385    method,
3386    ColorSpaceName::Hsl | ColorSpaceName::Hwb | ColorSpaceName::LCH | ColorSpaceName::OKLCH
3387  ) {
3388    let hue_method = input.try_parse(HueInterpolationMethod::parse);
3389    if hue_method.is_ok() {
3390      input.expect_ident_matching("hue")?;
3391    }
3392    hue_method
3393  } else {
3394    Ok(HueInterpolationMethod::Shorter)
3395  };
3396
3397  let hue_method = hue_method.unwrap_or(HueInterpolationMethod::Shorter);
3398  input.expect_comma()?;
3399
3400  let first_percent = input.try_parse(|input| input.expect_percentage());
3401  let first_color = CssColor::parse(input)?;
3402  let first_percent = first_percent
3403    .or_else(|_| input.try_parse(|input| input.expect_percentage()))
3404    .ok();
3405  input.expect_comma()?;
3406
3407  let second_percent = input.try_parse(|input| input.expect_percentage());
3408  let second_color = CssColor::parse(input)?;
3409  let second_percent = second_percent
3410    .or_else(|_| input.try_parse(|input| input.expect_percentage()))
3411    .ok();
3412
3413  // https://drafts.csswg.org/css-color-5/#color-mix-percent-norm
3414  let (p1, p2) = if first_percent.is_none() && second_percent.is_none() {
3415    (0.5, 0.5)
3416  } else {
3417    let p2 = second_percent.unwrap_or_else(|| 1.0 - first_percent.unwrap());
3418    let p1 = first_percent.unwrap_or_else(|| 1.0 - second_percent.unwrap());
3419    (p1, p2)
3420  };
3421
3422  if (p1 + p2) == 0.0 {
3423    return Err(input.new_custom_error(ParserError::InvalidValue));
3424  }
3425
3426  match method {
3427    ColorSpaceName::SRGB => first_color.interpolate::<SRGB>(p1, &second_color, p2, hue_method),
3428    ColorSpaceName::SRGBLinear => first_color.interpolate::<SRGBLinear>(p1, &second_color, p2, hue_method),
3429    ColorSpaceName::Hsl => first_color.interpolate::<HSL>(p1, &second_color, p2, hue_method),
3430    ColorSpaceName::Hwb => first_color.interpolate::<HWB>(p1, &second_color, p2, hue_method),
3431    ColorSpaceName::LAB => first_color.interpolate::<LAB>(p1, &second_color, p2, hue_method),
3432    ColorSpaceName::LCH => first_color.interpolate::<LCH>(p1, &second_color, p2, hue_method),
3433    ColorSpaceName::OKLAB => first_color.interpolate::<OKLAB>(p1, &second_color, p2, hue_method),
3434    ColorSpaceName::OKLCH => first_color.interpolate::<OKLCH>(p1, &second_color, p2, hue_method),
3435    ColorSpaceName::XYZ | ColorSpaceName::XYZd65 => {
3436      first_color.interpolate::<XYZd65>(p1, &second_color, p2, hue_method)
3437    }
3438    ColorSpaceName::XYZd50 => first_color.interpolate::<XYZd50>(p1, &second_color, p2, hue_method),
3439  }
3440  .map_err(|_| input.new_custom_error(ParserError::InvalidValue))
3441}
3442
3443impl CssColor {
3444  fn get_type_id(&self) -> TypeId {
3445    match self {
3446      CssColor::RGBA(..) => TypeId::of::<SRGB>(),
3447      CssColor::LAB(lab) => match &**lab {
3448        LABColor::LAB(..) => TypeId::of::<LAB>(),
3449        LABColor::LCH(..) => TypeId::of::<LCH>(),
3450        LABColor::OKLAB(..) => TypeId::of::<OKLAB>(),
3451        LABColor::OKLCH(..) => TypeId::of::<OKLCH>(),
3452      },
3453      CssColor::Predefined(predefined) => match &**predefined {
3454        PredefinedColor::SRGB(..) => TypeId::of::<SRGB>(),
3455        PredefinedColor::SRGBLinear(..) => TypeId::of::<SRGBLinear>(),
3456        PredefinedColor::DisplayP3(..) => TypeId::of::<P3>(),
3457        PredefinedColor::A98(..) => TypeId::of::<A98>(),
3458        PredefinedColor::ProPhoto(..) => TypeId::of::<ProPhoto>(),
3459        PredefinedColor::Rec2020(..) => TypeId::of::<Rec2020>(),
3460        PredefinedColor::XYZd50(..) => TypeId::of::<XYZd50>(),
3461        PredefinedColor::XYZd65(..) => TypeId::of::<XYZd65>(),
3462      },
3463      CssColor::Float(float) => match &**float {
3464        FloatColor::RGB(..) => TypeId::of::<SRGB>(),
3465        FloatColor::HSL(..) => TypeId::of::<HSL>(),
3466        FloatColor::HWB(..) => TypeId::of::<HWB>(),
3467      },
3468      _ => unreachable!(),
3469    }
3470  }
3471
3472  fn to_light_dark(&self) -> CssColor {
3473    match self {
3474      CssColor::LightDark(..) => self.clone(),
3475      _ => CssColor::LightDark(Box::new(self.clone()), Box::new(self.clone())),
3476    }
3477  }
3478
3479  /// Mixes this color with another color, including the specified amount of each.
3480  /// Implemented according to the [`color-mix()`](https://www.w3.org/TR/css-color-5/#color-mix) function.
3481  pub fn interpolate<T>(
3482    &self,
3483    mut p1: f32,
3484    other: &CssColor,
3485    mut p2: f32,
3486    method: HueInterpolationMethod,
3487  ) -> Result<CssColor, ()>
3488  where
3489    for<'a> T: 'static
3490      + TryFrom<&'a CssColor>
3491      + Interpolate
3492      + Into<CssColor>
3493      + Into<OKLCH>
3494      + ColorGamut
3495      + Into<OKLAB>
3496      + From<OKLCH>
3497      + Copy,
3498  {
3499    if matches!(self, CssColor::CurrentColor | CssColor::System(..))
3500      || matches!(other, CssColor::CurrentColor | CssColor::System(..))
3501    {
3502      return Err(());
3503    }
3504
3505    if matches!(self, CssColor::LightDark(..)) || matches!(other, CssColor::LightDark(..)) {
3506      if let (CssColor::LightDark(al, ad), CssColor::LightDark(bl, bd)) =
3507        (self.to_light_dark(), other.to_light_dark())
3508      {
3509        return Ok(CssColor::LightDark(
3510          Box::new(al.interpolate::<T>(p1, &bl, p2, method)?),
3511          Box::new(ad.interpolate::<T>(p1, &bd, p2, method)?),
3512        ));
3513      }
3514    }
3515
3516    let type_id = TypeId::of::<T>();
3517    let converted_first = self.get_type_id() != type_id;
3518    let converted_second = other.get_type_id() != type_id;
3519
3520    // https://drafts.csswg.org/css-color-5/#color-mix-result
3521    let mut first_color = T::try_from(self).map_err(|_| ())?;
3522    let mut second_color = T::try_from(other).map_err(|_| ())?;
3523
3524    if converted_first && !first_color.in_gamut() {
3525      first_color = map_gamut(first_color);
3526    }
3527
3528    if converted_second && !second_color.in_gamut() {
3529      second_color = map_gamut(second_color);
3530    }
3531
3532    // https://www.w3.org/TR/css-color-4/#powerless
3533    if converted_first {
3534      first_color.adjust_powerless_components();
3535    }
3536
3537    if converted_second {
3538      second_color.adjust_powerless_components();
3539    }
3540
3541    // https://drafts.csswg.org/css-color-4/#interpolation-missing
3542    first_color.fill_missing_components(&second_color);
3543    second_color.fill_missing_components(&first_color);
3544
3545    // https://www.w3.org/TR/css-color-4/#hue-interpolation
3546    first_color.adjust_hue(&mut second_color, method);
3547
3548    // https://www.w3.org/TR/css-color-4/#interpolation-alpha
3549    first_color.premultiply();
3550    second_color.premultiply();
3551
3552    // https://drafts.csswg.org/css-color-5/#color-mix-percent-norm
3553    let mut alpha_multiplier = p1 + p2;
3554    if alpha_multiplier != 1.0 {
3555      p1 = p1 / alpha_multiplier;
3556      p2 = p2 / alpha_multiplier;
3557      if alpha_multiplier > 1.0 {
3558        alpha_multiplier = 1.0;
3559      }
3560    }
3561
3562    let mut result_color = first_color.interpolate(p1, &second_color, p2);
3563    result_color.unpremultiply(alpha_multiplier);
3564
3565    Ok(result_color.into())
3566  }
3567}
3568
3569/// A trait that colors implement to support interpolation.
3570pub trait Interpolate {
3571  /// Adjusts components that are powerless to be NaN.
3572  fn adjust_powerless_components(&mut self) {}
3573  /// Fills missing components (represented as NaN) to match the other color to interpolate with.
3574  fn fill_missing_components(&mut self, other: &Self);
3575  /// Adjusts the color hue according to the given hue interpolation method.
3576  fn adjust_hue(&mut self, _: &mut Self, _: HueInterpolationMethod) {}
3577  /// Premultiplies the color by its alpha value.
3578  fn premultiply(&mut self);
3579  /// Un-premultiplies the color by the given alpha multiplier.
3580  fn unpremultiply(&mut self, alpha_multiplier: f32);
3581  /// Interpolates the color with another using the given amounts of each.
3582  fn interpolate(&self, p1: f32, other: &Self, p2: f32) -> Self;
3583}
3584
3585macro_rules! interpolate {
3586  ($a: ident, $b: ident, $c: ident) => {
3587    fn fill_missing_components(&mut self, other: &Self) {
3588      if self.$a.is_nan() {
3589        self.$a = other.$a;
3590      }
3591
3592      if self.$b.is_nan() {
3593        self.$b = other.$b;
3594      }
3595
3596      if self.$c.is_nan() {
3597        self.$c = other.$c;
3598      }
3599
3600      if self.alpha.is_nan() {
3601        self.alpha = other.alpha;
3602      }
3603    }
3604
3605    fn interpolate(&self, p1: f32, other: &Self, p2: f32) -> Self {
3606      Self {
3607        $a: self.$a * p1 + other.$a * p2,
3608        $b: self.$b * p1 + other.$b * p2,
3609        $c: self.$c * p1 + other.$c * p2,
3610        alpha: self.alpha * p1 + other.alpha * p2,
3611      }
3612    }
3613  };
3614}
3615
3616macro_rules! rectangular_premultiply {
3617  ($a: ident, $b: ident, $c: ident) => {
3618    fn premultiply(&mut self) {
3619      if !self.alpha.is_nan() {
3620        self.$a *= self.alpha;
3621        self.$b *= self.alpha;
3622        self.$c *= self.alpha;
3623      }
3624    }
3625
3626    fn unpremultiply(&mut self, alpha_multiplier: f32) {
3627      if !self.alpha.is_nan() && self.alpha != 0.0 {
3628        self.$a /= self.alpha;
3629        self.$b /= self.alpha;
3630        self.$c /= self.alpha;
3631        self.alpha *= alpha_multiplier;
3632      }
3633    }
3634  };
3635}
3636
3637macro_rules! polar_premultiply {
3638  ($a: ident, $b: ident) => {
3639    fn premultiply(&mut self) {
3640      if !self.alpha.is_nan() {
3641        self.$a *= self.alpha;
3642        self.$b *= self.alpha;
3643      }
3644    }
3645
3646    fn unpremultiply(&mut self, alpha_multiplier: f32) {
3647      self.h %= 360.0;
3648      if !self.alpha.is_nan() {
3649        self.$a /= self.alpha;
3650        self.$b /= self.alpha;
3651        self.alpha *= alpha_multiplier;
3652      }
3653    }
3654  };
3655}
3656
3657macro_rules! adjust_powerless_lab {
3658  () => {
3659    fn adjust_powerless_components(&mut self) {
3660      // If the lightness of a LAB color is 0%, both the a and b components are powerless.
3661      if self.l.abs() < f32::EPSILON {
3662        self.a = f32::NAN;
3663        self.b = f32::NAN;
3664      }
3665    }
3666  };
3667}
3668
3669macro_rules! adjust_powerless_lch {
3670  () => {
3671    fn adjust_powerless_components(&mut self) {
3672      // If the chroma of an LCH color is 0%, the hue component is powerless.
3673      // If the lightness of an LCH color is 0%, both the hue and chroma components are powerless.
3674      if self.c.abs() < f32::EPSILON {
3675        self.h = f32::NAN;
3676      }
3677
3678      if self.l.abs() < f32::EPSILON {
3679        self.c = f32::NAN;
3680        self.h = f32::NAN;
3681      }
3682    }
3683
3684    fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {
3685      method.interpolate(&mut self.h, &mut other.h);
3686    }
3687  };
3688}
3689
3690impl Interpolate for SRGB {
3691  rectangular_premultiply!(r, g, b);
3692  interpolate!(r, g, b);
3693}
3694
3695impl Interpolate for SRGBLinear {
3696  rectangular_premultiply!(r, g, b);
3697  interpolate!(r, g, b);
3698}
3699
3700impl Interpolate for XYZd50 {
3701  rectangular_premultiply!(x, y, z);
3702  interpolate!(x, y, z);
3703}
3704
3705impl Interpolate for XYZd65 {
3706  rectangular_premultiply!(x, y, z);
3707  interpolate!(x, y, z);
3708}
3709
3710impl Interpolate for LAB {
3711  adjust_powerless_lab!();
3712  rectangular_premultiply!(l, a, b);
3713  interpolate!(l, a, b);
3714}
3715
3716impl Interpolate for OKLAB {
3717  adjust_powerless_lab!();
3718  rectangular_premultiply!(l, a, b);
3719  interpolate!(l, a, b);
3720}
3721
3722impl Interpolate for LCH {
3723  adjust_powerless_lch!();
3724  polar_premultiply!(l, c);
3725  interpolate!(l, c, h);
3726}
3727
3728impl Interpolate for OKLCH {
3729  adjust_powerless_lch!();
3730  polar_premultiply!(l, c);
3731  interpolate!(l, c, h);
3732}
3733
3734impl Interpolate for HSL {
3735  polar_premultiply!(s, l);
3736
3737  fn adjust_powerless_components(&mut self) {
3738    // If the saturation of an HSL color is 0%, then the hue component is powerless.
3739    // If the lightness of an HSL color is 0% or 100%, both the saturation and hue components are powerless.
3740    if self.s.abs() < f32::EPSILON {
3741      self.h = f32::NAN;
3742    }
3743
3744    if self.l.abs() < f32::EPSILON || (self.l - 100.0).abs() < f32::EPSILON {
3745      self.h = f32::NAN;
3746      self.s = f32::NAN;
3747    }
3748  }
3749
3750  fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {
3751    method.interpolate(&mut self.h, &mut other.h);
3752  }
3753
3754  interpolate!(h, s, l);
3755}
3756
3757impl Interpolate for HWB {
3758  polar_premultiply!(w, b);
3759
3760  fn adjust_powerless_components(&mut self) {
3761    // If white+black is equal to 100% (after normalization), it defines an achromatic color,
3762    // i.e. some shade of gray, without any hint of the chosen hue. In this case, the hue component is powerless.
3763    if (self.w + self.b - 100.0).abs() < f32::EPSILON {
3764      self.h = f32::NAN;
3765    }
3766  }
3767
3768  fn adjust_hue(&mut self, other: &mut Self, method: HueInterpolationMethod) {
3769    method.interpolate(&mut self.h, &mut other.h);
3770  }
3771
3772  interpolate!(h, w, b);
3773}
3774
3775impl HueInterpolationMethod {
3776  fn interpolate(&self, a: &mut f32, b: &mut f32) {
3777    // https://drafts.csswg.org/css-color/#hue-interpolation
3778    if *self != HueInterpolationMethod::Specified {
3779      *a = ((*a % 360.0) + 360.0) % 360.0;
3780      *b = ((*b % 360.0) + 360.0) % 360.0;
3781    }
3782
3783    match self {
3784      HueInterpolationMethod::Shorter => {
3785        // https://www.w3.org/TR/css-color-4/#hue-shorter
3786        let delta = *b - *a;
3787        if delta > 180.0 {
3788          *a += 360.0;
3789        } else if delta < -180.0 {
3790          *b += 360.0;
3791        }
3792      }
3793      HueInterpolationMethod::Longer => {
3794        // https://www.w3.org/TR/css-color-4/#hue-longer
3795        let delta = *b - *a;
3796        if 0.0 < delta && delta < 180.0 {
3797          *a += 360.0;
3798        } else if -180.0 < delta && delta < 0.0 {
3799          *b += 360.0;
3800        }
3801      }
3802      HueInterpolationMethod::Increasing => {
3803        // https://www.w3.org/TR/css-color-4/#hue-increasing
3804        if *b < *a {
3805          *b += 360.0;
3806        }
3807      }
3808      HueInterpolationMethod::Decreasing => {
3809        // https://www.w3.org/TR/css-color-4/#hue-decreasing
3810        if *a < *b {
3811          *a += 360.0;
3812        }
3813      }
3814      HueInterpolationMethod::Specified => {}
3815    }
3816  }
3817}
3818
3819#[cfg(feature = "visitor")]
3820#[cfg_attr(docsrs, doc(cfg(feature = "visitor")))]
3821impl<'i, V: ?Sized + Visitor<'i, T>, T: Visit<'i, T, V>> Visit<'i, T, V> for RGBA {
3822  const CHILD_TYPES: VisitTypes = VisitTypes::empty();
3823  fn visit_children(&mut self, _: &mut V) -> Result<(), V::Error> {
3824    Ok(())
3825  }
3826}
3827
3828#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
3829#[css(case = lower)]
3830#[cfg_attr(feature = "visitor", derive(Visit))]
3831#[cfg_attr(
3832  feature = "serde",
3833  derive(serde::Serialize, serde::Deserialize),
3834  serde(rename_all = "lowercase")
3835)]
3836#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
3837#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
3838/// A CSS [system color](https://drafts.csswg.org/css-color/#css-system-colors) keyword.
3839pub enum SystemColor {
3840  /// Background of accented user interface controls.
3841  AccentColor,
3842  /// Text of accented user interface controls.
3843  AccentColorText,
3844  /// Text in active links. For light backgrounds, traditionally red.
3845  ActiveText,
3846  /// The base border color for push buttons.
3847  ButtonBorder,
3848  /// The face background color for push buttons.
3849  ButtonFace,
3850  /// Text on push buttons.
3851  ButtonText,
3852  /// Background of application content or documents.
3853  Canvas,
3854  /// Text in application content or documents.
3855  CanvasText,
3856  /// Background of input fields.
3857  Field,
3858  /// Text in input fields.
3859  FieldText,
3860  /// Disabled text. (Often, but not necessarily, gray.)
3861  GrayText,
3862  /// Background of selected text, for example from ::selection.
3863  Highlight,
3864  /// Text of selected text.
3865  HighlightText,
3866  /// Text in non-active, non-visited links. For light backgrounds, traditionally blue.
3867  LinkText,
3868  /// Background of text that has been specially marked (such as by the HTML mark element).
3869  Mark,
3870  /// Text that has been specially marked (such as by the HTML mark element).
3871  MarkText,
3872  /// Background of selected items, for example a selected checkbox.
3873  SelectedItem,
3874  /// Text of selected items.
3875  SelectedItemText,
3876  /// Text in visited links. For light backgrounds, traditionally purple.
3877  VisitedText,
3878
3879  // Deprecated colors: https://drafts.csswg.org/css-color/#deprecated-system-colors
3880  /// Active window border. Same as ButtonBorder.
3881  ActiveBorder,
3882  /// Active window caption. Same as Canvas.
3883  ActiveCaption,
3884  /// Background color of multiple document interface. Same as Canvas.
3885  AppWorkspace,
3886  /// Desktop background. Same as Canvas.
3887  Background,
3888  /// 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.
3889  ButtonHighlight,
3890  /// 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.
3891  ButtonShadow,
3892  /// Text in caption, size box, and scrollbar arrow box. Same as CanvasText.
3893  CaptionText,
3894  /// Inactive window border. Same as ButtonBorder.
3895  InactiveBorder,
3896  /// Inactive window caption. Same as Canvas.
3897  InactiveCaption,
3898  /// Color of text in an inactive caption. Same as GrayText.
3899  InactiveCaptionText,
3900  /// Background color for tooltip controls. Same as Canvas.
3901  InfoBackground,
3902  /// Text color for tooltip controls. Same as CanvasText.
3903  InfoText,
3904  /// Menu background. Same as Canvas.
3905  Menu,
3906  /// Text in menus. Same as CanvasText.
3907  MenuText,
3908  /// Scroll bar gray area. Same as Canvas.
3909  Scrollbar,
3910  /// 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.
3911  ThreeDDarkShadow,
3912  /// The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border. Same as ButtonFace.
3913  ThreeDFace,
3914  /// 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.
3915  ThreeDHighlight,
3916  /// 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.
3917  ThreeDLightShadow,
3918  /// 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.
3919  ThreeDShadow,
3920  /// Window background. Same as Canvas.
3921  Window,
3922  /// Window frame. Same as ButtonBorder.
3923  WindowFrame,
3924  /// Text in windows. Same as CanvasText.
3925  WindowText,
3926}
3927
3928impl IsCompatible for SystemColor {
3929  fn is_compatible(&self, browsers: Browsers) -> bool {
3930    use SystemColor::*;
3931    match self {
3932      AccentColor | AccentColorText => Feature::AccentSystemColor.is_compatible(browsers),
3933      _ => true,
3934    }
3935  }
3936}