Skip to main content

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    AbsoluteColor, ColorFlags, ColorSpace,
11    component::ColorComponent,
12    convert::normalize_hue,
13    parsing::{NumberOrAngleComponent, NumberOrPercentageComponent},
14};
15use crate::derives::*;
16use crate::values::{
17    computed, computed::color::Color as ComputedColor, generics::Optional, normalize,
18    specified::color::Color as SpecifiedColor,
19};
20use cssparser::color::{OPAQUE, clamp_floor_256_f32};
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    /// <https://drafts.csswg.org/css-color-5/#relative-alpha>
92    Alpha(
93        OriginColor,                                 // origin
94        ColorComponent<NumberOrPercentageComponent>, // alpha
95    ),
96}
97
98impl ColorFunction<SpecifiedColor> {
99    /// Return true if the color function has an origin color specified.
100    pub fn has_origin_color(&self) -> bool {
101        self.origin_color().is_some()
102    }
103
104    /// Try to compute the color function to a computed color.
105    ///
106    /// If the origin color (if any) is absolute, the function is resolved to an
107    /// absolute color in a single pass over the components; otherwise it is
108    /// computed and preserved so it can be resolved at use-value time.
109    pub fn to_computed_color(
110        &self,
111        context: Option<&computed::Context>,
112    ) -> Result<ComputedColor, ()> {
113        // Compute the origin color (if any) once.
114        let origin = match self.origin_color() {
115            Some(o) => Some(o.to_computed_color(context)?),
116            None => None,
117        };
118        // We can only resolve to an absolute color if there is no origin color or
119        // it is already absolute.
120        let resolvable = origin.as_ref().is_none_or(|o| o.is_absolute());
121
122        let computed = self.to_computed_value(context, origin)?;
123        if resolvable && let Ok(absolute) = computed.to_absolute_color() {
124            return Ok(ComputedColor::Absolute(absolute));
125        }
126
127        Ok(ComputedColor::ColorFunction(Box::new(computed)))
128    }
129}
130
131impl<Color> ColorFunction<Color> {
132    /// Map the origin color to another type.
133    pub fn map_origin_color<U>(
134        &self,
135        f: impl FnOnce(&Color) -> Result<U, ()>,
136    ) -> Result<ColorFunction<U>, ()> {
137        macro_rules! map {
138            ($f:ident, $o:expr, $c0:expr, $c1:expr, $c2:expr, $alpha:expr) => {{
139                ColorFunction::$f(
140                    match $o.as_ref() {
141                        Some(c) => Some(f(c)?),
142                        None => None,
143                    }
144                    .into(),
145                    $c0.clone(),
146                    $c1.clone(),
147                    $c2.clone(),
148                    $alpha.clone(),
149                )
150            }};
151        }
152        Ok(match self {
153            ColorFunction::Rgb(o, c0, c1, c2, alpha) => map!(Rgb, o, c0, c1, c2, alpha),
154            ColorFunction::Hsl(o, c0, c1, c2, alpha) => map!(Hsl, o, c0, c1, c2, alpha),
155            ColorFunction::Hwb(o, c0, c1, c2, alpha) => map!(Hwb, o, c0, c1, c2, alpha),
156            ColorFunction::Lab(o, c0, c1, c2, alpha) => map!(Lab, o, c0, c1, c2, alpha),
157            ColorFunction::Lch(o, c0, c1, c2, alpha) => map!(Lch, o, c0, c1, c2, alpha),
158            ColorFunction::Oklab(o, c0, c1, c2, alpha) => map!(Oklab, o, c0, c1, c2, alpha),
159            ColorFunction::Oklch(o, c0, c1, c2, alpha) => map!(Oklch, o, c0, c1, c2, alpha),
160            ColorFunction::Color(o, c0, c1, c2, alpha, color_space) => ColorFunction::Color(
161                match o.as_ref() {
162                    Some(c) => Some(f(c)?),
163                    None => None,
164                }
165                .into(),
166                c0.clone(),
167                c1.clone(),
168                c2.clone(),
169                alpha.clone(),
170                *color_space,
171            ),
172            ColorFunction::Alpha(o, alpha) => ColorFunction::Alpha(f(o)?, alpha.clone()),
173        })
174    }
175
176    /// Returns the origin color of this color function, if it has one.
177    pub fn origin_color(&self) -> Option<&Color> {
178        match self {
179            Self::Rgb(o, ..)
180            | Self::Hsl(o, ..)
181            | Self::Hwb(o, ..)
182            | Self::Lab(o, ..)
183            | Self::Lch(o, ..)
184            | Self::Oklab(o, ..)
185            | Self::Oklch(o, ..)
186            | Self::Color(o, ..) => o.as_ref(),
187            Self::Alpha(o, ..) => Some(o),
188        }
189    }
190
191    /// `origin` is the (already computed) origin color, if any.
192    fn to_computed_value(
193        &self,
194        context: Option<&computed::Context>,
195        origin: Option<ComputedColor>,
196    ) -> Result<ColorFunction<ComputedColor>, ()> {
197        // The absolute origin color, if available, used to substitute channels.
198        let abs_origin = origin.as_ref().and_then(|o| o.as_absolute());
199        // Builds a variant where the origin is converted to `$space` (the
200        // function's color space) before its channels are read.
201        macro_rules! convert {
202            ($variant:ident, $space:expr, $c0:expr, $c1:expr, $c2:expr, $alpha:expr) => {{
203                let converted = abs_origin.map(|o| o.to_color_space($space));
204                ColorFunction::$variant(
205                    Optional::from(origin),
206                    $c0.to_computed_value(context, converted.as_ref())?,
207                    $c1.to_computed_value(context, converted.as_ref())?,
208                    $c2.to_computed_value(context, converted.as_ref())?,
209                    $alpha.to_computed_value(context, converted.as_ref())?,
210                )
211            }};
212        }
213
214        Ok(match self {
215            ColorFunction::Rgb(_, r, g, b, alpha) => {
216                // rgb(..) channels are in the [0..255] range, so map the origin's
217                // components accordingly.
218                let converted = abs_origin.map(|o| {
219                    let o = o.to_color_space(ColorSpace::Srgb);
220                    AbsoluteColor::new(
221                        ColorSpace::Srgb,
222                        o.c0().map(|v| v * 255.0),
223                        o.c1().map(|v| v * 255.0),
224                        o.c2().map(|v| v * 255.0),
225                        o.alpha(),
226                    )
227                });
228                ColorFunction::Rgb(
229                    Optional::from(origin),
230                    r.to_computed_value(context, converted.as_ref())?,
231                    g.to_computed_value(context, converted.as_ref())?,
232                    b.to_computed_value(context, converted.as_ref())?,
233                    alpha.to_computed_value(context, converted.as_ref())?,
234                )
235            },
236            ColorFunction::Hsl(_, c0, c1, c2, alpha) => {
237                convert!(Hsl, ColorSpace::Hsl, c0, c1, c2, alpha)
238            },
239            ColorFunction::Hwb(_, c0, c1, c2, alpha) => {
240                convert!(Hwb, ColorSpace::Hwb, c0, c1, c2, alpha)
241            },
242            ColorFunction::Lab(_, c0, c1, c2, alpha) => {
243                convert!(Lab, ColorSpace::Lab, c0, c1, c2, alpha)
244            },
245            ColorFunction::Lch(_, c0, c1, c2, alpha) => {
246                convert!(Lch, ColorSpace::Lch, c0, c1, c2, alpha)
247            },
248            ColorFunction::Oklab(_, c0, c1, c2, alpha) => {
249                convert!(Oklab, ColorSpace::Oklab, c0, c1, c2, alpha)
250            },
251            ColorFunction::Oklch(_, c0, c1, c2, alpha) => {
252                convert!(Oklch, ColorSpace::Oklch, c0, c1, c2, alpha)
253            },
254            ColorFunction::Color(_, c0, c1, c2, alpha, color_space) => {
255                let converted = abs_origin.map(|o| {
256                    let mut result = o.to_color_space(*color_space);
257                    // Drop the legacy flag so the origin is read as a `color()`.
258                    result.flags.set(ColorFlags::IS_LEGACY_SRGB, false);
259                    result
260                });
261                ColorFunction::Color(
262                    Optional::from(origin),
263                    c0.to_computed_value(context, converted.as_ref())?,
264                    c1.to_computed_value(context, converted.as_ref())?,
265                    c2.to_computed_value(context, converted.as_ref())?,
266                    alpha.to_computed_value(context, converted.as_ref())?,
267                    *color_space,
268                )
269            },
270            ColorFunction::Alpha(_, alpha) => {
271                let alpha = alpha.to_computed_value(context, abs_origin)?;
272                let stored = origin.expect("alpha() is always relative");
273                ColorFunction::Alpha(stored, alpha)
274            },
275        })
276    }
277}
278
279impl ColorFunction<ComputedColor> {
280    fn to_absolute_color(&self) -> Result<AbsoluteColor, ()> {
281        macro_rules! alpha {
282            ($alpha:expr) => {{
283                $alpha
284                    .resolve()?
285                    .map(|value| normalize(value.to_number(1.0)).clamp(0.0, OPAQUE))
286            }};
287        }
288
289        Ok(match self {
290            ColorFunction::Rgb(origin_color, r, g, b, alpha) => {
291                // Use `color(srgb ...)` to serialize `rgb(...)` if an origin color is available;
292                // this is the only reason for now.
293                let use_color_syntax = origin_color.is_some();
294
295                if use_color_syntax {
296                    // The components have already been mapped into the [0..255]
297                    // range against the origin, so map them back to [0..1).
298                    AbsoluteColor::new(
299                        ColorSpace::Srgb,
300                        r.resolve()?.map(|c| c.to_number(255.0) / 255.0),
301                        g.resolve()?.map(|c| c.to_number(255.0) / 255.0),
302                        b.resolve()?.map(|c| c.to_number(255.0) / 255.0),
303                        alpha!(alpha),
304                    )
305                } else {
306                    // Resolve a component to its legacy sRGB value in [0..1], preserving
307                    // `none` as `None` so the `*_IS_NONE` flags are kept on the color. Legacy
308                    // syntax cannot serialize `none`, but the flags are still needed to carry
309                    // missing components forward through interpolation (e.g. `color-mix`).
310                    #[inline]
311                    fn resolve(
312                        component: &ColorComponent<NumberOrPercentageComponent>,
313                    ) -> Result<Option<f32>, ()> {
314                        Ok(component.resolve()?.map(|value| {
315                            clamp_floor_256_f32(value.to_number(u8::MAX as f32)) as f32 / 255.0
316                        }))
317                    }
318
319                    let mut result = AbsoluteColor::new(
320                        ColorSpace::Srgb,
321                        resolve(r)?,
322                        resolve(g)?,
323                        resolve(b)?,
324                        alpha!(alpha),
325                    );
326                    result.flags.insert(ColorFlags::IS_LEGACY_SRGB);
327                    result
328                }
329            },
330            ColorFunction::Hsl(origin_color, h, s, l, alpha) => {
331                // Percent reference range for S and L: 0% = 0.0, 100% = 100.0
332                const LIGHTNESS_RANGE: f32 = 100.0;
333                const SATURATION_RANGE: f32 = 100.0;
334
335                // If the origin color was *NOT* specified, then we stick with the
336                // old way of serializing the value to rgb(..). Otherwise we don't
337                // use the rgb(..) syntax, because we should allow the color to be
338                // out of gamut and not clamp.
339                let use_rgb_sytax = origin_color.is_none();
340
341                let mut result = AbsoluteColor::new(
342                    ColorSpace::Hsl,
343                    h.resolve()?.map(|angle| normalize_hue(angle.degrees())),
344                    s.resolve()?.map(|s| {
345                        if use_rgb_sytax {
346                            s.to_number(SATURATION_RANGE).clamp(0.0, SATURATION_RANGE)
347                        } else {
348                            s.to_number(SATURATION_RANGE)
349                        }
350                    }),
351                    l.resolve()?.map(|l| {
352                        if use_rgb_sytax {
353                            l.to_number(LIGHTNESS_RANGE).clamp(0.0, LIGHTNESS_RANGE)
354                        } else {
355                            l.to_number(LIGHTNESS_RANGE)
356                        }
357                    }),
358                    alpha!(alpha),
359                );
360
361                if use_rgb_sytax {
362                    result.flags.insert(ColorFlags::IS_LEGACY_SRGB);
363                }
364
365                result
366            },
367            ColorFunction::Hwb(origin_color, h, w, b, alpha) => {
368                let use_rgb_sytax = origin_color.is_none();
369
370                // Percent reference range for W and B: 0% = 0.0, 100% = 100.0
371                const WHITENESS_RANGE: f32 = 100.0;
372                const BLACKNESS_RANGE: f32 = 100.0;
373
374                let mut result = AbsoluteColor::new(
375                    ColorSpace::Hwb,
376                    h.resolve()?.map(|angle| normalize_hue(angle.degrees())),
377                    w.resolve()?.map(|w| w.to_number(WHITENESS_RANGE)),
378                    b.resolve()?.map(|b| b.to_number(BLACKNESS_RANGE)),
379                    alpha!(alpha),
380                );
381
382                if use_rgb_sytax {
383                    result.flags.insert(ColorFlags::IS_LEGACY_SRGB);
384                }
385
386                result
387            },
388            ColorFunction::Lab(_, l, a, b, alpha) => {
389                // for L: 0% = 0.0, 100% = 100.0
390                // for a and b: -100% = -125, 100% = 125
391                const LIGHTNESS_RANGE: f32 = 100.0;
392                const A_B_RANGE: f32 = 125.0;
393
394                AbsoluteColor::new(
395                    ColorSpace::Lab,
396                    l.resolve()?.map(|l| l.to_number(LIGHTNESS_RANGE)),
397                    a.resolve()?.map(|a| a.to_number(A_B_RANGE)),
398                    b.resolve()?.map(|b| b.to_number(A_B_RANGE)),
399                    alpha!(alpha),
400                )
401            },
402            ColorFunction::Lch(_, l, c, h, alpha) => {
403                // for L: 0% = 0.0, 100% = 100.0
404                // for C: 0% = 0, 100% = 150
405                const LIGHTNESS_RANGE: f32 = 100.0;
406                const CHROMA_RANGE: f32 = 150.0;
407
408                AbsoluteColor::new(
409                    ColorSpace::Lch,
410                    l.resolve()?.map(|l| l.to_number(LIGHTNESS_RANGE)),
411                    c.resolve()?.map(|c| c.to_number(CHROMA_RANGE)),
412                    h.resolve()?.map(|angle| normalize_hue(angle.degrees())),
413                    alpha!(alpha),
414                )
415            },
416            ColorFunction::Oklab(_, l, a, b, alpha) => {
417                // for L: 0% = 0.0, 100% = 1.0
418                // for a and b: -100% = -0.4, 100% = 0.4
419                const LIGHTNESS_RANGE: f32 = 1.0;
420                const A_B_RANGE: f32 = 0.4;
421
422                AbsoluteColor::new(
423                    ColorSpace::Oklab,
424                    l.resolve()?.map(|l| l.to_number(LIGHTNESS_RANGE)),
425                    a.resolve()?.map(|a| a.to_number(A_B_RANGE)),
426                    b.resolve()?.map(|b| b.to_number(A_B_RANGE)),
427                    alpha!(alpha),
428                )
429            },
430            ColorFunction::Oklch(_, l, c, h, alpha) => {
431                // for L: 0% = 0.0, 100% = 1.0
432                // for C: 0% = 0.0 100% = 0.4
433                const LIGHTNESS_RANGE: f32 = 1.0;
434                const CHROMA_RANGE: f32 = 0.4;
435
436                AbsoluteColor::new(
437                    ColorSpace::Oklch,
438                    l.resolve()?.map(|l| l.to_number(LIGHTNESS_RANGE)),
439                    c.resolve()?.map(|c| c.to_number(CHROMA_RANGE)),
440                    h.resolve()?.map(|angle| normalize_hue(angle.degrees())),
441                    alpha!(alpha),
442                )
443            },
444            ColorFunction::Color(_, r, g, b, alpha, color_space) => AbsoluteColor::new(
445                *color_space,
446                r.resolve()?.map(|c| c.to_number(1.0)),
447                g.resolve()?.map(|c| c.to_number(1.0)),
448                b.resolve()?.map(|c| c.to_number(1.0)),
449                alpha!(alpha),
450            ),
451            ColorFunction::Alpha(origin_color, alpha) => {
452                let origin_color = origin_color.as_absolute().ok_or(())?;
453                origin_color.with_alpha(alpha!(alpha))
454            },
455        })
456    }
457
458    /// Resolve a computed color function to an absolute computed color.
459    pub fn resolve_to_absolute(&self, current_color: &AbsoluteColor) -> AbsoluteColor {
460        // Resolve the origin color (e.g. currentcolor) to an absolute color, then
461        // substitute and assemble.
462        let origin = self
463            .origin_color()
464            .map(|o| ComputedColor::Absolute(o.resolve_to_absolute(current_color)));
465        self.to_computed_value(None, origin)
466            .and_then(|r| r.to_absolute_color())
467            .unwrap_or_else(|_| {
468                debug_assert!(
469                    false,
470                    "the color could not be resolved even with a currentcolor specified?"
471                );
472                AbsoluteColor::TRANSPARENT_BLACK
473            })
474    }
475}
476
477impl<C: style_traits::ToCss> style_traits::ToCss for ColorFunction<C> {
478    fn to_css<W>(&self, dest: &mut style_traits::CssWriter<W>) -> std::fmt::Result
479    where
480        W: std::fmt::Write,
481    {
482        let (origin_color, alpha, trailing_space) = match self {
483            Self::Rgb(origin_color, _, _, _, alpha) => {
484                dest.write_str("rgb(")?;
485                (origin_color.as_ref(), alpha, true)
486            },
487            Self::Hsl(origin_color, _, _, _, alpha) => {
488                dest.write_str("hsl(")?;
489                (origin_color.as_ref(), alpha, true)
490            },
491            Self::Hwb(origin_color, _, _, _, alpha) => {
492                dest.write_str("hwb(")?;
493                (origin_color.as_ref(), alpha, true)
494            },
495            Self::Lab(origin_color, _, _, _, alpha) => {
496                dest.write_str("lab(")?;
497                (origin_color.as_ref(), alpha, true)
498            },
499            Self::Lch(origin_color, _, _, _, alpha) => {
500                dest.write_str("lch(")?;
501                (origin_color.as_ref(), alpha, true)
502            },
503            Self::Oklab(origin_color, _, _, _, alpha) => {
504                dest.write_str("oklab(")?;
505                (origin_color.as_ref(), alpha, true)
506            },
507            Self::Oklch(origin_color, _, _, _, alpha) => {
508                dest.write_str("oklch(")?;
509                (origin_color.as_ref(), alpha, true)
510            },
511            Self::Color(origin_color, _, _, _, alpha, _) => {
512                dest.write_str("color(")?;
513                (origin_color.as_ref(), alpha, true)
514            },
515            Self::Alpha(origin_color, alpha) => {
516                dest.write_str("alpha(")?;
517                (Some(origin_color), alpha, false)
518            },
519        };
520
521        if let Some(origin_color) = origin_color {
522            dest.write_str("from ")?;
523            origin_color.to_css(dest)?;
524            if trailing_space {
525                dest.write_str(" ")?;
526            }
527        }
528
529        macro_rules! serialize_components {
530            ($c0:expr, $c1:expr, $c2:expr) => {{
531                debug_assert!(!matches!($c0, ColorComponent::AlphaOmitted));
532                debug_assert!(!matches!($c1, ColorComponent::AlphaOmitted));
533                debug_assert!(!matches!($c2, ColorComponent::AlphaOmitted));
534
535                $c0.to_css(dest)?;
536                dest.write_str(" ")?;
537                $c1.to_css(dest)?;
538                dest.write_str(" ")?;
539                $c2.to_css(dest)?;
540            }};
541        }
542
543        match self {
544            Self::Rgb(_, c0, c1, c2, _) => {
545                serialize_components!(c0, c1, c2);
546            },
547            Self::Hsl(_, c0, c1, c2, _) => {
548                serialize_components!(c0, c1, c2);
549            },
550            Self::Hwb(_, c0, c1, c2, _) => {
551                serialize_components!(c0, c1, c2);
552            },
553            Self::Lab(_, c0, c1, c2, _) => {
554                serialize_components!(c0, c1, c2);
555            },
556            Self::Lch(_, c0, c1, c2, _) => {
557                serialize_components!(c0, c1, c2);
558            },
559            Self::Oklab(_, c0, c1, c2, _) => {
560                serialize_components!(c0, c1, c2);
561            },
562            Self::Oklch(_, c0, c1, c2, _) => {
563                serialize_components!(c0, c1, c2);
564            },
565            Self::Color(_, c0, c1, c2, _, color_space) => {
566                color_space.to_css(dest)?;
567                dest.write_str(" ")?;
568                serialize_components!(c0, c1, c2);
569            },
570            Self::Alpha(_, _) => {},
571        }
572
573        // We can avoid serializing the alpha if it's 1, but only if the color is not relative
574        // (since otherwise it's different from the omitted alpha).
575        let omit_alpha = match *alpha {
576            ColorComponent::AlphaOmitted => true,
577            ColorComponent::Value(ref v) => origin_color.is_none() && v.to_number(OPAQUE) == OPAQUE,
578            _ => false,
579        };
580
581        if !omit_alpha {
582            dest.write_str(" / ")?;
583            alpha.to_css(dest)?;
584        }
585
586        dest.write_str(")")
587    }
588}