1
//! CSS color types and parser.
2
//!
3
//! Core types: [`ColorU`] (u8 RGBA), [`ColorF`] (f32 RGBA), [`ColorOrSystem`]
4
//! (concrete color or runtime system-theme reference). The parser supports hex,
5
//! `rgb()`/`rgba()`, `hsl()`/`hsla()`, CSS named colors, and `system:*` syntax.
6

            
7
use alloc::string::{String, ToString};
8
use core::fmt;
9
use crate::corety::AzString;
10
use crate::props::basic::error::{ParseFloatError, ParseIntError};
11

            
12
use crate::{
13
    impl_option,
14
    props::basic::{
15
        direction::{
16
            parse_direction, CssDirectionParseError, CssDirectionParseErrorOwned, Direction,
17
        },
18
        length::{PercentageParseError, PercentageValue},
19
    },
20
};
21

            
22
/// u8-based color, range 0 to 255 (similar to webrenders ColorU)
23
#[derive(Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Hash)]
24
#[repr(C)]
25
pub struct ColorU {
26
    pub r: u8,
27
    pub g: u8,
28
    pub b: u8,
29
    pub a: u8,
30
}
31

            
32
impl_option!(
33
    ColorU,
34
    OptionColorU,
35
    [Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Hash]
36
);
37

            
38
impl Default for ColorU {
39
39564
    fn default() -> Self {
40
39564
        ColorU::BLACK
41
39564
    }
42
}
43

            
44
impl 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

            
57
impl 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
    // Additional common colors
98
    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
    // Constructor functions for C API (become AzColorU_red(), AzColorU_cyan(), etc.)
138
    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
    /// Creates a new color with RGBA values.
184
15
    pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
185
15
        Self { r, g, b, a }
186
15
    }
187
    /// Creates a new color with RGB values (alpha = 255).
188
2053
    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
189
2053
        Self { r, g, b, a: 255 }
190
2053
    }
191
    /// Alias for `rgba` - kept for internal compatibility, not exposed in FFI.
192
    #[inline(always)]
193
15
    pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
194
15
        Self::rgba(r, g, b, a)
195
15
    }
196
    /// Alias for `rgb` - kept for internal compatibility, not exposed in FFI.
197
    #[inline(always)]
198
2053
    pub const fn new_rgb(r: u8, g: u8, b: u8) -> Self {
199
2053
        Self::rgb(r, g, b)
200
2053
    }
201

            
202
    /// Linearly interpolate all four RGBA channels between `self` and `other`.
203
    /// `t = 0.0` returns `self`, `t = 1.0` returns `other`.
204
    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
    /// Lighten a color by a percentage (0.0 to 1.0).
214
    /// Returns a new color blended towards white, preserving the original alpha.
215
    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
    /// Darken a color by a percentage (0.0 to 1.0).
222
    /// Returns a new color blended towards black, preserving the original alpha.
223
    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
    /// Mix two colors together with a given ratio (0.0 = self, 1.0 = other).
230
    pub fn mix(&self, other: &Self, ratio: f32) -> Self {
231
        self.interpolate(other, ratio.clamp(0.0, 1.0))
232
    }
233
    
234
    /// Create a hover variant (slightly lighter for dark colors, darker for light colors).
235
    /// This is useful for button hover states.
236
    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
    /// Create an active/pressed variant (darker than hover).
246
    /// This is useful for button active states.
247
    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
    /// Calculate approximate luminance (0.0 = black, 1.0 = white).
257
    ///
258
    /// **Note:** This applies BT.709 coefficients directly to gamma-encoded sRGB
259
    /// values without linearizing first, so it is only an approximation.
260
    /// For accurate results (e.g. WCAG contrast checks), use [`relative_luminance()`].
261
    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
    /// Returns white or black text color for best contrast on this background.
269
    pub fn contrast_text(&self) -> Self {
270
        self.best_contrast_text()
271
    }
272
    
273
    // ============================================================
274
    // WCAG Accessibility and Contrast Helpers
275
    // Based on W3C WCAG 2.1 guidelines and Chromium research
276
    // ============================================================
277
    
278
    /// Converts a single sRGB channel to linear RGB.
279
    /// Used for accurate luminance and contrast calculations.
280
    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
    /// Calculate relative luminance per WCAG 2.1 specification.
289
    /// Returns a value between 0.0 (darkest) and 1.0 (lightest).
290
    /// Uses the sRGB to linear conversion for accurate results.
291
    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
    /// Calculate the contrast ratio between this color and another.
299
    /// Returns a value between 1.0 (no contrast) and 21.0 (max contrast).
300
    /// 
301
    /// WCAG 2.1 requirements:
302
    /// - AA normal text: >= 4.5:1
303
    /// - AA large text: >= 3.0:1
304
    /// - AAA normal text: >= 7.0:1
305
    /// - AAA large text: >= 4.5:1
306
    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
    /// Check if the contrast ratio meets WCAG AA requirements for normal text (>= 4.5:1).
315
    pub fn meets_wcag_aa(&self, other: &Self) -> bool {
316
        self.contrast_ratio(other) >= 4.5
317
    }
318
    
319
    /// Check if the contrast ratio meets WCAG AA requirements for large text (>= 3.0:1).
320
    /// Large text is defined as 18pt+ or 14pt+ bold.
321
    pub fn meets_wcag_aa_large(&self, other: &Self) -> bool {
322
        self.contrast_ratio(other) >= 3.0
323
    }
324
    
325
    /// Check if the contrast ratio meets WCAG AAA requirements for normal text (>= 7.0:1).
326
    pub fn meets_wcag_aaa(&self, other: &Self) -> bool {
327
        self.contrast_ratio(other) >= 7.0
328
    }
329
    
330
    /// Check if the contrast ratio meets WCAG AAA requirements for large text (>= 4.5:1).
331
    pub fn meets_wcag_aaa_large(&self, other: &Self) -> bool {
332
        self.contrast_ratio(other) >= 4.5
333
    }
334
    
335
    /// Returns true if this color is considered "light" (relative luminance > 0.5).
336
    /// Useful for determining if dark or light text should be used.
337
    pub fn is_light(&self) -> bool {
338
        self.relative_luminance() > 0.5
339
    }
340

            
341
    /// Returns true if this color is considered "dark" (relative luminance <= 0.5).
342
    pub fn is_dark(&self) -> bool {
343
        self.relative_luminance() <= 0.5
344
    }
345
    
346
    /// Suggest the best text color (black or white) for this background,
347
    /// ensuring WCAG AA compliance for normal text.
348
    /// 
349
    /// If neither black nor white meets AA requirements (unlikely), 
350
    /// returns the one with higher contrast.
351
    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
    /// Adjust the color to ensure it meets the minimum contrast ratio against a background.
363
    /// Lightens or darkens the color as needed.
364
    /// 
365
    /// Returns the original color if it already meets the requirement,
366
    /// otherwise returns an adjusted color that meets the minimum contrast.
367
    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
        // Determine if we should lighten or darken
374
        let bg_luminance = background.relative_luminance();
375
        let should_lighten = bg_luminance < 0.5;
376
        
377
        // Binary search for the right amount
378
        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
    /// Calculate the APCA (Accessible Perceptual Contrast Algorithm) contrast.
402
    /// This is a newer algorithm that may replace WCAG contrast in future standards.
403
    /// Returns a value between -108 (white on black) and 106 (black on white).
404
    ///
405
    /// **Note:** This is an approximation — it reuses the WCAG piecewise sRGB
406
    /// linearization and BT.709 luminance coefficients rather than the APCA-specific
407
    /// TRC exponents and coefficients from the full 0.0.98G specification.
408
    ///
409
    /// The sign indicates polarity (negative = light text on dark bg).
