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 self.map_origin_color(|o| o.resolve_to_absolute())?
375 .resolve_to_absolute()
376 }
377}
378
379impl<Color> ColorFunction<Color> {
380 pub fn map_origin_color<U>(
382 &self,
383 f: impl FnOnce(&Color) -> Result<U, ()>,
384 ) -> Result<ColorFunction<U>, ()> {
385 macro_rules! map {
386 ($f:ident, $o:expr, $c0:expr, $c1:expr, $c2:expr, $alpha:expr) => {{
387 ColorFunction::$f(
388 match $o.as_ref() {
389 Some(c) => Some(f(c)?),
390 None => None,
391 }
392 .into(),
393 $c0.clone(),
394 $c1.clone(),
395 $c2.clone(),
396 $alpha.clone(),
397 )
398 }};
399 }
400 Ok(match self {
401 ColorFunction::Rgb(o, c0, c1, c2, alpha) => map!(Rgb, o, c0, c1, c2, alpha),
402 ColorFunction::Hsl(o, c0, c1, c2, alpha) => map!(Hsl, o, c0, c1, c2, alpha),
403 ColorFunction::Hwb(o, c0, c1, c2, alpha) => map!(Hwb, o, c0, c1, c2, alpha),
404 ColorFunction::Lab(o, c0, c1, c2, alpha) => map!(Lab, o, c0, c1, c2, alpha),
405 ColorFunction::Lch(o, c0, c1, c2, alpha) => map!(Lch, o, c0, c1, c2, alpha),
406 ColorFunction::Oklab(o, c0, c1, c2, alpha) => map!(Oklab, o, c0, c1, c2, alpha),
407 ColorFunction::Oklch(o, c0, c1, c2, alpha) => map!(Oklch, o, c0, c1, c2, alpha),
408 ColorFunction::Color(o, c0, c1, c2, alpha, color_space) => ColorFunction::Color(
409 match o.as_ref() {
410 Some(c) => Some(f(c)?),
411 None => None,
412 }
413 .into(),
414 c0.clone(),
415 c1.clone(),
416 c2.clone(),
417 alpha.clone(),
418 color_space.clone(),
419 ),
420 })
421 }
422}
423
424impl ColorFunction<ComputedColor> {
425 pub fn resolve_to_absolute(&self, current_color: &AbsoluteColor) -> AbsoluteColor {
427 let resolvable = self
429 .map_origin_color(|o| Ok(o.resolve_to_absolute(current_color)))
430 .unwrap();
431 match resolvable.resolve_to_absolute() {
432 Ok(color) => color,
433 Err(..) => {
434 debug_assert!(
435 false,
436 "the color could not be resolved even with a currentcolor specified?"
437 );
438 AbsoluteColor::TRANSPARENT_BLACK
439 },
440 }
441 }
442}
443
444impl<C: style_traits::ToCss> style_traits::ToCss for ColorFunction<C> {
445 fn to_css<W>(&self, dest: &mut style_traits::CssWriter<W>) -> std::fmt::Result
446 where
447 W: std::fmt::Write,
448 {
449 let (origin_color, alpha) = match self {
450 Self::Rgb(origin_color, _, _, _, alpha) => {
451 dest.write_str("rgb(")?;
452 (origin_color, alpha)
453 },
454 Self::Hsl(origin_color, _, _, _, alpha) => {
455 dest.write_str("hsl(")?;
456 (origin_color, alpha)
457 },
458 Self::Hwb(origin_color, _, _, _, alpha) => {
459 dest.write_str("hwb(")?;
460 (origin_color, alpha)
461 },
462 Self::Lab(origin_color, _, _, _, alpha) => {
463 dest.write_str("lab(")?;
464 (origin_color, alpha)
465 },
466 Self::Lch(origin_color, _, _, _, alpha) => {
467 dest.write_str("lch(")?;
468 (origin_color, alpha)
469 },
470 Self::Oklab(origin_color, _, _, _, alpha) => {
471 dest.write_str("oklab(")?;
472 (origin_color, alpha)
473 },
474 Self::Oklch(origin_color, _, _, _, alpha) => {
475 dest.write_str("oklch(")?;
476 (origin_color, alpha)
477 },
478 Self::Color(origin_color, _, _, _, alpha, _) => {
479 dest.write_str("color(")?;
480 (origin_color, alpha)
481 },
482 };
483
484 if let Optional::Some(origin_color) = origin_color {
485 dest.write_str("from ")?;
486 origin_color.to_css(dest)?;
487 dest.write_str(" ")?;
488 }
489
490 let is_opaque = if let ColorComponent::Value(value) = *alpha {
491 value.to_number(OPAQUE) == OPAQUE
492 } else {
493 false
494 };
495
496 macro_rules! serialize_alpha {
497 ($alpha_component:expr) => {{
498 if !is_opaque && !matches!($alpha_component, ColorComponent::AlphaOmitted) {
499 dest.write_str(" / ")?;
500 $alpha_component.to_css(dest)?;
501 }
502 }};
503 }
504
505 macro_rules! serialize_components {
506 ($c0:expr, $c1:expr, $c2:expr) => {{
507 debug_assert!(!matches!($c0, ColorComponent::AlphaOmitted));
508 debug_assert!(!matches!($c1, ColorComponent::AlphaOmitted));
509 debug_assert!(!matches!($c2, ColorComponent::AlphaOmitted));
510
511 $c0.to_css(dest)?;
512 dest.write_str(" ")?;
513 $c1.to_css(dest)?;
514 dest.write_str(" ")?;
515 $c2.to_css(dest)?;
516 }};
517 }
518
519 match self {
520 Self::Rgb(_, c0, c1, c2, alpha) => {
521 serialize_components!(c0, c1, c2);
522 serialize_alpha!(alpha);
523 },
524 Self::Hsl(_, c0, c1, c2, alpha) => {
525 serialize_components!(c0, c1, c2);
526 serialize_alpha!(alpha);
527 },
528 Self::Hwb(_, c0, c1, c2, alpha) => {
529 serialize_components!(c0, c1, c2);
530 serialize_alpha!(alpha);
531 },
532 Self::Lab(_, c0, c1, c2, alpha) => {
533 serialize_components!(c0, c1, c2);
534 serialize_alpha!(alpha);
535 },
536 Self::Lch(_, c0, c1, c2, alpha) => {
537 serialize_components!(c0, c1, c2);
538 serialize_alpha!(alpha);
539 },
540 Self::Oklab(_, c0, c1, c2, alpha) => {
541 serialize_components!(c0, c1, c2);
542 serialize_alpha!(alpha);
543 },
544 Self::Oklch(_, c0, c1, c2, alpha) => {
545 serialize_components!(c0, c1, c2);
546 serialize_alpha!(alpha);
547 },
548 Self::Color(_, c0, c1, c2, alpha, color_space) => {
549 color_space.to_css(dest)?;
550 dest.write_str(" ")?;
551 serialize_components!(c0, c1, c2);
552 serialize_alpha!(alpha);
553 },
554 }
555
556 dest.write_str(")")
557 }
558}