to_css.rs (10626B)
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 7 use super::{ 8 parsing::{NumberOrAngleComponent, NumberOrPercentageComponent}, 9 AbsoluteColor, ColorFlags, ColorSpace, 10 }; 11 use crate::values::normalize; 12 use cssparser::color::{clamp_unit_f32, serialize_color_alpha, OPAQUE}; 13 use std::fmt::{self, Write}; 14 use style_traits::{CssWriter, ToCss}; 15 16 /// A [`ModernComponent`] can serialize to `none`, `nan`, `infinity` and 17 /// floating point values. 18 struct ModernComponent<'a>(&'a Option<f32>); 19 20 impl<'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 44 impl 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 59 impl 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 74 impl 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 166 impl 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 }