410
    /// For most purposes, use the absolute value.
411
    pub fn apca_contrast(&self, background: &Self) -> f32 {
412
        // Convert to Y (luminance) using sRGB TRC
413
        let text_y = self.relative_luminance();
414
        let bg_y = background.relative_luminance();
415
        
416
        // Soft clamp
417
        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
        // APCA 0.0.98G constants
421
        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
        // Clamp black levels
430
        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
        // Calculate contrast
442
        if bg_clamp > txt_clamp {
443
            // Dark text on light bg
444
            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
            // Light text on dark bg
448
            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
    /// Check if the APCA contrast meets the recommended minimum for body text (|Lc| >= 60).
454
    pub fn meets_apca_body(&self, background: &Self) -> bool {
455
        libm::fabsf(self.apca_contrast(background)) >= 60.0
456
    }
457
    
458
    /// Check if the APCA contrast meets the minimum for large text (|Lc| >= 45).
459
    pub fn meets_apca_large(&self, background: &Self) -> bool {
460
        libm::fabsf(self.apca_contrast(background)) >= 45.0
461
    }
462
    
463
    /// Set the alpha channel while keeping RGB values.
464
    pub fn with_alpha(&self, a: u8) -> Self {
465
        Self { r: self.r, g: self.g, b: self.b, a }
466
    }
467
    
468
    /// Set the alpha as a float (0.0 to 1.0).
469
    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
    /// Invert the color (keeping alpha).
474
    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
    /// Convert to grayscale using luminance weights.
484
    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
    /// Returns `true` if the alpha channel is not fully opaque (i.e. `a != 255`).
490
    pub const fn has_alpha(&self) -> bool {
491
        self.a != Self::ALPHA_OPAQUE
492
    }
493

            
494
    /// Format the color as an 8-digit lowercase hex string (e.g. `#ff0000ff`).
495
    pub fn to_hash(&self) -> String {
496
        format!("#{:02x}{:02x}{:02x}{:02x}", self.r, self.g, self.b, self.a)
497
    }
498

            
499
    // ============================================================
500
    // Elementary OS color palette (with shade parameter 100-900)
501
    // ============================================================
502

            
503
    /// Strawberry color palette (shade: 100, 300, 500, 700, 900)
504
    pub fn strawberry(shade: usize) -> Self {
505
        match shade {
506
            0..=200 => Self::rgb(0xff, 0x8c, 0x82),   // 100: #ff8c82
507
            201..=400 => Self::rgb(0xed, 0x53, 0x53), // 300: #ed5353
508
            401..=600 => Self::rgb(0xc6, 0x26, 0x2e), // 500: #c6262e
509
            601..=800 => Self::rgb(0xa1, 0x07, 0x05), // 700: #a10705
510
            _ => Self::rgb(0x7a, 0x00, 0x00),         // 900: #7a0000
511
        }
512
    }
513

            
514
    /// Orange color palette (shade: 100, 300, 500, 700, 900)
515
    pub fn palette_orange(shade: usize) -> Self {
516
        match shade {
517
            0..=200 => Self::rgb(0xff, 0xc2, 0x7d),   // 100: #ffc27d
518
            201..=400 => Self::rgb(0xff, 0xa1, 0x54), // 300: #ffa154
519
            401..=600 => Self::rgb(0xf3, 0x73, 0x29), // 500: #f37329
520
            601..=800 => Self::rgb(0xcc, 0x3b, 0x02), // 700: #cc3b02
521
            _ => Self::rgb(0xa6, 0x21, 0x00),         // 900: #a62100
522
        }
523
    }
524

            
525
    /// Banana color palette (shade: 100, 300, 500, 700, 900)
526
    pub fn banana(shade: usize) -> Self {
527
        match shade {
528
            0..=200 => Self::rgb(0xff, 0xf3, 0x94),   // 100: #fff394
529
            201..=400 => Self::rgb(0xff, 0xe1, 0x6b), // 300: #ffe16b
530
            401..=600 => Self::rgb(0xf9, 0xc4, 0x40), // 500: #f9c440
531
            601..=800 => Self::rgb(0xd4, 0x8e, 0x15), // 700: #d48e15
532
            _ => Self::rgb(0xad, 0x5f, 0x00),         // 900: #ad5f00
533
        }
534
    }
535

            
536
    /// Lime color palette (shade: 100, 300, 500, 700, 900)
537
    pub fn palette_lime(shade: usize) -> Self {
538
        match shade {
539
            0..=200 => Self::rgb(0xd1, 0xff, 0x82),   // 100: #d1ff82
540
            201..=400 => Self::rgb(0x9b, 0xdb, 0x4d), // 300: #9bdb4d
541
            401..=600 => Self::rgb(0x68, 0xb7, 0x23), // 500: #68b723
542
            601..=800 => Self::rgb(0x3a, 0x91, 0x04), // 700: #3a9104
543
            _ => Self::rgb(0x20, 0x6b, 0x00),         // 900: #206b00
544
        }
545
    }
546

            
547
    /// Mint color palette (shade: 100, 300, 500, 700, 900)
548
    pub fn mint(shade: usize) -> Self {
549
        match shade {
550
            0..=200 => Self::rgb(0x89, 0xff, 0xdd),   // 100: #89ffdd
551
            201..=400 => Self::rgb(0x43, 0xd6, 0xb5), // 300: #43d6b5
552
            401..=600 => Self::rgb(0x28, 0xbc, 0xa3), // 500: #28bca3
553
            601..=800 => Self::rgb(0x0e, 0x9a, 0x83), // 700: #0e9a83
554
            _ => Self::rgb(0x00, 0x73, 0x67),         // 900: #007367
555
        }
556
    }
557

            
558
    /// Blueberry color palette (shade: 100, 300, 500, 700, 900)
559
    pub fn blueberry(shade: usize) -> Self {
560
        match shade {
561
            0..=200 => Self::rgb(0x8c, 0xd5, 0xff),   // 100: #8cd5ff
562
            201..=400 => Self::rgb(0x64, 0xba, 0xff), // 300: #64baff
563
            401..=600 => Self::rgb(0x36, 0x89, 0xe6), // 500: #3689e6
564
            601..=800 => Self::rgb(0x0d, 0x52, 0xbf), // 700: #0d52bf
565
            _ => Self::rgb(0x00, 0x2e, 0x99),         // 900: #002e99
566
        }
567
    }
568

            
569
    /// Grape color palette (shade: 100, 300, 500, 700, 900)
570
    pub fn grape(shade: usize) -> Self {
571
        match shade {
572
            0..=200 => Self::rgb(0xe4, 0xc6, 0xfa),   // 100: #e4c6fa
573
            201..=400 => Self::rgb(0xcd, 0x9e, 0xf7), // 300: #cd9ef7
574
            401..=600 => Self::rgb(0xa5, 0x6d, 0xe2), // 500: #a56de2
575
            601..=800 => Self::rgb(0x72, 0x39, 0xb3), // 700: #7239b3
576
            _ => Self::rgb(0x45, 0x29, 0x81),         // 900: #452981
577
        }
578
    }
579

            
580
    /// Bubblegum color palette (shade: 100, 300, 500, 700, 900)
581
    pub fn bubblegum(shade: usize) -> Self {
582
        match shade {
583
            0..=200 => Self::rgb(0xfe, 0x9a, 0xb8),   // 100: #fe9ab8
584
            201..=400 => Self::rgb(0xf4, 0x67, 0x9d), // 300: #f4679d
585
            401..=600 => Self::rgb(0xde, 0x3e, 0x80), // 500: #de3e80
586
            601..=800 => Self::rgb(0xbc, 0x24, 0x5d), // 700: #bc245d
587
            _ => Self::rgb(0x91, 0x0e, 0x38),         // 900: #910e38
588
        }
589
    }
590

            
591
    /// Cocoa color palette (shade: 100, 300, 500, 700, 900)
592
    pub fn cocoa(shade: usize) -> Self {
593
        match shade {
594
            0..=200 => Self::rgb(0xa3, 0x90, 0x7c),   // 100: #a3907c
595
            201..=400 => Self::rgb(0x8a, 0x71, 0x5e), // 300: #8a715e
596
            401..=600 => Self::rgb(0x71, 0x53, 0x44), // 500: #715344
597
            601..=800 => Self::rgb(0x57, 0x39, 0x2d), // 700: #57392d
598
            _ => Self::rgb(0x3d, 0x21, 0x1b),         // 900: #3d211b
599
        }
600
    }
601

            
602
    /// Silver color palette (shade: 100, 300, 500, 700, 900)
603
    pub fn palette_silver(shade: usize) -> Self {
604
        match shade {
605
            0..=200 => Self::rgb(0xfa, 0xfa, 0xfa),   // 100: #fafafa
606
            201..=400 => Self::rgb(0xd4, 0xd4, 0xd4), // 300: #d4d4d4
607
            401..=600 => Self::rgb(0xab, 0xac, 0xae), // 500: #abacae
608
            601..=800 => Self::rgb(0x7e, 0x80, 0x87), // 700: #7e8087
609
            _ => Self::rgb(0x55, 0x57, 0x61),         // 900: #555761
610
        }
611
    }
612

            
613
    /// Slate color palette (shade: 100, 300, 500, 700, 900)
614
    pub fn slate(shade: usize) -> Self {
615
        match shade {
616
            0..=200 => Self::rgb(0x95, 0xa3, 0xab),   // 100: #95a3ab
617
            201..=400 => Self::rgb(0x66, 0x78, 0x85), // 300: #667885
618
            401..=600 => Self::rgb(0x48, 0x5a, 0x6c), // 500: #485a6c
619
            601..=800 => Self::rgb(0x27, 0x34, 0x45), // 700: #273445
620
            _ => Self::rgb(0x0e, 0x14, 0x1f),         // 900: #0e141f
621
        }
622
    }
623

            
624
    /// Dark color palette (shade: 100, 300, 500, 700, 900)
625
    pub fn dark(shade: usize) -> Self {
626
        match shade {
627
            0..=200 => Self::rgb(0x66, 0x66, 0x66),   // 100: #666
628
            201..=400 => Self::rgb(0x4d, 0x4d, 0x4d), // 300: #4d4d4d
629
            401..=600 => Self::rgb(0x33, 0x33, 0x33), // 500: #333
630
            601..=800 => Self::rgb(0x1a, 0x1a, 0x1a), // 700: #1a1a1a
631
            _ => Self::rgb(0x00, 0x00, 0x00),         // 900: #000
632
        }
633
    }
634

            
635
    // ============================================================
636
    // Apple System Colors (light and dark variants)
637
    // ============================================================
638

            
639
    /// Apple Red (light mode)
640
    pub fn apple_red() -> Self { Self::rgb(255, 59, 48) }
641
    /// Apple Red (dark mode)
642
    pub fn apple_red_dark() -> Self { Self::rgb(255, 69, 58) }
643
    /// Apple Orange (light mode)
644
    pub fn apple_orange() -> Self { Self::rgb(255, 149, 0) }
645
    /// Apple Orange (dark mode)
646
    pub fn apple_orange_dark() -> Self { Self::rgb(255, 159, 10) }
647
    /// Apple Yellow (light mode)
648
    pub fn apple_yellow() -> Self { Self::rgb(255, 204, 0) }
649
    /// Apple Yellow (dark mode)
650
    pub fn apple_yellow_dark() -> Self { Self::rgb(255, 214, 10) }
651
    /// Apple Green (light mode)
652
    pub fn apple_green() -> Self { Self::rgb(40, 205, 65) }
653
    /// Apple Green (dark mode)
654
    pub fn apple_green_dark() -> Self { Self::rgb(40, 215, 75) }
655
    /// Apple Mint (light mode)
656
    pub fn apple_mint() -> Self { Self::rgb(0, 199, 190) }
657
    /// Apple Mint (dark mode)
658
    pub fn apple_mint_dark() -> Self { Self::rgb(102, 212, 207) }
659
    /// Apple Teal (light mode)
660
    pub fn apple_teal() -> Self { Self::rgb(89, 173, 196) }
661
    /// Apple Teal (dark mode)
662
    pub fn apple_teal_dark() -> Self { Self::rgb(106, 196, 220) }
663
    /// Apple Cyan (light mode)
664
    pub fn apple_cyan() -> Self { Self::rgb(85, 190, 240) }
665
    /// Apple Cyan (dark mode)
666
    pub fn apple_cyan_dark() -> Self { Self::rgb(90, 200, 245) }
667
    /// Apple Blue (light mode)
668
    pub fn apple_blue() -> Self { Self::rgb(0, 122, 255) }
669
    /// Apple Blue (dark mode)
670
    pub fn apple_blue_dark() -> Self { Self::rgb(10, 132, 255) }
671
    /// Apple Indigo (light mode)
672
    pub fn apple_indigo() -> Self { Self::rgb(88, 86, 214) }
673
    /// Apple Indigo (dark mode)
674
    pub fn apple_indigo_dark() -> Self { Self::rgb(94, 92, 230) }
675
    /// Apple Purple (light mode)
676
    pub fn apple_purple() -> Self { Self::rgb(175, 82, 222) }
677
    /// Apple Purple (dark mode)
678
    pub fn apple_purple_dark() -> Self { Self::rgb(191, 90, 242) }
679
    /// Apple Pink (light mode)
680
    pub fn apple_pink() -> Self { Self::rgb(255, 45, 85) }
681
    /// Apple Pink (dark mode)
682
    pub fn apple_pink_dark() -> Self { Self::rgb(255, 55, 95) }
683
    /// Apple Brown (light mode)
684
    pub fn apple_brown() -> Self { Self::rgb(162, 132, 94) }
685
    /// Apple Brown (dark mode)
686
    pub fn apple_brown_dark() -> Self { Self::rgb(172, 142, 104) }
687
    /// Apple Gray (light mode)
688
    pub fn apple_gray() -> Self { Self::rgb(142, 142, 147) }
689
    /// Apple Gray (dark mode)
690
    pub fn apple_gray_dark() -> Self { Self::rgb(152, 152, 157) }
691

            
692
    // ============================================================
693
    // Bootstrap-style semantic button colors
694
    // These provide consistent button styling across platforms
695
    // ============================================================
696

            
697
    /// Primary button color (blue) - used for main actions
698
    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
    /// Secondary button color (gray) - used for secondary actions
703
    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
    /// Success button color (green) - used for confirmations
708
    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
    /// Danger button color (red) - used for destructive actions
713
    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
    /// Warning button color (yellow) - used for warnings, uses BLACK text
718
    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
    /// Info button color (teal/cyan) - used for informational actions
723
    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
    /// Light button color - used for light-themed buttons
728
    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
    /// Dark button color - used for dark-themed buttons
733
    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
    /// Link button text color
738
    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
/// f32-based color, range 0.0 to 1.0 (similar to webrenders ColorF)
743
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
744
pub struct ColorF {
745
    pub r: f32,
746
    pub g: f32,
747
    pub b: f32,
748
    pub a: f32,
749
}
750

            
751
impl Default for ColorF {
752
    fn default() -> Self {
753
        ColorF::BLACK
754
    }
755
}
756

            
757
impl 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

            
770
impl 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

            
793
impl 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

            
804
impl 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
/// A color reference that can be either a concrete color or a system color.
816
/// System colors are lazily evaluated at runtime based on the user's system theme.
817
/// 
818
/// CSS syntax: `system:accent`, `system:text`, `system:background`, etc.
819
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
820
#[repr(C, u8)]
821
pub enum ColorOrSystem {
822
    /// A concrete RGBA color value.
823
    Color(ColorU),
824
    /// A reference to a system color, resolved at runtime.
825
    System(SystemColorRef),
826
}
827

            
828
impl Default for ColorOrSystem {
829
    fn default() -> Self {
830
        ColorOrSystem::Color(ColorU::BLACK)
831
    }
832
}
833

            
834
impl From<ColorU> for ColorOrSystem {
835
    fn from(color: ColorU) -> Self {
836
        ColorOrSystem::Color(color)
837
    }
838
}
839

            
840
impl ColorOrSystem {
841
    /// Create a new ColorOrSystem from a concrete color.
842
    pub const fn color(c: ColorU) -> Self {
843
        ColorOrSystem::Color(c)
844
    }
845
    
846
    /// Create a new ColorOrSystem from a system color reference.
847
    pub const fn system(s: SystemColorRef) -> Self {
848
        ColorOrSystem::System(s)
849
    }
850
    
851
    /// Resolve the color against a SystemColors struct.
852
    /// Returns the system color if available, or falls back to the provided default.
853
5
    pub fn resolve(&self, system_colors: &crate::system::SystemColors, fallback: ColorU) -> ColorU {
854
5
        match self {
855
1
            ColorOrSystem::Color(c) => *c,
856
4
            ColorOrSystem::System(ref_type) => ref_type.resolve(system_colors, fallback),
857
        }
858
5
    }
859
    
860
    /// Returns the concrete color if available, or a default fallback for system colors.
861
    /// Use this when SystemColors is not available (e.g., during rendering setup).
862
1
    pub fn to_color_u_with_fallback(&self, fallback: ColorU) -> ColorU {
863
1
        match self {
864
1
            ColorOrSystem::Color(c) => *c,
865
            ColorOrSystem::System(_) => fallback,
866
        }
867
1
    }
868
    
869
    /// Returns the concrete color if available, or a gray fallback for system colors.
870
1
    pub fn to_color_u_default(&self) -> ColorU {
871
1
        self.to_color_u_with_fallback(ColorU { r: 128, g: 128, b: 128, a: 255 })
872
1
    }
873
}
874

            
875
/// Reference to a specific system color.
876
/// These are resolved at runtime based on the user's system preferences.
877
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
878
#[repr(C)]
879
pub enum SystemColorRef {
880
    /// System text color (e.g., black on light theme, white on dark)
881
    Text,
882
    /// System background color
883
    Background,
884
    /// System accent color (user-selected highlight color)
885
    Accent,
886
    /// Text color when on accent background
887
    AccentText,
888
    /// Button face background color
889
    ButtonFace,
890
    /// Button text color
891
    ButtonText,
892
    /// Window/panel background color
893
    WindowBackground,
894
    /// Selection/highlight background color
895
    SelectionBackground,
896
    /// Text color when selected
897
    SelectionText,
898
}
899

            
900
impl SystemColorRef {
901
    /// Resolve this system color reference against actual system colors.
902
4
    pub fn resolve(&self, colors: &crate::system::SystemColors, fallback: ColorU) -> ColorU {
903
4
        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
4
            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
4
    }
915
    
916
    /// Get the CSS syntax for this system color reference.
917
4
    pub fn as_css_str(&self) -> &'static str {
918
4
        match self {
919
1
            SystemColorRef::Text => "system:text",
920
1
            SystemColorRef::Background => "system:background",
921
1
            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
1
            SystemColorRef::SelectionBackground => "system:selection-background",
927
            SystemColorRef::SelectionText => "system:selection-text",
928
        }
929
4
    }
930
}
931

            
932
// --- PARSER ---
933

            
934
#[derive(Debug, Copy, Clone, PartialEq)]
935
#[repr(C)]
936
pub enum CssColorComponent {
937
    Red,
938
    Green,
939
    Blue,
940
    Hue,
941
    Saturation,
942
    Lightness,
943
    Alpha,
944
}
945

            
946
#[derive(Clone, PartialEq)]
947
pub 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

            
963
impl_debug_as_display!(CssColorParseError<'a>);
964
impl_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

            
980
impl<'a> From<ParseIntError> for CssColorParseError<'a> {
981
    fn from(e: ParseIntError) -> Self {
982
        CssColorParseError::IntValueParseErr(e)
983
    }
984
}
985
impl<'a> From<ParseFloatError> for CssColorParseError<'a> {
986
    fn from(e: ParseFloatError) -> Self {
987
        CssColorParseError::FloatValueParseErr(e)
988
    }
989
}
990
impl<'a> From<core::num::ParseIntError> for CssColorParseError<'a> {
991
2
    fn from(e: core::num::ParseIntError) -> Self {
992
2
        CssColorParseError::IntValueParseErr(ParseIntError::from(e))
993
2
    }
994
}
995
impl<'a> From<core::num::ParseFloatError> for CssColorParseError<'a> {
996
    fn from(e: core::num::ParseFloatError) -> Self {
997
        CssColorParseError::FloatValueParseErr(ParseFloatError::from(e))
998
    }
999
}
impl_from!(
    CssDirectionParseError<'a>,
    CssColorParseError::DirectionParseError
);
#[derive(Debug, Clone, PartialEq)]
#[repr(C, u8)]
pub enum CssColorParseErrorOwned {
    InvalidColor(AzString),
    InvalidFunctionName(AzString),
    InvalidColorComponent(u8),
    IntValueParseErr(ParseIntError),
    FloatValueParseErr(ParseFloatError),
    FloatValueOutOfRange(f32),
    MissingColorComponent(CssColorComponent),
    ExtraArguments(AzString),
    UnclosedColor(AzString),
    EmptyInput,
    DirectionParseError(CssDirectionParseErrorOwned),
    UnsupportedDirection(AzString),
    InvalidPercentage(PercentageParseError),
}
impl<'a> CssColorParseError<'a> {
    pub fn to_contained(&self) -> CssColorParseErrorOwned {
        match self {
            CssColorParseError::InvalidColor(s) => {
                CssColorParseErrorOwned::InvalidColor(s.to_string().into())
            }
            CssColorParseError::InvalidFunctionName(s) => {
                CssColorParseErrorOwned::InvalidFunctionName(s.to_string().into())
            }
            CssColorParseError::InvalidColorComponent(n) => {
                CssColorParseErrorOwned::InvalidColorComponent(*n)
            }
            CssColorParseError::IntValueParseErr(e) => {
                CssColorParseErrorOwned::IntValueParseErr((*e))
            }
            CssColorParseError::FloatValueParseErr(e) => {
                CssColorParseErrorOwned::FloatValueParseErr((*e))
            }
            CssColorParseError::FloatValueOutOfRange(n) => {
                CssColorParseErrorOwned::FloatValueOutOfRange(*n)
            }
            CssColorParseError::MissingColorComponent(c) => {
                CssColorParseErrorOwned::MissingColorComponent(*c)
            }
            CssColorParseError::ExtraArguments(s) => {
                CssColorParseErrorOwned::ExtraArguments(s.to_string().into())
            }
            CssColorParseError::UnclosedColor(s) => {
                CssColorParseErrorOwned::UnclosedColor(s.to_string().into())
            }
            CssColorParseError::EmptyInput => CssColorParseErrorOwned::EmptyInput,
            CssColorParseError::DirectionParseError(e) => {
                CssColorParseErrorOwned::DirectionParseError(e.to_contained())
            }
            CssColorParseError::UnsupportedDirection(s) => {
                CssColorParseErrorOwned::UnsupportedDirection(s.to_string().into())
            }
            CssColorParseError::InvalidPercentage(e) => {
                CssColorParseErrorOwned::InvalidPercentage(e.clone())
            }
        }
    }
}
impl CssColorParseErrorOwned {
    pub fn to_shared<'a>(&'a self) -> CssColorParseError<'a> {
        match self {
            CssColorParseErrorOwned::InvalidColor(s) => CssColorParseError::InvalidColor(s),
            CssColorParseErrorOwned::InvalidFunctionName(s) => {
                CssColorParseError::InvalidFunctionName(s)
            }
            CssColorParseErrorOwned::InvalidColorComponent(n) => {
                CssColorParseError::InvalidColorComponent(*n)
            }
            CssColorParseErrorOwned::IntValueParseErr(e) => {
                CssColorParseError::IntValueParseErr(*e)
            }
            CssColorParseErrorOwned::FloatValueParseErr(e) => {
                CssColorParseError::FloatValueParseErr(*e)
            }
            CssColorParseErrorOwned::FloatValueOutOfRange(n) => {
                CssColorParseError::FloatValueOutOfRange(*n)
            }
            CssColorParseErrorOwned::MissingColorComponent(c) => {
                CssColorParseError::MissingColorComponent(*c)
            }
            CssColorParseErrorOwned::ExtraArguments(s) => CssColorParseError::ExtraArguments(s),
            CssColorParseErrorOwned::UnclosedColor(s) => CssColorParseError::UnclosedColor(s),
            CssColorParseErrorOwned::EmptyInput => CssColorParseError::EmptyInput,
            CssColorParseErrorOwned::DirectionParseError(e) => {
                CssColorParseError::DirectionParseError(e.to_shared())
            }
            CssColorParseErrorOwned::UnsupportedDirection(s) => {
                CssColorParseError::UnsupportedDirection(s)
            }
            CssColorParseErrorOwned::InvalidPercentage(e) => {
                CssColorParseError::InvalidPercentage(e.clone())
            }
        }
    }
}
#[cfg(feature = "parser")]
6705
pub fn parse_css_color<'a>(input: &'a str) -> Result<ColorU, CssColorParseError<'a>> {
6705
    let input = input.trim();
