1use std::fmt::Write;
8
9use super::{
10 AbsoluteColor, ColorFlags, ColorSpace,
11 component::ColorComponent,
12 convert::normalize_hue,
13 parsing::{NumberOrAngleComponent, NumberOrPercentageComponent},
14};
15use crate::derives::*;
16use crate::values::{
17 computed, computed::color::Color as ComputedColor, generics::Optional, normalize,
18 specified::color::Color as SpecifiedColor,
19};
20use cssparser::color::{OPAQUE, clamp_floor_256_f32};
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 Alpha(
93 OriginColor, ColorComponent<NumberOrPercentageComponent>, ),
96}
97
98impl ColorFunction<SpecifiedColor> {
99 pub fn has_origin_color(&self) -> bool {
101 self.origin_color().is_some()
102 }
103
104 pub fn to_computed_color(
110 &self,
111 context: Option<&computed::Context>,
112 ) -> Result<ComputedColor, ()> {
113 let origin = match self.origin_color() {
115 Some(o) => Some(o.to_computed_color(context)?),
116 None => None,
117 };
118 let resolvable = origin.as_ref().is_none_or(|o| o.is_absolute());
121
122 let computed = self.to_computed_value(context, origin)?;
123 if resolvable && let Ok(absolute) = computed.to_absolute_color() {
124 return Ok(ComputedColor::Absolute(absolute));
125 }
126
127 Ok(ComputedColor::ColorFunction(Box::new(computed)))
128 }
129}
130
131impl<Color> ColorFunction<Color> {
132 pub fn map_origin_color<U>(
134 &self,
135 f: impl FnOnce(&Color) -> Result<U, ()>,
136 ) -> Result<ColorFunction<U>, ()> {
137 macro_rules! map {
138 ($f:ident, $o:expr, $c0:expr, $c1:expr, $c2:expr, $alpha:expr) => {{
139 ColorFunction::$f(
140 match $o.as_ref() {
141 Some(c) => Some(f(c)?),
142 None => None,
143 }
144 .into(),
145 $c0.clone(),
146 $c1.clone(),
147 $c2.clone(),
148 $alpha.clone(),
149 )
150 }};
151 }
152 Ok(match self {
153 ColorFunction::Rgb(o, c0, c1, c2, alpha) => map!(Rgb, o, c0, c1, c2, alpha),
154 ColorFunction::Hsl(o, c0, c1, c2, alpha) => map!(Hsl, o, c0, c1, c2, alpha),
155 ColorFunction::Hwb(o, c0, c1, c2, alpha) => map!(Hwb, o, c0, c1, c2, alpha),
156 ColorFunction::Lab(o, c0, c1, c2, alpha) => map!(Lab, o, c0, c1, c2, alpha),
157 ColorFunction::Lch(o, c0, c1, c2, alpha) => map!(Lch, o, c0, c1, c2, alpha),
158 ColorFunction::Oklab(o, c0, c1, c2, alpha) => map!(Oklab, o, c0, c1, c2, alpha),
159 ColorFunction::Oklch(o, c0, c1, c2, alpha) => map!(Oklch, o, c0, c1, c2, alpha),
160 ColorFunction::Color(o, c0, c1, c2, alpha, color_space) => ColorFunction::Color(
161 match o.as_ref() {
162 Some(c) => Some(f(c)?),
163 None => None,
164 }
165 .into(),
166 c0.clone(),
167 c1.clone(),
168 c2.clone(),
169 alpha.clone(),
170 *color_space,
171 ),
172 ColorFunction::Alpha(o, alpha) => ColorFunction::Alpha(f(o)?, alpha.clone()),
173 })
174 }
175
176 pub fn origin_color(&self) -> Option<&Color> {
178 match self {
179 Self::Rgb(o, ..)
180 | Self::Hsl(o, ..)
181 | Self::Hwb(o, ..)
182 | Self::Lab(o, ..)
183 | Self::Lch(o, ..)
184 | Self::Oklab(o, ..)
185 | Self::Oklch(o, ..)
186 | Self::Color(o, ..) => o.as_ref(),
187 Self::Alpha(o, ..) => Some(o),
188 }
189 }
190
191 fn to_computed_value(
193 &self,
194 context: Option<&computed::Context>,
195 origin: Option<ComputedColor>,
196 ) -> Result<ColorFunction<ComputedColor>, ()> {
197 let abs_origin = origin.as_ref().and_then(|o| o.as_absolute());
199 macro_rules! convert {
202 ($variant:ident, $space:expr, $c0:expr, $c1:expr, $c2:expr, $alpha:expr) => {{
203 let converted = abs_origin.map(|o| o.to_color_space($space));
204 ColorFunction::$variant(
205 Optional::from(origin),
206 $c0.to_computed_value(context, converted.as_ref())?,
207 $c1.to_computed_value(context, converted.as_ref())?,
208 $c2.to_computed_value(context, converted.as_ref())?,
209 $alpha.to_computed_value(context, converted.as_ref())?,
210 )
211 }};
212 }
213
214 Ok(match self {
215 ColorFunction::Rgb(_, r, g, b, alpha) => {
216 let converted = abs_origin.map(|o| {
219 let o = o.to_color_space(ColorSpace::Srgb);
220 AbsoluteColor::new(
221 ColorSpace::Srgb,
222 o.c0().map(|v| v * 255.0),
223 o.c1().map(|v| v * 255.0),
224 o.c2().map(|v| v * 255.0),
225 o.alpha(),
226 )
227 });
228 ColorFunction::Rgb(
229 Optional::from(origin),
230 r.to_computed_value(context, converted.as_ref())?,
231 g.to_computed_value(context, converted.as_ref())?,
232 b.to_computed_value(context, converted.as_ref())?,
233 alpha.to_computed_value(context, converted.as_ref())?,
234 )
235 },
236 ColorFunction::Hsl(_, c0, c1, c2, alpha) => {
237 convert!(Hsl, ColorSpace::Hsl, c0, c1, c2, alpha)
238 },
239 ColorFunction::Hwb(_, c0, c1, c2, alpha) => {
240 convert!(Hwb, ColorSpace::Hwb, c0, c1, c2, alpha)
241 },
242 ColorFunction::Lab(_, c0, c1, c2, alpha) => {
243 convert!(Lab, ColorSpace::Lab, c0, c1, c2, alpha)
244 },
245 ColorFunction::Lch(_, c0, c1, c2, alpha) => {
246 convert!(Lch, ColorSpace::Lch, c0, c1, c2, alpha)
247 },
248 ColorFunction::Oklab(_, c0, c1, c2, alpha) => {
249 convert!(Oklab, ColorSpace::Oklab, c0, c1, c2, alpha)
250 },
251 ColorFunction::Oklch(_, c0, c1, c2, alpha) => {
252 convert!(Oklch, ColorSpace::Oklch, c0, c1, c2, alpha)
253 },
254 ColorFunction::Color(_, c0, c1, c2, alpha, color_space) => {
255 let converted = abs_origin.map(|o| {
256 let mut result = o.to_color_space(*color_space);
257 result.flags.set(ColorFlags::IS_LEGACY_SRGB, false);
259 result
260 });
261 ColorFunction::Color(
262 Optional::from(origin),
263 c0.to_computed_value(context, converted.as_ref())?,
264 c1.to_computed_value(context, converted.as_ref())?,
265 c2.to_computed_value(context, converted.as_ref())?,
266 alpha.to_computed_value(context, converted.as_ref())?,
267 *color_space,
268 )
269 },
270 ColorFunction::Alpha(_, alpha) => {
271 let alpha = alpha.to_computed_value(context, abs_origin)?;
272 let stored = origin.expect("alpha() is always relative");
273 ColorFunction::Alpha(stored, alpha)
274 },
275 })
276 }
277}
278
279impl ColorFunction<ComputedColor> {
280 fn to_absolute_color(&self) -> Result<AbsoluteColor, ()> {
281 macro_rules! alpha {
282 ($alpha:expr) => {{
283 $alpha
284 .resolve()?
285 .map(|value| normalize(value.to_number(1.0)).clamp(0.0, OPAQUE))
286 }};
287 }
288
289 Ok(match self {
290 ColorFunction::Rgb(origin_color, r, g, b, alpha) => {
291 let use_color_syntax = origin_color.is_some();
294
295 if use_color_syntax {
296 AbsoluteColor::new(
299 ColorSpace::Srgb,
300 r.resolve()?.map(|c| c.to_number(255.0) / 255.0),
301 g.resolve()?.map(|c| c.to_number(255.0) / 255.0),
302 b.resolve()?.map(|c| c.to_number(255.0) / 255.0),
303 alpha!(alpha),
304 )
305 } else {
306 #[inline]
311 fn resolve(
312 component: &ColorComponent<NumberOrPercentageComponent>,
313 ) -> Result<Option<f32>, ()> {
314 Ok(component.resolve()?.map(|value| {
315 clamp_floor_256_f32(value.to_number(u8::MAX as f32)) as f32 / 255.0
316 }))
317 }
318
319 let mut result = AbsoluteColor::new(
320 ColorSpace::Srgb,
321 resolve(r)?,
322 resolve(g)?,
323 resolve(b)?,
324 alpha!(alpha),
325 );
326 result.flags.insert(ColorFlags::IS_LEGACY_SRGB);
327 result
328 }
329 },
330 ColorFunction::Hsl(origin_color, h, s, l, alpha) => {
331 const LIGHTNESS_RANGE: f32 = 100.0;
333 const SATURATION_RANGE: f32 = 100.0;
334
335 let use_rgb_sytax = origin_color.is_none();
340
341 let mut result = AbsoluteColor::new(
342 ColorSpace::Hsl,
343 h.resolve()?.map(|angle| normalize_hue(angle.degrees())),
344 s.resolve()?.map(|s| {
345 if use_rgb_sytax {
346 s.to_number(SATURATION_RANGE).clamp(0.0, SATURATION_RANGE)
347 } else {
348 s.to_number(SATURATION_RANGE)
349 }
350 }),
351 l.resolve()?.map(|l| {
352 if use_rgb_sytax {
353 l.to_number(LIGHTNESS_RANGE).clamp(0.0, LIGHTNESS_RANGE)
354 } else {
355 l.to_number(LIGHTNESS_RANGE)
356 }
357 }),
358 alpha!(alpha),
359 );
360
361 if use_rgb_sytax {
362 result.flags.insert(ColorFlags::IS_LEGACY_SRGB);
363 }
364
365 result
366 },
367 ColorFunction::Hwb(origin_color, h, w, b, alpha) => {
368 let use_rgb_sytax = origin_color.is_none();
369
370 const WHITENESS_RANGE: f32 = 100.0;
372 const BLACKNESS_RANGE: f32 = 100.0;
373
374 let mut result = AbsoluteColor::new(
375 ColorSpace::Hwb,
376 h.resolve()?.map(|angle| normalize_hue(angle.degrees())),
377 w.resolve()?.map(|w| w.to_number(WHITENESS_RANGE)),
378 b.resolve()?.map(|b| b.to_number(BLACKNESS_RANGE)),
379 alpha!(alpha),
380 );
381
382 if use_rgb_sytax {
383 result.flags.insert(ColorFlags::IS_LEGACY_SRGB);
384 }
385
386 result
387 },
388 ColorFunction::Lab(_, l, a, b, alpha) => {
389 const LIGHTNESS_RANGE: f32 = 100.0;
392 const A_B_RANGE: f32 = 125.0;
393
394 AbsoluteColor::new(
395 ColorSpace::Lab,
396 l.resolve()?.map(|l| l.to_number(LIGHTNESS_RANGE)),
397 a.resolve()?.map(|a| a.to_number(A_B_RANGE)),
398 b.resolve()?.map(|b| b.to_number(A_B_RANGE)),
399 alpha!(alpha),
400 )
401 },
402 ColorFunction::Lch(_, l, c, h, alpha) => {
403 const LIGHTNESS_RANGE: f32 = 100.0;
406 const CHROMA_RANGE: f32 = 150.0;
407
408 AbsoluteColor::new(
409 ColorSpace::Lch,
410 l.resolve()?.map(|l| l.to_number(LIGHTNESS_RANGE)),
411 c.resolve()?.map(|c| c.to_number(CHROMA_RANGE)),
412 h.resolve()?.map(|angle| normalize_hue(angle.degrees())),
413 alpha!(alpha),
414 )
415 },
416 ColorFunction::Oklab(_, l, a, b, alpha) => {
417 const LIGHTNESS_RANGE: f32 = 1.0;
420 const A_B_RANGE: f32 = 0.4;
421
422 AbsoluteColor::new(
423 ColorSpace::Oklab,
424 l.resolve()?.map(|l| l.to_number(LIGHTNESS_RANGE)),
425 a.resolve()?.map(|a| a.to_number(A_B_RANGE)),
426 b.resolve()?.map(|b| b.to_number(A_B_RANGE)),
427 alpha!(alpha),
428 )
429 },
430 ColorFunction::Oklch(_, l, c, h, alpha) => {
431 const LIGHTNESS_RANGE: f32 = 1.0;
434 const CHROMA_RANGE: f32 = 0.4;
435
436 AbsoluteColor::new(
437 ColorSpace::Oklch,
438 l.resolve()?.map(|l| l.to_number(LIGHTNESS_RANGE)),
439 c.resolve()?.map(|c| c.to_number(CHROMA_RANGE)),
440 h.resolve()?.map(|angle| normalize_hue(angle.degrees())),
441 alpha!(alpha),
442 )
443 },
444 ColorFunction::Color(_, r, g, b, alpha, color_space) => AbsoluteColor::new(
445 *color_space,
446 r.resolve()?.map(|c| c.to_number(1.0)),
447 g.resolve()?.map(|c| c.to_number(1.0)),
448 b.resolve()?.map(|c| c.to_number(1.0)),
449 alpha!(alpha),
450 ),
451 ColorFunction::Alpha(origin_color, alpha) => {
452 let origin_color = origin_color.as_absolute().ok_or(())?;
453 origin_color.with_alpha(alpha!(alpha))
454 },
455 })
456 }
457
458 pub fn resolve_to_absolute(&self, current_color: &AbsoluteColor) -> AbsoluteColor {
460 let origin = self
463 .origin_color()
464 .map(|o| ComputedColor::Absolute(o.resolve_to_absolute(current_color)));
465 self.to_computed_value(None, origin)
466 .and_then(|r| r.to_absolute_color())
467 .unwrap_or_else(|_| {
468 debug_assert!(
469 false,
470 "the color could not be resolved even with a currentcolor specified?"
471 );
472 AbsoluteColor::TRANSPARENT_BLACK
473 })
474 }
475}
476
477impl<C: style_traits::ToCss> style_traits::ToCss for ColorFunction<C> {
478 fn to_css<W>(&self, dest: &mut style_traits::CssWriter<W>) -> std::fmt::Result
479 where
480 W: std::fmt::Write,
481 {
482 let (origin_color, alpha, trailing_space) = match self {
483 Self::Rgb(origin_color, _, _, _, alpha) => {
484 dest.write_str("rgb(")?;
485 (origin_color.as_ref(), alpha, true)
486 },
487 Self::Hsl(origin_color, _, _, _, alpha) => {
488 dest.write_str("hsl(")?;
489 (origin_color.as_ref(), alpha, true)
490 },
491 Self::Hwb(origin_color, _, _, _, alpha) => {
492 dest.write_str("hwb(")?;
493 (origin_color.as_ref(), alpha, true)
494 },
495 Self::Lab(origin_color, _, _, _, alpha) => {
496 dest.write_str("lab(")?;
497 (origin_color.as_ref(), alpha, true)
498 },
499 Self::Lch(origin_color, _, _, _, alpha) => {
500 dest.write_str("lch(")?;
501 (origin_color.as_ref(), alpha, true)
502 },
503 Self::Oklab(origin_color, _, _, _, alpha) => {
504 dest.write_str("oklab(")?;
505 (origin_color.as_ref(), alpha, true)
506 },
507 Self::Oklch(origin_color, _, _, _, alpha) => {
508 dest.write_str("oklch(")?;
509 (origin_color.as_ref(), alpha, true)
510 },
511 Self::Color(origin_color, _, _, _, alpha, _) => {
512 dest.write_str("color(")?;
513 (origin_color.as_ref(), alpha, true)
514 },
515 Self::Alpha(origin_color, alpha) => {
516 dest.write_str("alpha(")?;
517 (Some(origin_color), alpha, false)
518 },
519 };
520
521 if let Some(origin_color) = origin_color {
522 dest.write_str("from ")?;
523 origin_color.to_css(dest)?;
524 if trailing_space {
525 dest.write_str(" ")?;
526 }
527 }
528
529 macro_rules! serialize_components {
530 ($c0:expr, $c1:expr, $c2:expr) => {{
531 debug_assert!(!matches!($c0, ColorComponent::AlphaOmitted));
532 debug_assert!(!matches!($c1, ColorComponent::AlphaOmitted));
533 debug_assert!(!matches!($c2, ColorComponent::AlphaOmitted));
534
535 $c0.to_css(dest)?;
536 dest.write_str(" ")?;
537 $c1.to_css(dest)?;
538 dest.write_str(" ")?;
539 $c2.to_css(dest)?;
540 }};
541 }
542
543 match self {
544 Self::Rgb(_, c0, c1, c2, _) => {
545 serialize_components!(c0, c1, c2);
546 },
547 Self::Hsl(_, c0, c1, c2, _) => {
548 serialize_components!(c0, c1, c2);
549 },
550 Self::Hwb(_, c0, c1, c2, _) => {
551 serialize_components!(c0, c1, c2);
552 },
553 Self::Lab(_, c0, c1, c2, _) => {
554 serialize_components!(c0, c1, c2);
555 },
556 Self::Lch(_, c0, c1, c2, _) => {
557 serialize_components!(c0, c1, c2);
558 },
559 Self::Oklab(_, c0, c1, c2, _) => {
560 serialize_components!(c0, c1, c2);
561 },
562 Self::Oklch(_, c0, c1, c2, _) => {
563 serialize_components!(c0, c1, c2);
564 },
565 Self::Color(_, c0, c1, c2, _, color_space) => {
566 color_space.to_css(dest)?;
567 dest.write_str(" ")?;
568 serialize_components!(c0, c1, c2);
569 },
570 Self::Alpha(_, _) => {},
571 }
572
573 let omit_alpha = match *alpha {
576 ColorComponent::AlphaOmitted => true,
577 ColorComponent::Value(ref v) => origin_color.is_none() && v.to_number(OPAQUE) == OPAQUE,
578 _ => false,
579 };
580
581 if !omit_alpha {
582 dest.write_str(" / ")?;
583 alpha.to_css(dest)?;
584 }
585
586 dest.write_str(")")
587 }
588}