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::A98Rgb
132                    | ColorSpace::ProphotoRgb
133                    | ColorSpace::Rec2020
134                    | ColorSpace::XyzD50
135                    | ColorSpace::XyzD65 => {
136                        // These color spaces are allowed.
137                    },
138                    _ => {
139                        unreachable!("other color spaces do not support color() syntax")
140                    },
141                };
142
143                dest.write_str("color(")?;
144                self.color_space.to_css(dest)?;
145                dest.write_char(' ')?;
146                ModernComponent(&self.c0()).to_css(dest)?;
147                dest.write_char(' ')?;
148                ModernComponent(&self.c1()).to_css(dest)?;
149                dest.write_char(' ')?;
150                ModernComponent(&self.c2()).to_css(dest)?;
151
152                serialize_color_alpha(dest, self.alpha(), false)?;
153
154                dest.write_char(')')
155            },
156        }
157    }
158}
159
160impl AbsoluteColor {
161    /// Write a string to `dest` that represents a color as an author would
162    /// enter it.
163    /// NOTE: The format of the output is NOT according to any specification,
164    /// but makes assumptions about the best ways that authors would want to
165    /// enter color values in style sheets, devtools, etc.
166    pub fn write_author_preferred_value<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
167    where
168        W: Write,
169    {
170        macro_rules! precision {
171            ($v:expr) => {{
172                ($v * 100.0).round() / 100.0
173            }};
174        }
175        macro_rules! number {
176            ($c:expr) => {{
177                if let Some(v) = $c.map(normalize) {
178                    precision!(v).to_css(dest)?;
179                } else {
180                    write!(dest, "none")?;
181                }
182            }};
183        }
184        macro_rules! percentage {
185            ($c:expr) => {{
186                if let Some(v) = $c.map(normalize) {
187                    precision!(v).to_css(dest)?;
188                    dest.write_char('%')?;
189                } else {
190                    write!(dest, "none")?;
191                }
192            }};
193        }
194        macro_rules! unit_percentage {
195            ($c:expr) => {{
196                if let Some(v) = $c.map(normalize) {
197                    precision!(v * 100.0).to_css(dest)?;
198                    dest.write_char('%')?;
199                } else {
200                    write!(dest, "none")?;
201                }
202            }};
203        }
204        macro_rules! angle {
205            ($c:expr) => {{
206                if let Some(v) = $c.map(normalize) {
207                    precision!(v).to_css(dest)?;
208                    dest.write_str("deg")?;
209                } else {
210                    write!(dest, "none")?;
211                }
212            }};
213        }
214
215        match self.color_space {
216            ColorSpace::Srgb => {
217                write!(dest, "rgb(")?;
218                unit_percentage!(self.c0());
219                dest.write_char(' ')?;
220                unit_percentage!(self.c1());
221                dest.write_char(' ')?;
222                unit_percentage!(self.c2());
223                serialize_color_alpha(dest, self.alpha(), false)?;
224                dest.write_char(')')
225            },
226            ColorSpace::Hsl | ColorSpace::Hwb => {
227                dest.write_str(if self.color_space == ColorSpace::Hsl {
228                    "hsl("
229                } else {
230                    "hwb("
231                })?;
232                angle!(self.c0());
233                dest.write_char(' ')?;
234                percentage!(self.c1());
235                dest.write_char(' ')?;
236                percentage!(self.c2());
237                serialize_color_alpha(dest, self.alpha(), false)?;
238                dest.write_char(')')
239            },
240            ColorSpace::Lab | ColorSpace::Oklab => {
241                if self.color_space == ColorSpace::Oklab {
242                    dest.write_str("ok")?;
243                }
244                dest.write_str("lab(")?;
245                if self.color_space == ColorSpace::Lab {
246                    percentage!(self.c0())
247                } else {
248                    unit_percentage!(self.c0())
249                }
250                dest.write_char(' ')?;
251                number!(self.c1());
252                dest.write_char(' ')?;
253                number!(self.c2());
254                serialize_color_alpha(dest, self.alpha(), false)?;
255                dest.write_char(')')
256            },
257            ColorSpace::Lch | ColorSpace::Oklch => {
258                if self.color_space == ColorSpace::Oklch {
259                    dest.write_str("ok")?;
260                }
261                dest.write_str("lch(")?;
262                number!(self.c0());
263                dest.write_char(' ')?;
264                number!(self.c1());
265                dest.write_char(' ')?;
266                angle!(self.c2());
267                serialize_color_alpha(dest, self.alpha(), false)?;
268                dest.write_char(')')
269            },
270            ColorSpace::SrgbLinear
271            | ColorSpace::DisplayP3
272            | ColorSpace::A98Rgb
273            | ColorSpace::ProphotoRgb
274            | ColorSpace::Rec2020
275            | ColorSpace::XyzD50
276            | ColorSpace::XyzD65 => {
277                dest.write_str("color(")?;
278                self.color_space.to_css(dest)?;
279                dest.write_char(' ')?;
280                number!(self.c0());
281                dest.write_char(' ')?;
282                number!(self.c1());
283                dest.write_char(' ')?;
284                number!(self.c2());
285                serialize_color_alpha(dest, self.alpha(), false)?;
286                dest.write_char(')')
287            },
288        }
289    }
290}