6705
    if input.starts_with('#') {
2054
        parse_color_no_hash(&input[1..])
    } else {
        use crate::props::basic::parse::{parse_parentheses, ParenthesisParseError};
4651
        match parse_parentheses(input, &["rgba", "rgb", "hsla", "hsl"]) {
73
            Ok((stopword, inner_value)) => match stopword {
73
                "rgba" => parse_color_rgb(inner_value, true),
17
                "rgb" => parse_color_rgb(inner_value, false),
5
                "hsla" => parse_color_hsl(inner_value, true),
4
                "hsl" => parse_color_hsl(inner_value, false),
                _ => unreachable!(),
            },
4578
            Err(e) => match e {
                ParenthesisParseError::UnclosedBraces => {
                    Err(CssColorParseError::UnclosedColor(input))
                }
                ParenthesisParseError::EmptyInput => Err(CssColorParseError::EmptyInput),
                ParenthesisParseError::StopWordNotFound(stopword) => {
                    Err(CssColorParseError::InvalidFunctionName(stopword))
                }
                ParenthesisParseError::NoClosingBraceFound => {
7
                    Err(CssColorParseError::UnclosedColor(input))
                }
4571
                ParenthesisParseError::NoOpeningBraceFound => parse_color_builtin(input),
            },
        }
    }
