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