1use super::{
8 AbsoluteColor, ColorFlags, ColorSpace,
9 parsing::{NumberOrAngleComponent, NumberOrPercentageComponent},
10};
11use crate::values::normalize;
12use cssparser::color::{OPAQUE, clamp_unit_f32, serialize_color_alpha};
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.intersects(ColorFlags::NONE_FLAGS) {
98 dest.write_str(if self.color_space == ColorSpace::Hsl {
99 "hsl("
100 } else {
101 "hwb("
102 })?;
103 ModernComponent(&self.c0()).to_css(dest)?;
104 for component in [self.c1(), self.c2()] {
105 dest.write_char(' ')?;
106 if let Some(value) = component {
107 value.to_css(dest)?;
108 dest.write_char('%')?;
109 } else {
110 dest.write_str("none")?;
111 }
112 }
113 serialize_color_alpha(dest, self.alpha(), false)?;
114 dest.write_char(')')
115 } else if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) {
116 self.into_srgb_legacy().to_css(dest)
117 } else {
118 self.to_color_space(ColorSpace::Srgb).to_css(dest)
119 }
120 },
121 ColorSpace::Oklab | ColorSpace::Lab | ColorSpace::Oklch | ColorSpace::Lch => {
122 if let ColorSpace::Oklab | ColorSpace::Oklch = self.color_space {
123 dest.write_str("ok")?;
124 }
125 if let ColorSpace::Oklab | ColorSpace::Lab = self.color_space {
126 dest.write_str("lab(")?;
127 } else {
128 dest.write_str("lch(")?;
129 }
130 ModernComponent(&self.c0()).to_css(dest)?;
131 dest.write_char(' ')?;
132 ModernComponent(&self.c1()).to_css(dest)?;
133 dest.write_char(' ')?;
134 ModernComponent(&self.c2()).to_css(dest)?;
135 serialize_color_alpha(dest, self.alpha(), false)?;
136 dest.write_char(')')
137 },
138 _ => {
139 #[cfg(debug_assertions)]
140 match self.color_space {
141 ColorSpace::Srgb => {
142 debug_assert!(
143 !self.flags.contains(ColorFlags::IS_LEGACY_SRGB),
144 "legacy srgb is not a color function"
145 );
146 },
147 ColorSpace::SrgbLinear
148 | ColorSpace::DisplayP3
149 | ColorSpace::DisplayP3Linear
150 | ColorSpace::A98Rgb
151 | ColorSpace::ProphotoRgb
152 | ColorSpace::Rec2020
153 | ColorSpace::XyzD50
154 | ColorSpace::XyzD65 => {
155 },
157 ColorSpace::Hsl
158 | ColorSpace::Hwb
159 | ColorSpace::Lab
160 | ColorSpace::Oklab
161 | ColorSpace::Lch
162 | ColorSpace::Oklch => {
163 unreachable!("other color spaces do not support color() syntax")
164 },
165 };
166
167 dest.write_str("color(")?;
168 self.color_space.to_css(dest)?;
169 dest.write_char(' ')?;
170 ModernComponent(&self.c0()).to_css(dest)?;
171 dest.write_char(' ')?;
172 ModernComponent(&self.c1()).to_css(dest)?;
173 dest.write_char(' ')?;
174 ModernComponent(&self.c2()).to_css(dest)?;
175
176 serialize_color_alpha(dest, self.alpha(), false)?;
177
178 dest.write_char(')')
179 },
180 }
181 }
182}
183
184impl AbsoluteColor {
185 pub fn write_author_preferred_value<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
191 where
192 W: Write,
193 {
194 macro_rules! precision {
195 ($v:expr) => {{ ($v * 100.0).round() / 100.0 }};
196 }
197 macro_rules! number {
198 ($c:expr) => {{
199 if let Some(v) = $c.map(normalize) {
200 precision!(v).to_css(dest)?;
201 } else {
202 write!(dest, "none")?;
203 }
204 }};
205 }
206 macro_rules! percentage {
207 ($c:expr) => {{
208 if let Some(v) = $c.map(normalize) {
209 precision!(v).to_css(dest)?;
210 dest.write_char('%')?;
211 } else {
212 write!(dest, "none")?;
213 }
214 }};
215 }
216 macro_rules! unit_percentage {
217 ($c:expr) => {{
218 if let Some(v) = $c.map(normalize) {
219 precision!(v * 100.0).to_css(dest)?;
220 dest.write_char('%')?;
221 } else {
222 write!(dest, "none")?;
223 }
224 }};
225 }
226 macro_rules! angle {
227 ($c:expr) => {{
228 if let Some(v) = $c.map(normalize) {
229 precision!(v).to_css(dest)?;
230 dest.write_str("deg")?;
231 } else {
232 write!(dest, "none")?;
233 }
234 }};
235 }
236
237 match self.color_space {
238 ColorSpace::Srgb => {
239 write!(dest, "rgb(")?;
240 unit_percentage!(self.c0());
241 dest.write_char(' ')?;
242 unit_percentage!(self.c1());
243 dest.write_char(' ')?;
244 unit_percentage!(self.c2());
245 serialize_color_alpha(dest, self.alpha(), false)?;
246 dest.write_char(')')
247 },
248 ColorSpace::Hsl | ColorSpace::Hwb => {
249 dest.write_str(if self.color_space == ColorSpace::Hsl {
250 "hsl("
251 } else {
252 "hwb("
253 })?;
254 angle!(self.c0());
255 dest.write_char(' ')?;
256 percentage!(self.c1());
257 dest.write_char(' ')?;
258 percentage!(self.c2());
259 serialize_color_alpha(dest, self.alpha(), false)?;
260 dest.write_char(')')
261 },
262 ColorSpace::Lab | ColorSpace::Oklab => {
263 if self.color_space == ColorSpace::Oklab {
264 dest.write_str("ok")?;
265 }
266 dest.write_str("lab(")?;
267 if self.color_space == ColorSpace::Lab {
268 percentage!(self.c0())
269 } else {
270 unit_percentage!(self.c0())
271 }
272 dest.write_char(' ')?;
273 number!(self.c1());
274 dest.write_char(' ')?;
275 number!(self.c2());
276 serialize_color_alpha(dest, self.alpha(), false)?;
277 dest.write_char(')')
278 },
279 ColorSpace::Lch | ColorSpace::Oklch => {
280 if self.color_space == ColorSpace::Oklch {
281 dest.write_str("ok")?;
282 }
283 dest.write_str("lch(")?;
284 number!(self.c0());
285 dest.write_char(' ')?;
286 number!(self.c1());
287 dest.write_char(' ')?;
288 angle!(self.c2());
289 serialize_color_alpha(dest, self.alpha(), false)?;
290 dest.write_char(')')
291 },
292 ColorSpace::SrgbLinear
293 | ColorSpace::DisplayP3
294 | ColorSpace::DisplayP3Linear
295 | ColorSpace::A98Rgb
296 | ColorSpace::ProphotoRgb
297 | ColorSpace::Rec2020
298 | ColorSpace::XyzD50
299 | ColorSpace::XyzD65 => {
300 dest.write_str("color(")?;
301 self.color_space.to_css(dest)?;
302 dest.write_char(' ')?;
303 number!(self.c0());
304 dest.write_char(' ')?;
305 number!(self.c1());
306 dest.write_char(' ')?;
307 number!(self.c2());
308 serialize_color_alpha(dest, self.alpha(), false)?;
309 dest.write_char(')')
310 },
311 }
312 }
313}