6705
}
/// Parse a color that can be either a concrete color or a system color reference.
/// 
/// Supports all standard CSS color formats plus:
/// - `system:accent` - System accent/highlight color
/// - `system:text` - System text color
/// - `system:background` - System background color
/// - `system:selection-background` - Selection/highlight background
/// - `system:selection-text` - Text color when selected
/// - `system:button-face` - Button background color
/// - `system:button-text` - Button text color
/// - `system:window-background` - Window background color
/// - `system:accent-text` - Text color on accent background
#[cfg(feature = "parser")]
165
pub fn parse_color_or_system<'a>(input: &'a str) -> Result<ColorOrSystem, CssColorParseError<'a>> {
165
    let input = input.trim();
    // Check for system color syntax: "system:name"
165
    if let Some(system_name) = input.strip_prefix("system:") {
11
        let system_ref = match system_name.trim() {
11
            "text" => SystemColorRef::Text,
10
            "background" => SystemColorRef::Background,
9
            "accent" => SystemColorRef::Accent,
7
            "accent-text" => SystemColorRef::AccentText,
6
            "button-face" => SystemColorRef::ButtonFace,
5
            "button-text" => SystemColorRef::ButtonText,
4
            "window-background" => SystemColorRef::WindowBackground,
3
            "selection-background" => SystemColorRef::SelectionBackground,
2
            "selection-text" => SystemColorRef::SelectionText,
1
            _ => return Err(CssColorParseError::InvalidColor(input)),
        };
10
        return Ok(ColorOrSystem::System(system_ref));
154
    }
    // Otherwise parse as regular color
