1#![deny(missing_docs)]
6
7#[cfg(test)]
12mod tests;
13
14use cssparser::color::{
15 clamp_floor_256_f32, clamp_unit_f32, parse_hash_color, serialize_color_alpha,
16 PredefinedColorSpace, OPAQUE,
17};
18use cssparser::{match_ignore_ascii_case, CowRcStr, ParseError, Parser, ToCss, Token};
19use std::f32::consts::PI;
20use std::fmt;
21
22#[allow(clippy::result_unit_err)]
28#[inline]
29pub fn parse_color_keyword<Output>(ident: &str) -> Result<Output, ()>
30where
31 Output: FromParsedColor,
32{
33 Ok(match_ignore_ascii_case! { ident ,
34 "transparent" => Output::from_rgba(0, 0, 0, 0.0),
35 "currentcolor" => Output::from_current_color(),
36 _ => {
37 let (r, g, b) = cssparser::color::parse_named_color(ident)?;
38 Output::from_rgba(r, g, b, OPAQUE)
39 }
40 })
41}
42
43pub fn parse_color_with<'i, 't, P>(
46 color_parser: &P,
47 input: &mut Parser<'i, 't>,
48) -> Result<P::Output, ParseError<'i, P::Error>>
49where
50 P: ColorParser<'i>,
51{
52 let location = input.current_source_location();
53 let token = input.next()?;
54 match *token {
55 Token::Hash(ref value) | Token::IDHash(ref value) => {
56 parse_hash_color(value.as_bytes()).map(|(r, g, b, a)| P::Output::from_rgba(r, g, b, a))
57 }
58 Token::Ident(ref value) => parse_color_keyword(value),
59 Token::Function(ref name) => {
60 let name = name.clone();
61 return input.parse_nested_block(|arguments| {
62 parse_color_function(color_parser, name, arguments)
63 });
64 }
65 _ => Err(()),
66 }
67 .map_err(|()| location.new_unexpected_token_error(token.clone()))
68}
69
70#[inline]
72fn parse_color_function<'i, 't, P>(
73 color_parser: &P,
74 name: CowRcStr<'i>,
75 arguments: &mut Parser<'i, 't>,
76) -> Result<P::Output, ParseError<'i, P::Error>>
77where
78 P: ColorParser<'i>,
79{
80 let color = match_ignore_ascii_case! { &name,
81 "rgb" | "rgba" => parse_rgb(color_parser, arguments),
82
83 "hsl" | "hsla" => parse_hsl(color_parser, arguments),
84
85 "hwb" => parse_hwb(color_parser, arguments),
86
87 "lab" => parse_lab_like(color_parser, arguments, 100.0, 125.0, P::Output::from_lab),
90
91 "lch" => parse_lch_like(color_parser, arguments, 100.0, 150.0, P::Output::from_lch),
94
95 "oklab" => parse_lab_like(color_parser, arguments, 1.0, 0.4, P::Output::from_oklab),
98
99 "oklch" => parse_lch_like(color_parser, arguments, 1.0, 0.4, P::Output::from_oklch),
102
103 "color" => parse_color_with_color_space(color_parser, arguments),
104
105 _ => return Err(arguments.new_unexpected_token_error(Token::Ident(name))),
106 }?;
107
108 arguments.expect_exhausted()?;
109
110 Ok(color)
111}
112
113#[inline]
116fn parse_alpha_component<'i, 't, P>(
117 color_parser: &P,
118 arguments: &mut Parser<'i, 't>,
119) -> Result<f32, ParseError<'i, P::Error>>
120where
121 P: ColorParser<'i>,
122{
123 Ok(color_parser
124 .parse_number_or_percentage(arguments)?
125 .unit_value()
126 .clamp(0.0, OPAQUE))
127}
128
129fn parse_legacy_alpha<'i, 't, P>(
130 color_parser: &P,
131 arguments: &mut Parser<'i, 't>,
132) -> Result<f32, ParseError<'i, P::Error>>
133where
134 P: ColorParser<'i>,
135{
136 Ok(if !arguments.is_exhausted() {
137 arguments.expect_comma()?;
138 parse_alpha_component(color_parser, arguments)?
139 } else {
140 OPAQUE
141 })
142}
143
144fn parse_modern_alpha<'i, 't, P>(
145 color_parser: &P,
146 arguments: &mut Parser<'i, 't>,
147) -> Result<Option<f32>, ParseError<'i, P::Error>>
148where
149 P: ColorParser<'i>,
150{
151 if !arguments.is_exhausted() {
152 arguments.expect_delim('/')?;
153 parse_none_or(arguments, |p| parse_alpha_component(color_parser, p))
154 } else {
155 Ok(Some(OPAQUE))
156 }
157}
158
159#[inline]
160fn parse_rgb<'i, 't, P>(
161 color_parser: &P,
162 arguments: &mut Parser<'i, 't>,
163) -> Result<P::Output, ParseError<'i, P::Error>>
164where
165 P: ColorParser<'i>,
166{
167 let maybe_red = parse_none_or(arguments, |p| color_parser.parse_number_or_percentage(p))?;
168
169 let is_legacy_syntax = maybe_red.is_some() && arguments.try_parse(|p| p.expect_comma()).is_ok();
172
173 let (red, green, blue, alpha) = if is_legacy_syntax {
174 let (red, green, blue) = match maybe_red.unwrap() {
175 NumberOrPercentage::Number { value } => {
176 let red = clamp_floor_256_f32(value);
177 let green = clamp_floor_256_f32(color_parser.parse_number(arguments)?);
178 arguments.expect_comma()?;
179 let blue = clamp_floor_256_f32(color_parser.parse_number(arguments)?);
180 (red, green, blue)
181 }
182 NumberOrPercentage::Percentage { unit_value } => {
183 let red = clamp_unit_f32(unit_value);
184 let green = clamp_unit_f32(color_parser.parse_percentage(arguments)?);
185 arguments.expect_comma()?;
186 let blue = clamp_unit_f32(color_parser.parse_percentage(arguments)?);
187 (red, green, blue)
188 }
189 };
190
191 let alpha = parse_legacy_alpha(color_parser, arguments)?;
192
193 (red, green, blue, alpha)
194 } else {
195 #[inline]
196 fn get_component_value(c: Option<NumberOrPercentage>) -> u8 {
197 c.map(|c| match c {
198 NumberOrPercentage::Number { value } => clamp_floor_256_f32(value),
199 NumberOrPercentage::Percentage { unit_value } => clamp_unit_f32(unit_value),
200 })
201 .unwrap_or(0)
202 }
203
204 let red = get_component_value(maybe_red);
205
206 let green = get_component_value(parse_none_or(arguments, |p| {
207 color_parser.parse_number_or_percentage(p)
208 })?);
209
210 let blue = get_component_value(parse_none_or(arguments, |p| {
211 color_parser.parse_number_or_percentage(p)
212 })?);
213
214 let alpha = parse_modern_alpha(color_parser, arguments)?.unwrap_or(0.0);
215
216 (red, green, blue, alpha)
217 };
218
219 Ok(P::Output::from_rgba(red, green, blue, alpha))
220}
221
222#[inline]
226fn parse_hsl<'i, 't, P>(
227 color_parser: &P,
228 arguments: &mut Parser<'i, 't>,
229) -> Result<P::Output, ParseError<'i, P::Error>>
230where
231 P: ColorParser<'i>,
232{
233 let maybe_hue = parse_none_or(arguments, |p| color_parser.parse_angle_or_number(p))?;
234
235 let is_legacy_syntax = maybe_hue.is_some() && arguments.try_parse(|p| p.expect_comma()).is_ok();
238
239 let saturation: Option<f32>;
240 let lightness: Option<f32>;
241
242 let alpha = if is_legacy_syntax {
243 saturation = Some(color_parser.parse_percentage(arguments)?);
244 arguments.expect_comma()?;
245 lightness = Some(color_parser.parse_percentage(arguments)?);
246 Some(parse_legacy_alpha(color_parser, arguments)?)
247 } else {
248 saturation = parse_none_or(arguments, |p| color_parser.parse_percentage(p))?;
249 lightness = parse_none_or(arguments, |p| color_parser.parse_percentage(p))?;
250
251 parse_modern_alpha(color_parser, arguments)?
252 };
253
254 let hue = maybe_hue.map(|h| normalize_hue(h.degrees()));
255 let saturation = saturation.map(|s| s.clamp(0.0, 1.0));
256 let lightness = lightness.map(|l| l.clamp(0.0, 1.0));
257
258 Ok(P::Output::from_hsl(hue, saturation, lightness, alpha))
259}
260
261#[inline]
265fn parse_hwb<'i, 't, P>(
266 color_parser: &P,
267 arguments: &mut Parser<'i, 't>,
268) -> Result<P::Output, ParseError<'i, P::Error>>
269where
270 P: ColorParser<'i>,
271{
272 let (hue, whiteness, blackness, alpha) = parse_components(
273 color_parser,
274 arguments,
275 P::parse_angle_or_number,
276 P::parse_percentage,
277 P::parse_percentage,
278 )?;
279
280 let hue = hue.map(|h| normalize_hue(h.degrees()));
281 let whiteness = whiteness.map(|w| w.clamp(0.0, 1.0));
282 let blackness = blackness.map(|b| b.clamp(0.0, 1.0));
283
284 Ok(P::Output::from_hwb(hue, whiteness, blackness, alpha))
285}
286
287#[inline]
289pub fn hwb_to_rgb(h: f32, w: f32, b: f32) -> (f32, f32, f32) {
290 if w + b >= 1.0 {
291 let gray = w / (w + b);
292 return (gray, gray, gray);
293 }
294
295 let (mut red, mut green, mut blue) = hsl_to_rgb(h, 1.0, 0.5);
297 let x = 1.0 - w - b;
298 red = red * x + w;
299 green = green * x + w;
300 blue = blue * x + w;
301 (red, green, blue)
302}
303
304#[inline]
307pub fn hsl_to_rgb(hue: f32, saturation: f32, lightness: f32) -> (f32, f32, f32) {
308 debug_assert!((0.0..=1.0).contains(&hue));
309
310 fn hue_to_rgb(m1: f32, m2: f32, mut h3: f32) -> f32 {
311 if h3 < 0. {
312 h3 += 3.
313 }
314 if h3 > 3. {
315 h3 -= 3.
316 }
317 if h3 * 2. < 1. {
318 m1 + (m2 - m1) * h3 * 2.
319 } else if h3 * 2. < 3. {
320 m2
321 } else if h3 < 2. {
322 m1 + (m2 - m1) * (2. - h3) * 2.
323 } else {
324 m1
325 }
326 }
327 let m2 = if lightness <= 0.5 {
328 lightness * (saturation + 1.)
329 } else {
330 lightness + saturation - lightness * saturation
331 };
332 let m1 = lightness * 2. - m2;
333 let hue_times_3 = hue * 3.;
334 let red = hue_to_rgb(m1, m2, hue_times_3 + 1.);
335 let green = hue_to_rgb(m1, m2, hue_times_3);
336 let blue = hue_to_rgb(m1, m2, hue_times_3 - 1.);
337 (red, green, blue)
338}
339
340type IntoColorFn<Output> =
341 fn(l: Option<f32>, a: Option<f32>, b: Option<f32>, alpha: Option<f32>) -> Output;
342
343#[inline]
344fn parse_lab_like<'i, 't, P>(
345 color_parser: &P,
346 arguments: &mut Parser<'i, 't>,
347 lightness_range: f32,
348 a_b_range: f32,
349 into_color: IntoColorFn<P::Output>,
350) -> Result<P::Output, ParseError<'i, P::Error>>
351where
352 P: ColorParser<'i>,
353{
354 let (lightness, a, b, alpha) = parse_components(
355 color_parser,
356 arguments,
357 P::parse_number_or_percentage,
358 P::parse_number_or_percentage,
359 P::parse_number_or_percentage,
360 )?;
361
362 let lightness = lightness.map(|l| l.value(lightness_range));
363 let a = a.map(|a| a.value(a_b_range));
364 let b = b.map(|b| b.value(a_b_range));
365
366 Ok(into_color(lightness, a, b, alpha))
367}
368
369#[inline]
370fn parse_lch_like<'i, 't, P>(
371 color_parser: &P,
372 arguments: &mut Parser<'i, 't>,
373 lightness_range: f32,
374 chroma_range: f32,
375 into_color: IntoColorFn<P::Output>,
376) -> Result<P::Output, ParseError<'i, P::Error>>
377where
378 P: ColorParser<'i>,
379{
380 let (lightness, chroma, hue, alpha) = parse_components(
381 color_parser,
382 arguments,
383 P::parse_number_or_percentage,
384 P::parse_number_or_percentage,
385 P::parse_angle_or_number,
386 )?;
387
388 let lightness = lightness.map(|l| l.value(lightness_range));
389 let chroma = chroma.map(|c| c.value(chroma_range));
390 let hue = hue.map(|h| normalize_hue(h.degrees()));
391
392 Ok(into_color(lightness, chroma, hue, alpha))
393}
394
395#[inline]
397fn parse_color_with_color_space<'i, 't, P>(
398 color_parser: &P,
399 arguments: &mut Parser<'i, 't>,
400) -> Result<P::Output, ParseError<'i, P::Error>>
401where
402 P: ColorParser<'i>,
403{
404 let color_space = PredefinedColorSpace::parse(arguments)?;
405
406 let (c1, c2, c3, alpha) = parse_components(
407 color_parser,
408 arguments,
409 P::parse_number_or_percentage,
410 P::parse_number_or_percentage,
411 P::parse_number_or_percentage,
412 )?;
413
414 let c1 = c1.map(|c| c.unit_value());
415 let c2 = c2.map(|c| c.unit_value());
416 let c3 = c3.map(|c| c.unit_value());
417
418 Ok(P::Output::from_color_function(
419 color_space,
420 c1,
421 c2,
422 c3,
423 alpha,
424 ))
425}
426
427type ComponentParseResult<'i, R1, R2, R3, Error> =
428 Result<(Option<R1>, Option<R2>, Option<R3>, Option<f32>), ParseError<'i, Error>>;
429
430pub fn parse_components<'i, 't, P, F1, F2, F3, R1, R2, R3>(
432 color_parser: &P,
433 input: &mut Parser<'i, 't>,
434 f1: F1,
435 f2: F2,
436 f3: F3,
437) -> ComponentParseResult<'i, R1, R2, R3, P::Error>
438where
439 P: ColorParser<'i>,
440 F1: FnOnce(&P, &mut Parser<'i, 't>) -> Result<R1, ParseError<'i, P::Error>>,
441 F2: FnOnce(&P, &mut Parser<'i, 't>) -> Result<R2, ParseError<'i, P::Error>>,
442 F3: FnOnce(&P, &mut Parser<'i, 't>) -> Result<R3, ParseError<'i, P::Error>>,
443{
444 let r1 = parse_none_or(input, |p| f1(color_parser, p))?;
445 let r2 = parse_none_or(input, |p| f2(color_parser, p))?;
446 let r3 = parse_none_or(input, |p| f3(color_parser, p))?;
447
448 let alpha = parse_modern_alpha(color_parser, input)?;
449
450 Ok((r1, r2, r3, alpha))
451}
452
453fn parse_none_or<'i, 't, F, T, E>(input: &mut Parser<'i, 't>, thing: F) -> Result<Option<T>, E>
454where
455 F: FnOnce(&mut Parser<'i, 't>) -> Result<T, E>,
456{
457 match input.try_parse(|p| p.expect_ident_matching("none")) {
458 Ok(_) => Ok(None),
459 Err(_) => Ok(Some(thing(input)?)),
460 }
461}
462
463struct ModernComponent<'a>(&'a Option<f32>);
466
467impl<'a> ToCss for ModernComponent<'a> {
468 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
469 where
470 W: fmt::Write,
471 {
472 if let Some(value) = self.0 {
473 if value.is_finite() {
474 value.to_css(dest)
475 } else if value.is_nan() {
476 dest.write_str("calc(NaN)")
477 } else {
478 debug_assert!(value.is_infinite());
479 if value.is_sign_negative() {
480 dest.write_str("calc(-infinity)")
481 } else {
482 dest.write_str("calc(infinity)")
483 }
484 }
485 } else {
486 dest.write_str("none")
487 }
488 }
489}
490
491fn normalize_hue(hue: f32) -> f32 {
493 hue - 360.0 * (hue / 360.0).floor()
496}
497
498#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
500#[derive(Clone, Copy, PartialEq, Debug)]
501pub struct RgbaLegacy {
502 pub red: u8,
504 pub green: u8,
506 pub blue: u8,
508 pub alpha: f32,
510}
511
512impl RgbaLegacy {
513 #[inline]
517 pub fn from_floats(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
518 Self::new(
519 clamp_unit_f32(red),
520 clamp_unit_f32(green),
521 clamp_unit_f32(blue),
522 alpha.clamp(0.0, OPAQUE),
523 )
524 }
525
526 #[inline]
528 pub const fn new(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
529 Self {
530 red,
531 green,
532 blue,
533 alpha,
534 }
535 }
536}
537
538impl ToCss for RgbaLegacy {
539 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
540 where
541 W: fmt::Write,
542 {
543 let has_alpha = self.alpha != OPAQUE;
544
545 dest.write_str(if has_alpha { "rgba(" } else { "rgb(" })?;
546 self.red.to_css(dest)?;
547 dest.write_str(", ")?;
548 self.green.to_css(dest)?;
549 dest.write_str(", ")?;
550 self.blue.to_css(dest)?;
551
552 serialize_color_alpha(dest, Some(self.alpha), true)?;
554
555 dest.write_char(')')
556 }
557}
558
559#[derive(Clone, Copy, PartialEq, Debug)]
561#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
562pub struct Hsl {
563 pub hue: Option<f32>,
565 pub saturation: Option<f32>,
567 pub lightness: Option<f32>,
569 pub alpha: Option<f32>,
571}
572
573impl Hsl {
574 pub fn new(
576 hue: Option<f32>,
577 saturation: Option<f32>,
578 lightness: Option<f32>,
579 alpha: Option<f32>,
580 ) -> Self {
581 Self {
582 hue,
583 saturation,
584 lightness,
585 alpha,
586 }
587 }
588}
589
590impl ToCss for Hsl {
591 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
592 where
593 W: fmt::Write,
594 {
595 let (red, green, blue) = hsl_to_rgb(
597 self.hue.unwrap_or(0.0) / 360.0,
598 self.saturation.unwrap_or(0.0),
599 self.lightness.unwrap_or(0.0),
600 );
601
602 RgbaLegacy::from_floats(red, green, blue, self.alpha.unwrap_or(OPAQUE)).to_css(dest)
603 }
604}
605
606#[derive(Clone, Copy, PartialEq, Debug)]
608#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
609pub struct Hwb {
610 pub hue: Option<f32>,
612 pub whiteness: Option<f32>,
614 pub blackness: Option<f32>,
616 pub alpha: Option<f32>,
618}
619
620impl Hwb {
621 pub fn new(
623 hue: Option<f32>,
624 whiteness: Option<f32>,
625 blackness: Option<f32>,
626 alpha: Option<f32>,
627 ) -> Self {
628 Self {
629 hue,
630 whiteness,
631 blackness,
632 alpha,
633 }
634 }
635}
636
637impl ToCss for Hwb {
638 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
639 where
640 W: fmt::Write,
641 {
642 let (red, green, blue) = hwb_to_rgb(
644 self.hue.unwrap_or(0.0) / 360.0,
645 self.whiteness.unwrap_or(0.0),
646 self.blackness.unwrap_or(0.0),
647 );
648
649 RgbaLegacy::from_floats(red, green, blue, self.alpha.unwrap_or(OPAQUE)).to_css(dest)
650 }
651}
652
653#[derive(Clone, Copy, PartialEq, Debug)]
658#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
659pub struct Lab {
660 pub lightness: Option<f32>,
662 pub a: Option<f32>,
664 pub b: Option<f32>,
666 pub alpha: Option<f32>,
668}
669
670#[derive(Clone, Copy, PartialEq, Debug)]
672#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
673pub struct Oklab {
674 pub lightness: Option<f32>,
676 pub a: Option<f32>,
678 pub b: Option<f32>,
680 pub alpha: Option<f32>,
682}
683
684macro_rules! impl_lab_like {
685 ($cls:ident, $fname:literal) => {
686 impl $cls {
687 pub fn new(
689 lightness: Option<f32>,
690 a: Option<f32>,
691 b: Option<f32>,
692 alpha: Option<f32>,
693 ) -> Self {
694 Self {
695 lightness,
696 a,
697 b,
698 alpha,
699 }
700 }
701 }
702
703 impl ToCss for $cls {
704 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
705 where
706 W: fmt::Write,
707 {
708 dest.write_str($fname)?;
709 dest.write_str("(")?;
710 ModernComponent(&self.lightness).to_css(dest)?;
711 dest.write_char(' ')?;
712 ModernComponent(&self.a).to_css(dest)?;
713 dest.write_char(' ')?;
714 ModernComponent(&self.b).to_css(dest)?;
715 serialize_color_alpha(dest, self.alpha, false)?;
716 dest.write_char(')')
717 }
718 }
719 };
720}
721
722impl_lab_like!(Lab, "lab");
723impl_lab_like!(Oklab, "oklab");
724
725#[derive(Clone, Copy, PartialEq, Debug)]
730#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
731pub struct Lch {
732 pub lightness: Option<f32>,
734 pub chroma: Option<f32>,
736 pub hue: Option<f32>,
738 pub alpha: Option<f32>,
740}
741
742#[derive(Clone, Copy, PartialEq, Debug)]
744#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
745pub struct Oklch {
746 pub lightness: Option<f32>,
748 pub chroma: Option<f32>,
750 pub hue: Option<f32>,
752 pub alpha: Option<f32>,
754}
755
756macro_rules! impl_lch_like {
757 ($cls:ident, $fname:literal) => {
758 impl $cls {
759 pub fn new(
761 lightness: Option<f32>,
762 chroma: Option<f32>,
763 hue: Option<f32>,
764 alpha: Option<f32>,
765 ) -> Self {
766 Self {
767 lightness,
768 chroma,
769 hue,
770 alpha,
771 }
772 }
773 }
774
775 impl ToCss for $cls {
776 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
777 where
778 W: fmt::Write,
779 {
780 dest.write_str($fname)?;
781 dest.write_str("(")?;
782 ModernComponent(&self.lightness).to_css(dest)?;
783 dest.write_char(' ')?;
784 ModernComponent(&self.chroma).to_css(dest)?;
785 dest.write_char(' ')?;
786 ModernComponent(&self.hue).to_css(dest)?;
787 serialize_color_alpha(dest, self.alpha, false)?;
788 dest.write_char(')')
789 }
790 }
791 };
792}
793
794impl_lch_like!(Lch, "lch");
795impl_lch_like!(Oklch, "oklch");
796
797#[derive(Clone, Copy, PartialEq, Debug)]
800#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
801pub struct ColorFunction {
802 pub color_space: PredefinedColorSpace,
804 pub c1: Option<f32>,
806 pub c2: Option<f32>,
808 pub c3: Option<f32>,
810 pub alpha: Option<f32>,
812}
813
814impl ColorFunction {
815 pub fn new(
818 color_space: PredefinedColorSpace,
819 c1: Option<f32>,
820 c2: Option<f32>,
821 c3: Option<f32>,
822 alpha: Option<f32>,
823 ) -> Self {
824 Self {
825 color_space,
826 c1,
827 c2,
828 c3,
829 alpha,
830 }
831 }
832}
833
834impl ToCss for ColorFunction {
835 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
836 where
837 W: fmt::Write,
838 {
839 dest.write_str("color(")?;
840 self.color_space.to_css(dest)?;
841 dest.write_char(' ')?;
842 ModernComponent(&self.c1).to_css(dest)?;
843 dest.write_char(' ')?;
844 ModernComponent(&self.c2).to_css(dest)?;
845 dest.write_char(' ')?;
846 ModernComponent(&self.c3).to_css(dest)?;
847
848 serialize_color_alpha(dest, self.alpha, false)?;
849
850 dest.write_char(')')
851 }
852}
853
854#[derive(Clone, Copy, PartialEq, Debug)]
862#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
863#[cfg_attr(feature = "serde", serde(tag = "type"))]
864pub enum Color {
865 CurrentColor,
867 Rgba(RgbaLegacy),
869 Hsl(Hsl),
871 Hwb(Hwb),
873 Lab(Lab),
877 Lch(Lch),
880 Oklab(Oklab),
884 Oklch(Oklch),
887 ColorFunction(ColorFunction),
889}
890
891impl ToCss for Color {
892 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
893 where
894 W: fmt::Write,
895 {
896 match *self {
897 Color::CurrentColor => dest.write_str("currentcolor"),
898 Color::Rgba(rgba) => rgba.to_css(dest),
899 Color::Hsl(hsl) => hsl.to_css(dest),
900 Color::Hwb(hwb) => hwb.to_css(dest),
901 Color::Lab(lab) => lab.to_css(dest),
902 Color::Lch(lch) => lch.to_css(dest),
903 Color::Oklab(lab) => lab.to_css(dest),
904 Color::Oklch(lch) => lch.to_css(dest),
905 Color::ColorFunction(color_function) => color_function.to_css(dest),
906 }
907 }
908}
909
910pub enum NumberOrPercentage {
912 Number {
914 value: f32,
916 },
917 Percentage {
919 unit_value: f32,
922 },
923}
924
925impl NumberOrPercentage {
926 pub fn unit_value(&self) -> f32 {
928 match *self {
929 NumberOrPercentage::Number { value } => value,
930 NumberOrPercentage::Percentage { unit_value } => unit_value,
931 }
932 }
933
934 pub fn value(&self, percentage_basis: f32) -> f32 {
937 match *self {
938 Self::Number { value } => value,
939 Self::Percentage { unit_value } => unit_value * percentage_basis,
940 }
941 }
942}
943
944pub enum AngleOrNumber {
946 Number {
948 value: f32,
950 },
951 Angle {
953 degrees: f32,
955 },
956}
957
958impl AngleOrNumber {
959 pub fn degrees(&self) -> f32 {
962 match *self {
963 AngleOrNumber::Number { value } => value,
964 AngleOrNumber::Angle { degrees } => degrees,
965 }
966 }
967}
968
969pub trait ColorParser<'i> {
974 type Output: FromParsedColor;
976
977 type Error: 'i;
979
980 fn parse_angle_or_number<'t>(
984 &self,
985 input: &mut Parser<'i, 't>,
986 ) -> Result<AngleOrNumber, ParseError<'i, Self::Error>> {
987 let location = input.current_source_location();
988 Ok(match *input.next()? {
989 Token::Number { value, .. } => AngleOrNumber::Number { value },
990 Token::Dimension {
991 value: v, ref unit, ..
992 } => {
993 let degrees = match_ignore_ascii_case! { unit,
994 "deg" => v,
995 "grad" => v * 360. / 400.,
996 "rad" => v * 360. / (2. * PI),
997 "turn" => v * 360.,
998 _ => {
999 return Err(location.new_unexpected_token_error(Token::Ident(unit.clone())))
1000 }
1001 };
1002
1003 AngleOrNumber::Angle { degrees }
1004 }
1005 ref t => return Err(location.new_unexpected_token_error(t.clone())),
1006 })
1007 }
1008
1009 fn parse_percentage<'t>(
1013 &self,
1014 input: &mut Parser<'i, 't>,
1015 ) -> Result<f32, ParseError<'i, Self::Error>> {
1016 input.expect_percentage().map_err(From::from)
1017 }
1018
1019 fn parse_number<'t>(
1021 &self,
1022 input: &mut Parser<'i, 't>,
1023 ) -> Result<f32, ParseError<'i, Self::Error>> {
1024 input.expect_number().map_err(From::from)
1025 }
1026
1027 fn parse_number_or_percentage<'t>(
1029 &self,
1030 input: &mut Parser<'i, 't>,
1031 ) -> Result<NumberOrPercentage, ParseError<'i, Self::Error>> {
1032 let location = input.current_source_location();
1033 Ok(match *input.next()? {
1034 Token::Number { value, .. } => NumberOrPercentage::Number { value },
1035 Token::Percentage { unit_value, .. } => NumberOrPercentage::Percentage { unit_value },
1036 ref t => return Err(location.new_unexpected_token_error(t.clone())),
1037 })
1038 }
1039}
1040
1041pub struct DefaultColorParser;
1043
1044impl<'i> ColorParser<'i> for DefaultColorParser {
1045 type Output = Color;
1046 type Error = ();
1047}
1048
1049impl Color {
1050 pub fn parse<'i>(input: &mut Parser<'i, '_>) -> Result<Color, ParseError<'i, ()>> {
1054 parse_color_with(&DefaultColorParser, input)
1055 }
1056}
1057
1058pub trait FromParsedColor {
1060 fn from_current_color() -> Self;
1062
1063 fn from_rgba(red: u8, green: u8, blue: u8, alpha: f32) -> Self;
1065
1066 fn from_hsl(
1068 hue: Option<f32>,
1069 saturation: Option<f32>,
1070 lightness: Option<f32>,
1071 alpha: Option<f32>,
1072 ) -> Self;
1073
1074 fn from_hwb(
1076 hue: Option<f32>,
1077 whiteness: Option<f32>,
1078 blackness: Option<f32>,
1079 alpha: Option<f32>,
1080 ) -> Self;
1081
1082 fn from_lab(lightness: Option<f32>, a: Option<f32>, b: Option<f32>, alpha: Option<f32>)
1084 -> Self;
1085
1086 fn from_lch(
1088 lightness: Option<f32>,
1089 chroma: Option<f32>,
1090 hue: Option<f32>,
1091 alpha: Option<f32>,
1092 ) -> Self;
1093
1094 fn from_oklab(
1096 lightness: Option<f32>,
1097 a: Option<f32>,
1098 b: Option<f32>,
1099 alpha: Option<f32>,
1100 ) -> Self;
1101
1102 fn from_oklch(
1104 lightness: Option<f32>,
1105 chroma: Option<f32>,
1106 hue: Option<f32>,
1107 alpha: Option<f32>,
1108 ) -> Self;
1109
1110 fn from_color_function(
1112 color_space: PredefinedColorSpace,
1113 c1: Option<f32>,
1114 c2: Option<f32>,
1115 c3: Option<f32>,
1116 alpha: Option<f32>,
1117 ) -> Self;
1118}
1119
1120impl FromParsedColor for Color {
1121 #[inline]
1122 fn from_current_color() -> Self {
1123 Color::CurrentColor
1124 }
1125
1126 #[inline]
1127 fn from_rgba(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
1128 Color::Rgba(RgbaLegacy::new(red, green, blue, alpha))
1129 }
1130
1131 fn from_hsl(
1132 hue: Option<f32>,
1133 saturation: Option<f32>,
1134 lightness: Option<f32>,
1135 alpha: Option<f32>,
1136 ) -> Self {
1137 Color::Hsl(Hsl::new(hue, saturation, lightness, alpha))
1138 }
1139
1140 fn from_hwb(
1141 hue: Option<f32>,
1142 blackness: Option<f32>,
1143 whiteness: Option<f32>,
1144 alpha: Option<f32>,
1145 ) -> Self {
1146 Color::Hwb(Hwb::new(hue, blackness, whiteness, alpha))
1147 }
1148
1149 #[inline]
1150 fn from_lab(
1151 lightness: Option<f32>,
1152 a: Option<f32>,
1153 b: Option<f32>,
1154 alpha: Option<f32>,
1155 ) -> Self {
1156 Color::Lab(Lab::new(lightness, a, b, alpha))
1157 }
1158
1159 #[inline]
1160 fn from_lch(
1161 lightness: Option<f32>,
1162 chroma: Option<f32>,
1163 hue: Option<f32>,
1164 alpha: Option<f32>,
1165 ) -> Self {
1166 Color::Lch(Lch::new(lightness, chroma, hue, alpha))
1167 }
1168
1169 #[inline]
1170 fn from_oklab(
1171 lightness: Option<f32>,
1172 a: Option<f32>,
1173 b: Option<f32>,
1174 alpha: Option<f32>,
1175 ) -> Self {
1176 Color::Oklab(Oklab::new(lightness, a, b, alpha))
1177 }
1178
1179 #[inline]
1180 fn from_oklch(
1181 lightness: Option<f32>,
1182 chroma: Option<f32>,
1183 hue: Option<f32>,
1184 alpha: Option<f32>,
1185 ) -> Self {
1186 Color::Oklch(Oklch::new(lightness, chroma, hue, alpha))
1187 }
1188
1189 #[inline]
1190 fn from_color_function(
1191 color_space: PredefinedColorSpace,
1192 c1: Option<f32>,
1193 c2: Option<f32>,
1194 c3: Option<f32>,
1195 alpha: Option<f32>,
1196 ) -> Self {
1197 Color::ColorFunction(ColorFunction::new(color_space, c1, c2, c3, alpha))
1198 }
1199}