1#![allow(non_upper_case_globals)]
4
5use super::angle::Angle;
6use super::calc::Calc;
7use super::number::CSSNumber;
8use super::percentage::Percentage;
9use crate::compat::Feature;
10use crate::error::{ParserError, PrinterError};
11use crate::macros::enum_property;
12use crate::printer::Printer;
13use crate::properties::PropertyId;
14use crate::rules::supports::SupportsCondition;
15use crate::targets::{should_compile, Browsers, Targets};
16use crate::traits::{FallbackValues, IsCompatible, Parse, ToCss};
17#[cfg(feature = "visitor")]
18use crate::visitor::{Visit, VisitTypes, Visitor};
19use bitflags::bitflags;
20use cssparser::color::{parse_hash_color, parse_named_color};
21use cssparser::*;
22use cssparser_color::{hsl_to_rgb, AngleOrNumber, ColorParser, NumberOrPercentage};
23use std::any::TypeId;
24use std::f32::consts::PI;
25use std::fmt::Write;
26
27#[derive(Debug, Clone, PartialEq)]
37#[cfg_attr(feature = "visitor", derive(Visit))]
38#[cfg_attr(feature = "visitor", visit(visit_color, COLORS))]
39#[cfg_attr(
40 feature = "serde",
41 derive(serde::Serialize, serde::Deserialize),
42 serde(untagged, rename_all = "lowercase")
43)]
44#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
45#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
46pub enum CssColor {
47 #[cfg_attr(feature = "serde", serde(with = "CurrentColor"))]
49 CurrentColor,
50 #[cfg_attr(
52 feature = "serde",
53 serde(serialize_with = "serialize_rgba", deserialize_with = "deserialize_rgba")
54 )]
55 #[cfg_attr(feature = "jsonschema", schemars(with = "RGBColor"))]
56 RGBA(RGBA),
57 LAB(Box<LABColor>),
59 Predefined(Box<PredefinedColor>),
61 Float(Box<FloatColor>),
63 #[cfg_attr(feature = "visitor", skip_type)]
65 #[cfg_attr(feature = "serde", serde(with = "LightDark"))]
66 LightDark(Box<CssColor>, Box<CssColor>),
67 System(SystemColor),
69}
70
71#[cfg(feature = "serde")]
72#[derive(serde::Serialize, serde::Deserialize)]
73#[serde(tag = "type", rename_all = "lowercase")]
74#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
75enum CurrentColor {
76 CurrentColor,
77}
78
79#[cfg(feature = "serde")]
80impl CurrentColor {
81 fn serialize<S>(serializer: S) -> Result<S::Ok, S::Error>
82 where
83 S: serde::Serializer,
84 {
85 serde::Serialize::serialize(&CurrentColor::CurrentColor, serializer)
86 }
87
88 fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error>
89 where
90 D: serde::Deserializer<'de>,
91 {
92 use serde::Deserialize;
93 let _: CurrentColor = Deserialize::deserialize(deserializer)?;
94 Ok(())
95 }
96}
97
98#[cfg(feature = "serde")]
100#[derive(serde::Serialize, serde::Deserialize)]
101#[serde(tag = "type", rename_all = "lowercase")]
102#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
103enum RGBColor {
104 RGB(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#[cfg(feature = "serde")]
129#[derive(serde::Serialize, serde::Deserialize)]
130#[serde(tag = "type", rename_all = "kebab-case")]
131#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
132enum LightDark {
133 LightDark { light: CssColor, dark: CssColor },
134}
135
136#[cfg(feature = "serde")]
137impl<'de> LightDark {
138 pub fn serialize<S>(light: &Box<CssColor>, dark: &Box<CssColor>, serializer: S) -> Result<S::Ok, S::Error>
139 where
140 S: serde::Serializer,
141 {
142 let wrapper = LightDark::LightDark {
143 light: (**light).clone(),
144 dark: (**dark).clone(),
145 };
146 serde::Serialize::serialize(&wrapper, serializer)
147 }
148
149 pub fn deserialize<D>(deserializer: D) -> Result<(Box<CssColor>, Box<CssColor>), D::Error>
150 where
151 D: serde::Deserializer<'de>,
152 {
153 let v: LightDark = serde::Deserialize::deserialize(deserializer)?;
154 match v {
155 LightDark::LightDark { light, dark } => Ok((Box::new(light), Box::new(dark))),
156 }
157 }
158}
159
160#[derive(Debug, Clone, Copy, PartialEq)]
162#[cfg_attr(feature = "visitor", derive(Visit))]
163#[cfg_attr(
164 feature = "serde",
165 derive(serde::Serialize, serde::Deserialize),
166 serde(tag = "type", rename_all = "lowercase")
167)]
168#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
169pub enum LABColor {
170 LAB(LAB),
172 LCH(LCH),
174 OKLAB(OKLAB),
176 OKLCH(OKLCH),
178}
179
180#[derive(Debug, Clone, Copy, PartialEq)]
182#[cfg_attr(feature = "visitor", derive(Visit))]
183#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type"))]
184#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
185pub enum PredefinedColor {
186 #[cfg_attr(feature = "serde", serde(rename = "srgb"))]
188 SRGB(SRGB),
189 #[cfg_attr(feature = "serde", serde(rename = "srgb-linear"))]
191 SRGBLinear(SRGBLinear),
192 #[cfg_attr(feature = "serde", serde(rename = "display-p3"))]
194 DisplayP3(P3),
195 #[cfg_attr(feature = "serde", serde(rename = "a98-rgb"))]
197 A98(A98),
198 #[cfg_attr(feature = "serde", serde(rename = "prophoto-rgb"))]
200 ProPhoto(ProPhoto),
201 #[cfg_attr(feature = "serde", serde(rename = "rec2020"))]
203 Rec2020(Rec2020),
204 #[cfg_attr(feature = "serde", serde(rename = "xyz-d50"))]
206 XYZd50(XYZd50),
207 #[cfg_attr(feature = "serde", serde(rename = "xyz-d65"))]
209 XYZd65(XYZd65),
210}
211
212#[derive(Debug, Clone, Copy, PartialEq)]
216#[cfg_attr(feature = "visitor", derive(Visit))]
217#[cfg_attr(
218 feature = "serde",
219 derive(serde::Serialize, serde::Deserialize),
220 serde(tag = "type", rename_all = "lowercase")
221)]
222#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
223pub enum FloatColor {
224 RGB(SRGB),
226 HSL(HSL),
228 HWB(HWB),
230}
231
232bitflags! {
233 #[derive(PartialEq, Eq, Clone, Copy)]
235 pub struct ColorFallbackKind: u8 {
236 const RGB = 0b01;
238 const P3 = 0b10;
240 const LAB = 0b100;
242 const OKLAB = 0b1000;
244 }
245}
246
247enum_property! {
248 enum ColorSpaceName {
251 "srgb": SRGB,
252 "srgb-linear": SRGBLinear,
253 "lab": LAB,
254 "oklab": OKLAB,
255 "xyz": XYZ,
256 "xyz-d50": XYZd50,
257 "xyz-d65": XYZd65,
258 "hsl": Hsl,
259 "hwb": Hwb,
260 "lch": LCH,
261 "oklch": OKLCH,
262 }
263}
264
265enum_property! {
266 pub enum HueInterpolationMethod {
269 Shorter,
271 Longer,
273 Increasing,
275 Decreasing,
277 Specified,
279 }
280}
281
282impl ColorFallbackKind {
283 pub(crate) fn lowest(&self) -> ColorFallbackKind {
284 *self & ColorFallbackKind::from_bits_truncate(self.bits().wrapping_neg())
286 }
287
288 pub(crate) fn highest(&self) -> ColorFallbackKind {
289 if self.is_empty() {
291 return ColorFallbackKind::empty();
292 }
293
294 let zeros = 7 - self.bits().leading_zeros();
295 ColorFallbackKind::from_bits_truncate(1 << zeros)
296 }
297
298 pub(crate) fn and_below(&self) -> ColorFallbackKind {
299 if self.is_empty() {
300 return ColorFallbackKind::empty();
301 }
302
303 *self | ColorFallbackKind::from_bits_truncate(self.bits() - 1)
304 }
305
306 pub(crate) fn supports_condition<'i>(&self) -> SupportsCondition<'i> {
307 let s = match *self {
308 ColorFallbackKind::P3 => "color(display-p3 0 0 0)",
309 ColorFallbackKind::LAB => "lab(0% 0 0)",
310 _ => unreachable!(),
311 };
312
313 SupportsCondition::Declaration {
314 property_id: PropertyId::Color,
315 value: s.into(),
316 }
317 }
318}
319
320impl CssColor {
321 pub fn current_color() -> CssColor {
323 CssColor::CurrentColor
324 }
325
326 pub fn transparent() -> CssColor {
328 CssColor::RGBA(RGBA::transparent())
329 }
330
331 pub fn to_rgb(&self) -> Result<CssColor, ()> {
333 match self {
334 CssColor::LightDark(light, dark) => {
335 Ok(CssColor::LightDark(Box::new(light.to_rgb()?), Box::new(dark.to_rgb()?)))
336 }
337 _ => Ok(RGBA::try_from(self)?.into()),
338 }
339 }
340
341 pub fn to_lab(&self) -> Result<CssColor, ()> {
343 match self {
344 CssColor::LightDark(light, dark) => {
345 Ok(CssColor::LightDark(Box::new(light.to_lab()?), Box::new(dark.to_lab()?)))
346 }
347 _ => Ok(LAB::try_from(self)?.into()),
348 }
349 }
350
351 pub fn to_p3(&self) -> Result<CssColor, ()> {
353 match self {
354 CssColor::LightDark(light, dark) => {
355 Ok(CssColor::LightDark(Box::new(light.to_p3()?), Box::new(dark.to_p3()?)))
356 }
357 _ => Ok(P3::try_from(self)?.into()),
358 }
359 }
360
361 pub(crate) fn get_possible_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
362 let mut fallbacks = match self {
366 CssColor::CurrentColor | CssColor::RGBA(_) | CssColor::Float(..) | CssColor::System(..) => {
367 return ColorFallbackKind::empty()
368 }
369 CssColor::LAB(lab) => match &**lab {
370 LABColor::LAB(..) | LABColor::LCH(..) if should_compile!(targets, LabColors) => {
371 ColorFallbackKind::LAB.and_below()
372 }
373 LABColor::OKLAB(..) | LABColor::OKLCH(..) if should_compile!(targets, OklabColors) => {
374 ColorFallbackKind::OKLAB.and_below()
375 }
376 _ => return ColorFallbackKind::empty(),
377 },
378 CssColor::Predefined(predefined) => match &**predefined {
379 PredefinedColor::DisplayP3(..) if should_compile!(targets, P3Colors) => ColorFallbackKind::P3.and_below(),
380 _ if should_compile!(targets, ColorFunction) => ColorFallbackKind::LAB.and_below(),
381 _ => return ColorFallbackKind::empty(),
382 },
383 CssColor::LightDark(light, dark) => {
384 return light.get_possible_fallbacks(targets) | dark.get_possible_fallbacks(targets);
385 }
386 };
387
388 if fallbacks.contains(ColorFallbackKind::OKLAB) {
389 if !should_compile!(targets, OklabColors) {
390 fallbacks.remove(ColorFallbackKind::LAB.and_below());
391 }
392 }
393
394 if fallbacks.contains(ColorFallbackKind::LAB) {
395 if !should_compile!(targets, LabColors) {
396 fallbacks.remove(ColorFallbackKind::P3.and_below());
397 } else if targets
398 .browsers
399 .map(|targets| Feature::LabColors.is_partially_compatible(targets))
400 .unwrap_or(false)
401 {
402 fallbacks.remove(ColorFallbackKind::P3);
405 }
406 }
407
408 if fallbacks.contains(ColorFallbackKind::P3) {
409 if !should_compile!(targets, P3Colors) {
410 fallbacks.remove(ColorFallbackKind::RGB);
411 } else if fallbacks.highest() != ColorFallbackKind::P3
412 && !targets
413 .browsers
414 .map(|targets| Feature::P3Colors.is_partially_compatible(targets))
415 .unwrap_or(false)
416 {
417 fallbacks.remove(ColorFallbackKind::P3);
420 }
421 }
422
423 fallbacks
424 }
425
426 pub fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
428 let fallbacks = self.get_possible_fallbacks(targets);
431 fallbacks - fallbacks.highest()
432 }
433
434 pub fn get_fallback(&self, kind: ColorFallbackKind) -> CssColor {
436 if matches!(self, CssColor::RGBA(_)) {
437 return self.clone();
438 }
439
440 match kind {
441 ColorFallbackKind::RGB => self.to_rgb().unwrap(),
442 ColorFallbackKind::P3 => self.to_p3().unwrap(),
443 ColorFallbackKind::LAB => self.to_lab().unwrap(),
444 _ => unreachable!(),
445 }
446 }
447}
448
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 should_compile!(dest.targets, HexAlphaColors) {
546 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 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 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
621fn compact_hex(v: u32) -> u32 {
624 return ((v & 0x0FF00000) >> 12) | ((v & 0x00000FF0) >> 4);
625}
626
627fn 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 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
985fn 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#[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 input.parse_nested_block(|input| {
1073 parser.parse_relative::<T, _, _>(input, |input, parser| {
1074 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#[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 input.parse_nested_block(|input| {
1095 parser.parse_relative::<T, _, _>(input, |input, parser| {
1096 if let Some(from) = &mut parser.from {
1097 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 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 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#[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 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 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 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 #[derive(PartialEq, Eq, Clone, Copy)]
1444 pub struct ChannelType: u8 {
1445 const Percentage = 0b001;
1447 const Angle = 0b010;
1449 const Number = 0b100;
1451 }
1452}
1453
1454pub trait ColorSpace {
1456 fn components(&self) -> (f32, f32, f32, f32);
1458 fn channels(&self) -> (&'static str, &'static str, &'static str);
1460 fn types(&self) -> (ChannelType, ChannelType, ChannelType);
1462 fn resolve_missing(&self) -> Self;
1464 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 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 pub struct SRGB {
1534 #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_rgb_component", deserialize_with = "deserialize_rgb_component"))]
1536 r: Percentage,
1537 #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_rgb_component", deserialize_with = "deserialize_rgb_component"))]
1539 g: Percentage,
1540 #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_rgb_component", deserialize_with = "deserialize_rgb_component"))]
1542 b: Percentage
1543 }
1544}
1545
1546#[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#[derive(Clone, Copy, PartialEq, Debug)]
1573pub struct RGBA {
1574 pub red: u8,
1576 pub green: u8,
1578 pub blue: u8,
1580 pub alpha: u8,
1582}
1583
1584impl RGBA {
1585 #[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 #[inline]
1595 pub fn transparent() -> Self {
1596 Self::new(0, 0, 0, 0.0)
1597 }
1598
1599 #[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 #[inline]
1612 pub fn red_f32(&self) -> f32 {
1613 self.red as f32 / 255.0
1614 }
1615
1616 #[inline]
1618 pub fn green_f32(&self) -> f32 {
1619 self.green as f32 / 255.0
1620 }
1621
1622 #[inline]
1624 pub fn blue_f32(&self) -> f32 {
1625 self.blue as f32 / 255.0
1626 }
1627
1628 #[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 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 pub struct SRGBLinear {
1660 r: Percentage,
1662 g: Percentage,
1664 b: Percentage
1666 }
1667}
1668
1669define_colorspace! {
1670 pub struct P3 {
1672 r: Percentage,
1674 g: Percentage,
1676 b: Percentage
1678 }
1679}
1680
1681define_colorspace! {
1682 pub struct A98 {
1684 r: Percentage,
1686 g: Percentage,
1688 b: Percentage
1690 }
1691}
1692
1693define_colorspace! {
1694 pub struct ProPhoto {
1696 r: Percentage,
1698 g: Percentage,
1700 b: Percentage
1702 }
1703}
1704
1705define_colorspace! {
1706 pub struct Rec2020 {
1708 r: Percentage,
1710 g: Percentage,
1712 b: Percentage
1714 }
1715}
1716
1717define_colorspace! {
1718 pub struct LAB {
1720 l: Percentage,
1722 a: Number,
1724 b: Number
1726 }
1727}
1728
1729define_colorspace! {
1730 pub struct LCH {
1732 l: Percentage,
1734 c: Number,
1736 h: Angle
1738 }
1739}
1740
1741define_colorspace! {
1742 pub struct OKLAB {
1744 l: Percentage,
1746 a: Number,
1748 b: Number
1750 }
1751}
1752
1753define_colorspace! {
1754 pub struct OKLCH {
1756 l: Percentage,
1758 c: Number,
1760 h: Angle
1762 }
1763}
1764
1765define_colorspace! {
1766 pub struct XYZd50 {
1768 x: Percentage,
1770 y: Percentage,
1772 z: Percentage
1774 }
1775}
1776
1777define_colorspace! {
1778 pub struct XYZd65 {
1780 x: Percentage,
1782 y: Percentage,
1784 z: Percentage
1786 }
1787}
1788
1789define_colorspace! {
1790 pub struct HSL {
1792 h: Angle,
1794 s: Percentage,
1796 l: Percentage
1798 }
1799}
1800
1801define_colorspace! {
1802 pub struct HWB {
1804 h: Angle,
1806 w: Percentage,
1808 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 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 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 const K: f32 = 24389.0 / 27.0; const E: f32 = 216.0 / 24389.0; let lab = lab.resolve_missing();
1914 let l = lab.l * 100.0;
1915 let a = lab.a;
1916 let b = lab.b;
1917
1918 let f1 = (l + 16.0) / 116.0;
1920 let f0 = a / 500.0 + f1;
1921 let f2 = f1 - b / 200.0;
1922
1923 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 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 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 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 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 #[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 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 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 const E: f32 = 216.0 / 24389.0; const K: f32 = 24389.0 / 27.0; 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 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 #[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 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 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); 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 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 #[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 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 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 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 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 #[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 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 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 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 #[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 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 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 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 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 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 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
2722via!(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
2813via!(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 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
2988pub trait ColorGamut {
2990 fn in_gamut(&self) -> bool;
2992 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 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 let mut current: OKLCH = color.into();
3089
3090 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 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 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 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 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 if converted_first {
3289 first_color.adjust_powerless_components();
3290 }
3291
3292 if converted_second {
3293 second_color.adjust_powerless_components();
3294 }
3295
3296 first_color.fill_missing_components(&second_color);
3298 second_color.fill_missing_components(&first_color);
3299
3300 first_color.adjust_hue(&mut second_color, method);
3302
3303 first_color.premultiply();
3305 second_color.premultiply();
3306
3307 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
3324pub trait Interpolate {
3326 fn adjust_powerless_components(&mut self) {}
3328 fn fill_missing_components(&mut self, other: &Self);
3330 fn adjust_hue(&mut self, _: &mut Self, _: HueInterpolationMethod) {}
3332 fn premultiply(&mut self);
3334 fn unpremultiply(&mut self, alpha_multiplier: f32);
3336 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 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 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 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 (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 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 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 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 if *b < *a {
3560 *b += 360.0;
3561 }
3562 }
3563 HueInterpolationMethod::Decreasing => {
3564 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))]
3593pub enum SystemColor {
3595 AccentColor,
3597 AccentColorText,
3599 ActiveText,
3601 ButtonBorder,
3603 ButtonFace,
3605 ButtonText,
3607 Canvas,
3609 CanvasText,
3611 Field,
3613 FieldText,
3615 GrayText,
3617 Highlight,
3619 HighlightText,
3621 LinkText,
3623 Mark,
3625 MarkText,
3627 SelectedItem,
3629 SelectedItemText,
3631 VisitedText,
3633
3634 ActiveBorder,
3637 ActiveCaption,
3639 AppWorkspace,
3641 Background,
3643 ButtonHighlight,
3645 ButtonShadow,
3647 CaptionText,
3649 InactiveBorder,
3651 InactiveCaption,
3653 InactiveCaptionText,
3655 InfoBackground,
3657 InfoText,
3659 Menu,
3661 MenuText,
3663 Scrollbar,
3665 ThreeDDarkShadow,
3667 ThreeDFace,
3669 ThreeDHighlight,
3671 ThreeDLightShadow,
3673 ThreeDShadow,
3675 Window,
3677 WindowFrame,
3679 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}