154
    parse_css_color(input).map(ColorOrSystem::Color)
165
}
#[cfg(feature = "parser")]
2054
fn parse_color_no_hash<'a>(input: &'a str) -> Result<ColorU, CssColorParseError<'a>> {
    #[inline]
173
    fn from_hex<'a>(c: u8) -> Result<u8, CssColorParseError<'a>> {
173
        match c {
173
            b'0'..=b'9' => Ok(c - b'0'),
12
            b'a'..=b'f' => Ok(c - b'a' + 10),
1
            b'A'..=b'F' => Ok(c - b'A' + 10),
1
            _ => Err(CssColorParseError::InvalidColorComponent(c)),
        }
173
    }
2054
    match input.len() {
        3 => {
57
            let mut bytes = input.bytes();
57
            let r = bytes.next().unwrap();
57
            let g = bytes.next().unwrap();
57
            let b = bytes.next().unwrap();
56
            Ok(ColorU::new_rgb(
57
                from_hex(r)? * 17,
56
                from_hex(g)? * 17,
56
                from_hex(b)? * 17,
            ))
        }
        4 => {
1
            let mut bytes = input.bytes();
1
            let r = bytes.next().unwrap();
1
            let g = bytes.next().unwrap();
1
            let b = bytes.next().unwrap();
1
            let a = bytes.next().unwrap();
1
            Ok(ColorU::new(
1
                from_hex(r)? * 17,
1
                from_hex(g)? * 17,
1
                from_hex(b)? * 17,
1
                from_hex(a)? * 17,
            ))
        }
        6 => {
1980
            let val = u32::from_str_radix(input, 16)?;
1980
            Ok(ColorU::new_rgb(
1980
                ((val >> 16) & 0xFF) as u8,
1980
                ((val >> 8) & 0xFF) as u8,
1980
                (val & 0xFF) as u8,
1980
            ))
        }
        8 => {
8
            let val = u32::from_str_radix(input, 16)?;
8
            Ok(ColorU::new(
8
                ((val >> 24) & 0xFF) as u8,
8
                ((val >> 16) & 0xFF) as u8,
8
                ((val >> 8) & 0xFF) as u8,
8
                (val & 0xFF) as u8,
8
            ))
        }
8
        _ => Err(CssColorParseError::InvalidColor(input)),
    }
2054
}
#[cfg(feature = "parser")]
68
fn parse_color_rgb<'a>(
68
    input: &'a str,
68
    parse_alpha: bool,
68
) -> Result<ColorU, CssColorParseError<'a>> {
255
    let mut components = input.split(',').map(|c| c.trim());
68
    let rgb_color = parse_color_rgb_components(&mut components)?;
65
    let a = if parse_alpha {
56
        parse_alpha_component(&mut components)?
    } else {
9
        255
    };
