1use alloc::string::{String, ToString};
4use core::{
5 fmt,
6 num::{ParseFloatError, ParseIntError},
7};
8
9use crate::{
10 impl_option,
11 props::basic::{
12 direction::{
13 parse_direction, CssDirectionParseError, CssDirectionParseErrorOwned, Direction,
14 },
15 length::{PercentageParseError, PercentageValue},
16 },
17};
18
19#[derive(Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Hash)]
21#[repr(C)]
22pub struct ColorU {
23 pub r: u8,
24 pub g: u8,
25 pub b: u8,
26 pub a: u8,
27}
28
29impl_option!(
30 ColorU,
31 OptionColorU,
32 [Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Hash]
33);
34
35impl Default for ColorU {
36 fn default() -> Self {
37 ColorU::BLACK
38 }
39}
40
41impl fmt::Display for ColorU {
42 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43 write!(
44 f,
45 "rgba({}, {}, {}, {})",
46 self.r,
47 self.g,
48 self.b,
49 self.a as f32 / 255.0
50 )
51 }
52}
53
54impl ColorU {
55 pub const ALPHA_TRANSPARENT: u8 = 0;
56 pub const ALPHA_OPAQUE: u8 = 255;
57 pub const RED: ColorU = ColorU {
58 r: 255,
59 g: 0,
60 b: 0,
61 a: Self::ALPHA_OPAQUE,
62 };
63 pub const GREEN: ColorU = ColorU {
64 r: 0,
65 g: 255,
66 b: 0,
67 a: Self::ALPHA_OPAQUE,
68 };
69 pub const BLUE: ColorU = ColorU {
70 r: 0,
71 g: 0,
72 b: 255,
73 a: Self::ALPHA_OPAQUE,
74 };
75 pub const WHITE: ColorU = ColorU {
76 r: 255,
77 g: 255,
78 b: 255,
79 a: Self::ALPHA_OPAQUE,
80 };
81 pub const BLACK: ColorU = ColorU {
82 r: 0,
83 g: 0,
84 b: 0,
85 a: Self::ALPHA_OPAQUE,
86 };
87 pub const TRANSPARENT: ColorU = ColorU {
88 r: 0,
89 g: 0,
90 b: 0,
91 a: Self::ALPHA_TRANSPARENT,
92 };
93
94 pub const YELLOW: ColorU = ColorU { r: 255, g: 255, b: 0, a: Self::ALPHA_OPAQUE };
96 pub const CYAN: ColorU = ColorU { r: 0, g: 255, b: 255, a: Self::ALPHA_OPAQUE };
97 pub const MAGENTA: ColorU = ColorU { r: 255, g: 0, b: 255, a: Self::ALPHA_OPAQUE };
98 pub const ORANGE: ColorU = ColorU { r: 255, g: 165, b: 0, a: Self::ALPHA_OPAQUE };
99 pub const PINK: ColorU = ColorU { r: 255, g: 192, b: 203, a: Self::ALPHA_OPAQUE };
100 pub const PURPLE: ColorU = ColorU { r: 128, g: 0, b: 128, a: Self::ALPHA_OPAQUE };
101 pub const BROWN: ColorU = ColorU { r: 139, g: 69, b: 19, a: Self::ALPHA_OPAQUE };
102 pub const GRAY: ColorU = ColorU { r: 128, g: 128, b: 128, a: Self::ALPHA_OPAQUE };
103 pub const LIGHT_GRAY: ColorU = ColorU { r: 211, g: 211, b: 211, a: Self::ALPHA_OPAQUE };
104 pub const DARK_GRAY: ColorU = ColorU { r: 64, g: 64, b: 64, a: Self::ALPHA_OPAQUE };
105 pub const NAVY: ColorU = ColorU { r: 0, g: 0, b: 128, a: Self::ALPHA_OPAQUE };
106 pub const TEAL: ColorU = ColorU { r: 0, g: 128, b: 128, a: Self::ALPHA_OPAQUE };
107 pub const OLIVE: ColorU = ColorU { r: 128, g: 128, b: 0, a: Self::ALPHA_OPAQUE };
108 pub const MAROON: ColorU = ColorU { r: 128, g: 0, b: 0, a: Self::ALPHA_OPAQUE };
109 pub const LIME: ColorU = ColorU { r: 0, g: 255, b: 0, a: Self::ALPHA_OPAQUE };
110 pub const AQUA: ColorU = ColorU { r: 0, g: 255, b: 255, a: Self::ALPHA_OPAQUE };
111 pub const SILVER: ColorU = ColorU { r: 192, g: 192, b: 192, a: Self::ALPHA_OPAQUE };
112 pub const FUCHSIA: ColorU = ColorU { r: 255, g: 0, b: 255, a: Self::ALPHA_OPAQUE };
113 pub const INDIGO: ColorU = ColorU { r: 75, g: 0, b: 130, a: Self::ALPHA_OPAQUE };
114 pub const GOLD: ColorU = ColorU { r: 255, g: 215, b: 0, a: Self::ALPHA_OPAQUE };
115 pub const CORAL: ColorU = ColorU { r: 255, g: 127, b: 80, a: Self::ALPHA_OPAQUE };
116 pub const SALMON: ColorU = ColorU { r: 250, g: 128, b: 114, a: Self::ALPHA_OPAQUE };
117 pub const TURQUOISE: ColorU = ColorU { r: 64, g: 224, b: 208, a: Self::ALPHA_OPAQUE };
118 pub const VIOLET: ColorU = ColorU { r: 238, g: 130, b: 238, a: Self::ALPHA_OPAQUE };
119 pub const CRIMSON: ColorU = ColorU { r: 220, g: 20, b: 60, a: Self::ALPHA_OPAQUE };
120 pub const CHOCOLATE: ColorU = ColorU { r: 210, g: 105, b: 30, a: Self::ALPHA_OPAQUE };
121 pub const SKY_BLUE: ColorU = ColorU { r: 135, g: 206, b: 235, a: Self::ALPHA_OPAQUE };
122 pub const FOREST_GREEN: ColorU = ColorU { r: 34, g: 139, b: 34, a: Self::ALPHA_OPAQUE };
123 pub const SEA_GREEN: ColorU = ColorU { r: 46, g: 139, b: 87, a: Self::ALPHA_OPAQUE };
124 pub const SLATE_GRAY: ColorU = ColorU { r: 112, g: 128, b: 144, a: Self::ALPHA_OPAQUE };
125 pub const MIDNIGHT_BLUE: ColorU = ColorU { r: 25, g: 25, b: 112, a: Self::ALPHA_OPAQUE };
126 pub const DARK_RED: ColorU = ColorU { r: 139, g: 0, b: 0, a: Self::ALPHA_OPAQUE };
127 pub const DARK_GREEN: ColorU = ColorU { r: 0, g: 100, b: 0, a: Self::ALPHA_OPAQUE };
128 pub const DARK_BLUE: ColorU = ColorU { r: 0, g: 0, b: 139, a: Self::ALPHA_OPAQUE };
129 pub const LIGHT_BLUE: ColorU = ColorU { r: 173, g: 216, b: 230, a: Self::ALPHA_OPAQUE };
130 pub const LIGHT_GREEN: ColorU = ColorU { r: 144, g: 238, b: 144, a: Self::ALPHA_OPAQUE };
131 pub const LIGHT_YELLOW: ColorU = ColorU { r: 255, g: 255, b: 224, a: Self::ALPHA_OPAQUE };
132 pub const LIGHT_PINK: ColorU = ColorU { r: 255, g: 182, b: 193, a: Self::ALPHA_OPAQUE };
133
134 pub fn red() -> Self { Self::RED }
136 pub fn green() -> Self { Self::GREEN }
137 pub fn blue() -> Self { Self::BLUE }
138 pub fn white() -> Self { Self::WHITE }
139 pub fn black() -> Self { Self::BLACK }
140 pub fn transparent() -> Self { Self::TRANSPARENT }
141 pub fn yellow() -> Self { Self::YELLOW }
142 pub fn cyan() -> Self { Self::CYAN }
143 pub fn magenta() -> Self { Self::MAGENTA }
144 pub fn orange() -> Self { Self::ORANGE }
145 pub fn pink() -> Self { Self::PINK }
146 pub fn purple() -> Self { Self::PURPLE }
147 pub fn brown() -> Self { Self::BROWN }
148 pub fn gray() -> Self { Self::GRAY }
149 pub fn light_gray() -> Self { Self::LIGHT_GRAY }
150 pub fn dark_gray() -> Self { Self::DARK_GRAY }
151 pub fn navy() -> Self { Self::NAVY }
152 pub fn teal() -> Self { Self::TEAL }
153 pub fn olive() -> Self { Self::OLIVE }
154 pub fn maroon() -> Self { Self::MAROON }
155 pub fn lime() -> Self { Self::LIME }
156 pub fn aqua() -> Self { Self::AQUA }
157 pub fn silver() -> Self { Self::SILVER }
158 pub fn fuchsia() -> Self { Self::FUCHSIA }
159 pub fn indigo() -> Self { Self::INDIGO }
160 pub fn gold() -> Self { Self::GOLD }
161 pub fn coral() -> Self { Self::CORAL }
162 pub fn salmon() -> Self { Self::SALMON }
163 pub fn turquoise() -> Self { Self::TURQUOISE }
164 pub fn violet() -> Self { Self::VIOLET }
165 pub fn crimson() -> Self { Self::CRIMSON }
166 pub fn chocolate() -> Self { Self::CHOCOLATE }
167 pub fn sky_blue() -> Self { Self::SKY_BLUE }
168 pub fn forest_green() -> Self { Self::FOREST_GREEN }
169 pub fn sea_green() -> Self { Self::SEA_GREEN }
170 pub fn slate_gray() -> Self { Self::SLATE_GRAY }
171 pub fn midnight_blue() -> Self { Self::MIDNIGHT_BLUE }
172 pub fn dark_red() -> Self { Self::DARK_RED }
173 pub fn dark_green() -> Self { Self::DARK_GREEN }
174 pub fn dark_blue() -> Self { Self::DARK_BLUE }
175 pub fn light_blue() -> Self { Self::LIGHT_BLUE }
176 pub fn light_green() -> Self { Self::LIGHT_GREEN }
177 pub fn light_yellow() -> Self { Self::LIGHT_YELLOW }
178 pub fn light_pink() -> Self { Self::LIGHT_PINK }
179
180 pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
182 Self { r, g, b, a }
183 }
184 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
186 Self { r, g, b, a: 255 }
187 }
188 #[inline(always)]
190 pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
191 Self::rgba(r, g, b, a)
192 }
193 #[inline(always)]
195 pub const fn new_rgb(r: u8, g: u8, b: u8) -> Self {
196 Self::rgb(r, g, b)
197 }
198
199 pub fn interpolate(&self, other: &Self, t: f32) -> Self {
200 Self {
201 r: libm::roundf(self.r as f32 + (other.r as f32 - self.r as f32) * t) as u8,
202 g: libm::roundf(self.g as f32 + (other.g as f32 - self.g as f32) * t) as u8,
203 b: libm::roundf(self.b as f32 + (other.b as f32 - self.b as f32) * t) as u8,
204 a: libm::roundf(self.a as f32 + (other.a as f32 - self.a as f32) * t) as u8,
205 }
206 }
207
208 pub fn lighten(&self, amount: f32) -> Self {
211 self.interpolate(&Self::WHITE, amount.clamp(0.0, 1.0))
212 }
213
214 pub fn darken(&self, amount: f32) -> Self {
217 self.interpolate(&Self::BLACK, amount.clamp(0.0, 1.0))
218 }
219
220 pub fn mix(&self, other: &Self, ratio: f32) -> Self {
222 self.interpolate(other, ratio.clamp(0.0, 1.0))
223 }
224
225 pub fn hover_variant(&self) -> Self {
228 let luminance = self.luminance();
229 if luminance > 0.5 {
230 self.darken(0.08)
231 } else {
232 self.lighten(0.12)
233 }
234 }
235
236 pub fn active_variant(&self) -> Self {
239 let luminance = self.luminance();
240 if luminance > 0.5 {
241 self.darken(0.15)
242 } else {
243 self.lighten(0.05)
244 }
245 }
246
247 pub fn luminance(&self) -> f32 {
250 let r = (self.r as f32) / 255.0;
251 let g = (self.g as f32) / 255.0;
252 let b = (self.b as f32) / 255.0;
253 0.2126 * r + 0.7152 * g + 0.0722 * b
254 }
255
256 pub fn contrast_text(&self) -> Self {
258 if self.luminance() > 0.5 {
259 Self::BLACK
260 } else {
261 Self::WHITE
262 }
263 }
264
265 fn srgb_to_linear(c: f32) -> f32 {
273 if c <= 0.03928 {
274 c / 12.92
275 } else {
276 libm::powf((c + 0.055) / 1.055, 2.4)
277 }
278 }
279
280 pub fn relative_luminance(&self) -> f32 {
284 let r = Self::srgb_to_linear((self.r as f32) / 255.0);
285 let g = Self::srgb_to_linear((self.g as f32) / 255.0);
286 let b = Self::srgb_to_linear((self.b as f32) / 255.0);
287 0.2126 * r + 0.7152 * g + 0.0722 * b
288 }
289
290 pub fn contrast_ratio(&self, other: &Self) -> f32 {
299 let l1 = self.relative_luminance();
300 let l2 = other.relative_luminance();
301 let lighter = if l1 > l2 { l1 } else { l2 };
302 let darker = if l1 > l2 { l2 } else { l1 };
303 (lighter + 0.05) / (darker + 0.05)
304 }
305
306 pub fn meets_wcag_aa(&self, other: &Self) -> bool {
308 self.contrast_ratio(other) >= 4.5
309 }
310
311 pub fn meets_wcag_aa_large(&self, other: &Self) -> bool {
314 self.contrast_ratio(other) >= 3.0
315 }
316
317 pub fn meets_wcag_aaa(&self, other: &Self) -> bool {
319 self.contrast_ratio(other) >= 7.0
320 }
321
322 pub fn meets_wcag_aaa_large(&self, other: &Self) -> bool {
324 self.contrast_ratio(other) >= 4.5
325 }
326
327 pub fn is_light(&self) -> bool {
330 self.luminance() > 0.5
331 }
332
333 pub fn is_dark(&self) -> bool {
335 self.luminance() <= 0.5
336 }
337
338 pub fn best_contrast_text(&self) -> Self {
344 let white_contrast = self.contrast_ratio(&Self::WHITE);
345 let black_contrast = self.contrast_ratio(&Self::BLACK);
346
347 if white_contrast >= black_contrast {
348 Self::WHITE
349 } else {
350 Self::BLACK
351 }
352 }
353
354 pub fn ensure_contrast(&self, background: &Self, min_ratio: f32) -> Self {
360 let current_ratio = self.contrast_ratio(background);
361 if current_ratio >= min_ratio {
362 return *self;
363 }
364
365 let bg_luminance = background.relative_luminance();
367 let should_lighten = bg_luminance < 0.5;
368
369 let mut low = 0.0f32;
371 let mut high = 1.0f32;
372 let mut result = *self;
373
374 for _ in 0..16 {
375 let mid = (low + high) / 2.0;
376 let candidate = if should_lighten {
377 self.lighten(mid)
378 } else {
379 self.darken(mid)
380 };
381
382 if candidate.contrast_ratio(background) >= min_ratio {
383 result = candidate;
384 high = mid;
385 } else {
386 low = mid;
387 }
388 }
389
390 result
391 }
392
393 pub fn apca_contrast(&self, background: &Self) -> f32 {
400 let text_y = self.relative_luminance();
402 let bg_y = background.relative_luminance();
403
404 let text_y = if text_y < 0.0 { 0.0 } else { text_y };
406 let bg_y = if bg_y < 0.0 { 0.0 } else { bg_y };
407
408 const NORMBLKTXT: f32 = 0.56;
410 const NORMWHT: f32 = 0.57;
411 const REVTXT: f32 = 0.62;
412 const REVWHT: f32 = 0.65;
413 const BLKTHRS: f32 = 0.022;
414 const SCALEBLKT: f32 = 1.414;
415 const SCALEWHT: f32 = 1.14;
416
417 let txt_clamp = if text_y < BLKTHRS {
419 text_y + libm::powf(BLKTHRS - text_y, SCALEBLKT)
420 } else {
421 text_y
422 };
423 let bg_clamp = if bg_y < BLKTHRS {
424 bg_y + libm::powf(BLKTHRS - bg_y, SCALEBLKT)
425 } else {
426 bg_y
427 };
428
429 let sapc = if bg_clamp > txt_clamp {
431 let s = (libm::powf(bg_clamp, NORMWHT) - libm::powf(txt_clamp, NORMBLKTXT)) * SCALEWHT;
433 if s < 0.1 { 0.0 } else { s * 100.0 }
434 } else {
435 let s = (libm::powf(bg_clamp, REVWHT) - libm::powf(txt_clamp, REVTXT)) * SCALEWHT;
437 if s > -0.1 { 0.0 } else { s * 100.0 }
438 };
439
440 sapc
441 }
442
443 pub fn meets_apca_body(&self, background: &Self) -> bool {
445 libm::fabsf(self.apca_contrast(background)) >= 60.0
446 }
447
448 pub fn meets_apca_large(&self, background: &Self) -> bool {
450 libm::fabsf(self.apca_contrast(background)) >= 45.0
451 }
452
453 pub fn with_alpha(&self, a: u8) -> Self {
455 Self { r: self.r, g: self.g, b: self.b, a }
456 }
457
458 pub fn with_alpha_f32(&self, a: f32) -> Self {
460 self.with_alpha((a.clamp(0.0, 1.0) * 255.0) as u8)
461 }
462
463 pub fn invert(&self) -> Self {
465 Self {
466 r: 255 - self.r,
467 g: 255 - self.g,
468 b: 255 - self.b,
469 a: self.a,
470 }
471 }
472
473 pub fn to_grayscale(&self) -> Self {
475 let gray = (0.299 * self.r as f32 + 0.587 * self.g as f32 + 0.114 * self.b as f32) as u8;
476 Self { r: gray, g: gray, b: gray, a: self.a }
477 }
478
479 pub const fn has_alpha(&self) -> bool {
480 self.a != Self::ALPHA_OPAQUE
481 }
482
483 pub fn to_hash(&self) -> String {
484 format!("#{:02x}{:02x}{:02x}{:02x}", self.r, self.g, self.b, self.a)
485 }
486
487 pub fn strawberry(shade: usize) -> Self {
493 match shade {
494 0..=200 => Self::rgb(0xff, 0x8c, 0x82), 201..=400 => Self::rgb(0xed, 0x53, 0x53), 401..=600 => Self::rgb(0xc6, 0x26, 0x2e), 601..=800 => Self::rgb(0xa1, 0x07, 0x05), _ => Self::rgb(0x7a, 0x00, 0x00), }
500 }
501
502 pub fn palette_orange(shade: usize) -> Self {
504 match shade {
505 0..=200 => Self::rgb(0xff, 0xc2, 0x7d), 201..=400 => Self::rgb(0xff, 0xa1, 0x54), 401..=600 => Self::rgb(0xf3, 0x73, 0x29), 601..=800 => Self::rgb(0xcc, 0x3b, 0x02), _ => Self::rgb(0xa6, 0x21, 0x00), }
511 }
512
513 pub fn banana(shade: usize) -> Self {
515 match shade {
516 0..=200 => Self::rgb(0xff, 0xf3, 0x94), 201..=400 => Self::rgb(0xff, 0xe1, 0x6b), 401..=600 => Self::rgb(0xf9, 0xc4, 0x40), 601..=800 => Self::rgb(0xd4, 0x8e, 0x15), _ => Self::rgb(0xad, 0x5f, 0x00), }
522 }
523
524 pub fn palette_lime(shade: usize) -> Self {
526 match shade {
527 0..=200 => Self::rgb(0xd1, 0xff, 0x82), 201..=400 => Self::rgb(0x9b, 0xdb, 0x4d), 401..=600 => Self::rgb(0x68, 0xb7, 0x23), 601..=800 => Self::rgb(0x3a, 0x91, 0x04), _ => Self::rgb(0x20, 0x6b, 0x00), }
533 }
534
535 pub fn mint(shade: usize) -> Self {
537 match shade {
538 0..=200 => Self::rgb(0x89, 0xff, 0xdd), 201..=400 => Self::rgb(0x43, 0xd6, 0xb5), 401..=600 => Self::rgb(0x28, 0xbc, 0xa3), 601..=800 => Self::rgb(0x0e, 0x9a, 0x83), _ => Self::rgb(0x00, 0x73, 0x67), }
544 }
545
546 pub fn blueberry(shade: usize) -> Self {
548 match shade {
549 0..=200 => Self::rgb(0x8c, 0xd5, 0xff), 201..=400 => Self::rgb(0x64, 0xba, 0xff), 401..=600 => Self::rgb(0x36, 0x89, 0xe6), 601..=800 => Self::rgb(0x0d, 0x52, 0xbf), _ => Self::rgb(0x00, 0x2e, 0x99), }
555 }
556
557 pub fn grape(shade: usize) -> Self {
559 match shade {
560 0..=200 => Self::rgb(0xe4, 0xc6, 0xfa), 201..=400 => Self::rgb(0xcd, 0x9e, 0xf7), 401..=600 => Self::rgb(0xa5, 0x6d, 0xe2), 601..=800 => Self::rgb(0x72, 0x39, 0xb3), _ => Self::rgb(0x45, 0x29, 0x81), }
566 }
567
568 pub fn bubblegum(shade: usize) -> Self {
570 match shade {
571 0..=200 => Self::rgb(0xfe, 0x9a, 0xb8), 201..=400 => Self::rgb(0xf4, 0x67, 0x9d), 401..=600 => Self::rgb(0xde, 0x3e, 0x80), 601..=800 => Self::rgb(0xbc, 0x24, 0x5d), _ => Self::rgb(0x91, 0x0e, 0x38), }
577 }
578
579 pub fn cocoa(shade: usize) -> Self {
581 match shade {
582 0..=200 => Self::rgb(0xa3, 0x90, 0x7c), 201..=400 => Self::rgb(0x8a, 0x71, 0x5e), 401..=600 => Self::rgb(0x71, 0x53, 0x44), 601..=800 => Self::rgb(0x57, 0x39, 0x2d), _ => Self::rgb(0x3d, 0x21, 0x1b), }
588 }
589
590 pub fn palette_silver(shade: usize) -> Self {
592 match shade {
593 0..=200 => Self::rgb(0xfa, 0xfa, 0xfa), 201..=400 => Self::rgb(0xd4, 0xd4, 0xd4), 401..=600 => Self::rgb(0xab, 0xac, 0xae), 601..=800 => Self::rgb(0x7e, 0x80, 0x87), _ => Self::rgb(0x55, 0x57, 0x61), }
599 }
600
601 pub fn slate(shade: usize) -> Self {
603 match shade {
604 0..=200 => Self::rgb(0x95, 0xa3, 0xab), 201..=400 => Self::rgb(0x66, 0x78, 0x85), 401..=600 => Self::rgb(0x48, 0x5a, 0x6c), 601..=800 => Self::rgb(0x27, 0x34, 0x45), _ => Self::rgb(0x0e, 0x14, 0x1f), }
610 }
611
612 pub fn dark(shade: usize) -> Self {
614 match shade {
615 0..=200 => Self::rgb(0x66, 0x66, 0x66), 201..=400 => Self::rgb(0x4d, 0x4d, 0x4d), 401..=600 => Self::rgb(0x33, 0x33, 0x33), 601..=800 => Self::rgb(0x1a, 0x1a, 0x1a), _ => Self::rgb(0x00, 0x00, 0x00), }
621 }
622
623 pub fn apple_red() -> Self { Self::rgb(255, 59, 48) }
629 pub fn apple_red_dark() -> Self { Self::rgb(255, 69, 58) }
631 pub fn apple_orange() -> Self { Self::rgb(255, 149, 0) }
633 pub fn apple_orange_dark() -> Self { Self::rgb(255, 159, 10) }
635 pub fn apple_yellow() -> Self { Self::rgb(255, 204, 0) }
637 pub fn apple_yellow_dark() -> Self { Self::rgb(255, 214, 10) }
639 pub fn apple_green() -> Self { Self::rgb(40, 205, 65) }
641 pub fn apple_green_dark() -> Self { Self::rgb(40, 215, 75) }
643 pub fn apple_mint() -> Self { Self::rgb(0, 199, 190) }
645 pub fn apple_mint_dark() -> Self { Self::rgb(102, 212, 207) }
647 pub fn apple_teal() -> Self { Self::rgb(89, 173, 196) }
649 pub fn apple_teal_dark() -> Self { Self::rgb(106, 196, 220) }
651 pub fn apple_cyan() -> Self { Self::rgb(85, 190, 240) }
653 pub fn apple_cyan_dark() -> Self { Self::rgb(90, 200, 245) }
655 pub fn apple_blue() -> Self { Self::rgb(0, 122, 255) }
657 pub fn apple_blue_dark() -> Self { Self::rgb(10, 132, 255) }
659 pub fn apple_indigo() -> Self { Self::rgb(88, 86, 214) }
661 pub fn apple_indigo_dark() -> Self { Self::rgb(94, 92, 230) }
663 pub fn apple_purple() -> Self { Self::rgb(175, 82, 222) }
665 pub fn apple_purple_dark() -> Self { Self::rgb(191, 90, 242) }
667 pub fn apple_pink() -> Self { Self::rgb(255, 45, 85) }
669 pub fn apple_pink_dark() -> Self { Self::rgb(255, 55, 95) }
671 pub fn apple_brown() -> Self { Self::rgb(162, 132, 94) }
673 pub fn apple_brown_dark() -> Self { Self::rgb(172, 142, 104) }
675 pub fn apple_gray() -> Self { Self::rgb(142, 142, 147) }
677 pub fn apple_gray_dark() -> Self { Self::rgb(152, 152, 157) }
679
680 pub fn bootstrap_primary() -> Self { Self::rgb(13, 110, 253) }
687 pub fn bootstrap_primary_hover() -> Self { Self::rgb(11, 94, 215) }
688 pub fn bootstrap_primary_active() -> Self { Self::rgb(10, 88, 202) }
689
690 pub fn bootstrap_secondary() -> Self { Self::rgb(108, 117, 125) }
692 pub fn bootstrap_secondary_hover() -> Self { Self::rgb(92, 99, 106) }
693 pub fn bootstrap_secondary_active() -> Self { Self::rgb(86, 94, 100) }
694
695 pub fn bootstrap_success() -> Self { Self::rgb(25, 135, 84) }
697 pub fn bootstrap_success_hover() -> Self { Self::rgb(21, 115, 71) }
698 pub fn bootstrap_success_active() -> Self { Self::rgb(20, 108, 67) }
699
700 pub fn bootstrap_danger() -> Self { Self::rgb(220, 53, 69) }
702 pub fn bootstrap_danger_hover() -> Self { Self::rgb(187, 45, 59) }
703 pub fn bootstrap_danger_active() -> Self { Self::rgb(176, 42, 55) }
704
705 pub fn bootstrap_warning() -> Self { Self::rgb(255, 193, 7) }
707 pub fn bootstrap_warning_hover() -> Self { Self::rgb(255, 202, 44) }
708 pub fn bootstrap_warning_active() -> Self { Self::rgb(255, 205, 57) }
709
710 pub fn bootstrap_info() -> Self { Self::rgb(13, 202, 240) }
712 pub fn bootstrap_info_hover() -> Self { Self::rgb(49, 210, 242) }
713 pub fn bootstrap_info_active() -> Self { Self::rgb(61, 213, 243) }
714
715 pub fn bootstrap_light() -> Self { Self::rgb(248, 249, 250) }
717 pub fn bootstrap_light_hover() -> Self { Self::rgb(233, 236, 239) }
718 pub fn bootstrap_light_active() -> Self { Self::rgb(218, 222, 226) }
719
720 pub fn bootstrap_dark() -> Self { Self::rgb(33, 37, 41) }
722 pub fn bootstrap_dark_hover() -> Self { Self::rgb(66, 70, 73) }
723 pub fn bootstrap_dark_active() -> Self { Self::rgb(78, 81, 84) }
724
725 pub fn bootstrap_link() -> Self { Self::rgb(13, 110, 253) }
727 pub fn bootstrap_link_hover() -> Self { Self::rgb(10, 88, 202) }
728}
729
730#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
732pub struct ColorF {
733 pub r: f32,
734 pub g: f32,
735 pub b: f32,
736 pub a: f32,
737}
738
739impl Default for ColorF {
740 fn default() -> Self {
741 ColorF::BLACK
742 }
743}
744
745impl fmt::Display for ColorF {
746 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
747 write!(
748 f,
749 "rgba({}, {}, {}, {})",
750 self.r * 255.0,
751 self.g * 255.0,
752 self.b * 255.0,
753 self.a
754 )
755 }
756}
757
758impl ColorF {
759 pub const ALPHA_TRANSPARENT: f32 = 0.0;
760 pub const ALPHA_OPAQUE: f32 = 1.0;
761 pub const WHITE: ColorF = ColorF {
762 r: 1.0,
763 g: 1.0,
764 b: 1.0,
765 a: Self::ALPHA_OPAQUE,
766 };
767 pub const BLACK: ColorF = ColorF {
768 r: 0.0,
769 g: 0.0,
770 b: 0.0,
771 a: Self::ALPHA_OPAQUE,
772 };
773 pub const TRANSPARENT: ColorF = ColorF {
774 r: 0.0,
775 g: 0.0,
776 b: 0.0,
777 a: Self::ALPHA_TRANSPARENT,
778 };
779}
780
781impl From<ColorU> for ColorF {
782 fn from(input: ColorU) -> ColorF {
783 ColorF {
784 r: (input.r as f32) / 255.0,
785 g: (input.g as f32) / 255.0,
786 b: (input.b as f32) / 255.0,
787 a: (input.a as f32) / 255.0,
788 }
789 }
790}
791
792impl From<ColorF> for ColorU {
793 fn from(input: ColorF) -> ColorU {
794 ColorU {
795 r: (input.r.min(1.0) * 255.0) as u8,
796 g: (input.g.min(1.0) * 255.0) as u8,
797 b: (input.b.min(1.0) * 255.0) as u8,
798 a: (input.a.min(1.0) * 255.0) as u8,
799 }
800 }
801}
802
803#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
808#[repr(C, u8)]
809pub enum ColorOrSystem {
810 Color(ColorU),
812 System(SystemColorRef),
814}
815
816impl Default for ColorOrSystem {
817 fn default() -> Self {
818 ColorOrSystem::Color(ColorU::BLACK)
819 }
820}
821
822impl From<ColorU> for ColorOrSystem {
823 fn from(color: ColorU) -> Self {
824 ColorOrSystem::Color(color)
825 }
826}
827
828impl ColorOrSystem {
829 pub const fn color(c: ColorU) -> Self {
831 ColorOrSystem::Color(c)
832 }
833
834 pub const fn system(s: SystemColorRef) -> Self {
836 ColorOrSystem::System(s)
837 }
838
839 pub fn resolve(&self, system_colors: &crate::system::SystemColors, fallback: ColorU) -> ColorU {
842 match self {
843 ColorOrSystem::Color(c) => *c,
844 ColorOrSystem::System(ref_type) => ref_type.resolve(system_colors, fallback),
845 }
846 }
847
848 pub fn to_color_u_with_fallback(&self, fallback: ColorU) -> ColorU {
851 match self {
852 ColorOrSystem::Color(c) => *c,
853 ColorOrSystem::System(_) => fallback,
854 }
855 }
856
857 pub fn to_color_u_default(&self) -> ColorU {
859 self.to_color_u_with_fallback(ColorU { r: 128, g: 128, b: 128, a: 255 })
860 }
861}
862
863#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
866#[repr(C)]
867pub enum SystemColorRef {
868 Text,
870 Background,
872 Accent,
874 AccentText,
876 ButtonFace,
878 ButtonText,
880 WindowBackground,
882 SelectionBackground,
884 SelectionText,
886}
887
888impl SystemColorRef {
889 pub fn resolve(&self, colors: &crate::system::SystemColors, fallback: ColorU) -> ColorU {
891 match self {
892 SystemColorRef::Text => colors.text.as_option().copied().unwrap_or(fallback),
893 SystemColorRef::Background => colors.background.as_option().copied().unwrap_or(fallback),
894 SystemColorRef::Accent => colors.accent.as_option().copied().unwrap_or(fallback),
895 SystemColorRef::AccentText => colors.accent_text.as_option().copied().unwrap_or(fallback),
896 SystemColorRef::ButtonFace => colors.button_face.as_option().copied().unwrap_or(fallback),
897 SystemColorRef::ButtonText => colors.button_text.as_option().copied().unwrap_or(fallback),
898 SystemColorRef::WindowBackground => colors.window_background.as_option().copied().unwrap_or(fallback),
899 SystemColorRef::SelectionBackground => colors.selection_background.as_option().copied().unwrap_or(fallback),
900 SystemColorRef::SelectionText => colors.selection_text.as_option().copied().unwrap_or(fallback),
901 }
902 }
903
904 pub fn as_css_str(&self) -> &'static str {
906 match self {
907 SystemColorRef::Text => "system:text",
908 SystemColorRef::Background => "system:background",
909 SystemColorRef::Accent => "system:accent",
910 SystemColorRef::AccentText => "system:accent-text",
911 SystemColorRef::ButtonFace => "system:button-face",
912 SystemColorRef::ButtonText => "system:button-text",
913 SystemColorRef::WindowBackground => "system:window-background",
914 SystemColorRef::SelectionBackground => "system:selection-background",
915 SystemColorRef::SelectionText => "system:selection-text",
916 }
917 }
918}
919
920#[derive(Debug, Copy, Clone, PartialEq)]
923pub enum CssColorComponent {
924 Red,
925 Green,
926 Blue,
927 Hue,
928 Saturation,
929 Lightness,
930 Alpha,
931}
932
933#[derive(Clone, PartialEq)]
934pub enum CssColorParseError<'a> {
935 InvalidColor(&'a str),
936 InvalidFunctionName(&'a str),
937 InvalidColorComponent(u8),
938 IntValueParseErr(ParseIntError),
939 FloatValueParseErr(ParseFloatError),
940 FloatValueOutOfRange(f32),
941 MissingColorComponent(CssColorComponent),
942 ExtraArguments(&'a str),
943 UnclosedColor(&'a str),
944 EmptyInput,
945 DirectionParseError(CssDirectionParseError<'a>),
946 UnsupportedDirection(&'a str),
947 InvalidPercentage(PercentageParseError),
948}
949
950impl_debug_as_display!(CssColorParseError<'a>);
951impl_display! {CssColorParseError<'a>, {
952 InvalidColor(i) => format!("Invalid CSS color: \"{}\"", i),
953 InvalidFunctionName(i) => format!("Invalid function name, expected one of: \"rgb\", \"rgba\", \"hsl\", \"hsla\" got: \"{}\"", i),
954 InvalidColorComponent(i) => format!("Invalid color component when parsing CSS color: \"{}\"", i),
955 IntValueParseErr(e) => format!("CSS color component: Value not in range between 00 - FF: \"{}\"", e),
956 FloatValueParseErr(e) => format!("CSS color component: Value cannot be parsed as floating point number: \"{}\"", e),
957 FloatValueOutOfRange(v) => format!("CSS color component: Value not in range between 0.0 - 1.0: \"{}\"", v),
958 MissingColorComponent(c) => format!("CSS color is missing {:?} component", c),
959 ExtraArguments(a) => format!("Extra argument to CSS color: \"{}\"", a),
960 EmptyInput => format!("Empty color string."),
961 UnclosedColor(i) => format!("Unclosed color: \"{}\"", i),
962 DirectionParseError(e) => format!("Could not parse direction argument for CSS color: \"{}\"", e),
963 UnsupportedDirection(d) => format!("Unsupported direction type for CSS color: \"{}\"", d),
964 InvalidPercentage(p) => format!("Invalid percentage when parsing CSS color: \"{}\"", p),
965}}
966
967impl<'a> From<ParseIntError> for CssColorParseError<'a> {
968 fn from(e: ParseIntError) -> Self {
969 CssColorParseError::IntValueParseErr(e)
970 }
971}
972impl<'a> From<ParseFloatError> for CssColorParseError<'a> {
973 fn from(e: ParseFloatError) -> Self {
974 CssColorParseError::FloatValueParseErr(e)
975 }
976}
977impl_from!(
978 CssDirectionParseError<'a>,
979 CssColorParseError::DirectionParseError
980);
981
982#[derive(Debug, Clone, PartialEq)]
983pub enum CssColorParseErrorOwned {
984 InvalidColor(String),
985 InvalidFunctionName(String),
986 InvalidColorComponent(u8),
987 IntValueParseErr(ParseIntError),
988 FloatValueParseErr(ParseFloatError),
989 FloatValueOutOfRange(f32),
990 MissingColorComponent(CssColorComponent),
991 ExtraArguments(String),
992 UnclosedColor(String),
993 EmptyInput,
994 DirectionParseError(CssDirectionParseErrorOwned),
995 UnsupportedDirection(String),
996 InvalidPercentage(PercentageParseError),
997}
998
999impl<'a> CssColorParseError<'a> {
1000 pub fn to_contained(&self) -> CssColorParseErrorOwned {
1001 match self {
1002 CssColorParseError::InvalidColor(s) => {
1003 CssColorParseErrorOwned::InvalidColor(s.to_string())
1004 }
1005 CssColorParseError::InvalidFunctionName(s) => {
1006 CssColorParseErrorOwned::InvalidFunctionName(s.to_string())
1007 }
1008 CssColorParseError::InvalidColorComponent(n) => {
1009 CssColorParseErrorOwned::InvalidColorComponent(*n)
1010 }
1011 CssColorParseError::IntValueParseErr(e) => {
1012 CssColorParseErrorOwned::IntValueParseErr(e.clone())
1013 }
1014 CssColorParseError::FloatValueParseErr(e) => {
1015 CssColorParseErrorOwned::FloatValueParseErr(e.clone())
1016 }
1017 CssColorParseError::FloatValueOutOfRange(n) => {
1018 CssColorParseErrorOwned::FloatValueOutOfRange(*n)
1019 }
1020 CssColorParseError::MissingColorComponent(c) => {
1021 CssColorParseErrorOwned::MissingColorComponent(*c)
1022 }
1023 CssColorParseError::ExtraArguments(s) => {
1024 CssColorParseErrorOwned::ExtraArguments(s.to_string())
1025 }
1026 CssColorParseError::UnclosedColor(s) => {
1027 CssColorParseErrorOwned::UnclosedColor(s.to_string())
1028 }
1029 CssColorParseError::EmptyInput => CssColorParseErrorOwned::EmptyInput,
1030 CssColorParseError::DirectionParseError(e) => {
1031 CssColorParseErrorOwned::DirectionParseError(e.to_contained())
1032 }
1033 CssColorParseError::UnsupportedDirection(s) => {
1034 CssColorParseErrorOwned::UnsupportedDirection(s.to_string())
1035 }
1036 CssColorParseError::InvalidPercentage(e) => {
1037 CssColorParseErrorOwned::InvalidPercentage(e.clone())
1038 }
1039 }
1040 }
1041}
1042
1043impl CssColorParseErrorOwned {
1044 pub fn to_shared<'a>(&'a self) -> CssColorParseError<'a> {
1045 match self {
1046 CssColorParseErrorOwned::InvalidColor(s) => CssColorParseError::InvalidColor(s),
1047 CssColorParseErrorOwned::InvalidFunctionName(s) => {
1048 CssColorParseError::InvalidFunctionName(s)
1049 }
1050 CssColorParseErrorOwned::InvalidColorComponent(n) => {
1051 CssColorParseError::InvalidColorComponent(*n)
1052 }
1053 CssColorParseErrorOwned::IntValueParseErr(e) => {
1054 CssColorParseError::IntValueParseErr(e.clone())
1055 }
1056 CssColorParseErrorOwned::FloatValueParseErr(e) => {
1057 CssColorParseError::FloatValueParseErr(e.clone())
1058 }
1059 CssColorParseErrorOwned::FloatValueOutOfRange(n) => {
1060 CssColorParseError::FloatValueOutOfRange(*n)
1061 }
1062 CssColorParseErrorOwned::MissingColorComponent(c) => {
1063 CssColorParseError::MissingColorComponent(*c)
1064 }
1065 CssColorParseErrorOwned::ExtraArguments(s) => CssColorParseError::ExtraArguments(s),
1066 CssColorParseErrorOwned::UnclosedColor(s) => CssColorParseError::UnclosedColor(s),
1067 CssColorParseErrorOwned::EmptyInput => CssColorParseError::EmptyInput,
1068 CssColorParseErrorOwned::DirectionParseError(e) => {
1069 CssColorParseError::DirectionParseError(e.to_shared())
1070 }
1071 CssColorParseErrorOwned::UnsupportedDirection(s) => {
1072 CssColorParseError::UnsupportedDirection(s)
1073 }
1074 CssColorParseErrorOwned::InvalidPercentage(e) => {
1075 CssColorParseError::InvalidPercentage(e.clone())
1076 }
1077 }
1078 }
1079}
1080
1081#[cfg(feature = "parser")]
1082pub fn parse_css_color<'a>(input: &'a str) -> Result<ColorU, CssColorParseError<'a>> {
1083 let input = input.trim();
1084 if input.starts_with('#') {
1085 parse_color_no_hash(&input[1..])
1086 } else {
1087 use crate::props::basic::parse::{parse_parentheses, ParenthesisParseError};
1088 match parse_parentheses(input, &["rgba", "rgb", "hsla", "hsl"]) {
1089 Ok((stopword, inner_value)) => match stopword {
1090 "rgba" => parse_color_rgb(inner_value, true),
1091 "rgb" => parse_color_rgb(inner_value, false),
1092 "hsla" => parse_color_hsl(inner_value, true),
1093 "hsl" => parse_color_hsl(inner_value, false),
1094 _ => unreachable!(),
1095 },
1096 Err(e) => match e {
1097 ParenthesisParseError::UnclosedBraces => {
1098 Err(CssColorParseError::UnclosedColor(input))
1099 }
1100 ParenthesisParseError::EmptyInput => Err(CssColorParseError::EmptyInput),
1101 ParenthesisParseError::StopWordNotFound(stopword) => {
1102 Err(CssColorParseError::InvalidFunctionName(stopword))
1103 }
1104 ParenthesisParseError::NoClosingBraceFound => {
1105 Err(CssColorParseError::UnclosedColor(input))
1106 }
1107 ParenthesisParseError::NoOpeningBraceFound => parse_color_builtin(input),
1108 },
1109 }
1110 }
1111}
1112
1113#[cfg(feature = "parser")]
1126pub fn parse_color_or_system<'a>(input: &'a str) -> Result<ColorOrSystem, CssColorParseError<'a>> {
1127 let input = input.trim();
1128
1129 if let Some(system_name) = input.strip_prefix("system:") {
1131 let system_ref = match system_name.trim() {
1132 "text" => SystemColorRef::Text,
1133 "background" => SystemColorRef::Background,
1134 "accent" => SystemColorRef::Accent,
1135 "accent-text" => SystemColorRef::AccentText,
1136 "button-face" => SystemColorRef::ButtonFace,
1137 "button-text" => SystemColorRef::ButtonText,
1138 "window-background" => SystemColorRef::WindowBackground,
1139 "selection-background" => SystemColorRef::SelectionBackground,
1140 "selection-text" => SystemColorRef::SelectionText,
1141 _ => return Err(CssColorParseError::InvalidColor(input)),
1142 };
1143 return Ok(ColorOrSystem::System(system_ref));
1144 }
1145
1146 parse_css_color(input).map(ColorOrSystem::Color)
1148}
1149
1150#[cfg(feature = "parser")]
1151fn parse_color_no_hash<'a>(input: &'a str) -> Result<ColorU, CssColorParseError<'a>> {
1152 #[inline]
1153 fn from_hex<'a>(c: u8) -> Result<u8, CssColorParseError<'a>> {
1154 match c {
1155 b'0'..=b'9' => Ok(c - b'0'),
1156 b'a'..=b'f' => Ok(c - b'a' + 10),
1157 b'A'..=b'F' => Ok(c - b'A' + 10),
1158 _ => Err(CssColorParseError::InvalidColorComponent(c)),
1159 }
1160 }
1161
1162 match input.len() {
1163 3 => {
1164 let mut bytes = input.bytes();
1165 let r = bytes.next().unwrap();
1166 let g = bytes.next().unwrap();
1167 let b = bytes.next().unwrap();
1168 Ok(ColorU::new_rgb(
1169 from_hex(r)? * 17,
1170 from_hex(g)? * 17,
1171 from_hex(b)? * 17,
1172 ))
1173 }
1174 4 => {
1175 let mut bytes = input.bytes();
1176 let r = bytes.next().unwrap();
1177 let g = bytes.next().unwrap();
1178 let b = bytes.next().unwrap();
1179 let a = bytes.next().unwrap();
1180 Ok(ColorU::new(
1181 from_hex(r)? * 17,
1182 from_hex(g)? * 17,
1183 from_hex(b)? * 17,
1184 from_hex(a)? * 17,
1185 ))
1186 }
1187 6 => {
1188 let val = u32::from_str_radix(input, 16)?;
1189 Ok(ColorU::new_rgb(
1190 ((val >> 16) & 0xFF) as u8,
1191 ((val >> 8) & 0xFF) as u8,
1192 (val & 0xFF) as u8,
1193 ))
1194 }
1195 8 => {
1196 let val = u32::from_str_radix(input, 16)?;
1197 Ok(ColorU::new(
1198 ((val >> 24) & 0xFF) as u8,
1199 ((val >> 16) & 0xFF) as u8,
1200 ((val >> 8) & 0xFF) as u8,
1201 (val & 0xFF) as u8,
1202 ))
1203 }
1204 _ => Err(CssColorParseError::InvalidColor(input)),
1205 }
1206}
1207
1208#[cfg(feature = "parser")]
1209fn parse_color_rgb<'a>(
1210 input: &'a str,
1211 parse_alpha: bool,
1212) -> Result<ColorU, CssColorParseError<'a>> {
1213 let mut components = input.split(',').map(|c| c.trim());
1214 let rgb_color = parse_color_rgb_components(&mut components)?;
1215 let a = if parse_alpha {
1216 parse_alpha_component(&mut components)?
1217 } else {
1218 255
1219 };
1220 if let Some(arg) = components.next() {
1221 return Err(CssColorParseError::ExtraArguments(arg));
1222 }
1223 Ok(ColorU { a, ..rgb_color })
1224}
1225
1226#[cfg(feature = "parser")]
1227fn parse_color_rgb_components<'a>(
1228 components: &mut dyn Iterator<Item = &'a str>,
1229) -> Result<ColorU, CssColorParseError<'a>> {
1230 #[inline]
1231 fn component_from_str<'a>(
1232 components: &mut dyn Iterator<Item = &'a str>,
1233 which: CssColorComponent,
1234 ) -> Result<u8, CssColorParseError<'a>> {
1235 let c = components
1236 .next()
1237 .ok_or(CssColorParseError::MissingColorComponent(which))?;
1238 if c.is_empty() {
1239 return Err(CssColorParseError::MissingColorComponent(which));
1240 }
1241 Ok(c.parse::<u8>()?)
1242 }
1243 Ok(ColorU {
1244 r: component_from_str(components, CssColorComponent::Red)?,
1245 g: component_from_str(components, CssColorComponent::Green)?,
1246 b: component_from_str(components, CssColorComponent::Blue)?,
1247 a: 255,
1248 })
1249}
1250
1251#[cfg(feature = "parser")]
1252fn parse_color_hsl<'a>(
1253 input: &'a str,
1254 parse_alpha: bool,
1255) -> Result<ColorU, CssColorParseError<'a>> {
1256 let mut components = input.split(',').map(|c| c.trim());
1257 let rgb_color = parse_color_hsl_components(&mut components)?;
1258 let a = if parse_alpha {
1259 parse_alpha_component(&mut components)?
1260 } else {
1261 255
1262 };
1263 if let Some(arg) = components.next() {
1264 return Err(CssColorParseError::ExtraArguments(arg));
1265 }
1266 Ok(ColorU { a, ..rgb_color })
1267}
1268
1269#[cfg(feature = "parser")]
1270fn parse_color_hsl_components<'a>(
1271 components: &mut dyn Iterator<Item = &'a str>,
1272) -> Result<ColorU, CssColorParseError<'a>> {
1273 #[inline]
1274 fn angle_from_str<'a>(
1275 components: &mut dyn Iterator<Item = &'a str>,
1276 which: CssColorComponent,
1277 ) -> Result<f32, CssColorParseError<'a>> {
1278 let c = components
1279 .next()
1280 .ok_or(CssColorParseError::MissingColorComponent(which))?;
1281 if c.is_empty() {
1282 return Err(CssColorParseError::MissingColorComponent(which));
1283 }
1284 let dir = parse_direction(c)?;
1285 match dir {
1286 Direction::Angle(deg) => Ok(deg.to_degrees()),
1287 Direction::FromTo(_) => Err(CssColorParseError::UnsupportedDirection(c)),
1288 }
1289 }
1290
1291 #[inline]
1292 fn percent_from_str<'a>(
1293 components: &mut dyn Iterator<Item = &'a str>,
1294 which: CssColorComponent,
1295 ) -> Result<f32, CssColorParseError<'a>> {
1296 use crate::props::basic::parse_percentage_value;
1297
1298 let c = components
1299 .next()
1300 .ok_or(CssColorParseError::MissingColorComponent(which))?;
1301 if c.is_empty() {
1302 return Err(CssColorParseError::MissingColorComponent(which));
1303 }
1304
1305 Ok(parse_percentage_value(c)
1307 .map_err(CssColorParseError::InvalidPercentage)?
1308 .normalized()
1309 * 100.0)
1310 }
1311
1312 #[inline]
1313 fn hsl_to_rgb(h: f32, s: f32, l: f32) -> (u8, u8, u8) {
1314 let s = s / 100.0;
1315 let l = l / 100.0;
1316 let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
1317 let h_prime = h / 60.0;
1318 let x = c * (1.0 - ((h_prime % 2.0) - 1.0).abs());
1319 let (r1, g1, b1) = if h_prime >= 0.0 && h_prime < 1.0 {
1320 (c, x, 0.0)
1321 } else if h_prime >= 1.0 && h_prime < 2.0 {
1322 (x, c, 0.0)
1323 } else if h_prime >= 2.0 && h_prime < 3.0 {
1324 (0.0, c, x)
1325 } else if h_prime >= 3.0 && h_prime < 4.0 {
1326 (0.0, x, c)
1327 } else if h_prime >= 4.0 && h_prime < 5.0 {
1328 (x, 0.0, c)
1329 } else {
1330 (c, 0.0, x)
1331 };
1332 let m = l - c / 2.0;
1333 (
1334 ((r1 + m) * 255.0) as u8,
1335 ((g1 + m) * 255.0) as u8,
1336 ((b1 + m) * 255.0) as u8,
1337 )
1338 }
1339
1340 let (h, s, l) = (
1341 angle_from_str(components, CssColorComponent::Hue)?,
1342 percent_from_str(components, CssColorComponent::Saturation)?,
1343 percent_from_str(components, CssColorComponent::Lightness)?,
1344 );
1345
1346 let (r, g, b) = hsl_to_rgb(h, s, l);
1347 Ok(ColorU { r, g, b, a: 255 })
1348}
1349
1350#[cfg(feature = "parser")]
1351fn parse_alpha_component<'a>(
1352 components: &mut dyn Iterator<Item = &'a str>,
1353) -> Result<u8, CssColorParseError<'a>> {
1354 let a_str = components
1355 .next()
1356 .ok_or(CssColorParseError::MissingColorComponent(
1357 CssColorComponent::Alpha,
1358 ))?;
1359 if a_str.is_empty() {
1360 return Err(CssColorParseError::MissingColorComponent(
1361 CssColorComponent::Alpha,
1362 ));
1363 }
1364 let a = a_str.parse::<f32>()?;
1365 if a < 0.0 || a > 1.0 {
1366 return Err(CssColorParseError::FloatValueOutOfRange(a));
1367 }
1368 Ok((a * 255.0).round() as u8)
1369}
1370
1371#[cfg(feature = "parser")]
1372fn parse_color_builtin<'a>(input: &'a str) -> Result<ColorU, CssColorParseError<'a>> {
1373 let (r, g, b, a) = match input.to_lowercase().as_str() {
1374 "aliceblue" => (240, 248, 255, 255),
1375 "antiquewhite" => (250, 235, 215, 255),
1376 "aqua" => (0, 255, 255, 255),
1377 "aquamarine" => (127, 255, 212, 255),
1378 "azure" => (240, 255, 255, 255),
1379 "beige" => (245, 245, 220, 255),
1380 "bisque" => (255, 228, 196, 255),
1381 "black" => (0, 0, 0, 255),
1382 "blanchedalmond" => (255, 235, 205, 255),
1383 "blue" => (0, 0, 255, 255),
1384 "blueviolet" => (138, 43, 226, 255),
1385 "brown" => (165, 42, 42, 255),
1386 "burlywood" => (222, 184, 135, 255),
1387 "cadetblue" => (95, 158, 160, 255),
1388 "chartreuse" => (127, 255, 0, 255),
1389 "chocolate" => (210, 105, 30, 255),
1390 "coral" => (255, 127, 80, 255),
1391 "cornflowerblue" => (100, 149, 237, 255),
1392 "cornsilk" => (255, 248, 220, 255),
1393 "crimson" => (220, 20, 60, 255),
1394 "cyan" => (0, 255, 255, 255),
1395 "darkblue" => (0, 0, 139, 255),
1396 "darkcyan" => (0, 139, 139, 255),
1397 "darkgoldenrod" => (184, 134, 11, 255),
1398 "darkgray" | "darkgrey" => (169, 169, 169, 255),
1399 "darkgreen" => (0, 100, 0, 255),
1400 "darkkhaki" => (189, 183, 107, 255),
1401 "darkmagenta" => (139, 0, 139, 255),
1402 "darkolivegreen" => (85, 107, 47, 255),
1403 "darkorange" => (255, 140, 0, 255),
1404 "darkorchid" => (153, 50, 204, 255),
1405 "darkred" => (139, 0, 0, 255),
1406 "darksalmon" => (233, 150, 122, 255),
1407 "darkseagreen" => (143, 188, 143, 255),
1408 "darkslateblue" => (72, 61, 139, 255),
1409 "darkslategray" | "darkslategrey" => (47, 79, 79, 255),
1410 "darkturquoise" => (0, 206, 209, 255),
1411 "darkviolet" => (148, 0, 211, 255),
1412 "deeppink" => (255, 20, 147, 255),
1413 "deepskyblue" => (0, 191, 255, 255),
1414 "dimgray" | "dimgrey" => (105, 105, 105, 255),
1415 "dodgerblue" => (30, 144, 255, 255),
1416 "firebrick" => (178, 34, 34, 255),
1417 "floralwhite" => (255, 250, 240, 255),
1418 "forestgreen" => (34, 139, 34, 255),
1419 "fuchsia" => (255, 0, 255, 255),
1420 "gainsboro" => (220, 220, 220, 255),
1421 "ghostwhite" => (248, 248, 255, 255),
1422 "gold" => (255, 215, 0, 255),
1423 "goldenrod" => (218, 165, 32, 255),
1424 "gray" | "grey" => (128, 128, 128, 255),
1425 "green" => (0, 128, 0, 255),
1426 "greenyellow" => (173, 255, 47, 255),
1427 "honeydew" => (240, 255, 240, 255),
1428 "hotpink" => (255, 105, 180, 255),
1429 "indianred" => (205, 92, 92, 255),
1430 "indigo" => (75, 0, 130, 255),
1431 "ivory" => (255, 255, 240, 255),
1432 "khaki" => (240, 230, 140, 255),
1433 "lavender" => (230, 230, 250, 255),
1434 "lavenderblush" => (255, 240, 245, 255),
1435 "lawngreen" => (124, 252, 0, 255),
1436 "lemonchiffon" => (255, 250, 205, 255),
1437 "lightblue" => (173, 216, 230, 255),
1438 "lightcoral" => (240, 128, 128, 255),
1439 "lightcyan" => (224, 255, 255, 255),
1440 "lightgoldenrodyellow" => (250, 250, 210, 255),
1441 "lightgray" | "lightgrey" => (211, 211, 211, 255),
1442 "lightgreen" => (144, 238, 144, 255),
1443 "lightpink" => (255, 182, 193, 255),
1444 "lightsalmon" => (255, 160, 122, 255),
1445 "lightseagreen" => (32, 178, 170, 255),
1446 "lightskyblue" => (135, 206, 250, 255),
1447 "lightslategray" | "lightslategrey" => (119, 136, 153, 255),
1448 "lightsteelblue" => (176, 196, 222, 255),
1449 "lightyellow" => (255, 255, 224, 255),
1450 "lime" => (0, 255, 0, 255),
1451 "limegreen" => (50, 205, 50, 255),
1452 "linen" => (250, 240, 230, 255),
1453 "magenta" => (255, 0, 255, 255),
1454 "maroon" => (128, 0, 0, 255),
1455 "mediumaquamarine" => (102, 205, 170, 255),
1456 "mediumblue" => (0, 0, 205, 255),
1457 "mediumorchid" => (186, 85, 211, 255),
1458 "mediumpurple" => (147, 112, 219, 255),
1459 "mediumseagreen" => (60, 179, 113, 255),
1460 "mediumslateblue" => (123, 104, 238, 255),
1461 "mediumspringgreen" => (0, 250, 154, 255),
1462 "mediumturquoise" => (72, 209, 204, 255),
1463 "mediumvioletred" => (199, 21, 133, 255),
1464 "midnightblue" => (25, 25, 112, 255),
1465 "mintcream" => (245, 255, 250, 255),
1466 "mistyrose" => (255, 228, 225, 255),
1467 "moccasin" => (255, 228, 181, 255),
1468 "navajowhite" => (255, 222, 173, 255),
1469 "navy" => (0, 0, 128, 255),
1470 "oldlace" => (253, 245, 230, 255),
1471 "olive" => (128, 128, 0, 255),
1472 "olivedrab" => (107, 142, 35, 255),
1473 "orange" => (255, 165, 0, 255),
1474 "orangered" => (255, 69, 0, 255),
1475 "orchid" => (218, 112, 214, 255),
1476 "palegoldenrod" => (238, 232, 170, 255),
1477 "palegreen" => (152, 251, 152, 255),
1478 "paleturquoise" => (175, 238, 238, 255),
1479 "palevioletred" => (219, 112, 147, 255),
1480 "papayawhip" => (255, 239, 213, 255),
1481 "peachpuff" => (255, 218, 185, 255),
1482 "peru" => (205, 133, 63, 255),
1483 "pink" => (255, 192, 203, 255),
1484 "plum" => (221, 160, 221, 255),
1485 "powderblue" => (176, 224, 230, 255),
1486 "purple" => (128, 0, 128, 255),
1487 "rebeccapurple" => (102, 51, 153, 255),
1488 "red" => (255, 0, 0, 255),
1489 "rosybrown" => (188, 143, 143, 255),
1490 "royalblue" => (65, 105, 225, 255),
1491 "saddlebrown" => (139, 69, 19, 255),
1492 "salmon" => (250, 128, 114, 255),
1493 "sandybrown" => (244, 164, 96, 255),
1494 "seagreen" => (46, 139, 87, 255),
1495 "seashell" => (255, 245, 238, 255),
1496 "sienna" => (160, 82, 45, 255),
1497 "silver" => (192, 192, 192, 255),
1498 "skyblue" => (135, 206, 235, 255),
1499 "slateblue" => (106, 90, 205, 255),
1500 "slategray" | "slategrey" => (112, 128, 144, 255),
1501 "snow" => (255, 250, 250, 255),
1502 "springgreen" => (0, 255, 127, 255),
1503 "steelblue" => (70, 130, 180, 255),
1504 "tan" => (210, 180, 140, 255),
1505 "teal" => (0, 128, 128, 255),
1506 "thistle" => (216, 191, 216, 255),
1507 "tomato" => (255, 99, 71, 255),
1508 "transparent" => (0, 0, 0, 0),
1509 "turquoise" => (64, 224, 208, 255),
1510 "violet" => (238, 130, 238, 255),
1511 "wheat" => (245, 222, 179, 255),
1512 "white" => (255, 255, 255, 255),
1513 "whitesmoke" => (245, 245, 245, 255),
1514 "yellow" => (255, 255, 0, 255),
1515 "yellowgreen" => (154, 205, 50, 255),
1516 _ => return Err(CssColorParseError::InvalidColor(input)),
1517 };
1518 Ok(ColorU { r, g, b, a })
1519}
1520
1521#[cfg(all(test, feature = "parser"))]
1522mod tests {
1523 use super::*;
1524
1525 #[test]
1526 fn test_parse_color_keywords() {
1527 assert_eq!(parse_css_color("red").unwrap(), ColorU::RED);
1528 assert_eq!(parse_css_color("blue").unwrap(), ColorU::BLUE);
1529 assert_eq!(parse_css_color("transparent").unwrap(), ColorU::TRANSPARENT);
1530 assert_eq!(
1531 parse_css_color("rebeccapurple").unwrap(),
1532 ColorU::new_rgb(102, 51, 153)
1533 );
1534 }
1535
1536 #[test]
1537 fn test_parse_color_hex() {
1538 assert_eq!(parse_css_color("#f00").unwrap(), ColorU::RED);
1540 assert_eq!(
1542 parse_css_color("#f008").unwrap(),
1543 ColorU::new(255, 0, 0, 136)
1544 );
1545 assert_eq!(parse_css_color("#00ff00").unwrap(), ColorU::GREEN);
1547 assert_eq!(
1549 parse_css_color("#0000ff80").unwrap(),
1550 ColorU::new(0, 0, 255, 128)
1551 );
1552 assert_eq!(
1554 parse_css_color("#FFC0CB").unwrap(),
1555 ColorU::new_rgb(255, 192, 203)
1556 ); }
1558
1559 #[test]
1560 fn test_parse_color_rgb() {
1561 assert_eq!(parse_css_color("rgb(255, 0, 0)").unwrap(), ColorU::RED);
1562 assert_eq!(
1563 parse_css_color("rgba(0, 255, 0, 0.5)").unwrap(),
1564 ColorU::new(0, 255, 0, 128)
1565 );
1566 assert_eq!(
1567 parse_css_color("rgba(10, 20, 30, 1)").unwrap(),
1568 ColorU::new_rgb(10, 20, 30)
1569 );
1570 assert_eq!(parse_css_color("rgb( 0 , 0 , 0 )").unwrap(), ColorU::BLACK);
1571 }
1572
1573 #[test]
1574 fn test_parse_color_hsl() {
1575 assert_eq!(parse_css_color("hsl(0, 100%, 50%)").unwrap(), ColorU::RED);
1576 assert_eq!(
1577 parse_css_color("hsl(120, 100%, 50%)").unwrap(),
1578 ColorU::GREEN
1579 );
1580 assert_eq!(
1581 parse_css_color("hsla(240, 100%, 50%, 0.5)").unwrap(),
1582 ColorU::new(0, 0, 255, 128)
1583 );
1584 assert_eq!(parse_css_color("hsl(0, 0%, 0%)").unwrap(), ColorU::BLACK);
1585 }
1586
1587 #[test]
1588 fn test_parse_color_errors() {
1589 assert!(parse_css_color("redd").is_err());
1590 assert!(parse_css_color("#12345").is_err()); assert!(parse_css_color("#ggg").is_err()); assert!(parse_css_color("rgb(255, 0)").is_err()); assert!(parse_css_color("rgba(255, 0, 0, 2)").is_err()); assert!(parse_css_color("rgb(256, 0, 0)").is_err()); assert!(parse_css_color("hsl(0, 100, 50%)").is_ok()); assert!(parse_css_color("rgb(255 0 0)").is_err()); }
1600
1601 #[test]
1602 fn test_parse_system_colors() {
1603 assert_eq!(
1605 parse_color_or_system("system:accent").unwrap(),
1606 ColorOrSystem::System(SystemColorRef::Accent)
1607 );
1608 assert_eq!(
1609 parse_color_or_system("system:text").unwrap(),
1610 ColorOrSystem::System(SystemColorRef::Text)
1611 );
1612 assert_eq!(
1613 parse_color_or_system("system:background").unwrap(),
1614 ColorOrSystem::System(SystemColorRef::Background)
1615 );
1616 assert_eq!(
1617 parse_color_or_system("system:selection-background").unwrap(),
1618 ColorOrSystem::System(SystemColorRef::SelectionBackground)
1619 );
1620 assert_eq!(
1621 parse_color_or_system("system:selection-text").unwrap(),
1622 ColorOrSystem::System(SystemColorRef::SelectionText)
1623 );
1624 assert_eq!(
1625 parse_color_or_system("system:accent-text").unwrap(),
1626 ColorOrSystem::System(SystemColorRef::AccentText)
1627 );
1628 assert_eq!(
1629 parse_color_or_system("system:button-face").unwrap(),
1630 ColorOrSystem::System(SystemColorRef::ButtonFace)
1631 );
1632 assert_eq!(
1633 parse_color_or_system("system:button-text").unwrap(),
1634 ColorOrSystem::System(SystemColorRef::ButtonText)
1635 );
1636 assert_eq!(
1637 parse_color_or_system("system:window-background").unwrap(),
1638 ColorOrSystem::System(SystemColorRef::WindowBackground)
1639 );
1640
1641 assert!(parse_color_or_system("system:invalid").is_err());
1643
1644 assert_eq!(
1646 parse_color_or_system("red").unwrap(),
1647 ColorOrSystem::Color(ColorU::RED)
1648 );
1649 assert_eq!(
1650 parse_color_or_system("#ff0000").unwrap(),
1651 ColorOrSystem::Color(ColorU::RED)
1652 );
1653 }
1654
1655 #[test]
1656 fn test_system_color_resolution() {
1657 use crate::system::SystemColors;
1658
1659 let system_colors = SystemColors {
1660 text: OptionColorU::Some(ColorU::BLACK),
1661 secondary_text: OptionColorU::None,
1662 tertiary_text: OptionColorU::None,
1663 background: OptionColorU::Some(ColorU::WHITE),
1664 accent: OptionColorU::Some(ColorU::new_rgb(0, 122, 255)), accent_text: OptionColorU::Some(ColorU::WHITE),
1666 button_face: OptionColorU::Some(ColorU::new_rgb(240, 240, 240)),
1667 button_text: OptionColorU::Some(ColorU::BLACK),
1668 disabled_text: OptionColorU::None,
1669 window_background: OptionColorU::Some(ColorU::WHITE),
1670 under_page_background: OptionColorU::None,
1671 selection_background: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
1672 selection_text: OptionColorU::Some(ColorU::WHITE),
1673 selection_background_inactive: OptionColorU::None,
1674 selection_text_inactive: OptionColorU::None,
1675 link: OptionColorU::None,
1676 separator: OptionColorU::None,
1677 grid: OptionColorU::None,
1678 find_highlight: OptionColorU::None,
1679 sidebar_background: OptionColorU::None,
1680 sidebar_selection: OptionColorU::None,
1681 };
1682
1683 let accent_ref = ColorOrSystem::System(SystemColorRef::Accent);
1685 let resolved = accent_ref.resolve(&system_colors, ColorU::GRAY);
1686 assert_eq!(resolved, ColorU::new_rgb(0, 122, 255));
1687
1688 let empty_colors = SystemColors::default();
1690 let resolved_fallback = accent_ref.resolve(&empty_colors, ColorU::GRAY);
1691 assert_eq!(resolved_fallback, ColorU::GRAY);
1692
1693 let concrete = ColorOrSystem::Color(ColorU::RED);
1695 let resolved_concrete = concrete.resolve(&system_colors, ColorU::GRAY);
1696 assert_eq!(resolved_concrete, ColorU::RED);
1697 }
1698
1699 #[test]
1700 fn test_system_color_css_str() {
1701 assert_eq!(SystemColorRef::Accent.as_css_str(), "system:accent");
1702 assert_eq!(SystemColorRef::Text.as_css_str(), "system:text");
1703 assert_eq!(SystemColorRef::Background.as_css_str(), "system:background");
1704 assert_eq!(SystemColorRef::SelectionBackground.as_css_str(), "system:selection-background");
1705 }
1706}