1use std::fmt::Write;
8
9use super::{
10 component::ColorComponent,
11 convert::normalize_hue,
12 parsing::{NumberOrAngleComponent, NumberOrPercentageComponent},
13 AbsoluteColor, ColorFlags, ColorSpace,
14};
15use crate::derives::*;
16use crate::values::{
17 computed::color::Color as ComputedColor, generics::Optional, normalize,
18 specified::color::Color as SpecifiedColor,
19};
20use cssparser::color::{clamp_floor_256_f32, OPAQUE};
21
22#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
24#[repr(u8)]
25pub enum ColorFunction<OriginColor> {
26 Rgb(
28 Optional<OriginColor>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ),
34 Hsl(
36 Optional<OriginColor>, ColorComponent<NumberOrAngleComponent>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ),
42 Hwb(
44 Optional<OriginColor>, ColorComponent<NumberOrAngleComponent>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ),
50 Lab(
52 Optional<OriginColor>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ),
58 Lch(
60 Optional<OriginColor>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrAngleComponent>, ColorComponent<NumberOrPercentageComponent>, ),
66 Oklab(
68 Optional<OriginColor>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ),
74 Oklch(
76 Optional<OriginColor>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrAngleComponent>, ColorComponent<NumberOrPercentageComponent>, ),
82 Color(
84 Optional<OriginColor>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ColorComponent<NumberOrPercentageComponent>, ColorSpace,
90 ),
91}
92
93impl ColorFunction<AbsoluteColor> {
94 pub fn resolve_to_absolute(&self) -> Result<AbsoluteColor, ()> {
96 macro_rules! alpha {
97 ($alpha:expr, $origin_color:expr) => {{
98 $alpha
99 .resolve($origin_color)?
100 .map(|value| normalize(value.to_number(1.0)).clamp(0.0, OPAQUE))
101 }};
102 }
103
104 Ok(match self {
105 ColorFunction::Rgb(origin_color, r, g, b, alpha) => {
106 let use_color_syntax = origin_color.is_some();
109
110 if use_color_syntax {
111 let origin_color = origin_color.as_ref().map(|origin| {
112 let origin = origin.to_color_space(ColorSpace::Srgb);
113 AbsoluteColor::new(
118 ColorSpace::Srgb,
119 origin.c0().map(|v| v * 255.0),
120 origin.c1().map(|v| v * 255.0),
121 origin.c2().map(|v| v * 255.0),
122 origin.alpha(),
123 )
124 });
125
126 AbsoluteColor::new(
129 ColorSpace::Srgb,
130 r.resolve(origin_color.as_ref())?
131 .map(|c| c.to_number(255.0) / 255.0),
132 g.resolve(origin_color.as_ref())?
133 .map(|c| c.to_number(255.0) / 255.0),
134 b.resolve(origin_color.as_ref())?
135 .map(|c| c.to_number(255.0) / 255.0),
136 alpha!(alpha, origin_color.as_ref()),
137 )
138 } else {
139 #[inline]
140 fn resolve(
141 component: &ColorComponent<NumberOrPercentageComponent>,
142 origin_color: Option<&AbsoluteColor>,
143 ) -> Result<u8, ()> {
144 Ok(clamp_floor_256_f32(
145 component
146 .resolve(origin_color)?
147 .map_or(0.0, |value| value.to_number(u8::MAX as f32)),
148 ))
149 }
150
151 let origin_color = origin_color.as_ref().map(|o| o.into_srgb_legacy());
152
153 AbsoluteColor::srgb_legacy(
154 resolve(r, origin_color.as_ref())?,
155 resolve(g, origin_color.as_ref())?,
156 resolve(b, origin_color.as_ref())?,
157 alpha!(alpha, origin_color.as_ref()).unwrap_or(0.0),
158 )
159 }
160 },
161 ColorFunction::Hsl(origin_color, h, s, l, alpha) => {
162 const LIGHTNESS_RANGE: f32 = 100.0;
164 const SATURATION_RANGE: f32 = 100.0;
165
166 let use_rgb_sytax = origin_color.is_none();
172
173 let origin_color = origin_color
174 .as_ref()
175 .map(|o| o.to_color_space(ColorSpace::Hsl));
176
177 let mut result = AbsoluteColor::new(
178 ColorSpace::Hsl,
179 h.resolve(origin_color.as_ref())?
180 .map(|angle| normalize_hue(angle.degrees())),
181 s.resolve(origin_color.as_ref())?.map(|s| {
182 if use_rgb_sytax {
183 s.to_number(SATURATION_RANGE).clamp(0.0, SATURATION_RANGE)
184 } else {
185 s.to_number(SATURATION_RANGE)
186 }
187 }),
188 l.resolve(origin_color.as_ref())?.map(|l| {
189 if use_rgb_sytax {
190 l.to_number(LIGHTNESS_RANGE).clamp(0.0, LIGHTNESS_RANGE)
191 } else {
192 l.to_number(LIGHTNESS_RANGE)
193 }
194 }),
195 alpha!(alpha, origin_color.as_ref()),
196 );
197
198 if use_rgb_sytax {
199 result.flags.insert(ColorFlags::IS_LEGACY_SRGB);
200 }
201
202 result
203 },
204 ColorFunction::Hwb(origin_color, h, w, b, alpha) => {
205 let use_rgb_sytax = origin_color.is_none();
211
212 const WHITENESS_RANGE: f32 = 100.0;
214 const BLACKNESS_RANGE: f32 = 100.0;
215
216 let origin_color = origin_color
217 .as_ref()
218 .map(|o| o.to_color_space(ColorSpace::Hwb));
219
220 let mut result = AbsoluteColor::new(
221 ColorSpace::Hwb,
222 h.resolve(origin_color.as_ref())?
223 .map(|angle| normalize_hue(angle.degrees())),
224 w.resolve(origin_color.as_ref())?.map(|w| {
225 if use_rgb_sytax {
226 w.to_number(WHITENESS_RANGE).clamp(0.0, WHITENESS_RANGE)
227 } else {
228 w.to_number(WHITENESS_RANGE)
229 }
230 }),
231 b.resolve(origin_color.as_ref())?.map(|b| {
232 if use_rgb_sytax {
233 b.to_number(BLACKNESS_RANGE).clamp(0.0, BLACKNESS_RANGE)
234 } else {
235 b.to_number(BLACKNESS_RANGE)
236 }
237 }),
238 alpha!(alpha, origin_color.as_ref()),
239 );
240
241 if use_rgb_sytax {
242 result.flags.insert(ColorFlags::IS_LEGACY_SRGB);
243 }
244
245 result
246 },
247 ColorFunction::Lab(origin_color, l, a, b, alpha) => {
248 const LIGHTNESS_RANGE: f32 = 100.0;
251 const A_B_RANGE: f32 = 125.0;
252
253 let origin_color = origin_color
254 .as_ref()
255 .map(|o| o.to_color_space(ColorSpace::Lab));
256
257 AbsoluteColor::new(
258 ColorSpace::Lab,
259 l.resolve(origin_color.as_ref())?
260 .map(|l| l.to_number(LIGHTNESS_RANGE)),
261 a.resolve(origin_color.as_ref())?
262 .map(|a| a.to_number(A_B_RANGE)),
263 b.resolve(origin_color.as_ref())?
264 .map(|b| b.to_number(A_B_RANGE)),
265 alpha!(alpha, origin_color.as_ref()),
266 )
267 },
268 ColorFunction::Lch(origin_color, l, c, h, alpha) => {
269 const LIGHTNESS_RANGE: f32 = 100.0;
272 const CHROMA_RANGE: f32 = 150.0;
273
274 let origin_color = origin_color
275 .as_ref()
276 .map(|o| o.to_color_space(ColorSpace::Lch));
277
278 AbsoluteColor::new(
279 ColorSpace::Lch,
280 l.resolve(origin_color.as_ref())?
281 .map(|l| l.to_number(LIGHTNESS_RANGE)),
282 c.resolve(origin_color.as_ref())?
283 .map(|c| c.to_number(CHROMA_RANGE)),
284 h.resolve(origin_color.as_ref())?
285 .map(|angle| normalize_hue(angle.degrees())),
286 alpha!(alpha, origin_color.as_ref()),
287 )
288 },
289 ColorFunction::Oklab(origin_color, l, a, b, alpha) => {
290 const LIGHTNESS_RANGE: f32 = 1.0;
293 const A_B_RANGE: f32 = 0.4;
294
295 let origin_color = origin_color
296 .as_ref()
297 .map(|o| o.to_color_space(ColorSpace::Oklab));
298
299 AbsoluteColor::new(
300 ColorSpace::Oklab,
301 l.resolve(origin_color.as_ref())?
302 .map(|l| l.to_number(LIGHTNESS_RANGE)),
303 a.resolve(origin_color.as_ref())?
304 .map(|a| a.to_number(A_B_RANGE)),
305 b.resolve(origin_color.as_ref())?
306 .map(|b| b.to_number(A_B_RANGE)),
307 alpha!(alpha, origin_color.as_ref()),
308 )
309 },
310 ColorFunction::Oklch(origin_color, l, c, h, alpha) => {
311 const LIGHTNESS_RANGE: f32 = 1.0;
314 const CHROMA_RANGE: f32 = 0.4;
315
316 let origin_color = origin_color
317 .as_ref()
318 .map(|o| o.to_color_space(ColorSpace::Oklch));
319
320 AbsoluteColor::new(
321 ColorSpace::Oklch,
322 l.resolve(origin_color.as_ref())?
323 .map(|l| l.to_number(LIGHTNESS_RANGE)),
324 c.resolve(origin_color.as_ref())?
325 .map(|c| c.to_number(CHROMA_RANGE)),
326 h.resolve(origin_color.as_ref())?
327 .map(|angle| normalize_hue(angle.degrees())),
328 alpha!(alpha, origin_color.as_ref()),
329 )
330 },
331 ColorFunction::Color(origin_color, r, g, b, alpha, color_space) => {
332 let origin_color = origin_color.as_ref().map(|o| {
333 let mut result = o.to_color_space(*color_space);
334
335 result.flags.set(ColorFlags::IS_LEGACY_SRGB, false);
339
340 result
341 });
342
343 AbsoluteColor::new(
344 *color_space,
345 r.resolve(origin_color.as_ref())?.map(|c| c.to_number(1.0)),
346 g.resolve(origin_color.as_ref())?.map(|c| c.to_number(1.0)),
347 b.resolve(origin_color.as_ref())?.map(|c| c.to_number(1.0)),
348 alpha!(alpha, origin_color.as_ref()),
349 )
350 },
351 })
352 }
353}
354
355impl ColorFunction<SpecifiedColor> {
356 pub fn has_origin_color(&self) -> bool {
358 match self {
359 Self::Rgb(origin_color, ..)
360 | Self::Hsl(origin_color, ..)
361 | Self::Hwb(origin_color, ..)
362 | Self::Lab(origin_color, ..)
363 | Self::Lch(origin_color, ..)
364 | Self::Oklab(origin_color, ..)
365 | Self::Oklch(origin_color, ..)
366 | Self::Color(origin_color, ..) => origin_color.is_some(),
367 }
368 }
369
370 pub fn resolve_to_absolute(&self) -> Result<AbsoluteColor, ()> {
373 let resolvable = self.map_origin_color(|o| o.resolve_to_absolute());
375 resolvable.resolve_to_absolute()
376 }
377}
378
379impl<Color> ColorFunction<Color> {
380 pub fn map_origin_color<U>(&self, f: impl FnOnce(&Color) -> Option<U>) -> ColorFunction<U> {
382 macro_rules! map {
383 ($f:ident, $o:expr, $c0:expr, $c1:expr, $c2:expr, $alpha:expr) => {{
384 ColorFunction::$f(
385 $o.as_ref().and_then(f).into(),
386 $c0.clone(),
387 $c1.clone(),
388 $c2.clone(),
389 $alpha.clone(),
390 )
391 }};
392 }
393 match self {
394 ColorFunction::Rgb(o, c0, c1, c2, alpha) => map!(Rgb, o, c0, c1, c2, alpha),
395 ColorFunction::Hsl(o, c0, c1, c2, alpha) => map!(Hsl, o, c0, c1, c2, alpha),
396 ColorFunction::Hwb(o, c0, c1, c2, alpha) => map!(Hwb, o, c0, c1, c2, alpha),
397 ColorFunction::Lab(o, c0, c1, c2, alpha) => map!(Lab, o, c0, c1, c2, alpha),
398 ColorFunction::Lch(o, c0, c1, c2, alpha) => map!(Lch, o, c0, c1, c2, alpha),
399 ColorFunction::Oklab(o, c0, c1, c2, alpha) => map!(Oklab, o, c0, c1, c2, alpha),
400 ColorFunction::Oklch(o, c0, c1, c2, alpha) => map!(Oklch, o, c0, c1, c2, alpha),
401 ColorFunction::Color(o, c0, c1, c2, alpha, color_space) => ColorFunction::Color(
402 o.as_ref().and_then(f).into(),
403 c0.clone(),
404 c1.clone(),
405 c2.clone(),
406 alpha.clone(),
407 color_space.clone(),
408 ),
409 }
410 }
411}
412
413impl ColorFunction<ComputedColor> {
414 pub fn resolve_to_absolute(&self, current_color: &AbsoluteColor) -> AbsoluteColor {
416 let resolvable = self.map_origin_color(|o| Some(o.resolve_to_absolute(current_color)));
418 match resolvable.resolve_to_absolute() {
419 Ok(color) => color,
420 Err(..) => {
421 debug_assert!(
422 false,
423 "the color could not be resolved even with a currentcolor specified?"
424 );
425 AbsoluteColor::TRANSPARENT_BLACK
426 },
427 }
428 }
429}
430
431impl<C: style_traits::ToCss> style_traits::ToCss for ColorFunction<C> {
432 fn to_css<W>(&self, dest: &mut style_traits::CssWriter<W>) -> std::fmt::Result
433 where
434 W: std::fmt::Write,
435 {
436 let (origin_color, alpha) = match self {
437 Self::Rgb(origin_color, _, _, _, alpha) => {
438 dest.write_str("rgb(")?;
439 (origin_color, alpha)
440 },
441 Self::Hsl(origin_color, _, _, _, alpha) => {
442 dest.write_str("hsl(")?;
443 (origin_color, alpha)
444 },
445 Self::Hwb(origin_color, _, _, _, alpha) => {
446 dest.write_str("hwb(")?;
447 (origin_color, alpha)
448 },
449 Self::Lab(origin_color, _, _, _, alpha) => {
450 dest.write_str("lab(")?;
451 (origin_color, alpha)
452 },
453 Self::Lch(origin_color, _, _, _, alpha) => {
454 dest.write_str("lch(")?;
455 (origin_color, alpha)
456 },
457 Self::Oklab(origin_color, _, _, _, alpha) => {
458 dest.write_str("oklab(")?;
459 (origin_color, alpha)
460 },
461 Self::Oklch(origin_color, _, _, _, alpha) => {
462 dest.write_str("oklch(")?;
463 (origin_color, alpha)
464 },
465 Self::Color(origin_color, _, _, _, alpha, _) => {
466 dest.write_str("color(")?;
467 (origin_color, alpha)
468 },
469 };
470
471 if let Optional::Some(origin_color) = origin_color {
472 dest.write_str("from ")?;
473 origin_color.to_css(dest)?;
474 dest.write_str(" ")?;
475 }
476
477 let is_opaque = if let ColorComponent::Value(value) = *alpha {
478 value.to_number(OPAQUE) == OPAQUE
479 } else {
480 false
481 };
482
483 macro_rules! serialize_alpha {
484 ($alpha_component:expr) => {{
485 if !is_opaque && !matches!($alpha_component, ColorComponent::AlphaOmitted) {
486 dest.write_str(" / ")?;
487 $alpha_component.to_css(dest)?;
488 }
489 }};
490 }
491
492 macro_rules! serialize_components {
493 ($c0:expr, $c1:expr, $c2:expr) => {{
494 debug_assert!(!matches!($c0, ColorComponent::AlphaOmitted));
495 debug_assert!(!matches!($c1, ColorComponent::AlphaOmitted));
496 debug_assert!(!matches!($c2, ColorComponent::AlphaOmitted));
497
498 $c0.to_css(dest)?;
499 dest.write_str(" ")?;
500 $c1.to_css(dest)?;
501 dest.write_str(" ")?;
502 $c2.to_css(dest)?;
503 }};
504 }
505
506 match self {
507 Self::Rgb(_, c0, c1, c2, alpha) => {
508 serialize_components!(c0, c1, c2);
509 serialize_alpha!(alpha);
510 },
511 Self::Hsl(_, c0, c1, c2, alpha) => {
512 serialize_components!(c0, c1, c2);
513 serialize_alpha!(alpha);
514 },
515 Self::Hwb(_, c0, c1, c2, alpha) => {
516 serialize_components!(c0, c1, c2);
517 serialize_alpha!(alpha);
518 },
519 Self::Lab(_, c0, c1, c2, alpha) => {
520 serialize_components!(c0, c1, c2);
521 serialize_alpha!(alpha);
522 },
523 Self::Lch(_, c0, c1, c2, alpha) => {
524 serialize_components!(c0, c1, c2);
525 serialize_alpha!(alpha);
526 },
527 Self::Oklab(_, c0, c1, c2, alpha) => {
528 serialize_components!(c0, c1, c2);
529 serialize_alpha!(alpha);
530 },
531 Self::Oklch(_, c0, c1, c2, alpha) => {
532 serialize_components!(c0, c1, c2);
533 serialize_alpha!(alpha);
534 },
535 Self::Color(_, c0, c1, c2, alpha, color_space) => {
536 color_space.to_css(dest)?;
537 dest.write_str(" ")?;
538 serialize_components!(c0, c1, c2);
539 serialize_alpha!(alpha);
540 },
541 }
542
543 dest.write_str(")")
544 }
545}