1use 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
16struct 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 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 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 },
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 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}