style/color/
color_function.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 https://mozilla.org/MPL/2.0/. */
4
5//! Output of parsing a color function, e.g. rgb(..), hsl(..), color(..)
6
7use std::fmt::Write;
8
9use super::{
10    component::ColorComponent,
11    convert::normalize_hue,
12    parsing::{NumberOrAngleComponent, NumberOrPercentageComponent},
13    AbsoluteColor, ColorFlags, ColorSpace,
14};
15use crate::derives::*;
16use crate::values::{
17    computed::color::Color as ComputedColor, generics::Optional, normalize,
18    specified::color::Color as SpecifiedColor,
19};
20use cssparser::color::{clamp_floor_256_f32, OPAQUE};
21
22/// Represents a specified color function.
23#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
24#[repr(u8)]
25pub enum ColorFunction<OriginColor> {
26    /// <https://drafts.csswg.org/css-color-4/#rgb-functions>
27    Rgb(
28        Optional<OriginColor>,                       // origin
29        ColorComponent<NumberOrPercentageComponent>, // red
30        ColorComponent<NumberOrPercentageComponent>, // green
31        ColorComponent<NumberOrPercentageComponent>, // blue
32        ColorComponent<NumberOrPercentageComponent>, // alpha
33    ),
34    /// <https://drafts.csswg.org/css-color-4/#the-hsl-notation>
35    Hsl(
36        Optional<OriginColor>,                       // origin
37        ColorComponent<NumberOrAngleComponent>,      // hue
38        ColorComponent<NumberOrPercentageComponent>, // saturation
39        ColorComponent<NumberOrPercentageComponent>, // lightness
40        ColorComponent<NumberOrPercentageComponent>, // alpha
41    ),
42    /// <https://drafts.csswg.org/css-color-4/#the-hwb-notation>
43    Hwb(
44        Optional<OriginColor>,                       // origin
45        ColorComponent<NumberOrAngleComponent>,      // hue
46        ColorComponent<NumberOrPercentageComponent>, // whiteness
47        ColorComponent<NumberOrPercentageComponent>, // blackness
48        ColorComponent<NumberOrPercentageComponent>, // alpha
49    ),
50    /// <https://drafts.csswg.org/css-color-4/#specifying-lab-lch>
51    Lab(
52        Optional<OriginColor>,                       // origin
53        ColorComponent<NumberOrPercentageComponent>, // lightness
54        ColorComponent<NumberOrPercentageComponent>, // a
55        ColorComponent<NumberOrPercentageComponent>, // b
56        ColorComponent<NumberOrPercentageComponent>, // alpha
57    ),
58    /// <https://drafts.csswg.org/css-color-4/#specifying-lab-lch>
59    Lch(
60        Optional<OriginColor>,                       // origin
61        ColorComponent<NumberOrPercentageComponent>, // lightness
62        ColorComponent<NumberOrPercentageComponent>, // chroma
63        ColorComponent<NumberOrAngleComponent>,      // hue
64        ColorComponent<NumberOrPercentageComponent>, // alpha
65    ),
66    /// <https://drafts.csswg.org/css-color-4/#specifying-oklab-oklch>
67    Oklab(
68        Optional<OriginColor>,                       // origin
69        ColorComponent<NumberOrPercentageComponent>, // lightness
70        ColorComponent<NumberOrPercentageComponent>, // a
71        ColorComponent<NumberOrPercentageComponent>, // b
72        ColorComponent<NumberOrPercentageComponent>, // alpha
73    ),
74    /// <https://drafts.csswg.org/css-color-4/#specifying-oklab-oklch>
75    Oklch(
76        Optional<OriginColor>,                       // origin
77        ColorComponent<NumberOrPercentageComponent>, // lightness
78        ColorComponent<NumberOrPercentageComponent>, // chroma
79        ColorComponent<NumberOrAngleComponent>,      // hue
80        ColorComponent<NumberOrPercentageComponent>, // alpha
81    ),
82    /// <https://drafts.csswg.org/css-color-4/#color-function>
83    Color(
84        Optional<OriginColor>,                       // origin
85        ColorComponent<NumberOrPercentageComponent>, // red / x
86        ColorComponent<NumberOrPercentageComponent>, // green / y
87        ColorComponent<NumberOrPercentageComponent>, // blue / z
88        ColorComponent<NumberOrPercentageComponent>, // alpha
89        ColorSpace,
90    ),
91}
92
93impl ColorFunction<AbsoluteColor> {
94    /// Try to resolve into a valid absolute color.
95    pub fn resolve_to_absolute(&self) -> Result<AbsoluteColor, ()> {
96        macro_rules! alpha {
97            ($alpha:expr, $origin_color:expr) => {{
98                $alpha
99                    .resolve($origin_color)?
100                    .map(|value| normalize(value.to_number(1.0)).clamp(0.0, OPAQUE))
101            }};
102        }
103
104        Ok(match self {
105            ColorFunction::Rgb(origin_color, r, g, b, alpha) => {
106                // Use `color(srgb ...)` to serialize `rgb(...)` if an origin color is available;
107                // this is the only reason for now.
108                let use_color_syntax = origin_color.is_some();
109
110                if use_color_syntax {
111                    let origin_color = origin_color.as_ref().map(|origin| {
112                        let origin = origin.to_color_space(ColorSpace::Srgb);
113                        // Because rgb(..) syntax have components in range [0..255), we have to
114                        // map them.
115                        // NOTE: The IS_LEGACY_SRGB flag is not added back to the color, because
116                        //       we're going to return the modern color(srgb ..) syntax.
117                        AbsoluteColor::new(
118                            ColorSpace::Srgb,
119                            origin.c0().map(|v| v * 255.0),
120                            origin.c1().map(|v| v * 255.0),
121                            origin.c2().map(|v| v * 255.0),
122                            origin.alpha(),
123                        )
124                    });
125
126                    // We have to map all the components back to [0..1) range after all the
127                    // calculations.
128                    AbsoluteColor::new(
129                        ColorSpace::Srgb,
130                        r.resolve(origin_color.as_ref())?
131                            .map(|c| c.to_number(255.0) / 255.0),
132                        g.resolve(origin_color.as_ref())?
133                            .map(|c| c.to_number(255.0) / 255.0),
134                        b.resolve(origin_color.as_ref())?
135                            .map(|c| c.to_number(255.0) / 255.0),
136                        alpha!(alpha, origin_color.as_ref()),
137                    )
138                } else {
139                    #[inline]
140                    fn resolve(
141                        component: &ColorComponent<NumberOrPercentageComponent>,
142                        origin_color: Option<&AbsoluteColor>,
143                    ) -> Result<u8, ()> {
144                        Ok(clamp_floor_256_f32(
145                            component
146                                .resolve(origin_color)?
147                                .map_or(0.0, |value| value.to_number(u8::MAX as f32)),
148                        ))
149                    }
150
151                    let origin_color = origin_color.as_ref().map(|o| o.into_srgb_legacy());
152
153                    AbsoluteColor::srgb_legacy(
154                        resolve(r, origin_color.as_ref())?,
155                        resolve(g, origin_color.as_ref())?,
156                        resolve(b, origin_color.as_ref())?,
157                        alpha!(alpha, origin_color.as_ref()).unwrap_or(0.0),
158                    )
159                }
160            },
161            ColorFunction::Hsl(origin_color, h, s, l, alpha) => {
162                // Percent reference range for S and L: 0% = 0.0, 100% = 100.0
163                const LIGHTNESS_RANGE: f32 = 100.0;
164                const SATURATION_RANGE: f32 = 100.0;
165
166                // If the origin color:
167                // - was *NOT* specified, then we stick with the old way of serializing the
168                //   value to rgb(..).
169                // - was specified, we don't use the rgb(..) syntax, because we should allow the
170                //   color to be out of gamut and not clamp.
171                let use_rgb_sytax = origin_color.is_none();
172
173                let origin_color = origin_color
174                    .as_ref()
175                    .map(|o| o.to_color_space(ColorSpace::Hsl));
176
177                let mut result = AbsoluteColor::new(
178                    ColorSpace::Hsl,
179                    h.resolve(origin_color.as_ref())?
180                        .map(|angle| normalize_hue(angle.degrees())),
181                    s.resolve(origin_color.as_ref())?.map(|s| {
182                        if use_rgb_sytax {
183                            s.to_number(SATURATION_RANGE).clamp(0.0, SATURATION_RANGE)
184                        } else {
185                            s.to_number(SATURATION_RANGE)
186                        }
187                    }),
188                    l.resolve(origin_color.as_ref())?.map(|l| {
189                        if use_rgb_sytax {
190                            l.to_number(LIGHTNESS_RANGE).clamp(0.0, LIGHTNESS_RANGE)
191                        } else {
192                            l.to_number(LIGHTNESS_RANGE)
193                        }
194                    }),
195                    alpha!(alpha, origin_color.as_ref()),
196                );
197
198                if use_rgb_sytax {
199                    result.flags.insert(ColorFlags::IS_LEGACY_SRGB);
200                }
201
202                result
203            },
204            ColorFunction::Hwb(origin_color, h, w, b, alpha) => {
205                // If the origin color:
206                // - was *NOT* specified, then we stick with the old way of serializing the
207                //   value to rgb(..).
208                // - was specified, we don't use the rgb(..) syntax, because we should allow the
209                //   color to be out of gamut and not clamp.
210                let use_rgb_sytax = origin_color.is_none();
211
212                // Percent reference range for W and B: 0% = 0.0, 100% = 100.0
213                const WHITENESS_RANGE: f32 = 100.0;
214                const BLACKNESS_RANGE: f32 = 100.0;
215
216                let origin_color = origin_color
217                    .as_ref()
218                    .map(|o| o.to_color_space(ColorSpace::Hwb));
219
220                let mut result = AbsoluteColor::new(
221                    ColorSpace::Hwb,
222                    h.resolve(origin_color.as_ref())?
223                        .map(|angle| normalize_hue(angle.degrees())),
224                    w.resolve(origin_color.as_ref())?.map(|w| {
225                        if use_rgb_sytax {
226                            w.to_number(WHITENESS_RANGE).clamp(0.0, WHITENESS_RANGE)
227                        } else {
228                            w.to_number(WHITENESS_RANGE)
229                        }
230                    }),
231                    b.resolve(origin_color.as_ref())?.map(|b| {
232                        if use_rgb_sytax {
233                            b.to_number(BLACKNESS_RANGE).clamp(0.0, BLACKNESS_RANGE)
234                        } else {
235                            b.to_number(BLACKNESS_RANGE)
236                        }
237                    }),
238                    alpha!(alpha, origin_color.as_ref()),
239                );
240
241                if use_rgb_sytax {
242                    result.flags.insert(ColorFlags::IS_LEGACY_SRGB);
243                }
244
245                result
246            },
247            ColorFunction::Lab(origin_color, l, a, b, alpha) => {
248                // for L: 0% = 0.0, 100% = 100.0
249                // for a and b: -100% = -125, 100% = 125
250                const LIGHTNESS_RANGE: f32 = 100.0;
251                const A_B_RANGE: f32 = 125.0;
252
253                let origin_color = origin_color
254                    .as_ref()
255                    .map(|o| o.to_color_space(ColorSpace::Lab));
256
257                AbsoluteColor::new(
258                    ColorSpace::Lab,
259                    l.resolve(origin_color.as_ref())?
260                        .map(|l| l.to_number(LIGHTNESS_RANGE)),
261                    a.resolve(origin_color.as_ref())?
262                        .map(|a| a.to_number(A_B_RANGE)),
263                    b.resolve(origin_color.as_ref())?
264                        .map(|b| b.to_number(A_B_RANGE)),
265                    alpha!(alpha, origin_color.as_ref()),
266                )
267            },
268            ColorFunction::Lch(origin_color, l, c, h, alpha) => {
269                // for L: 0% = 0.0, 100% = 100.0
270                // for C: 0% = 0, 100% = 150
271                const LIGHTNESS_RANGE: f32 = 100.0;
272                const CHROMA_RANGE: f32 = 150.0;
273
274                let origin_color = origin_color
275                    .as_ref()
276                    .map(|o| o.to_color_space(ColorSpace::Lch));
277
278                AbsoluteColor::new(
279                    ColorSpace::Lch,
280                    l.resolve(origin_color.as_ref())?
281                        .map(|l| l.to_number(LIGHTNESS_RANGE)),
282                    c.resolve(origin_color.as_ref())?
283                        .map(|c| c.to_number(CHROMA_RANGE)),
284                    h.resolve(origin_color.as_ref())?
285                        .map(|angle| normalize_hue(angle.degrees())),
286                    alpha!(alpha, origin_color.as_ref()),
287                )
288            },
289            ColorFunction::Oklab(origin_color, l, a, b, alpha) => {
290                // for L: 0% = 0.0, 100% = 1.0
291                // for a and b: -100% = -0.4, 100% = 0.4
292                const LIGHTNESS_RANGE: f32 = 1.0;
293                const A_B_RANGE: f32 = 0.4;
294
295                let origin_color = origin_color
296                    .as_ref()
297                    .map(|o| o.to_color_space(ColorSpace::Oklab));
298
299                AbsoluteColor::new(
300                    ColorSpace::Oklab,
301                    l.resolve(origin_color.as_ref())?
302                        .map(|l| l.to_number(LIGHTNESS_RANGE)),
303                    a.resolve(origin_color.as_ref())?
304                        .map(|a| a.to_number(A_B_RANGE)),
305                    b.resolve(origin_color.as_ref())?
306                        .map(|b| b.to_number(A_B_RANGE)),
307                    alpha!(alpha, origin_color.as_ref()),
308                )
309            },
310            ColorFunction::Oklch(origin_color, l, c, h, alpha) => {
311                // for L: 0% = 0.0, 100% = 1.0
312                // for C: 0% = 0.0 100% = 0.4
313                const LIGHTNESS_RANGE: f32 = 1.0;
314                const CHROMA_RANGE: f32 = 0.4;
315
316                let origin_color = origin_color
317                    .as_ref()
318                    .map(|o| o.to_color_space(ColorSpace::Oklch));
319
320                AbsoluteColor::new(
321                    ColorSpace::Oklch,
322                    l.resolve(origin_color.as_ref())?
323                        .map(|l| l.to_number(LIGHTNESS_RANGE)),
324                    c.resolve(origin_color.as_ref())?
325                        .map(|c| c.to_number(CHROMA_RANGE)),
326                    h.resolve(origin_color.as_ref())?
327                        .map(|angle| normalize_hue(angle.degrees())),
328                    alpha!(alpha, origin_color.as_ref()),
329                )
330            },
331            ColorFunction::Color(origin_color, r, g, b, alpha, color_space) => {
332                let origin_color = origin_color.as_ref().map(|o| {
333                    let mut result = o.to_color_space(*color_space);
334
335                    // If the origin color was a `rgb(..)` function, we should
336                    // make sure it doesn't have the legacy flag any more so
337                    // that it is recognized as a `color(srgb ..)` function.
338                    result.flags.set(ColorFlags::IS_LEGACY_SRGB, false);
339
340                    result
341                });
342
343                AbsoluteColor::new(
344                    *color_space,
345                    r.resolve(origin_color.as_ref())?.map(|c| c.to_number(1.0)),
346                    g.resolve(origin_color.as_ref())?.map(|c| c.to_number(1.0)),
347                    b.resolve(origin_color.as_ref())?.map(|c| c.to_number(1.0)),
348                    alpha!(alpha, origin_color.as_ref()),
349                )
350            },
351        })
352    }
353}
354
355impl ColorFunction<SpecifiedColor> {
356    /// Return true if the color funciton has an origin color specified.
357    pub fn has_origin_color(&self) -> bool {
358        match self {
359            Self::Rgb(origin_color, ..)
360            | Self::Hsl(origin_color, ..)
361            | Self::Hwb(origin_color, ..)
362            | Self::Lab(origin_color, ..)
363            | Self::Lch(origin_color, ..)
364            | Self::Oklab(origin_color, ..)
365            | Self::Oklch(origin_color, ..)
366            | Self::Color(origin_color, ..) => origin_color.is_some(),
367        }
368    }
369
370    /// Try to resolve the color function to an [`AbsoluteColor`] that does not
371    /// contain any variables (currentcolor, color components, etc.).
372    pub fn resolve_to_absolute(&self) -> Result<AbsoluteColor, ()> {
373        // Map the color function to one with an absolute origin color.
374        let resolvable = self.map_origin_color(|o| o.resolve_to_absolute());
375        resolvable.resolve_to_absolute()
376    }
377}
378
379impl<Color> ColorFunction<Color> {
380    /// Map the origin color to another type.  Return None from `f` if the conversion fails.
381    pub fn map_origin_color<U>(&self, f: impl FnOnce(&Color) -> Option<U>) -> ColorFunction<U> {
382        macro_rules! map {
383            ($f:ident, $o:expr, $c0:expr, $c1:expr, $c2:expr, $alpha:expr) => {{
384                ColorFunction::$f(
385                    $o.as_ref().and_then(f).into(),
386                    $c0.clone(),
387                    $c1.clone(),
388                    $c2.clone(),
389                    $alpha.clone(),
390                )
391            }};
392        }
393        match self {
394            ColorFunction::Rgb(o, c0, c1, c2, alpha) => map!(Rgb, o, c0, c1, c2, alpha),
395            ColorFunction::Hsl(o, c0, c1, c2, alpha) => map!(Hsl, o, c0, c1, c2, alpha),
396            ColorFunction::Hwb(o, c0, c1, c2, alpha) => map!(Hwb, o, c0, c1, c2, alpha),
397            ColorFunction::Lab(o, c0, c1, c2, alpha) => map!(Lab, o, c0, c1, c2, alpha),
398            ColorFunction::Lch(o, c0, c1, c2, alpha) => map!(Lch, o, c0, c1, c2, alpha),
399            ColorFunction::Oklab(o, c0, c1, c2, alpha) => map!(Oklab, o, c0, c1, c2, alpha),
400            ColorFunction::Oklch(o, c0, c1, c2, alpha) => map!(Oklch, o, c0, c1, c2, alpha),
401            ColorFunction::Color(o, c0, c1, c2, alpha, color_space) => ColorFunction::Color(
402                o.as_ref().and_then(f).into(),
403                c0.clone(),
404                c1.clone(),
405                c2.clone(),
406                alpha.clone(),
407                color_space.clone(),
408            ),
409        }
410    }
411}
412
413impl ColorFunction<ComputedColor> {
414    /// Resolve a computed color function to an absolute computed color.
415    pub fn resolve_to_absolute(&self, current_color: &AbsoluteColor) -> AbsoluteColor {
416        // Map the color function to one with an absolute origin color.
417        let resolvable = self.map_origin_color(|o| Some(o.resolve_to_absolute(current_color)));
418        match resolvable.resolve_to_absolute() {
419            Ok(color) => color,
420            Err(..) => {
421                debug_assert!(
422                    false,
423                    "the color could not be resolved even with a currentcolor specified?"
424                );
425                AbsoluteColor::TRANSPARENT_BLACK
426            },
427        }
428    }
429}
430
431impl<C: style_traits::ToCss> style_traits::ToCss for ColorFunction<C> {
432    fn to_css<W>(&self, dest: &mut style_traits::CssWriter<W>) -> std::fmt::Result
433    where
434        W: std::fmt::Write,
435    {
436        let (origin_color, alpha) = match self {
437            Self::Rgb(origin_color, _, _, _, alpha) => {
438                dest.write_str("rgb(")?;
439                (origin_color, alpha)
440            },
441            Self::Hsl(origin_color, _, _, _, alpha) => {
442                dest.write_str("hsl(")?;
443                (origin_color, alpha)
444            },
445            Self::Hwb(origin_color, _, _, _, alpha) => {
446                dest.write_str("hwb(")?;
447                (origin_color, alpha)
448            },
449            Self::Lab(origin_color, _, _, _, alpha) => {
450                dest.write_str("lab(")?;
451                (origin_color, alpha)
452            },
453            Self::Lch(origin_color, _, _, _, alpha) => {
454                dest.write_str("lch(")?;
455                (origin_color, alpha)
456            },
457            Self::Oklab(origin_color, _, _, _, alpha) => {
458                dest.write_str("oklab(")?;
459                (origin_color, alpha)
460            },
461            Self::Oklch(origin_color, _, _, _, alpha) => {
462                dest.write_str("oklch(")?;
463                (origin_color, alpha)
464            },
465            Self::Color(origin_color, _, _, _, alpha, _) => {
466                dest.write_str("color(")?;
467                (origin_color, alpha)
468            },
469        };
470
471        if let Optional::Some(origin_color) = origin_color {
472            dest.write_str("from ")?;
473            origin_color.to_css(dest)?;
474            dest.write_str(" ")?;
475        }
476
477        let is_opaque = if let ColorComponent::Value(value) = *alpha {
478            value.to_number(OPAQUE) == OPAQUE
479        } else {
480            false
481        };
482
483        macro_rules! serialize_alpha {
484            ($alpha_component:expr) => {{
485                if !is_opaque && !matches!($alpha_component, ColorComponent::AlphaOmitted) {
486                    dest.write_str(" / ")?;
487                    $alpha_component.to_css(dest)?;
488                }
489            }};
490        }
491
492        macro_rules! serialize_components {
493            ($c0:expr, $c1:expr, $c2:expr) => {{
494                debug_assert!(!matches!($c0, ColorComponent::AlphaOmitted));
495                debug_assert!(!matches!($c1, ColorComponent::AlphaOmitted));
496                debug_assert!(!matches!($c2, ColorComponent::AlphaOmitted));
497
498                $c0.to_css(dest)?;
499                dest.write_str(" ")?;
500                $c1.to_css(dest)?;
501                dest.write_str(" ")?;
502                $c2.to_css(dest)?;
503            }};
504        }
505
506        match self {
507            Self::Rgb(_, c0, c1, c2, alpha) => {
508                serialize_components!(c0, c1, c2);
509                serialize_alpha!(alpha);
510            },
511            Self::Hsl(_, c0, c1, c2, alpha) => {
512                serialize_components!(c0, c1, c2);
513                serialize_alpha!(alpha);
514            },
515            Self::Hwb(_, c0, c1, c2, alpha) => {
516                serialize_components!(c0, c1, c2);
517                serialize_alpha!(alpha);
518            },
519            Self::Lab(_, c0, c1, c2, alpha) => {
520                serialize_components!(c0, c1, c2);
521                serialize_alpha!(alpha);
522            },
523            Self::Lch(_, c0, c1, c2, alpha) => {
524                serialize_components!(c0, c1, c2);
525                serialize_alpha!(alpha);
526            },
527            Self::Oklab(_, c0, c1, c2, alpha) => {
528                serialize_components!(c0, c1, c2);
529                serialize_alpha!(alpha);
530            },
531            Self::Oklch(_, c0, c1, c2, alpha) => {
532                serialize_components!(c0, c1, c2);
533                serialize_alpha!(alpha);
534            },
535            Self::Color(_, c0, c1, c2, alpha, color_space) => {
536                color_space.to_css(dest)?;
537                dest.write_str(" ")?;
538                serialize_components!(c0, c1, c2);
539                serialize_alpha!(alpha);
540            },
541        }
542
543        dest.write_str(")")
544    }
545}