cssparser_color/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5#![deny(missing_docs)]
6
7//! Fairly complete css-color implementation.
8//! Relative colors, color-mix, system colors, and other such things require better calc() support
9//! and integration.
10
11#[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/// Return the named color with the given name.
23///
24/// Matching is case-insensitive in the ASCII range.
25/// CSS escaping (if relevant) should be resolved before calling this function.
26/// (For example, the value of an `Ident` token is fine.)
27#[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
43/// Parse a CSS color using the specified [`ColorParser`] and return a new color
44/// value on success.
45pub 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/// Parse one of the color functions: rgba(), lab(), color(), etc.
71#[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        // for L: 0% = 0.0, 100% = 100.0
88        // for a and b: -100% = -125, 100% = 125
89        "lab" => parse_lab_like(color_parser, arguments, 100.0, 125.0, P::Output::from_lab),
90
91        // for L: 0% = 0.0, 100% = 100.0
92        // for C: 0% = 0, 100% = 150
93        "lch" => parse_lch_like(color_parser, arguments, 100.0, 150.0, P::Output::from_lch),
94
95        // for L: 0% = 0.0, 100% = 1.0
96        // for a and b: -100% = -0.4, 100% = 0.4
97        "oklab" => parse_lab_like(color_parser, arguments, 1.0, 0.4, P::Output::from_oklab),
98
99        // for L: 0% = 0.0, 100% = 1.0
100        // for C: 0% = 0.0 100% = 0.4
101        "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/// Parse the alpha component by itself from either number or percentage,
114/// clipping the result to [0.0..1.0].
115#[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    // If the first component is not "none" and is followed by a comma, then we
170    // are parsing the legacy syntax.
171    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/// Parses hsl syntax.
223///
224/// <https://drafts.csswg.org/css-color/#the-hsl-notation>
225#[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    // If the hue is not "none" and is followed by a comma, then we are parsing
236    // the legacy syntax.
237    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/// Parses hwb syntax.
262///
263/// <https://drafts.csswg.org/css-color/#the-hbw-notation>
264#[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/// <https://drafts.csswg.org/css-color-4/#hwb-to-rgb>
288#[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    // hue is expected in the range [0..1].
296    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/// <https://drafts.csswg.org/css-color/#hsl-color>
305/// except with h pre-multiplied by 3, to avoid some rounding errors.
306#[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/// Parse the color() function.
396#[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
430/// Parse the color components and alpha with the modern [color-4] syntax.
431pub 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
463/// A [`ModernComponent`] can serialize to `none`, `nan`, `infinity` and
464/// floating point values.
465struct 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
491// Guaratees hue in [0..360)
492fn normalize_hue(hue: f32) -> f32 {
493    // <https://drafts.csswg.org/css-values/#angles>
494    // Subtract an integer before rounding, to avoid some rounding errors:
495    hue - 360.0 * (hue / 360.0).floor()
496}
497
498/// A color with red, green, blue, and alpha components, in a byte each.
499#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
500#[derive(Clone, Copy, PartialEq, Debug)]
501pub struct RgbaLegacy {
502    /// The red component.
503    pub red: u8,
504    /// The green component.
505    pub green: u8,
506    /// The blue component.
507    pub blue: u8,
508    /// The alpha component.
509    pub alpha: f32,
510}
511
512impl RgbaLegacy {
513    /// Constructs a new RGBA value from float components. It expects the red,
514    /// green, blue and alpha channels in that order, and all values will be
515    /// clamped to the 0.0 ... 1.0 range.
516    #[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    /// Same thing, but with `u8` values instead of floats in the 0 to 1 range.
527    #[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        // Legacy syntax does not allow none components.
553        serialize_color_alpha(dest, Some(self.alpha), true)?;
554
555        dest.write_char(')')
556    }
557}
558
559/// Color specified by hue, saturation and lightness components.
560#[derive(Clone, Copy, PartialEq, Debug)]
561#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
562pub struct Hsl {
563    /// The hue component.
564    pub hue: Option<f32>,
565    /// The saturation component.
566    pub saturation: Option<f32>,
567    /// The lightness component.
568    pub lightness: Option<f32>,
569    /// The alpha component.
570    pub alpha: Option<f32>,
571}
572
573impl Hsl {
574    /// Construct a new HSL color from it's components.
575    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        // HSL serializes to RGB, so we have to convert it.
596        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/// Color specified by hue, whiteness and blackness components.
607#[derive(Clone, Copy, PartialEq, Debug)]
608#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
609pub struct Hwb {
610    /// The hue component.
611    pub hue: Option<f32>,
612    /// The whiteness component.
613    pub whiteness: Option<f32>,
614    /// The blackness component.
615    pub blackness: Option<f32>,
616    /// The alpha component.
617    pub alpha: Option<f32>,
618}
619
620impl Hwb {
621    /// Construct a new HWB color from it's components.
622    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        // HWB serializes to RGB, so we have to convert it.
643        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// NOTE: LAB and OKLAB is not declared inside the [impl_lab_like] macro,
654// because it causes cbindgen to ignore them.
655
656/// Color specified by lightness, a- and b-axis components.
657#[derive(Clone, Copy, PartialEq, Debug)]
658#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
659pub struct Lab {
660    /// The lightness component.
661    pub lightness: Option<f32>,
662    /// The a-axis component.
663    pub a: Option<f32>,
664    /// The b-axis component.
665    pub b: Option<f32>,
666    /// The alpha component.
667    pub alpha: Option<f32>,
668}
669
670/// Color specified by lightness, a- and b-axis components.
671#[derive(Clone, Copy, PartialEq, Debug)]
672#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
673pub struct Oklab {
674    /// The lightness component.
675    pub lightness: Option<f32>,
676    /// The a-axis component.
677    pub a: Option<f32>,
678    /// The b-axis component.
679    pub b: Option<f32>,
680    /// The alpha component.
681    pub alpha: Option<f32>,
682}
683
684macro_rules! impl_lab_like {
685    ($cls:ident, $fname:literal) => {
686        impl $cls {
687            /// Construct a new Lab color format with lightness, a, b and alpha components.
688            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// NOTE: LCH and OKLCH is not declared inside the [impl_lch_like] macro,
726// because it causes cbindgen to ignore them.
727
728/// Color specified by lightness, chroma and hue components.
729#[derive(Clone, Copy, PartialEq, Debug)]
730#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
731pub struct Lch {
732    /// The lightness component.
733    pub lightness: Option<f32>,
734    /// The chroma component.
735    pub chroma: Option<f32>,
736    /// The hue component.
737    pub hue: Option<f32>,
738    /// The alpha component.
739    pub alpha: Option<f32>,
740}
741
742/// Color specified by lightness, chroma and hue components.
743#[derive(Clone, Copy, PartialEq, Debug)]
744#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
745pub struct Oklch {
746    /// The lightness component.
747    pub lightness: Option<f32>,
748    /// The chroma component.
749    pub chroma: Option<f32>,
750    /// The hue component.
751    pub hue: Option<f32>,
752    /// The alpha component.
753    pub alpha: Option<f32>,
754}
755
756macro_rules! impl_lch_like {
757    ($cls:ident, $fname:literal) => {
758        impl $cls {
759            /// Construct a new color with lightness, chroma and hue components.
760            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/// A color specified by the color() function.
798/// <https://drafts.csswg.org/css-color-4/#color-function>
799#[derive(Clone, Copy, PartialEq, Debug)]
800#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
801pub struct ColorFunction {
802    /// The color space for this color.
803    pub color_space: PredefinedColorSpace,
804    /// The first component of the color.  Either red or x.
805    pub c1: Option<f32>,
806    /// The second component of the color.  Either green or y.
807    pub c2: Option<f32>,
808    /// The third component of the color.  Either blue or z.
809    pub c3: Option<f32>,
810    /// The alpha component of the color.
811    pub alpha: Option<f32>,
812}
813
814impl ColorFunction {
815    /// Construct a new color function definition with the given color space and
816    /// color components.
817    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/// Describes one of the value <color> values according to the CSS
855/// specification.
856///
857/// Most components are `Option<_>`, so when the value is `None`, that component
858/// serializes to the "none" keyword.
859///
860/// <https://drafts.csswg.org/css-color-4/#color-type>
861#[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    /// The 'currentcolor' keyword.
866    CurrentColor,
867    /// Specify sRGB colors directly by their red/green/blue/alpha chanels.
868    Rgba(RgbaLegacy),
869    /// Specifies a color in sRGB using hue, saturation and lightness components.
870    Hsl(Hsl),
871    /// Specifies a color in sRGB using hue, whiteness and blackness components.
872    Hwb(Hwb),
873    /// Specifies a CIELAB color by CIE Lightness and its a- and b-axis hue
874    /// coordinates (red/green-ness, and yellow/blue-ness) using the CIE LAB
875    /// rectangular coordinate model.
876    Lab(Lab),
877    /// Specifies a CIELAB color by CIE Lightness, Chroma, and hue using the
878    /// CIE LCH cylindrical coordinate model.
879    Lch(Lch),
880    /// Specifies an Oklab color by Oklab Lightness and its a- and b-axis hue
881    /// coordinates (red/green-ness, and yellow/blue-ness) using the Oklab
882    /// rectangular coordinate model.
883    Oklab(Oklab),
884    /// Specifies an Oklab color by Oklab Lightness, Chroma, and hue using
885    /// the OKLCH cylindrical coordinate model.
886    Oklch(Oklch),
887    /// Specifies a color in a predefined color space.
888    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
910/// Either a number or a percentage.
911pub enum NumberOrPercentage {
912    /// `<number>`.
913    Number {
914        /// The numeric value parsed, as a float.
915        value: f32,
916    },
917    /// `<percentage>`
918    Percentage {
919        /// The value as a float, divided by 100 so that the nominal range is
920        /// 0.0 to 1.0.
921        unit_value: f32,
922    },
923}
924
925impl NumberOrPercentage {
926    /// Return the value as a percentage.
927    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    /// Return the value as a number with a percentage adjusted to the
935    /// `percentage_basis`.
936    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
944/// Either an angle or a number.
945pub enum AngleOrNumber {
946    /// `<number>`.
947    Number {
948        /// The numeric value parsed, as a float.
949        value: f32,
950    },
951    /// `<angle>`
952    Angle {
953        /// The value as a number of degrees.
954        degrees: f32,
955    },
956}
957
958impl AngleOrNumber {
959    /// Return the angle in degrees. `AngleOrNumber::Number` is returned as
960    /// degrees, because it is the canonical unit.
961    pub fn degrees(&self) -> f32 {
962        match *self {
963            AngleOrNumber::Number { value } => value,
964            AngleOrNumber::Angle { degrees } => degrees,
965        }
966    }
967}
968
969/// A trait that can be used to hook into how `cssparser` parses color
970/// components, with the intention of implementing more complicated behavior.
971///
972/// For example, this is used by Servo to support calc() in color.
973pub trait ColorParser<'i> {
974    /// The type that the parser will construct on a successful parse.
975    type Output: FromParsedColor;
976
977    /// A custom error type that can be returned from the parsing functions.
978    type Error: 'i;
979
980    /// Parse an `<angle>` or `<number>`.
981    ///
982    /// Returns the result in degrees.
983    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    /// Parse a `<percentage>` value.
1010    ///
1011    /// Returns the result in a number from 0.0 to 1.0.
1012    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    /// Parse a `<number>` value.
1020    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    /// Parse a `<number>` value or a `<percentage>` value.
1028    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
1041/// Default implementation of a [`ColorParser`]
1042pub struct DefaultColorParser;
1043
1044impl<'i> ColorParser<'i> for DefaultColorParser {
1045    type Output = Color;
1046    type Error = ();
1047}
1048
1049impl Color {
1050    /// Parse a <color> value, per CSS Color Module Level 3.
1051    ///
1052    /// FIXME(#2) Deprecated CSS2 System Colors are not supported yet.
1053    pub fn parse<'i>(input: &mut Parser<'i, '_>) -> Result<Color, ParseError<'i, ()>> {
1054        parse_color_with(&DefaultColorParser, input)
1055    }
1056}
1057
1058/// This trait is used by the [`ColorParser`] to construct colors of any type.
1059pub trait FromParsedColor {
1060    /// Construct a new color from the CSS `currentcolor` keyword.
1061    fn from_current_color() -> Self;
1062
1063    /// Construct a new color from red, green, blue and alpha components.
1064    fn from_rgba(red: u8, green: u8, blue: u8, alpha: f32) -> Self;
1065
1066    /// Construct a new color from hue, saturation, lightness and alpha components.
1067    fn from_hsl(
1068        hue: Option<f32>,
1069        saturation: Option<f32>,
1070        lightness: Option<f32>,
1071        alpha: Option<f32>,
1072    ) -> Self;
1073
1074    /// Construct a new color from hue, blackness, whiteness and alpha components.
1075    fn from_hwb(
1076        hue: Option<f32>,
1077        whiteness: Option<f32>,
1078        blackness: Option<f32>,
1079        alpha: Option<f32>,
1080    ) -> Self;
1081
1082    /// Construct a new color from the `lab` notation.
1083    fn from_lab(lightness: Option<f32>, a: Option<f32>, b: Option<f32>, alpha: Option<f32>)
1084        -> Self;
1085
1086    /// Construct a new color from the `lch` notation.
1087    fn from_lch(
1088        lightness: Option<f32>,
1089        chroma: Option<f32>,
1090        hue: Option<f32>,
1091        alpha: Option<f32>,
1092    ) -> Self;
1093
1094    /// Construct a new color from the `oklab` notation.
1095    fn from_oklab(
1096        lightness: Option<f32>,
1097        a: Option<f32>,
1098        b: Option<f32>,
1099        alpha: Option<f32>,
1100    ) -> Self;
1101
1102    /// Construct a new color from the `oklch` notation.
1103    fn from_oklch(
1104        lightness: Option<f32>,
1105        chroma: Option<f32>,
1106        hue: Option<f32>,
1107        alpha: Option<f32>,
1108    ) -> Self;
1109
1110    /// Construct a new color with a predefined color space.
1111    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}