64
    if let Some(arg) = components.next() {
        return Err(CssColorParseError::ExtraArguments(arg));
64
    }
64
    Ok(ColorU { a, ..rgb_color })
68
}
#[cfg(feature = "parser")]
68
fn parse_color_rgb_components<'a>(
68
    components: &mut dyn Iterator<Item = &'a str>,
68
) -> Result<ColorU, CssColorParseError<'a>> {
    #[inline]
200
    fn component_from_str<'a>(
200
        components: &mut dyn Iterator<Item = &'a str>,
200
        which: CssColorComponent,
200
    ) -> Result<u8, CssColorParseError<'a>> {
200
        let c = components
200
            .next()
200
            .ok_or(CssColorParseError::MissingColorComponent(which))?;
199
        if c.is_empty() {
            return Err(CssColorParseError::MissingColorComponent(which));
199
        }
199
        Ok(c.parse::<u8>()?)
200
    }
    Ok(ColorU {
68
        r: component_from_str(components, CssColorComponent::Red)?,
66
        g: component_from_str(components, CssColorComponent::Green)?,
66
        b: component_from_str(components, CssColorComponent::Blue)?,
        a: 255,
    })
68
}
#[cfg(feature = "parser")]
5
fn parse_color_hsl<'a>(
5
    input: &'a str,
5
    parse_alpha: bool,
5
) -> Result<ColorU, CssColorParseError<'a>> {
16
    let mut components = input.split(',').map(|c| c.trim());
5
    let rgb_color = parse_color_hsl_components(&mut components)?;
5
    let a = if parse_alpha {
1
        parse_alpha_component(&mut components)?
    } else {
4
        255
    };
5
    if let Some(arg) = components.next() {
        return Err(CssColorParseError::ExtraArguments(arg));
5
    }
5
    Ok(ColorU { a, ..rgb_color })
5
}
#[cfg(feature = "parser")]
5
fn parse_color_hsl_components<'a>(
5
    components: &mut dyn Iterator<Item = &'a str>,
5
) -> Result<ColorU, CssColorParseError<'a>> {
    #[inline]
5
    fn angle_from_str<'a>(
5
        components: &mut dyn Iterator<Item = &'a str>,
5
        which: CssColorComponent,
5
    ) -> Result<f32, CssColorParseError<'a>> {
5
        let c = components
5
            .next()
5
            .ok_or(CssColorParseError::MissingColorComponent(which))?;
5
        if c.is_empty() {
            return Err(CssColorParseError::MissingColorComponent(which));
5
        }
5
        let dir = parse_direction(c)?;
5
        match dir {
5
            Direction::Angle(deg) => Ok(deg.to_degrees()),
            Direction::FromTo(_) => Err(CssColorParseError::UnsupportedDirection(c)),
        }
5
    }
    #[inline]
10
    fn percent_from_str<'a>(
10
        components: &mut dyn Iterator<Item = &'a str>,
10
        which: CssColorComponent,
10
    ) -> Result<f32, CssColorParseError<'a>> {
        use crate::props::basic::parse_percentage_value;
10
        let c = components
10
            .next()
10
            .ok_or(CssColorParseError::MissingColorComponent(which))?;
10
        if c.is_empty() {
            return Err(CssColorParseError::MissingColorComponent(which));
10
        }
        // Modern CSS allows both percentage and unitless values for HSL
10
        Ok(parse_percentage_value(c)
10
            .map_err(CssColorParseError::InvalidPercentage)?
10
            .normalized()
            * 100.0)
10
    }
    #[inline]
5
    fn hsl_to_rgb(h: f32, s: f32, l: f32) -> (u8, u8, u8) {
5
        let s = s / 100.0;
5
        let l = l / 100.0;
5
        let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
5
        let h_prime = h / 60.0;
5
        let x = c * (1.0 - ((h_prime % 2.0) - 1.0).abs());
5
        let (r1, g1, b1) = if (0.0..1.0).contains(&h_prime) {
3
            (c, x, 0.0)
2
        } else if (1.0..2.0).contains(&h_prime) {
            (x, c, 0.0)
2
        } else if (2.0..3.0).contains(&h_prime) {
1
            (0.0, c, x)
1
        } else if (3.0..4.0).contains(&h_prime) {
            (0.0, x, c)
1
        } else if (4.0..5.0).contains(&h_prime) {
1
            (x, 0.0, c)
        } else {
            (c, 0.0, x)
        };
5
        let m = l - c / 2.0;
5
        (
5
            ((r1 + m) * 255.0) as u8,
5
            ((g1 + m) * 255.0) as u8,
5
            ((b1 + m) * 255.0) as u8,
5
        )
5
    }
5
    let (h, s, l) = (
5
        angle_from_str(components, CssColorComponent::Hue)?,
5
        percent_from_str(components, CssColorComponent::Saturation)?,
5
        percent_from_str(components, CssColorComponent::Lightness)?,
    );
5
    let (r, g, b) = hsl_to_rgb(h, s, l);
5
    Ok(ColorU { r, g, b, a: 255 })
5
}
#[cfg(feature = "parser")]
57
fn parse_alpha_component<'a>(
57
    components: &mut dyn Iterator<Item = &'a str>,
57
) -> Result<u8, CssColorParseError<'a>> {
57
    let a_str = components
57
        .next()
57
        .ok_or(CssColorParseError::MissingColorComponent(
57
            CssColorComponent::Alpha,
57
        ))?;
57
    if a_str.is_empty() {
        return Err(CssColorParseError::MissingColorComponent(
            CssColorComponent::Alpha,
        ));
57
    }
57
    let a = a_str.parse::<f32>()?;
57
    if !(0.0..=1.0).contains(&a) {
1
        return Err(CssColorParseError::FloatValueOutOfRange(a));
56
    }
56
    Ok((a * 255.0).round() as u8)
