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}
92
93impl ColorFunction<AbsoluteColor> {
94 pub fn resolve_to_absolute(
96 &self,
97 context: Option<&computed::Context>,
98 ) -> Result<AbsoluteColor, ()> {
99 macro_rules! alpha {
100 ($alpha:expr, $origin_color:expr) => {{
101 $alpha
102 .resolve($origin_color, context)?
103 .map(|value| normalize(value.to_number(1.0)).clamp(0.0, OPAQUE))
104 }};
105 }
106
107 Ok(match self {
108 ColorFunction::Rgb(origin_color, r, g, b, alpha) => {
109 let use_color_syntax = origin_color.is_some();
112
113 if use_color_syntax {
114 let origin_color = origin_color.as_ref().map(|origin| {
115 let origin = origin.to_color_space(ColorSpace::Srgb);
116 AbsoluteColor::new(
121 ColorSpace::Srgb,
122 origin.c0().map(|v| v * 255.0),
123 origin.c1().map(|v| v * 255.0),
124 origin.c2().map(|v| v * 255.0),
125 origin.alpha(),
126 )
127 });
128
129 AbsoluteColor::new(
132 ColorSpace::Srgb,
133 r.resolve(origin_color.as_ref(), context)?
134 .map(|c| c.to_number(255.0) / 255.0),
135 g.resolve(origin_color.as_ref(), context)?
136 .map(|c| c.to_number(255.0) / 255.0),
137 b.resolve(origin_color.as_ref(), context)?
138 .map(|c| c.to_number(255.0) / 255.0),
139 alpha!(alpha, origin_color.as_ref()),
140 )
141 } else {
142 #[inline]
143 fn resolve(
144 component: &ColorComponent<NumberOrPercentageComponent>,
145 origin_color: Option<&AbsoluteColor>,
146 context: Option<&computed::Context>,
147 ) -> Result<u8, ()> {
148 Ok(clamp_floor_256_f32(
149 component
150 .resolve(origin_color, context)?
151 .map_or(0.0, |value| value.to_number(u8::MAX as f32)),
152 ))
153 }
154
155 let origin_color = origin_color.as_ref().map(|o| o.into_srgb_legacy());
156
157 AbsoluteColor::srgb_legacy(
158 resolve(r, origin_color.as_ref(), context)?,
159 resolve(g, origin_color.as_ref(), context)?,
160 resolve(b, origin_color.as_ref(), context)?,
161 alpha!(alpha, origin_color.as_ref()).unwrap_or(0.0),
162 )
163 }
164 },
165 ColorFunction::Hsl(origin_color, h, s, l, alpha) => {
166 const LIGHTNESS_RANGE: f32 = 100.0;
168 const SATURATION_RANGE: f32 = 100.0;
169
170 let use_rgb_sytax = origin_color.is_none();
176
177 let origin_color = origin_color
178 .as_ref()
179 .map(|o| o.to_color_space(ColorSpace::Hsl));
180
181 let mut result = AbsoluteColor::new(
182 ColorSpace::Hsl,
183 h.resolve(origin_color.as_ref(), context)?
184 .map(|angle| normalize_hue(angle.degrees())),
185 s.resolve(origin_color.as_ref(), context)?.map(|s| {
186 if use_rgb_sytax {
187 s.to_number(SATURATION_RANGE).clamp(0.0, SATURATION_RANGE)
188 } else {
189 s.to_number(SATURATION_RANGE)
190 }
191 }),
192 l.resolve(origin_color.as_ref(), context)?.map(|l| {
193 if use_rgb_sytax {
194 l.to_number(LIGHTNESS_RANGE).clamp(0.0, LIGHTNESS_RANGE)
195 } else {
196 l.to_number(LIGHTNESS_RANGE)
197 }
198 }),
199 alpha!(alpha, origin_color.as_ref()),
200 );
201
202 if use_rgb_sytax {
203 result.flags.insert(ColorFlags::IS_LEGACY_SRGB);
204 }
205
206 result
207 },
208 ColorFunction::Hwb(origin_color, h, w, b, alpha) => {
209 let use_rgb_sytax = origin_color.is_none();
215
216 const WHITENESS_RANGE: f32 = 100.0;
218 const BLACKNESS_RANGE: f32 = 100.0;
219
220 let origin_color = origin_color
221 .as_ref()
222 .map(|o| o.to_color_space(ColorSpace::Hwb));
223
224 let mut result = AbsoluteColor::new(
225 ColorSpace::Hwb,
226 h.resolve(origin_color.as_ref(), context)?
227 .map(|angle| normalize_hue(angle.degrees())),
228 w.resolve(origin_color.as_ref(), context)?.map(|w| {
229 if use_rgb_sytax {
230 w.to_number(WHITENESS_RANGE).clamp(0.0, WHITENESS_RANGE)
231 } else {
232 w.to_number(WHITENESS_RANGE)
233 }
234 }),
235 b.resolve(origin_color.as_ref(), context)?.map(|b| {
236 if use_rgb_sytax {
237 b.to_number(BLACKNESS_RANGE).clamp(0.0, BLACKNESS_RANGE)
238 } else {
239 b.to_number(BLACKNESS_RANGE)
240 }
241 }),
242 alpha!(alpha, origin_color.as_ref()),
243 );
244
245 if use_rgb_sytax {
246 result.flags.insert(ColorFlags::IS_LEGACY_SRGB);
247 }
248
249 result
250 },
251 ColorFunction::Lab(origin_color, l, a, b, alpha) => {
252 const LIGHTNESS_RANGE: f32 = 100.0;
255 const A_B_RANGE: f32 = 125.0;
256
257 let origin_color = origin_color
258 .as_ref()
259 .map(|o| o.to_color_space(ColorSpace::Lab));
260
261 AbsoluteColor::new(
262 ColorSpace::Lab,
263 l.resolve(origin_color.as_ref(), context)?
264 .map(|l| l.to_number(LIGHTNESS_RANGE)),
265 a.resolve(origin_color.as_ref(), context)?
266 .map(|a| a.to_number(A_B_RANGE)),
267 b.resolve(origin_color.as_ref(), context)?
268 .map(|b| b.to_number(A_B_RANGE)),
269 alpha!(alpha, origin_color.as_ref()),
270 )
271 },
272 ColorFunction::Lch(origin_color, l, c, h, alpha) => {
273 const LIGHTNESS_RANGE: f32 = 100.0;
276 const CHROMA_RANGE: f32 = 150.0;
277
278 let origin_color = origin_color
279 .as_ref()
280 .map(|o| o.to_color_space(ColorSpace::Lch));
281
282 AbsoluteColor::new(
283 ColorSpace::Lch,
284 l.resolve(origin_color.as_ref(), context)?
285 .map(|l| l.to_number(LIGHTNESS_RANGE)),
286 c.resolve(origin_color.as_ref(), context)?
287 .map(|c| c.to_number(CHROMA_RANGE)),
288 h.resolve(origin_color.as_ref(), context)?
289 .map(|angle| normalize_hue(angle.degrees())),
290 alpha!(alpha, origin_color.as_ref()),
291 )
292 },
293 ColorFunction::Oklab(origin_color, l, a, b, alpha) => {
294 const LIGHTNESS_RANGE: f32 = 1.0;
297 const A_B_RANGE: f32 = 0.4;
298
299 let origin_color = origin_color
300 .as_ref()
301 .map(|o| o.to_color_space(ColorSpace::Oklab));
302
303 AbsoluteColor::new(
304 ColorSpace::Oklab,
305 l.resolve(origin_color.as_ref(), context)?
306 .map(|l| l.to_number(LIGHTNESS_RANGE)),
307 a.resolve(origin_color.as_ref(), context)?
308 .map(|a| a.to_number(A_B_RANGE)),
309 b.resolve(origin_color.as_ref(), context)?
310 .map(|b| b.to_number(A_B_RANGE)),
311 alpha!(alpha, origin_color.as_ref()),
312 )
313 },
314 ColorFunction::Oklch(origin_color, l, c, h, alpha) => {
315 const LIGHTNESS_RANGE: f32 = 1.0;
318 const CHROMA_RANGE: f32 = 0.4;
319
320 let origin_color = origin_color
321 .as_ref()
322 .map(|o| o.to_color_space(ColorSpace::Oklch));
323
324 AbsoluteColor::new(
325 ColorSpace::Oklch,
326 l.resolve(origin_color.as_ref(), context)?
327 .map(|l| l.to_number(LIGHTNESS_RANGE)),
328 c.resolve(origin_color.as_ref(), context)?
329 .map(|c| c.to_number(CHROMA_RANGE)),
330 h.resolve(origin_color.as_ref(), context)?
331 .map(|angle| normalize_hue(angle.degrees())),
332 alpha!(alpha, origin_color.as_ref()),
333 )
334 },
335 ColorFunction::Color(origin_color, r, g, b, alpha, color_space) => {
336 let origin_color = origin_color.as_ref().map(|o| {
337 let mut result = o.to_color_space(*color_space);
338
339 result.flags.set(ColorFlags::IS_LEGACY_SRGB, false);
343
344 result
345 });
346
347 AbsoluteColor::new(
348 *color_space,
349 r.resolve(origin_color.as_ref(), context)?
350 .map(|c| c.to_number(1.0)),
351 g.resolve(origin_color.as_ref(), context)?
352 .map(|c| c.to_number(1.0)),
353 b.resolve(origin_color.as_ref(), context)?
354 .map(|c| c.to_number(1.0)),
355 alpha!(alpha, origin_color.as_ref()),
356 )
357 },
358 })
359 }
360}
361
362impl ColorFunction<SpecifiedColor> {
363 pub fn has_origin_color(&self) -> bool {
365 match self {
366 Self::Rgb(origin_color, ..)
367 | Self::Hsl(origin_color, ..)
368 | Self::Hwb(origin_color, ..)
369 | Self::Lab(origin_color, ..)
370 | Self::Lch(origin_color, ..)
371 | Self::Oklab(origin_color, ..)
372 | Self::Oklch(origin_color, ..)
373 | Self::Color(origin_color, ..) => origin_color.is_some(),
374 }
375 }
376
377 pub fn resolve_to_absolute(
380 &self,
381 context: Option<&computed::Context>,
382 ) -> Result<AbsoluteColor, ()> {
383 self.map_origin_color(|o| o.resolve_to_absolute(context))?
385 .resolve_to_absolute(context)
386 }
387}
388
389impl<Color> ColorFunction<Color> {
390 pub fn map_origin_color<U>(
392 &self,
393 f: impl FnOnce(&Color) -> Result<U, ()>,
394 ) -> Result<ColorFunction<U>, ()> {
395 macro_rules! map {
396 ($f:ident, $o:expr, $c0:expr, $c1:expr, $c2:expr, $alpha:expr) => {{
397 ColorFunction::$f(
398 match $o.as_ref() {
399 Some(c) => Some(f(c)?),
400 None => None,
401 }
402 .into(),
403 $c0.clone(),
404 $c1.clone(),
405 $c2.clone(),
406 $alpha.clone(),
407 )
408 }};
409 }
410 Ok(match self {
411 ColorFunction::Rgb(o, c0, c1, c2, alpha) => map!(Rgb, o, c0, c1, c2, alpha),
412 ColorFunction::Hsl(o, c0, c1, c2, alpha) => map!(Hsl, o, c0, c1, c2, alpha),
413 ColorFunction::Hwb(o, c0, c1, c2, alpha) => map!(Hwb, o, c0, c1, c2, alpha),
414 ColorFunction::Lab(o, c0, c1, c2, alpha) => map!(Lab, o, c0, c1, c2, alpha),
415 ColorFunction::Lch(o, c0, c1, c2, alpha) => map!(Lch, o, c0, c1, c2, alpha),
416 ColorFunction::Oklab(o, c0, c1, c2, alpha) => map!(Oklab, o, c0, c1, c2, alpha),
417 ColorFunction::Oklch(o, c0, c1, c2, alpha) => map!(Oklch, o, c0, c1, c2, alpha),
418 ColorFunction::Color(o, c0, c1, c2, alpha, color_space) => ColorFunction::Color(
419 match o.as_ref() {
420 Some(c) => Some(f(c)?),
421 None => None,
422 }
423 .into(),
424 c0.clone(),
425 c1.clone(),
426 c2.clone(),
427 alpha.clone(),
428 color_space.clone(),
429 ),
430 })
431 }
432}
433
434impl ColorFunction<ComputedColor> {
435 pub fn resolve_to_absolute(&self, current_color: &AbsoluteColor) -> AbsoluteColor {
437 let resolvable = self
439 .map_origin_color(|o| Ok(o.resolve_to_absolute(current_color)))
440 .unwrap();
441 match resolvable.resolve_to_absolute(None) {
443 Ok(color) => color,
444 Err(..) => {
445 debug_assert!(
446 false,
447 "the color could not be resolved even with a currentcolor specified?"
448 );
449 AbsoluteColor::TRANSPARENT_BLACK
450 },
451 }
452 }
453}
454
455impl<C: style_traits::ToCss> style_traits::ToCss for ColorFunction<C> {
456 fn to_css<W>(&self, dest: &mut style_traits::CssWriter<W>) -> std::fmt::Result
457 where
458 W: std::fmt::Write,
459 {
460 let (origin_color, alpha) = match self {
461 Self::Rgb(origin_color, _, _, _, alpha) => {
462 dest.write_str("rgb(")?;
463 (origin_color, alpha)
464 },
465 Self::Hsl(origin_color, _, _, _, alpha) => {
466 dest.write_str("hsl(")?;
467 (origin_color, alpha)
468 },
469 Self::Hwb(origin_color, _, _, _, alpha) => {
470 dest.write_str("hwb(")?;
471 (origin_color, alpha)
472 },
473 Self::Lab(origin_color, _, _, _, alpha) => {
474 dest.write_str("lab(")?;
475 (origin_color, alpha)
476 },
477 Self::Lch(origin_color, _, _, _, alpha) => {
478 dest.write_str("lch(")?;
479 (origin_color, alpha)
480 },
481 Self::Oklab(origin_color, _, _, _, alpha) => {
482 dest.write_str("oklab(")?;
483 (origin_color, alpha)
484 },
485 Self::Oklch(origin_color, _, _, _, alpha) => {
486 dest.write_str("oklch(")?;
487 (origin_color, alpha)
488 },
489 Self::Color(origin_color, _, _, _, alpha, _) => {
490 dest.write_str("color(")?;
491 (origin_color, alpha)
492 },
493 };
494
495 if let Optional::Some(origin_color) = origin_color {
496 dest.write_str("from ")?;
497 origin_color.to_css(dest)?;
498 dest.write_str(" ")?;
499 }
500
501 let is_opaque = if let ColorComponent::Value(value) = *alpha {
502 value.to_number(OPAQUE) == OPAQUE
503 } else {
504 false
505 };
506
507 macro_rules! serialize_alpha {
508 ($alpha_component:expr) => {{
509 if !is_opaque && !matches!($alpha_component, ColorComponent::AlphaOmitted) {
510 dest.write_str(" / ")?;
511 $alpha_component.to_css(dest)?;
512 }
513 }};
514 }
515
516 macro_rules! serialize_components {
517 ($c0:expr, $c1:expr, $c2:expr) => {{
518 debug_assert!(!matches!($c0, ColorComponent::AlphaOmitted));
519 debug_assert!(!matches!($c1, ColorComponent::AlphaOmitted));
520 debug_assert!(!matches!($c2, ColorComponent::AlphaOmitted));
521
522 $c0.to_css(dest)?;
523 dest.write_str(" ")?;
524 $c1.to_css(dest)?;
525 dest.write_str(" ")?;
526 $c2.to_css(dest)?;
527 }};
528 }
529
530 match self {
531 Self::Rgb(_, c0, c1, c2, alpha) => {
532 serialize_components!(c0, c1, c2);
533 serialize_alpha!(alpha);
534 },
535 Self::Hsl(_, c0, c1, c2, alpha) => {
536 serialize_components!(c0, c1, c2);
537 serialize_alpha!(alpha);
538 },
539 Self::Hwb(_, c0, c1, c2, alpha) => {
540 serialize_components!(c0, c1, c2);
541 serialize_alpha!(alpha);
542 },
543 Self::Lab(_, c0, c1, c2, alpha) => {
544 serialize_components!(c0, c1, c2);
545 serialize_alpha!(alpha);
546 },
547 Self::Lch(_, c0, c1, c2, alpha) => {
548 serialize_components!(c0, c1, c2);
549 serialize_alpha!(alpha);
550 },
551 Self::Oklab(_, c0, c1, c2, alpha) => {
552 serialize_components!(c0, c1, c2);
553 serialize_alpha!(alpha);
554 },
555 Self::Oklch(_, c0, c1, c2, alpha) => {
556 serialize_components!(c0, c1, c2);
557 serialize_alpha!(alpha);
558 },
559 Self::Color(_, c0, c1, c2, alpha, color_space) => {
560 color_space.to_css(dest)?;
561 dest.write_str(" ")?;
562 serialize_components!(c0, c1, c2);
563 serialize_alpha!(alpha);
564 },
565 }
566
567 dest.write_str(")")
568 }
569}