style/color/
to_css.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//! Write colors into CSS strings.
6
7use super::{
8    parsing::{NumberOrAngleComponent, NumberOrPercentageComponent},
9    AbsoluteColor, ColorFlags, ColorSpace,
10};
11use crate::values::normalize;
12use cssparser::color::{clamp_unit_f32, serialize_color_alpha, OPAQUE};
13use std::fmt::{self, Write};
14use style_traits::{CssWriter, ToCss};
15
16/// A [`ModernComponent`] can serialize to `none`, `nan`, `infinity` and
17/// floating point values.
18struct ModernComponent<'a>(&'a Option<f32>);
19
20impl<'a> ToCss for ModernComponent<'a> {
21    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
22    where
23        W: fmt::Write,
24    {
25        if let Some(value) = self.0 {
26            if value.is_finite() {
27                value.to_css(dest)
28            } else if value.is_nan() {
29                dest.write_str("calc(NaN)")
30            } else {
31                debug_assert!(value.is_infinite());
32                if value.is_sign_negative() {
33                    dest.write_str("calc(-infinity)")
34                } else {
35                    dest.write_str("calc(infinity)")
36                }
37            }
38        } else {
39            dest.write_str("none")
40        }
41    }
42}
43
44impl ToCss for NumberOrPercentageComponent {
45    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
46    where
47        W: Write,
48    {
49        use crate::values::computed::Percentage;
50
51        match self {
52            Self::Number(number) => number.to_css(dest)?,
53            Self::Percentage(percentage) => Percentage(*percentage).to_css(dest)?,
54        }
55        Ok(())
56    }
57}
58
59impl ToCss for NumberOrAngleComponent {
60    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
61    where
62        W: Write,
63    {
64        use crate::values::computed::Angle;
65
66        match self {
67            Self::Number(number) => number.to_css(dest)?,
68            Self::Angle(degrees) => Angle::from_degrees(*degrees).to_css(dest)?,
69        }
70        Ok(())
71    }
72}
73
74impl ToCss for AbsoluteColor {
75    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
76    where
77        W: Write,
78    {
79        match self.color_space {
80            ColorSpace::Srgb if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) => {
81                // The "none" keyword is not supported in the rgb/rgba legacy syntax.
82                let has_alpha = self.alpha != OPAQUE;
83
84                dest.write_str(if has_alpha { "rgba(" } else { "rgb(" })?;
85                clamp_unit_f32(self.components.0).to_css(dest)?;
86                dest.write_str(", ")?;
87                clamp_unit_f32(self.components.1).to_css(dest)?;
88                dest.write_str(", ")?;
89                clamp_unit_f32(self.components.2).to_css(dest)?;
90
91                // Legacy syntax does not allow none components.
92                serialize_color_alpha(dest, Some(self.alpha), true)?;
93
94                dest.write_char(')')
95            },
96            ColorSpace::Hsl | ColorSpace::Hwb => {
97                if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) {
98                    self.into_srgb_legacy().to_css(dest)
99                } else {
100                    self.to_color_space(ColorSpace::Srgb).to_css(dest)
101                }
102            },
103            ColorSpace::Oklab | ColorSpace::Lab | ColorSpace::Oklch | ColorSpace::Lch => {
104                if let ColorSpace::Oklab | ColorSpace::Oklch = self.color_space {
105                    dest.write_str("ok")?;
106                }
107                if let ColorSpace::Oklab | ColorSpace::Lab = self.color_space {
108                    dest.write_str("lab(")?;
109                } else {
110                    dest.write_str("lch(")?;
111                }
112                ModernComponent(&self.c0()).to_css(dest)?;
113                dest.write_char(' ')?;
114                ModernComponent(&self.c1()).to_css(dest)?;
115                dest.write_char(' ')?;
116                ModernComponent(&self.c2()).to_css(dest)?;
117                serialize_color_alpha(dest, self.alpha(), false)?;
118                dest.write_char(')')
119            },
120            _ => {
121                #[cfg(debug_assertions)]
122                match self.color_space {
123                    ColorSpace::Srgb => {
124                        debug_assert!(
125                            !self.flags.contains(ColorFlags::IS_LEGACY_SRGB),
126                            "legacy srgb is not a color function"
127                        );
128                    },
129                    ColorSpace::SrgbLinear
130                    | ColorSpace::DisplayP3
131                    | ColorSpace::DisplayP3Linear
132                    | ColorSpace::A98Rgb
133                    | ColorSpace::ProphotoRgb
134                    | ColorSpace::Rec2020
135                    | ColorSpace::XyzD50
136                    | ColorSpace::XyzD65 => {
137                        // These color spaces are allowed.
138                    },
139                    ColorSpace::Hsl
140                    | ColorSpace::Hwb
141                    | ColorSpace::Lab
142                    | ColorSpace::Oklab
143                    | ColorSpace::Lch
144                    | ColorSpace::Oklch => {
145                        unreachable!("other color spaces do not support color() syntax")
146                    },
147                };
148
149                dest.write_str("color(")?;
150                self.color_space.to_css(dest)?;
151                dest.write_char(' ')?;
152                ModernComponent(&self.c0()).to_css(dest)?;
153                dest.write_char(' ')?;
154                ModernComponent(&self.c1()).to_css(dest)?;
155                dest.write_char(' ')?;
156                ModernComponent(&self.c2()).to_css(dest)?;
157
158                serialize_color_alpha(dest, self.alpha(), false)?;
159
160                dest.write_char(')')
161            },
162        }
163    }
164}
165
166impl AbsoluteColor {
167    /// Write a string to `dest` that represents a color as an author would
168    /// enter it.
169    /// NOTE: The format of the output is NOT according to any specification,
170    /// but makes assumptions about the best ways that authors would want to
171    /// enter color values in style sheets, devtools, etc.
172    pub fn write_author_preferred_value<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
173    where
174        W: Write,
175    {
176        macro_rules! precision {
177            ($v:expr) => {{
178                ($v * 100.0).round() / 100.0
179            }};
180        }
181        macro_rules! number {
182            ($c:expr) => {{
183                if let Some(v) = $c.map(normalize) {
184                    precision!(v).to_css(dest)?;
185                } else {
186                    write!(dest, "none")?;
187                }
188            }};
189        }
190        macro_rules! percentage {
191            ($c:expr) => {{
192                if let Some(v) = $c.map(normalize) {
193                    precision!(v).to_css(dest)?;
194                    dest.write_char('%')?;
195                } else {
196                    write!(dest, "none")?;
197                }
198            }};
199        }
200        macro_rules! unit_percentage {
201            ($c:expr) => {{
202                if let Some(v) = $c.map(normalize) {
203                    precision!(v * 100.0).to_css(dest)?;
204                    dest.write_char('%')?;
205                } else {
206                    write!(dest, "none")?;
207                }
208            }};
209        }
210        macro_rules! angle {
211            ($c:expr) => {{
212                if let Some(v) = $c.map(normalize) {
213                    precision!(v).to_css(dest)?;
214                    dest.write_str("deg")?;
215                } else {
216                    write!(dest, "none")?;
217                }
218            }};
219        }
220
221        match self.color_space {
222            ColorSpace::Srgb => {
223                write!(dest, "rgb(")?;
224                unit_percentage!(self.c0());
225                dest.write_char(' ')?;
226                unit_percentage!(self.c1());
227                dest.write_char(' ')?;
228                unit_percentage!(self.c2());
229                serialize_color_alpha(dest, self.alpha(), false)?;
230                dest.write_char(')')
231            },
232            ColorSpace::Hsl | ColorSpace::Hwb => {
233                dest.write_str(if self.color_space == ColorSpace::Hsl {
234                    "hsl("
235                } else {
236                    "hwb("
237                })?;
238                angle!(self.c0());
239                dest.write_char(' ')?;
240                percentage!(self.c1());
241                dest.write_char(' ')?;
242                percentage!(self.c2());
243                serialize_color_alpha(dest, self.alpha(), false)?;
244                dest.write_char(')')
245            },
246            ColorSpace::Lab | ColorSpace::Oklab => {
247                if self.color_space == ColorSpace::Oklab {
248                    dest.write_str("ok")?;
249                }
250                dest.write_str("lab(")?;
251                if self.color_space == ColorSpace::Lab {
252                    percentage!(self.c0())
253                } else {
254                    unit_percentage!(self.c0())
255                }
256                dest.write_char(' ')?;
257                number!(self.c1());
258                dest.write_char(' ')?;
259                number!(self.c2());
260                serialize_color_alpha(dest, self.alpha(), false)?;
261                dest.write_char(')')
262            },
263            ColorSpace::Lch | ColorSpace::Oklch => {
264                if self.color_space == ColorSpace::Oklch {
265                    dest.write_str("ok")?;
266                }
267                dest.write_str("lch(")?;
268                number!(self.c0());
269                dest.write_char(' ')?;
270                number!(self.c1());
271                dest.write_char(' ')?;
272                angle!(self.c2());
273                serialize_color_alpha(dest, self.alpha(), false)?;
274                dest.write_char(')')
275            },
276            ColorSpace::SrgbLinear
277            | ColorSpace::DisplayP3
278            | ColorSpace::DisplayP3Linear
279            | ColorSpace::A98Rgb
280            | ColorSpace::ProphotoRgb
281            | ColorSpace::Rec2020
282            | ColorSpace::XyzD50
283            | ColorSpace::XyzD65 => {
284                dest.write_str("color(")?;
285                self.color_space.to_css(dest)?;
286                dest.write_char(' ')?;
287                number!(self.c0());
288                dest.write_char(' ')?;
289                number!(self.c1());
290                dest.write_char(' ')?;
291                number!(self.c2());
292                serialize_color_alpha(dest, self.alpha(), false)?;
293                dest.write_char(')')
294            },
295        }
296    }
297}