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