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