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