57
}
#[cfg(feature = "parser")]
4571
fn parse_color_builtin<'a>(input: &'a str) -> Result<ColorU, CssColorParseError<'a>> {
4571
    let (r, g, b, a) = match input.to_lowercase().as_str() {
4571
        "aliceblue" => (240, 248, 255, 255),
4571
        "antiquewhite" => (250, 235, 215, 255),
4571
        "aqua" => (0, 255, 255, 255),
4571
        "aquamarine" => (127, 255, 212, 255),
4571
        "azure" => (240, 255, 255, 255),
4571
        "beige" => (245, 245, 220, 255),
4571
        "bisque" => (255, 228, 196, 255),
4571
        "black" => (0, 0, 0, 255),
4397
        "blanchedalmond" => (255, 235, 205, 255),
4397
        "blue" => (0, 0, 255, 255),
3044
        "blueviolet" => (138, 43, 226, 255),
3044
        "brown" => (165, 42, 42, 255),
3044
        "burlywood" => (222, 184, 135, 255),
3044
        "cadetblue" => (95, 158, 160, 255),
3044
        "chartreuse" => (127, 255, 0, 255),
3044
        "chocolate" => (210, 105, 30, 255),
3044
        "coral" => (255, 127, 80, 255),
3044
        "cornflowerblue" => (100, 149, 237, 255),
3044
        "cornsilk" => (255, 248, 220, 255),
3044
        "crimson" => (220, 20, 60, 255),
3044
        "cyan" => (0, 255, 255, 255),
3044
        "darkblue" => (0, 0, 139, 255),
3044
        "darkcyan" => (0, 139, 139, 255),
3044
        "darkgoldenrod" => (184, 134, 11, 255),
3044
        "darkgray" | "darkgrey" => (169, 169, 169, 255),
3044
        "darkgreen" => (0, 100, 0, 255),
3044
        "darkkhaki" => (189, 183, 107, 255),
3044
        "darkmagenta" => (139, 0, 139, 255),
3044
        "darkolivegreen" => (85, 107, 47, 255),
3044
        "darkorange" => (255, 140, 0, 255),
3044
        "darkorchid" => (153, 50, 204, 255),
3044
        "darkred" => (139, 0, 0, 255),
3044
        "darksalmon" => (233, 150, 122, 255),
3044
        "darkseagreen" => (143, 188, 143, 255),
3044
        "darkslateblue" => (72, 61, 139, 255),
3044
        "darkslategray" | "darkslategrey" => (47, 79, 79, 255),
3044
        "darkturquoise" => (0, 206, 209, 255),
3044
        "darkviolet" => (148, 0, 211, 255),
3044
        "deeppink" => (255, 20, 147, 255),
3044
        "deepskyblue" => (0, 191, 255, 255),
3044
        "dimgray" | "dimgrey" => (105, 105, 105, 255),
3044
        "dodgerblue" => (30, 144, 255, 255),
3044
        "firebrick" => (178, 34, 34, 255),
3044
        "floralwhite" => (255, 250, 240, 255),
3044
        "forestgreen" => (34, 139, 34, 255),
3044
        "fuchsia" => (255, 0, 255, 255),
3044
        "gainsboro" => (220, 220, 220, 255),
3044
        "ghostwhite" => (248, 248, 255, 255),
3044
        "gold" => (255, 215, 0, 255),
3002
        "goldenrod" => (218, 165, 32, 255),
3002
        "gray" | "grey" => (128, 128, 128, 255),
2918
        "green" => (0, 128, 0, 255),
2340
        "greenyellow" => (173, 255, 47, 255),
2340
        "honeydew" => (240, 255, 240, 255),
2340
        "hotpink" => (255, 105, 180, 255),
2340
        "indianred" => (205, 92, 92, 255),
2340
        "indigo" => (75, 0, 130, 255),
2340
        "ivory" => (255, 255, 240, 255),
2340
        "khaki" => (240, 230, 140, 255),
2340
        "lavender" => (230, 230, 250, 255),
2340
        "lavenderblush" => (255, 240, 245, 255),
2340
        "lawngreen" => (124, 252, 0, 255),
2340
        "lemonchiffon" => (255, 250, 205, 255),
2340
        "lightblue" => (173, 216, 230, 255),
2340
        "lightcoral" => (240, 128, 128, 255),
2340
        "lightcyan" => (224, 255, 255, 255),
2340
        "lightgoldenrodyellow" => (250, 250, 210, 255),
2340
        "lightgray" | "lightgrey" => (211, 211, 211, 255),
2340
        "lightgreen" => (144, 238, 144, 255),
2340
        "lightpink" => (255, 182, 193, 255),
2340
        "lightsalmon" => (255, 160, 122, 255),
2340
        "lightseagreen" => (32, 178, 170, 255),
2340
        "lightskyblue" => (135, 206, 250, 255),
2340
        "lightslategray" | "lightslategrey" => (119, 136, 153, 255),
2340
        "lightsteelblue" => (176, 196, 222, 255),
2340
        "lightyellow" => (255, 255, 224, 255),
2340
        "lime" => (0, 255, 0, 255),
2333
        "limegreen" => (50, 205, 50, 255),
2333
        "linen" => (250, 240, 230, 255),
2333
        "magenta" => (255, 0, 255, 255),
2333
        "maroon" => (128, 0, 0, 255),
2333
        "mediumaquamarine" => (102, 205, 170, 255),
2333
        "mediumblue" => (0, 0, 205, 255),
2333
        "mediumorchid" => (186, 85, 211, 255),
2333
        "mediumpurple" => (147, 112, 219, 255),
2333
        "mediumseagreen" => (60, 179, 113, 255),
2333
        "mediumslateblue" => (123, 104, 238, 255),
2333
        "mediumspringgreen" => (0, 250, 154, 255),
2333
        "mediumturquoise" => (72, 209, 204, 255),
2333
        "mediumvioletred" => (199, 21, 133, 255),
2333
        "midnightblue" => (25, 25, 112, 255),
2333
        "mintcream" => (245, 255, 250, 255),
2333
        "mistyrose" => (255, 228, 225, 255),
2333
        "moccasin" => (255, 228, 181, 255),
2333
        "navajowhite" => (255, 222, 173, 255),
2333
        "navy" => (0, 0, 128, 255),
2333
        "oldlace" => (253, 245, 230, 255),
2333
        "olive" => (128, 128, 0, 255),
2333
        "olivedrab" => (107, 142, 35, 255),
2333
        "orange" => (255, 165, 0, 255),
2333
        "orangered" => (255, 69, 0, 255),
2333
        "orchid" => (218, 112, 214, 255),
2333
        "palegoldenrod" => (238, 232, 170, 255),
2333
        "palegreen" => (152, 251, 152, 255),
2333
        "paleturquoise" => (175, 238, 238, 255),
2333
        "palevioletred" => (219, 112, 147, 255),
2333
        "papayawhip" => (255, 239, 213, 255),
2333
        "peachpuff" => (255, 218, 185, 255),
2333
        "peru" => (205, 133, 63, 255),
2333
        "pink" => (255, 192, 203, 255),
2333
        "plum" => (221, 160, 221, 255),
2333
        "powderblue" => (176, 224, 230, 255),
2333
        "purple" => (128, 0, 128, 255),
2319
        "rebeccapurple" => (102, 51, 153, 255),
2318
        "red" => (255, 0, 0, 255),
232
        "rosybrown" => (188, 143, 143, 255),
232
        "royalblue" => (65, 105, 225, 255),
232
        "saddlebrown" => (139, 69, 19, 255),
232
        "salmon" => (250, 128, 114, 255),
232
        "sandybrown" => (244, 164, 96, 255),
232
        "seagreen" => (46, 139, 87, 255),
232
        "seashell" => (255, 245, 238, 255),
232
        "sienna" => (160, 82, 45, 255),
232
        "silver" => (192, 192, 192, 255),
232
        "skyblue" => (135, 206, 235, 255),
232
        "slateblue" => (106, 90, 205, 255),
232
        "slategray" | "slategrey" => (112, 128, 144, 255),
232
        "snow" => (255, 250, 250, 255),
232
        "springgreen" => (0, 255, 127, 255),
232
        "steelblue" => (70, 130, 180, 255),
232
        "tan" => (210, 180, 140, 255),
232
        "teal" => (0, 128, 128, 255),
232
        "thistle" => (216, 191, 216, 255),
232
        "tomato" => (255, 99, 71, 255),
232
        "transparent" => (0, 0, 0, 0),
231
        "turquoise" => (64, 224, 208, 255),
231
        "violet" => (238, 130, 238, 255),
231
        "wheat" => (245, 222, 179, 255),
231
        "white" => (255, 255, 255, 255),
131
        "whitesmoke" => (245, 245, 245, 255),
131
        "yellow" => (255, 255, 0, 255),
67
        "yellowgreen" => (154, 205, 50, 255),
67
        _ => return Err(CssColorParseError::InvalidColor(input)),
    };
4504
    Ok(ColorU { r, g, b, a })
