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::A98Rgb
132 | ColorSpace::ProphotoRgb
133 | ColorSpace::Rec2020
134 | ColorSpace::XyzD50
135 | ColorSpace::XyzD65 => {
136 },
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 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}