4571
}
#[cfg(all(test, feature = "parser"))]
mod tests {
    use super::*;
    #[test]
1
    fn test_parse_color_keywords() {
1
        assert_eq!(parse_css_color("red").unwrap(), ColorU::RED);
1
        assert_eq!(parse_css_color("blue").unwrap(), ColorU::BLUE);
1
        assert_eq!(parse_css_color("transparent").unwrap(), ColorU::TRANSPARENT);
1
        assert_eq!(
1
            parse_css_color("rebeccapurple").unwrap(),
1
            ColorU::new_rgb(102, 51, 153)
        );
1
    }
    #[test]
1
    fn test_parse_color_hex() {
        // 3-digit
1
        assert_eq!(parse_css_color("#f00").unwrap(), ColorU::RED);
        // 4-digit
1
        assert_eq!(
1
            parse_css_color("#f008").unwrap(),
1
            ColorU::new(255, 0, 0, 136)
        );
        // 6-digit
1
        assert_eq!(parse_css_color("#00ff00").unwrap(), ColorU::GREEN);
        // 8-digit
1
        assert_eq!(
1
            parse_css_color("#0000ff80").unwrap(),
1
            ColorU::new(0, 0, 255, 128)
        );
        // Uppercase
1
        assert_eq!(
1
            parse_css_color("#FFC0CB").unwrap(),
1
            ColorU::new_rgb(255, 192, 203)
        ); // Pink
1
    }
    #[test]
1
    fn test_parse_color_rgb() {
1
        assert_eq!(parse_css_color("rgb(255, 0, 0)").unwrap(), ColorU::RED);
1
        assert_eq!(
1
            parse_css_color("rgba(0, 255, 0, 0.5)").unwrap(),
1
            ColorU::new(0, 255, 0, 128)
        );
1
        assert_eq!(
1
            parse_css_color("rgba(10, 20, 30, 1)").unwrap(),
1
            ColorU::new_rgb(10, 20, 30)
        );
1
        assert_eq!(parse_css_color("rgb( 0 , 0 , 0 )").unwrap(), ColorU::BLACK);
1
    }
    #[test]
1
    fn test_parse_color_hsl() {
1
        assert_eq!(parse_css_color("hsl(0, 100%, 50%)").unwrap(), ColorU::RED);
1
        assert_eq!(
1
            parse_css_color("hsl(120, 100%, 50%)").unwrap(),
            ColorU::GREEN
        );
1
        assert_eq!(
1
            parse_css_color("hsla(240, 100%, 50%, 0.5)").unwrap(),
1
            ColorU::new(0, 0, 255, 128)
        );
1
        assert_eq!(parse_css_color("hsl(0, 0%, 0%)").unwrap(), ColorU::BLACK);
1
    }
    #[test]
1
    fn test_parse_color_errors() {
1
        assert!(parse_css_color("redd").is_err());
1
        assert!(parse_css_color("#12345").is_err()); // Invalid length
1
        assert!(parse_css_color("#ggg").is_err()); // Invalid hex digit
1
        assert!(parse_css_color("rgb(255, 0)").is_err()); // Missing component
1
        assert!(parse_css_color("rgba(255, 0, 0, 2)").is_err()); // Alpha out of range
1
        assert!(parse_css_color("rgb(256, 0, 0)").is_err()); // Value out of range
                                                             // Modern CSS allows both hsl(0, 100%, 50%) and hsl(0 100 50)
1
        assert!(parse_css_color("hsl(0, 100, 50%)").is_ok()); // Valid in modern CSS
1
        assert!(parse_css_color("rgb(255 0 0)").is_err()); // Missing commas (this implementation
                                                           // requires commas)
1
    }
    #[test]
1
    fn test_parse_system_colors() {
        // Test parsing system color syntax
1
        assert_eq!(
1
            parse_color_or_system("system:accent").unwrap(),
            ColorOrSystem::System(SystemColorRef::Accent)
        );
1
        assert_eq!(
1
            parse_color_or_system("system:text").unwrap(),
            ColorOrSystem::System(SystemColorRef::Text)
        );
1
        assert_eq!(
1
            parse_color_or_system("system:background").unwrap(),
            ColorOrSystem::System(SystemColorRef::Background)
        );
1
        assert_eq!(
1
            parse_color_or_system("system:selection-background").unwrap(),
            ColorOrSystem::System(SystemColorRef::SelectionBackground)
        );
1
        assert_eq!(
1
            parse_color_or_system("system:selection-text").unwrap(),
            ColorOrSystem::System(SystemColorRef::SelectionText)
        );
1
        assert_eq!(
1
            parse_color_or_system("system:accent-text").unwrap(),
            ColorOrSystem::System(SystemColorRef::AccentText)
        );
1
        assert_eq!(
1
            parse_color_or_system("system:button-face").unwrap(),
            ColorOrSystem::System(SystemColorRef::ButtonFace)
        );
1
        assert_eq!(
1
            parse_color_or_system("system:button-text").unwrap(),
            ColorOrSystem::System(SystemColorRef::ButtonText)
        );
1
        assert_eq!(
1
            parse_color_or_system("system:window-background").unwrap(),
            ColorOrSystem::System(SystemColorRef::WindowBackground)
        );
        // Invalid system color should error
1
        assert!(parse_color_or_system("system:invalid").is_err());
        // Regular colors should still work
1
        assert_eq!(
1
            parse_color_or_system("red").unwrap(),
            ColorOrSystem::Color(ColorU::RED)
        );
1
        assert_eq!(
1
            parse_color_or_system("#ff0000").unwrap(),
            ColorOrSystem::Color(ColorU::RED)
        );
1
    }
    #[test]
1
    fn test_system_color_resolution() {
        use crate::system::SystemColors;
1
        let system_colors = SystemColors {
1
            text: OptionColorU::Some(ColorU::BLACK),
1
            secondary_text: OptionColorU::None,
1
            tertiary_text: OptionColorU::None,
1
            background: OptionColorU::Some(ColorU::WHITE),
1
            accent: OptionColorU::Some(ColorU::new_rgb(0, 122, 255)), // macOS blue
1
            accent_text: OptionColorU::Some(ColorU::WHITE),
1
            button_face: OptionColorU::Some(ColorU::new_rgb(240, 240, 240)),
1
            button_text: OptionColorU::Some(ColorU::BLACK),
1
            disabled_text: OptionColorU::None,
1
            window_background: OptionColorU::Some(ColorU::WHITE),
1
            under_page_background: OptionColorU::None,
1
            selection_background: OptionColorU::Some(ColorU::new_rgb(0, 120, 215)),
1
            selection_text: OptionColorU::Some(ColorU::WHITE),
1
            selection_background_inactive: OptionColorU::None,
1
            selection_text_inactive: OptionColorU::None,
1
            link: OptionColorU::None,
1
            separator: OptionColorU::None,
1
            grid: OptionColorU::None,
1
            find_highlight: OptionColorU::None,
1
            sidebar_background: OptionColorU::None,
1
            sidebar_selection: OptionColorU::None,
1
        };
        // Test resolution of system colors
1
        let accent_ref = ColorOrSystem::System(SystemColorRef::Accent);
1
        let resolved = accent_ref.resolve(&system_colors, ColorU::GRAY);
1
        assert_eq!(resolved, ColorU::new_rgb(0, 122, 255));
        // Test resolution with fallback when color is not set
1
        let empty_colors = SystemColors::default();
1
        let resolved_fallback = accent_ref.resolve(&empty_colors, ColorU::GRAY);
1
        assert_eq!(resolved_fallback, ColorU::GRAY);
        // Test that concrete colors just return themselves
1
        let concrete = ColorOrSystem::Color(ColorU::RED);
1
        let resolved_concrete = concrete.resolve(&system_colors, ColorU::GRAY);
1
        assert_eq!(resolved_concrete, ColorU::RED);
1
    }
    #[test]
1
    fn test_system_color_css_str() {
1
        assert_eq!(SystemColorRef::Accent.as_css_str(), "system:accent");
1
        assert_eq!(SystemColorRef::Text.as_css_str(), "system:text");
1
        assert_eq!(SystemColorRef::Background.as_css_str(), "system:background");
1
        assert_eq!(SystemColorRef::SelectionBackground.as_css_str(), "system:selection-background");
1
    }
}