1
//! CSS properties for backgrounds, including colors, images, and gradients.
2

            
3
use alloc::{
4
    string::{String, ToString},
5
    vec::Vec,
6
};
7
use core::fmt;
8

            
9
#[cfg(feature = "parser")]
10
use crate::props::basic::{
11
    error::{InvalidValueErr, InvalidValueErrOwned},
12
    parse::{
13
        parse_parentheses, parse_image, split_string_respect_comma,
14
        CssImageParseError, CssImageParseErrorOwned,
15
        ParenthesisParseError, ParenthesisParseErrorOwned,
16
    },
17
    color::parse_color_or_system,
18
};
19
use crate::{
20
    corety::AzString,
21
    format_rust_code::GetHash,
22
    props::{
23
        basic::{
24
            angle::{
25
                parse_angle_value, AngleValue, CssAngleValueParseError,
26
                CssAngleValueParseErrorOwned, OptionAngleValue,
27
            },
28
            color::{parse_css_color, ColorU, ColorOrSystem, CssColorParseError, CssColorParseErrorOwned},
29
            direction::{
30
                parse_direction, CssDirectionParseError, CssDirectionParseErrorOwned, Direction,
31
            },
32
            length::{
33
                parse_percentage_value, OptionPercentageValue, PercentageParseError,
34
                PercentageParseErrorOwned, PercentageValue,
35
            },
36
            pixel::{
37
                parse_pixel_value, CssPixelValueParseError, CssPixelValueParseErrorOwned,
38
                PixelValue,
39
            },
40
        },
41
        formatter::PrintAsCssValue,
42
    },
43
};
44

            
45
// --- TYPE DEFINITIONS ---
46

            
47
/// Whether a `gradient` should be repeated or clamped to the edges.
48
#[derive(Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Hash)]
49
#[repr(C)]
50
#[derive(Default)]
51
pub enum ExtendMode {
52
    #[default]
53
    Clamp,
54
    Repeat,
55
}
56

            
57
// -- Main Background Content Type --
58

            
59
/// A single CSS background layer: a solid color, image URL, or gradient.
60
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
61
#[repr(C, u8)]
62
pub enum StyleBackgroundContent {
63
    LinearGradient(LinearGradient),
64
    RadialGradient(RadialGradient),
65
    ConicGradient(ConicGradient),
66
    Image(AzString),
67
    Color(ColorU),
68
}
69

            
70
impl_option!(
71
    StyleBackgroundContent,
72
    OptionStyleBackgroundContent,
73
    copy = false,
74
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
75
);
76

            
77
impl_vec!(StyleBackgroundContent, StyleBackgroundContentVec, StyleBackgroundContentVecDestructor, StyleBackgroundContentVecDestructorType, StyleBackgroundContentVecSlice, OptionStyleBackgroundContent);
78
impl_vec_debug!(StyleBackgroundContent, StyleBackgroundContentVec);
79
impl_vec_partialord!(StyleBackgroundContent, StyleBackgroundContentVec);
80
impl_vec_ord!(StyleBackgroundContent, StyleBackgroundContentVec);
81
impl_vec_clone!(
82
    StyleBackgroundContent,
83
    StyleBackgroundContentVec,
84
    StyleBackgroundContentVecDestructor
85
);
86
impl_vec_partialeq!(StyleBackgroundContent, StyleBackgroundContentVec);
87
impl_vec_eq!(StyleBackgroundContent, StyleBackgroundContentVec);
88
impl_vec_hash!(StyleBackgroundContent, StyleBackgroundContentVec);
89

            
90
impl Default for StyleBackgroundContent {
91
    fn default() -> StyleBackgroundContent {
92
        StyleBackgroundContent::Color(ColorU::TRANSPARENT)
93
    }
94
}
95

            
96
impl PrintAsCssValue for StyleBackgroundContent {
97
    fn print_as_css_value(&self) -> String {
98
        match self {
99
            StyleBackgroundContent::LinearGradient(lg) => {
100
                let prefix = if lg.extend_mode == ExtendMode::Repeat {
101
                    "repeating-linear-gradient"
102
                } else {
103
                    "linear-gradient"
104
                };
105
                format!("{}({})", prefix, lg.print_as_css_value())
106
            }
107
            StyleBackgroundContent::RadialGradient(rg) => {
108
                let prefix = if rg.extend_mode == ExtendMode::Repeat {
109
                    "repeating-radial-gradient"
110
                } else {
111
                    "radial-gradient"
112
                };
113
                format!("{}({})", prefix, rg.print_as_css_value())
114
            }
115
            StyleBackgroundContent::ConicGradient(cg) => {
116
                let prefix = if cg.extend_mode == ExtendMode::Repeat {
117
                    "repeating-conic-gradient"
118
                } else {
119
                    "conic-gradient"
120
                };
121
                format!("{}({})", prefix, cg.print_as_css_value())
122
            }
123
            StyleBackgroundContent::Image(id) => format!("url(\"{}\")", id.as_str()),
124
            StyleBackgroundContent::Color(c) => c.to_hash(),
125
        }
126
    }
127
}
128

            
129
// Formatting to Rust code for background-related vecs
130

            
131
impl crate::format_rust_code::FormatAsRustCode for StyleBackgroundContent {
132
    fn format_as_rust_code(&self, _tabs: usize) -> String {
133
        // Delegate to the CSS value representation for single backgrounds
134
        format!("StyleBackgroundContent::from_css(\"{}\")", self.print_as_css_value())
135
    }
136
}
137

            
138
impl crate::format_rust_code::FormatAsRustCode for StyleBackgroundSizeVec {
139
    fn format_as_rust_code(&self, _tabs: usize) -> String {
140
        format!(
141
            "StyleBackgroundSizeVec::from_const_slice(STYLE_BACKGROUND_SIZE_{}_ITEMS)",
142
            self.get_hash()
143
        )
144
    }
145
}
146

            
147
impl crate::format_rust_code::FormatAsRustCode for StyleBackgroundRepeatVec {
148
    fn format_as_rust_code(&self, _tabs: usize) -> String {
149
        format!(
150
            "StyleBackgroundRepeatVec::from_const_slice(STYLE_BACKGROUND_REPEAT_{}_ITEMS)",
151
            self.get_hash()
152
        )
153
    }
154
}
155

            
156
impl crate::format_rust_code::FormatAsRustCode for StyleBackgroundContentVec {
157
    fn format_as_rust_code(&self, _tabs: usize) -> String {
158
        format!(
159
            "StyleBackgroundContentVec::from_const_slice(STYLE_BACKGROUND_CONTENT_{}_ITEMS)",
160
            self.get_hash()
161
        )
162
    }
163
}
164

            
165
impl PrintAsCssValue for StyleBackgroundContentVec {
166
    fn print_as_css_value(&self) -> String {
167
        self.as_ref()
168
            .iter()
169
            .map(|f| f.print_as_css_value())
170
            .collect::<Vec<_>>()
171
            .join(", ")
172
    }
173
}
174

            
175
// -- Gradient Types --
176

            
177
/// A CSS `linear-gradient()` or `repeating-linear-gradient()` value.
178
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
179
#[repr(C)]
180
pub struct LinearGradient {
181
    pub direction: Direction,
182
    pub extend_mode: ExtendMode,
183
    pub stops: NormalizedLinearColorStopVec,
184
}
185
impl Default for LinearGradient {
186
63
    fn default() -> Self {
187
63
        Self {
188
63
            direction: Direction::default(),
189
63
            extend_mode: ExtendMode::default(),
190
63
            stops: Vec::new().into(),
191
63
        }
192
63
    }
193
}
194
impl PrintAsCssValue for LinearGradient {
195
    fn print_as_css_value(&self) -> String {
196
        let dir_str = self.direction.print_as_css_value();
197
        let stops_str = self
198
            .stops
199
            .iter()
200
            .map(|s| s.print_as_css_value())
201
            .collect::<Vec<_>>()
202
            .join(", ");
203
        if stops_str.is_empty() {
204
            dir_str
205
        } else {
206
            format!("{}, {}", dir_str, stops_str)
207
        }
208
    }
209
}
210

            
211
/// A CSS `radial-gradient()` or `repeating-radial-gradient()` value.
212
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
213
#[repr(C)]
214
pub struct RadialGradient {
215
    pub shape: Shape,
216
    pub size: RadialGradientSize,
217
    pub position: StyleBackgroundPosition,
218
    pub extend_mode: ExtendMode,
219
    pub stops: NormalizedLinearColorStopVec,
220
}
221
impl Default for RadialGradient {
222
6
    fn default() -> Self {
223
6
        Self {
224
6
            shape: Shape::default(),
225
6
            size: RadialGradientSize::default(),
226
6
            position: StyleBackgroundPosition::default(),
227
6
            extend_mode: ExtendMode::default(),
228
6
            stops: Vec::new().into(),
229
6
        }
230
6
    }
231
}
232
impl PrintAsCssValue for RadialGradient {
233
    fn print_as_css_value(&self) -> String {
234
        let stops_str = self
235
            .stops
236
            .iter()
237
            .map(|s| s.print_as_css_value())
238
            .collect::<Vec<_>>()
239
            .join(", ");
240
        format!(
241
            "{} {} at {}, {}",
242
            self.shape,
243
            self.size,
244
            self.position.print_as_css_value(),
245
            stops_str
246
        )
247
    }
248
}
249

            
250
/// A CSS `conic-gradient()` or `repeating-conic-gradient()` value.
251
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
252
#[repr(C)]
253
pub struct ConicGradient {
254
    pub extend_mode: ExtendMode,
255
    pub center: StyleBackgroundPosition,
256
    pub angle: AngleValue,
257
    pub stops: NormalizedRadialColorStopVec,
258
}
259
impl Default for ConicGradient {
260
5
    fn default() -> Self {
261
5
        Self {
262
5
            extend_mode: ExtendMode::default(),
263
5
            center: StyleBackgroundPosition::default(),
264
5
            angle: AngleValue::default(),
265
5
            stops: Vec::new().into(),
266
5
        }
267
5
    }
268
}
269
impl PrintAsCssValue for ConicGradient {
270
    fn print_as_css_value(&self) -> String {
271
        let stops_str = self
272
            .stops
273
            .iter()
274
            .map(|s| s.print_as_css_value())
275
            .collect::<Vec<_>>()
276
            .join(", ");
277
        format!(
278
            "from {} at {}, {}",
279
            self.angle,
280
            self.center.print_as_css_value(),
281
            stops_str
282
        )
283
    }
284
}
285

            
286
// -- Gradient Sub-types --
287

            
288
/// The shape of a radial gradient: `circle` or `ellipse`.
289
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
290
#[repr(C)]
291
#[derive(Default)]
292
pub enum Shape {
293
    #[default]
294
    Ellipse,
295
    Circle,
296
}
297
impl fmt::Display for Shape {
298
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
299
        write!(
300
            f,
301
            "{}",
302
            match self {
303
                Shape::Ellipse => "ellipse",
304
                Shape::Circle => "circle",
305
            }
306
        )
307
    }
308
}
309

            
310
/// The sizing keyword for a radial gradient (e.g. `closest-side`, `farthest-corner`).
311
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
312
#[repr(C)]
313
#[derive(Default)]
314
pub enum RadialGradientSize {
315
    ClosestSide,
316
    ClosestCorner,
317
    FarthestSide,
318
    #[default]
319
    FarthestCorner,
320
}
321
impl fmt::Display for RadialGradientSize {
322
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
323
        write!(
324
            f,
325
            "{}",
326
            match self {
327
                Self::ClosestSide => "closest-side",
328
                Self::ClosestCorner => "closest-corner",
329
                Self::FarthestSide => "farthest-side",
330
                Self::FarthestCorner => "farthest-corner",
331
            }
332
        )
333
    }
334
}
335

            
336
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
337
#[repr(C)]
338
pub struct NormalizedLinearColorStop {
339
    pub offset: PercentageValue,
340
    /// Color for this gradient stop. Can be a concrete color or a system color reference.
341
    pub color: ColorOrSystem,
342
}
343

            
344
impl NormalizedLinearColorStop {
345
    /// Create a new normalized linear color stop with a concrete color.
346
    pub const fn new(offset: PercentageValue, color: ColorU) -> Self {
347
        Self { offset, color: ColorOrSystem::color(color) }
348
    }
349

            
350
    /// Resolve the color against system colors.
351
2
    pub fn resolve(&self, system_colors: &crate::system::SystemColors, fallback: ColorU) -> ColorU {
352
2
        self.color.resolve(system_colors, fallback)
353
2
    }
354
}
355

            
356
impl_option!(
357
    NormalizedLinearColorStop,
358
    OptionNormalizedLinearColorStop,
359
    copy = false,
360
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
361
);
362
impl_vec!(NormalizedLinearColorStop, NormalizedLinearColorStopVec, NormalizedLinearColorStopVecDestructor, NormalizedLinearColorStopVecDestructorType, NormalizedLinearColorStopVecSlice, OptionNormalizedLinearColorStop);
363
impl_vec_debug!(NormalizedLinearColorStop, NormalizedLinearColorStopVec);
364
impl_vec_partialord!(NormalizedLinearColorStop, NormalizedLinearColorStopVec);
365
impl_vec_ord!(NormalizedLinearColorStop, NormalizedLinearColorStopVec);
366
impl_vec_clone!(
367
    NormalizedLinearColorStop,
368
    NormalizedLinearColorStopVec,
369
    NormalizedLinearColorStopVecDestructor
370
);
371
impl_vec_partialeq!(NormalizedLinearColorStop, NormalizedLinearColorStopVec);
372
impl_vec_eq!(NormalizedLinearColorStop, NormalizedLinearColorStopVec);
373
impl_vec_hash!(NormalizedLinearColorStop, NormalizedLinearColorStopVec);
374
impl PrintAsCssValue for NormalizedLinearColorStop {
375
    fn print_as_css_value(&self) -> String {
376
        match &self.color {
377
            ColorOrSystem::Color(c) => format!("{} {}", c.to_hash(), self.offset),
378
            ColorOrSystem::System(s) => format!("{} {}", s.as_css_str(), self.offset),
379
        }
380
    }
381
}
382

            
383
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
384
#[repr(C)]
385
pub struct NormalizedRadialColorStop {
386
    pub angle: AngleValue,
387
    /// Color for this gradient stop. Can be a concrete color or a system color reference.
388
    pub color: ColorOrSystem,
389
}
390

            
391
impl NormalizedRadialColorStop {
392
    /// Create a new normalized radial color stop with a concrete color.
393
    pub const fn new(angle: AngleValue, color: ColorU) -> Self {
394
        Self { angle, color: ColorOrSystem::color(color) }
395
    }
396

            
397
    /// Resolve the color against system colors.
398
    pub fn resolve(&self, system_colors: &crate::system::SystemColors, fallback: ColorU) -> ColorU {
399
        self.color.resolve(system_colors, fallback)
400
    }
401
}
402

            
403
impl_option!(
404
    NormalizedRadialColorStop,
405
    OptionNormalizedRadialColorStop,
406
    copy = false,
407
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
408
);
409
impl_vec!(NormalizedRadialColorStop, NormalizedRadialColorStopVec, NormalizedRadialColorStopVecDestructor, NormalizedRadialColorStopVecDestructorType, NormalizedRadialColorStopVecSlice, OptionNormalizedRadialColorStop);
410
impl_vec_debug!(NormalizedRadialColorStop, NormalizedRadialColorStopVec);
411
impl_vec_partialord!(NormalizedRadialColorStop, NormalizedRadialColorStopVec);
412
impl_vec_ord!(NormalizedRadialColorStop, NormalizedRadialColorStopVec);
413
impl_vec_clone!(
414
    NormalizedRadialColorStop,
415
    NormalizedRadialColorStopVec,
416
    NormalizedRadialColorStopVecDestructor
417
);
418
impl_vec_partialeq!(NormalizedRadialColorStop, NormalizedRadialColorStopVec);
419
impl_vec_eq!(NormalizedRadialColorStop, NormalizedRadialColorStopVec);
420
impl_vec_hash!(NormalizedRadialColorStop, NormalizedRadialColorStopVec);
421
impl PrintAsCssValue for NormalizedRadialColorStop {
422
    fn print_as_css_value(&self) -> String {
423
        match &self.color {
424
            ColorOrSystem::Color(c) => format!("{} {}", c.to_hash(), self.angle),
425
            ColorOrSystem::System(s) => format!("{} {}", s.as_css_str(), self.angle),
426
        }
427
    }
428
}
429

            
430
/// Transient struct for parsing linear color stops before normalization.
431
///
432
/// Per W3C CSS Images Level 3, a color stop can have 0, 1, or 2 positions:
433
/// - `red` (no position)
434
/// - `red 50%` (one position)
435
/// - `red 10% 30%` (two positions - creates two stops at same color)
436
/// 
437
/// Supports system colors like `system:accent` for theme-aware gradients.
438
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
439
pub struct LinearColorStop {
440
    pub color: ColorOrSystem,
441
    /// First position (optional)
442
    pub offset1: OptionPercentageValue,
443
    /// Second position (optional, only valid if offset1 is Some)
444
    /// When present, creates two color stops at the same color.
445
    pub offset2: OptionPercentageValue,
446
}
447

            
448
/// Transient struct for parsing radial/conic color stops before normalization.
449
///
450
/// Per W3C CSS Images Level 3, a color stop can have 0, 1, or 2 positions:
451
/// - `red` (no position)
452
/// - `red 90deg` (one position)
453
/// - `red 45deg 90deg` (two positions - creates two stops at same color)
454
/// 
455
/// Supports system colors like `system:accent` for theme-aware gradients.
456
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
457
pub struct RadialColorStop {
458
    pub color: ColorOrSystem,
459
    /// First position (optional)
460
    pub offset1: OptionAngleValue,
461
    /// Second position (optional, only valid if offset1 is Some)
462
    /// When present, creates two color stops at the same color.
463
    pub offset2: OptionAngleValue,
464
}
465

            
466
// -- Other Background Properties --
467

            
468
/// The `background-position` property (horizontal + vertical components).
469
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
470
#[repr(C)]
471
pub struct StyleBackgroundPosition {
472
    pub horizontal: BackgroundPositionHorizontal,
473
    pub vertical: BackgroundPositionVertical,
474
}
475

            
476
impl_option!(
477
    StyleBackgroundPosition,
478
    OptionStyleBackgroundPosition,
479
    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
480
);
481
impl_vec!(StyleBackgroundPosition, StyleBackgroundPositionVec, StyleBackgroundPositionVecDestructor, StyleBackgroundPositionVecDestructorType, StyleBackgroundPositionVecSlice, OptionStyleBackgroundPosition);
482
impl_vec_debug!(StyleBackgroundPosition, StyleBackgroundPositionVec);
483
impl_vec_partialord!(StyleBackgroundPosition, StyleBackgroundPositionVec);
484
impl_vec_ord!(StyleBackgroundPosition, StyleBackgroundPositionVec);
485
impl_vec_clone!(
486
    StyleBackgroundPosition,
487
    StyleBackgroundPositionVec,
488
    StyleBackgroundPositionVecDestructor
489
);
490
impl_vec_partialeq!(StyleBackgroundPosition, StyleBackgroundPositionVec);
491
impl_vec_eq!(StyleBackgroundPosition, StyleBackgroundPositionVec);
492
impl_vec_hash!(StyleBackgroundPosition, StyleBackgroundPositionVec);
493
impl Default for StyleBackgroundPosition {
494
13
    fn default() -> Self {
495
13
        Self {
496
13
            horizontal: BackgroundPositionHorizontal::Left,
497
13
            vertical: BackgroundPositionVertical::Top,
498
13
        }
499
13
    }
500
}
501

            
502
impl StyleBackgroundPosition {
503
    pub fn scale_for_dpi(&mut self, scale_factor: f32) {
504
        self.horizontal.scale_for_dpi(scale_factor);
505
        self.vertical.scale_for_dpi(scale_factor);
506
    }
507
}
508

            
509
impl PrintAsCssValue for StyleBackgroundPosition {
510
    fn print_as_css_value(&self) -> String {
511
        format!(
512
            "{} {}",
513
            self.horizontal.print_as_css_value(),
514
            self.vertical.print_as_css_value()
515
        )
516
    }
517
}
518
impl PrintAsCssValue for StyleBackgroundPositionVec {
519
    fn print_as_css_value(&self) -> String {
520
        self.iter()
521
            .map(|v| v.print_as_css_value())
522
            .collect::<Vec<_>>()
523
            .join(", ")
524
    }
525
}
526

            
527
// Formatting to Rust code for StyleBackgroundPositionVec
528
impl crate::format_rust_code::FormatAsRustCode for StyleBackgroundPositionVec {
529
    fn format_as_rust_code(&self, _tabs: usize) -> String {
530
        format!(
531
            "StyleBackgroundPositionVec::from_const_slice(STYLE_BACKGROUND_POSITION_{}_ITEMS)",
532
            self.get_hash()
533
        )
534
    }
535
}
536

            
537
/// Horizontal component of `background-position`: a keyword or exact pixel value.
538
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
539
#[repr(C, u8)]
540
pub enum BackgroundPositionHorizontal {
541
    Left,
542
    Center,
543
    Right,
544
    Exact(PixelValue),
545
}
546

            
547
impl BackgroundPositionHorizontal {
548
    pub fn scale_for_dpi(&mut self, scale_factor: f32) {
549
        if let BackgroundPositionHorizontal::Exact(s) = self {
550
            s.scale_for_dpi(scale_factor);
551
        }
552
    }
553
}
554

            
555
impl PrintAsCssValue for BackgroundPositionHorizontal {
556
    fn print_as_css_value(&self) -> String {
557
        match self {
558
            Self::Left => "left".to_string(),
559
            Self::Center => "center".to_string(),
560
            Self::Right => "right".to_string(),
561
            Self::Exact(px) => px.print_as_css_value(),
562
        }
563
    }
564
}
565

            
566
/// Vertical component of `background-position`: a keyword or exact pixel value.
567
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
568
#[repr(C, u8)]
569
pub enum BackgroundPositionVertical {
570
    Top,
571
    Center,
572
    Bottom,
573
    Exact(PixelValue),
574
}
575

            
576
impl BackgroundPositionVertical {
577
    pub fn scale_for_dpi(&mut self, scale_factor: f32) {
578
        if let BackgroundPositionVertical::Exact(s) = self {
579
            s.scale_for_dpi(scale_factor);
580
        }
581
    }
582
}
583

            
584
impl PrintAsCssValue for BackgroundPositionVertical {
585
    fn print_as_css_value(&self) -> String {
586
        match self {
587
            Self::Top => "top".to_string(),
588
            Self::Center => "center".to_string(),
589
            Self::Bottom => "bottom".to_string(),
590
            Self::Exact(px) => px.print_as_css_value(),
591
        }
592
    }
593
}
594

            
595
/// The `background-size` property: `contain`, `cover`, or an exact size.
596
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
597
#[repr(C, u8)]
598
#[derive(Default)]
599
pub enum StyleBackgroundSize {
600
    ExactSize(PixelValueSize),
601
    #[default]
602
    Contain,
603
    Cover,
604
}
605

            
606
impl_option!(
607
    StyleBackgroundSize,
608
    OptionStyleBackgroundSize,
609
    copy = false,
610
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
611
);
612

            
613
/// Two-dimensional size in PixelValue units (width, height)
614
/// Used for background-size and similar properties
615
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
616
#[repr(C)]
617
pub struct PixelValueSize {
618
    pub width: PixelValue,
619
    pub height: PixelValue,
620
}
621

            
622
impl_vec!(StyleBackgroundSize, StyleBackgroundSizeVec, StyleBackgroundSizeVecDestructor, StyleBackgroundSizeVecDestructorType, StyleBackgroundSizeVecSlice, OptionStyleBackgroundSize);
623
impl_vec_debug!(StyleBackgroundSize, StyleBackgroundSizeVec);
624
impl_vec_partialord!(StyleBackgroundSize, StyleBackgroundSizeVec);
625
impl_vec_ord!(StyleBackgroundSize, StyleBackgroundSizeVec);
626
impl_vec_clone!(
627
    StyleBackgroundSize,
628
    StyleBackgroundSizeVec,
629
    StyleBackgroundSizeVecDestructor
630
);
631
impl_vec_partialeq!(StyleBackgroundSize, StyleBackgroundSizeVec);
632
impl_vec_eq!(StyleBackgroundSize, StyleBackgroundSizeVec);
633
impl_vec_hash!(StyleBackgroundSize, StyleBackgroundSizeVec);
634

            
635
impl StyleBackgroundSize {
636
    pub fn scale_for_dpi(&mut self, scale_factor: f32) {
637
        if let StyleBackgroundSize::ExactSize(size) = self {
638
            size.width.scale_for_dpi(scale_factor);
639
            size.height.scale_for_dpi(scale_factor);
640
        }
641
    }
642
}
643

            
644
impl PrintAsCssValue for StyleBackgroundSize {
645
    fn print_as_css_value(&self) -> String {
646
        match self {
647
            Self::Contain => "contain".to_string(),
648
            Self::Cover => "cover".to_string(),
649
            Self::ExactSize(size) => {
650
                format!(
651
                    "{} {}",
652
                    size.width.print_as_css_value(),
653
                    size.height.print_as_css_value()
654
                )
655
            }
656
        }
657
    }
658
}
659
impl PrintAsCssValue for StyleBackgroundSizeVec {
660
    fn print_as_css_value(&self) -> String {
661
        self.iter()
662
            .map(|v| v.print_as_css_value())
663
            .collect::<Vec<_>>()
664
            .join(", ")
665
    }
666
}
667

            
668
/// The `background-repeat` property.
669
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
670
#[repr(C)]
671
#[derive(Default)]
672
pub enum StyleBackgroundRepeat {
673
    NoRepeat,
674
    #[default]
675
    PatternRepeat,
676
    RepeatX,
677
    RepeatY,
678
}
679

            
680
impl_option!(
681
    StyleBackgroundRepeat,
682
    OptionStyleBackgroundRepeat,
683
    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
684
);
685
impl_vec!(StyleBackgroundRepeat, StyleBackgroundRepeatVec, StyleBackgroundRepeatVecDestructor, StyleBackgroundRepeatVecDestructorType, StyleBackgroundRepeatVecSlice, OptionStyleBackgroundRepeat);
686
impl_vec_debug!(StyleBackgroundRepeat, StyleBackgroundRepeatVec);
687
impl_vec_partialord!(StyleBackgroundRepeat, StyleBackgroundRepeatVec);
688
impl_vec_ord!(StyleBackgroundRepeat, StyleBackgroundRepeatVec);
689
impl_vec_clone!(
690
    StyleBackgroundRepeat,
691
    StyleBackgroundRepeatVec,
692
    StyleBackgroundRepeatVecDestructor
693
);
694
impl_vec_partialeq!(StyleBackgroundRepeat, StyleBackgroundRepeatVec);
695
impl_vec_eq!(StyleBackgroundRepeat, StyleBackgroundRepeatVec);
696
impl_vec_hash!(StyleBackgroundRepeat, StyleBackgroundRepeatVec);
697
impl PrintAsCssValue for StyleBackgroundRepeat {
698
    fn print_as_css_value(&self) -> String {
699
        match self {
700
            Self::NoRepeat => "no-repeat".to_string(),
701
            Self::PatternRepeat => "repeat".to_string(),
702
            Self::RepeatX => "repeat-x".to_string(),
703
            Self::RepeatY => "repeat-y".to_string(),
704
        }
705
    }
706
}
707
impl PrintAsCssValue for StyleBackgroundRepeatVec {
708
    fn print_as_css_value(&self) -> String {
709
        self.iter()
710
            .map(|v| v.print_as_css_value())
711
            .collect::<Vec<_>>()
712
            .join(", ")
713
    }
714
}
715

            
716
// --- ERROR DEFINITIONS ---
717

            
718
#[derive(Clone, PartialEq)]
719
pub enum CssBackgroundParseError<'a> {
720
    Error(&'a str),
721
    InvalidBackground(ParenthesisParseError<'a>),
722
    UnclosedGradient(&'a str),
723
    NoDirection(&'a str),
724
    TooFewGradientStops(&'a str),
725
    DirectionParseError(CssDirectionParseError<'a>),
726
    GradientParseError(CssGradientStopParseError<'a>),
727
    ConicGradient(CssConicGradientParseError<'a>),
728
    ShapeParseError(CssShapeParseError<'a>),
729
    ImageParseError(CssImageParseError<'a>),
730
    ColorParseError(CssColorParseError<'a>),
731
}
732

            
733
impl_debug_as_display!(CssBackgroundParseError<'a>);
734
impl_display! { CssBackgroundParseError<'a>, {
735
    Error(e) => e,
736
    InvalidBackground(val) => format!("Invalid background value: \"{}\"", val),
737
    UnclosedGradient(val) => format!("Unclosed gradient: \"{}\"", val),
738
    NoDirection(val) => format!("Gradient has no direction: \"{}\"", val),
739
    TooFewGradientStops(val) => format!("Failed to parse gradient due to too few gradient steps: \"{}\"", val),
740
    DirectionParseError(e) => format!("Failed to parse gradient direction: \"{}\"", e),
741
    GradientParseError(e) => format!("Failed to parse gradient: {}", e),
742
    ConicGradient(e) => format!("Failed to parse conic gradient: {}", e),
743
    ShapeParseError(e) => format!("Failed to parse shape of radial gradient: {}", e),
744
    ImageParseError(e) => format!("Failed to parse image() value: {}", e),
745
    ColorParseError(e) => format!("Failed to parse color value: {}", e),
746
}}
747

            
748
#[cfg(feature = "parser")]
749
impl_from!(
750
    ParenthesisParseError<'a>,
751
    CssBackgroundParseError::InvalidBackground
752
);
753
#[cfg(feature = "parser")]
754
impl_from!(
755
    CssDirectionParseError<'a>,
756
    CssBackgroundParseError::DirectionParseError
757
);
758
#[cfg(feature = "parser")]
759
impl_from!(
760
    CssGradientStopParseError<'a>,
761
    CssBackgroundParseError::GradientParseError
762
);
763
#[cfg(feature = "parser")]
764
impl_from!(
765
    CssShapeParseError<'a>,
766
    CssBackgroundParseError::ShapeParseError
767
);
768
#[cfg(feature = "parser")]
769
impl_from!(
770
    CssImageParseError<'a>,
771
    CssBackgroundParseError::ImageParseError
772
);
773
#[cfg(feature = "parser")]
774
impl_from!(
775
    CssColorParseError<'a>,
776
    CssBackgroundParseError::ColorParseError
777
);
778
#[cfg(feature = "parser")]
779
impl_from!(
780
    CssConicGradientParseError<'a>,
781
    CssBackgroundParseError::ConicGradient
782
);
783

            
784
#[derive(Debug, Clone, PartialEq)]
785
#[repr(C, u8)]
786
pub enum CssBackgroundParseErrorOwned {
787
    Error(AzString),
788
    InvalidBackground(ParenthesisParseErrorOwned),
789
    UnclosedGradient(AzString),
790
    NoDirection(AzString),
791
    TooFewGradientStops(AzString),
792
    DirectionParseError(CssDirectionParseErrorOwned),
793
    GradientParseError(CssGradientStopParseErrorOwned),
794
    ConicGradient(CssConicGradientParseErrorOwned),
795
    ShapeParseError(CssShapeParseErrorOwned),
796
    ImageParseError(CssImageParseErrorOwned),
797
    ColorParseError(CssColorParseErrorOwned),
798
}
799

            
800
impl<'a> CssBackgroundParseError<'a> {
801
    pub fn to_contained(&self) -> CssBackgroundParseErrorOwned {
802
        match self {
803
            Self::Error(s) => CssBackgroundParseErrorOwned::Error(s.to_string().into()),
804
            Self::InvalidBackground(e) => {
805
                CssBackgroundParseErrorOwned::InvalidBackground(e.to_contained())
806
            }
807
            Self::UnclosedGradient(s) => {
808
                CssBackgroundParseErrorOwned::UnclosedGradient(s.to_string().into())
809
            }
810
            Self::NoDirection(s) => CssBackgroundParseErrorOwned::NoDirection(s.to_string().into()),
811
            Self::TooFewGradientStops(s) => {
812
                CssBackgroundParseErrorOwned::TooFewGradientStops(s.to_string().into())
813
            }
814
            Self::DirectionParseError(e) => {
815
                CssBackgroundParseErrorOwned::DirectionParseError(e.to_contained())
816
            }
817
            Self::GradientParseError(e) => {
818
                CssBackgroundParseErrorOwned::GradientParseError(e.to_contained())
819
            }
820
            Self::ConicGradient(e) => CssBackgroundParseErrorOwned::ConicGradient(e.to_contained()),
821
            Self::ShapeParseError(e) => {
822
                CssBackgroundParseErrorOwned::ShapeParseError(e.to_contained())
823
            }
824
            Self::ImageParseError(e) => {
825
                CssBackgroundParseErrorOwned::ImageParseError(e.to_contained())
826
            }
827
            Self::ColorParseError(e) => {
828
                CssBackgroundParseErrorOwned::ColorParseError(e.to_contained())
829
            }
830
        }
831
    }
832
}
833

            
834
impl CssBackgroundParseErrorOwned {
835
    pub fn to_shared<'a>(&'a self) -> CssBackgroundParseError<'a> {
836
        match self {
837
            Self::Error(s) => CssBackgroundParseError::Error(s),
838
            Self::InvalidBackground(e) => CssBackgroundParseError::InvalidBackground(e.to_shared()),
839
            Self::UnclosedGradient(s) => CssBackgroundParseError::UnclosedGradient(s),
840
            Self::NoDirection(s) => CssBackgroundParseError::NoDirection(s),
841
            Self::TooFewGradientStops(s) => CssBackgroundParseError::TooFewGradientStops(s),
842
            Self::DirectionParseError(e) => {
843
                CssBackgroundParseError::DirectionParseError(e.to_shared())
844
            }
845
            Self::GradientParseError(e) => {
846
                CssBackgroundParseError::GradientParseError(e.to_shared())
847
            }
848
            Self::ConicGradient(e) => CssBackgroundParseError::ConicGradient(e.to_shared()),
849
            Self::ShapeParseError(e) => CssBackgroundParseError::ShapeParseError(e.to_shared()),
850
            Self::ImageParseError(e) => CssBackgroundParseError::ImageParseError(e.to_shared()),
851
            Self::ColorParseError(e) => CssBackgroundParseError::ColorParseError(e.to_shared()),
852
        }
853
    }
854
}
855

            
856
#[derive(Clone, PartialEq)]
857
pub enum CssGradientStopParseError<'a> {
858
    Error(&'a str),
859
    Percentage(PercentageParseError),
860
    Angle(CssAngleValueParseError<'a>),
861
    ColorParseError(CssColorParseError<'a>),
862
}
863

            
864
impl_debug_as_display!(CssGradientStopParseError<'a>);
865
impl_display! { CssGradientStopParseError<'a>, {
866
    Error(e) => e,
867
    Percentage(e) => format!("Failed to parse offset percentage: {}", e),
868
    Angle(e) => format!("Failed to parse angle: {}", e),
869
    ColorParseError(e) => format!("{}", e),
870
}}
871
#[cfg(feature = "parser")]
872
impl_from!(
873
    CssColorParseError<'a>,
874
    CssGradientStopParseError::ColorParseError
875
);
876

            
877
#[derive(Debug, Clone, PartialEq)]
878
#[repr(C, u8)]
879
pub enum CssGradientStopParseErrorOwned {
880
    Error(AzString),
881
    Percentage(PercentageParseErrorOwned),
882
    Angle(CssAngleValueParseErrorOwned),
883
    ColorParseError(CssColorParseErrorOwned),
884
}
885

            
886
impl<'a> CssGradientStopParseError<'a> {
887
    pub fn to_contained(&self) -> CssGradientStopParseErrorOwned {
888
        match self {
889
            Self::Error(s) => CssGradientStopParseErrorOwned::Error(s.to_string().into()),
890
            Self::Percentage(e) => CssGradientStopParseErrorOwned::Percentage(e.to_contained()),
891
            Self::Angle(e) => CssGradientStopParseErrorOwned::Angle(e.to_contained()),
892
            Self::ColorParseError(e) => {
893
                CssGradientStopParseErrorOwned::ColorParseError(e.to_contained())
894
            }
895
        }
896
    }
897
}
898

            
899
impl CssGradientStopParseErrorOwned {
900
    pub fn to_shared<'a>(&'a self) -> CssGradientStopParseError<'a> {
901
        match self {
902
            Self::Error(s) => CssGradientStopParseError::Error(s),
903
            Self::Percentage(e) => CssGradientStopParseError::Percentage(e.to_shared()),
904
            Self::Angle(e) => CssGradientStopParseError::Angle(e.to_shared()),
905
            Self::ColorParseError(e) => CssGradientStopParseError::ColorParseError(e.to_shared()),
906
        }
907
    }
908
}
909

            
910
#[derive(Clone, PartialEq)]
911
pub enum CssConicGradientParseError<'a> {
912
    Position(CssBackgroundPositionParseError<'a>),
913
    Angle(CssAngleValueParseError<'a>),
914
    NoAngle(&'a str),
915
}
916
impl_debug_as_display!(CssConicGradientParseError<'a>);
917
impl_display! { CssConicGradientParseError<'a>, {
918
    Position(val) => format!("Invalid position attribute: \"{}\"", val),
919
    Angle(val) => format!("Invalid angle value: \"{}\"", val),
920
    NoAngle(val) => format!("Expected angle: \"{}\"", val),
921
}}
922
#[cfg(feature = "parser")]
923
impl_from!(
924
    CssAngleValueParseError<'a>,
925
    CssConicGradientParseError::Angle
926
);
927
#[cfg(feature = "parser")]
928
impl_from!(
929
    CssBackgroundPositionParseError<'a>,
930
    CssConicGradientParseError::Position
931
);
932

            
933
#[derive(Debug, Clone, PartialEq)]
934
#[repr(C, u8)]
935
pub enum CssConicGradientParseErrorOwned {
936
    Position(CssBackgroundPositionParseErrorOwned),
937
    Angle(CssAngleValueParseErrorOwned),
938
    NoAngle(AzString),
939
}
940
impl<'a> CssConicGradientParseError<'a> {
941
    pub fn to_contained(&self) -> CssConicGradientParseErrorOwned {
942
        match self {
943
            Self::Position(e) => CssConicGradientParseErrorOwned::Position(e.to_contained()),
944
            Self::Angle(e) => CssConicGradientParseErrorOwned::Angle(e.to_contained()),
945
            Self::NoAngle(s) => CssConicGradientParseErrorOwned::NoAngle(s.to_string().into()),
946
        }
947
    }
948
}
949
impl CssConicGradientParseErrorOwned {
950
    pub fn to_shared<'a>(&'a self) -> CssConicGradientParseError<'a> {
951
        match self {
952
            Self::Position(e) => CssConicGradientParseError::Position(e.to_shared()),
953
            Self::Angle(e) => CssConicGradientParseError::Angle(e.to_shared()),
954
            Self::NoAngle(s) => CssConicGradientParseError::NoAngle(s),
955
        }
956
    }
957
}
958

            
959
#[derive(Debug, Copy, Clone, PartialEq)]
960
pub enum CssShapeParseError<'a> {
961
    ShapeErr(InvalidValueErr<'a>),
962
}
963
impl_display! {CssShapeParseError<'a>, {
964
    ShapeErr(e) => format!("\"{}\"", e.0),
965
}}
966
#[derive(Debug, Clone, PartialEq)]
967
#[repr(C, u8)]
968
pub enum CssShapeParseErrorOwned {
969
    ShapeErr(InvalidValueErrOwned),
970
}
971
impl<'a> CssShapeParseError<'a> {
972
    pub fn to_contained(&self) -> CssShapeParseErrorOwned {
973
        match self {
974
            Self::ShapeErr(err) => CssShapeParseErrorOwned::ShapeErr(err.to_contained()),
975
        }
976
    }
977
}
978
impl CssShapeParseErrorOwned {
979
    pub fn to_shared<'a>(&'a self) -> CssShapeParseError<'a> {
980
        match self {
981
            Self::ShapeErr(err) => CssShapeParseError::ShapeErr(err.to_shared()),
982
        }
983
    }
984
}
985

            
986
#[derive(Debug, Clone, PartialEq)]
987
pub enum CssBackgroundPositionParseError<'a> {
988
    NoPosition(&'a str),
989
    TooManyComponents(&'a str),
990
    FirstComponentWrong(CssPixelValueParseError<'a>),
991
    SecondComponentWrong(CssPixelValueParseError<'a>),
992
}
993

            
994
impl_display! {CssBackgroundPositionParseError<'a>, {
995
    NoPosition(e) => format!("First background position missing: \"{}\"", e),
996
    TooManyComponents(e) => format!("background-position can only have one or two components, not more: \"{}\"", e),
997
    FirstComponentWrong(e) => format!("Failed to parse first component: \"{}\"", e),
998
    SecondComponentWrong(e) => format!("Failed to parse second component: \"{}\"", e),
999
}}
#[derive(Debug, Clone, PartialEq)]
#[repr(C, u8)]
pub enum CssBackgroundPositionParseErrorOwned {
    NoPosition(AzString),
    TooManyComponents(AzString),
    FirstComponentWrong(CssPixelValueParseErrorOwned),
    SecondComponentWrong(CssPixelValueParseErrorOwned),
}
impl<'a> CssBackgroundPositionParseError<'a> {
    pub fn to_contained(&self) -> CssBackgroundPositionParseErrorOwned {
        match self {
            Self::NoPosition(s) => CssBackgroundPositionParseErrorOwned::NoPosition(s.to_string().into()),
            Self::TooManyComponents(s) => {
                CssBackgroundPositionParseErrorOwned::TooManyComponents(s.to_string().into())
            }
            Self::FirstComponentWrong(e) => {
                CssBackgroundPositionParseErrorOwned::FirstComponentWrong(e.to_contained())
            }
            Self::SecondComponentWrong(e) => {
                CssBackgroundPositionParseErrorOwned::SecondComponentWrong(e.to_contained())
            }
        }
    }
}
impl CssBackgroundPositionParseErrorOwned {
    pub fn to_shared<'a>(&'a self) -> CssBackgroundPositionParseError<'a> {
        match self {
            Self::NoPosition(s) => CssBackgroundPositionParseError::NoPosition(s),
            Self::TooManyComponents(s) => CssBackgroundPositionParseError::TooManyComponents(s),
            Self::FirstComponentWrong(e) => {
                CssBackgroundPositionParseError::FirstComponentWrong(e.to_shared())
            }
            Self::SecondComponentWrong(e) => {
                CssBackgroundPositionParseError::SecondComponentWrong(e.to_shared())
            }
        }
    }
}
// --- PARSERS ---
#[cfg(feature = "parser")]
pub mod parser {
    use super::*;
    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
    enum GradientType {
        LinearGradient,
        RepeatingLinearGradient,
        RadialGradient,
        RepeatingRadialGradient,
        ConicGradient,
        RepeatingConicGradient,
    }
    impl GradientType {
74
        pub const fn get_extend_mode(&self) -> ExtendMode {
74
            match self {
                Self::LinearGradient | Self::RadialGradient | Self::ConicGradient => {
71
                    ExtendMode::Clamp
                }
                Self::RepeatingLinearGradient
                | Self::RepeatingRadialGradient
3
                | Self::RepeatingConicGradient => ExtendMode::Repeat,
            }
74
        }
    }
    // -- Top-level Parsers for background-* properties --
    /// Parses multiple backgrounds, such as "linear-gradient(red, green), url(image.png)".
3834
    pub fn parse_style_background_content_multiple<'a>(
3834
        input: &'a str,
3834
    ) -> Result<StyleBackgroundContentVec, CssBackgroundParseError<'a>> {
3834
        Ok(split_string_respect_comma(input)
3834
            .iter()
3835
            .map(|i| parse_style_background_content(i))
3834
            .collect::<Result<Vec<_>, _>>()?
3827
            .into())
3834
    }
    /// Parses a single background value, which can be a color, image, or gradient.
3862
    pub fn parse_style_background_content<'a>(
3862
        input: &'a str,
3862
    ) -> Result<StyleBackgroundContent, CssBackgroundParseError<'a>> {
3862
        match parse_parentheses(
3862
            input,
3862
            &[
3862
                "linear-gradient",
3862
                "repeating-linear-gradient",
3862
                "radial-gradient",
3862
                "repeating-radial-gradient",
3862
                "conic-gradient",
3862
                "repeating-conic-gradient",
3862
                "image",
3862
                "url",
3862
            ],
3862
        ) {
76
            Ok((background_type, brace_contents)) => {
76
                let gradient_type = match background_type {
76
                    "linear-gradient" => GradientType::LinearGradient,
14
                    "repeating-linear-gradient" => GradientType::RepeatingLinearGradient,
13
                    "radial-gradient" => GradientType::RadialGradient,
8
                    "repeating-radial-gradient" => GradientType::RepeatingRadialGradient,
7
                    "conic-gradient" => GradientType::ConicGradient,
3
                    "repeating-conic-gradient" => GradientType::RepeatingConicGradient,
2
                    "image" | "url" => {
                        return Ok(StyleBackgroundContent::Image(
2
                            parse_image(brace_contents)?,
                        ))
                    }
                    _ => unreachable!(),
                };
74
                parse_gradient(brace_contents, gradient_type)
            }
3786
            Err(_) => Ok(StyleBackgroundContent::Color(parse_css_color(input)?)),
        }
3862
    }
    /// Parses multiple `background-position` values.
    pub fn parse_style_background_position_multiple<'a>(
        input: &'a str,
    ) -> Result<StyleBackgroundPositionVec, CssBackgroundPositionParseError<'a>> {
        Ok(split_string_respect_comma(input)
            .iter()
            .map(|i| parse_style_background_position(i))
            .collect::<Result<Vec<_>, _>>()?
            .into())
    }
    /// Parses a single `background-position` value.
11
    pub fn parse_style_background_position<'a>(
11
        input: &'a str,
11
    ) -> Result<StyleBackgroundPosition, CssBackgroundPositionParseError<'a>> {
11
        let input = input.trim();
11
        let mut whitespace_iter = input.split_whitespace();
11
        let first = whitespace_iter
11
            .next()
11
            .ok_or(CssBackgroundPositionParseError::NoPosition(input))?;
11
        let second = whitespace_iter.next();
11
        if whitespace_iter.next().is_some() {
1
            return Err(CssBackgroundPositionParseError::TooManyComponents(input));
10
        }
        // Try to parse as horizontal first, if that fails, maybe it's a vertical keyword
10
        if let Ok(horizontal) = parse_background_position_horizontal(first) {
3
            let vertical = match second {
1
                Some(s) => parse_background_position_vertical(s)
1
                    .map_err(CssBackgroundPositionParseError::SecondComponentWrong)?,
2
                None => BackgroundPositionVertical::Center,
            };
3
            return Ok(StyleBackgroundPosition {
3
                horizontal,
3
                vertical,
3
            });
7
        }
        // If the first part wasn't a horizontal keyword, maybe it's a vertical one
7
        if let Ok(vertical) = parse_background_position_vertical(first) {
            let horizontal = match second {
                Some(s) => parse_background_position_horizontal(s)
                    .map_err(CssBackgroundPositionParseError::FirstComponentWrong)?,
                None => BackgroundPositionHorizontal::Center,
            };
            return Ok(StyleBackgroundPosition {
                horizontal,
                vertical,
            });
7
        }
7
        Err(CssBackgroundPositionParseError::FirstComponentWrong(
7
            CssPixelValueParseError::InvalidPixelValue(first),
7
        ))
11
    }
    /// Parses multiple `background-size` values.
    pub fn parse_style_background_size_multiple<'a>(
        input: &'a str,
    ) -> Result<StyleBackgroundSizeVec, InvalidValueErr<'a>> {
        Ok(split_string_respect_comma(input)
            .iter()
            .map(|i| parse_style_background_size(i))
            .collect::<Result<Vec<_>, _>>()?
            .into())
    }
    /// Parses a single `background-size` value.
5
    pub fn parse_style_background_size<'a>(
5
        input: &'a str,
5
    ) -> Result<StyleBackgroundSize, InvalidValueErr<'a>> {
5
        let input = input.trim();
5
        match input {
5
            "contain" => Ok(StyleBackgroundSize::Contain),
4
            "cover" => Ok(StyleBackgroundSize::Cover),
3
            other => {
3
                let mut iter = other.split_whitespace();
3
                let x_val = iter.next().ok_or(InvalidValueErr(input))?;
3
                let x_pos = parse_pixel_value(x_val).map_err(|_| InvalidValueErr(input))?;
2
                let y_pos = match iter.next() {
1
                    Some(y_val) => parse_pixel_value(y_val).map_err(|_| InvalidValueErr(input))?,
1
                    None => x_pos, // If only one value, it applies to both width and height
                };
2
                Ok(StyleBackgroundSize::ExactSize(PixelValueSize {
2
                    width: x_pos,
2
                    height: y_pos,
2
                }))
            }
        }
5
    }
    /// Parses multiple `background-repeat` values.
    pub fn parse_style_background_repeat_multiple<'a>(
        input: &'a str,
    ) -> Result<StyleBackgroundRepeatVec, InvalidValueErr<'a>> {
        Ok(split_string_respect_comma(input)
            .iter()
            .map(|i| parse_style_background_repeat(i))
            .collect::<Result<Vec<_>, _>>()?
            .into())
    }
    /// Parses a single `background-repeat` value.
5
    pub fn parse_style_background_repeat<'a>(
5
        input: &'a str,
5
    ) -> Result<StyleBackgroundRepeat, InvalidValueErr<'a>> {
5
        match input.trim() {
5
            "no-repeat" => Ok(StyleBackgroundRepeat::NoRepeat),
4
            "repeat" => Ok(StyleBackgroundRepeat::PatternRepeat),
3
            "repeat-x" => Ok(StyleBackgroundRepeat::RepeatX),
2
            "repeat-y" => Ok(StyleBackgroundRepeat::RepeatY),
1
            _ => Err(InvalidValueErr(input)),
        }
5
    }
    // -- Gradient Parsing Logic --
    /// Parses the contents of a gradient function.
74
    fn parse_gradient<'a>(
74
        input: &'a str,
74
        gradient_type: GradientType,
74
    ) -> Result<StyleBackgroundContent, CssBackgroundParseError<'a>> {
74
        let input = input.trim();
74
        let comma_separated_items = split_string_respect_comma(input);
74
        let mut brace_iterator = comma_separated_items.iter();
74
        let first_brace_item = brace_iterator
74
            .next()
74
            .ok_or(CssBackgroundParseError::NoDirection(input))?;
74
        match gradient_type {
            GradientType::LinearGradient | GradientType::RepeatingLinearGradient => {
63
                let mut linear_gradient = LinearGradient {
63
                    extend_mode: gradient_type.get_extend_mode(),
63
                    ..Default::default()
63
                };
63
                let mut linear_stops = Vec::new();
63
                if let Ok(dir) = parse_direction(first_brace_item) {
52
                    linear_gradient.direction = dir;
52
                } else {
11
                    linear_stops.push(parse_linear_color_stop(first_brace_item)?);
                }
181
                for item in brace_iterator {
118
                    linear_stops.push(parse_linear_color_stop(item)?);
                }
63
                linear_gradient.stops = get_normalized_linear_stops(&linear_stops).into();
63
                Ok(StyleBackgroundContent::LinearGradient(linear_gradient))
            }
            GradientType::RadialGradient | GradientType::RepeatingRadialGradient => {
                // Simplified parsing: assumes shape/size/position come first, then stops.
                // A more robust parser would handle them in any order.
6
                let mut radial_gradient = RadialGradient {
6
                    extend_mode: gradient_type.get_extend_mode(),
6
                    ..Default::default()
6
                };
6
                let mut radial_stops = Vec::new();
6
                let mut current_item = *first_brace_item;
6
                let mut items_consumed = false;
                // Greedily consume shape, size, position keywords
                loop {
11
                    let mut consumed_in_iteration = false;
11
                    let mut temp_iter = current_item.split_whitespace();
24
                    for word in temp_iter {
13
                        if let Ok(shape) = parse_shape(word) {
5
                            radial_gradient.shape = shape;
5
                            consumed_in_iteration = true;
8
                        } else if let Ok(size) = parse_radial_gradient_size(word) {
1
                            radial_gradient.size = size;
1
                            consumed_in_iteration = true;
7
                        } else if let Ok(pos) = parse_style_background_position(current_item) {
                            radial_gradient.position = pos;
                            consumed_in_iteration = true;
                            break; // position can have multiple words, so consume the rest of the
                                   // item
7
                        }
                    }
11
                    if consumed_in_iteration {
5
                        if let Some(next_item) = brace_iterator.next() {
5
                            current_item = next_item;
5
                            items_consumed = true;
5
                        } else {
                            break;
                        }
                    } else {
6
                        break;
                    }
                }
6
                if items_consumed || parse_linear_color_stop(current_item).is_ok() {
6
                    radial_stops.push(parse_linear_color_stop(current_item)?);
                }
12
                for item in brace_iterator {
6
                    radial_stops.push(parse_linear_color_stop(item)?);
                }
6
                radial_gradient.stops = get_normalized_linear_stops(&radial_stops).into();
6
                Ok(StyleBackgroundContent::RadialGradient(radial_gradient))
            }
            GradientType::ConicGradient | GradientType::RepeatingConicGradient => {
5
                let mut conic_gradient = ConicGradient {
5
                    extend_mode: gradient_type.get_extend_mode(),
5
                    ..Default::default()
5
                };
5
                let mut conic_stops = Vec::new();
5
                if let Some((angle, center)) = parse_conic_first_item(first_brace_item)? {
2
                    conic_gradient.angle = angle;
2
                    conic_gradient.center = center;
2
                } else {
3
                    conic_stops.push(parse_radial_color_stop(first_brace_item)?);
                }
13
                for item in brace_iterator {
8
                    conic_stops.push(parse_radial_color_stop(item)?);
                }
5
                conic_gradient.stops = get_normalized_radial_stops(&conic_stops).into();
5
                Ok(StyleBackgroundContent::ConicGradient(conic_gradient))
            }
        }
74
    }
    // -- Gradient Parsing Helpers --
    /// Parses color stops per W3C CSS Images Level 3:
    /// - "red" (no position)
    /// - "red 5%" (one position)
    /// - "red 10% 30%" (two positions - creates a hard color band)
    /// 
    /// Also supports system colors like `system:accent 50%` for theme-aware gradients.
142
    fn parse_linear_color_stop<'a>(
142
        input: &'a str,
142
    ) -> Result<LinearColorStop, CssGradientStopParseError<'a>> {
142
        let input = input.trim();
142
        let (color_str, offset1_str, offset2_str) = split_color_and_offsets(input);
142
        let color = parse_color_or_system(color_str)?;
142
        let offset1 = match offset1_str {
129
            None => OptionPercentageValue::None,
13
            Some(s) => OptionPercentageValue::Some(
13
                parse_percentage_value(s).map_err(CssGradientStopParseError::Percentage)?,
            ),
        };
142
        let offset2 = match offset2_str {
141
            None => OptionPercentageValue::None,
1
            Some(s) => OptionPercentageValue::Some(
1
                parse_percentage_value(s).map_err(CssGradientStopParseError::Percentage)?,
            ),
        };
142
        Ok(LinearColorStop {
142
            color,
142
            offset1,
142
            offset2,
142
        })
142
    }
    /// Parses color stops per W3C CSS Images Level 3:
    /// - "red" (no position)
    /// - "red 90deg" (one position)
    /// - "red 45deg 90deg" (two positions - creates a hard color band)
    /// 
    /// Also supports system colors like `system:accent 90deg` for theme-aware gradients.
11
    fn parse_radial_color_stop<'a>(
11
        input: &'a str,
11
    ) -> Result<RadialColorStop, CssGradientStopParseError<'a>> {
11
        let input = input.trim();
11
        let (color_str, offset1_str, offset2_str) = split_color_and_offsets(input);
11
        let color = parse_color_or_system(color_str)?;
11
        let offset1 = match offset1_str {
7
            None => OptionAngleValue::None,
4
            Some(s) => OptionAngleValue::Some(
4
                parse_angle_value(s).map_err(CssGradientStopParseError::Angle)?,
            ),
        };
11
        let offset2 = match offset2_str {
11
            None => OptionAngleValue::None,
            Some(s) => OptionAngleValue::Some(
                parse_angle_value(s).map_err(CssGradientStopParseError::Angle)?,
            ),
        };
11
        Ok(RadialColorStop {
11
            color,
11
            offset1,
11
            offset2,
11
        })
11
    }
    /// Helper to robustly split a string like "rgba(0,0,0,0.5) 10% 30%" into color and offset
    /// parts. Returns (color_str, offset1, offset2) where offsets are optional.
    ///
    /// Per W3C CSS Images Level 3, a color stop can have 0, 1, or 2 positions:
    /// - "red" -> ("red", None, None)
    /// - "red 50%" -> ("red", Some("50%"), None)
    /// - "red 10% 30%" -> ("red", Some("10%"), Some("30%"))
153
    fn split_color_and_offsets(input: &str) -> (&str, Option<&str>, Option<&str>) {
        // Strategy: scan from the end to find position values (contain digits + % or unit).
        // We need to handle complex colors like "rgba(0, 0, 0, 0.5)" that contain spaces and
        // digits.
153
        let input = input.trim();
        // Try to find the last position value (might be second of two)
153
        if let Some((remaining, last_offset)) = try_split_last_offset(input) {
            // Try to find another position value before it
17
            if let Some((color_part, first_offset)) = try_split_last_offset(remaining) {
1
                return (color_part.trim(), Some(first_offset), Some(last_offset));
16
            }
16
            return (remaining.trim(), Some(last_offset), None);
136
        }
136
        (input, None, None)
153
    }
    /// Try to split off the last whitespace-separated token if it looks like a position value.
    /// Returns (remaining, offset_str) if successful.
170
    fn try_split_last_offset(input: &str) -> Option<(&str, &str)> {
170
        let input = input.trim();
170
        if let Some(last_ws_idx) = input.rfind(char::is_whitespace) {
18
            let (potential_color, potential_offset) = input.split_at(last_ws_idx);
18
            let potential_offset = potential_offset.trim();
            // A valid offset must contain a digit and typically ends with % or a unit
            // This avoids misinterpreting "to right bottom" as containing offsets
18
            if is_likely_offset(potential_offset) {
18
                return Some((potential_color, potential_offset));
            }
152
        }
152
        None
170
    }
    /// Check if a string looks like a position value (percentage or length).
    /// Must contain a digit and typically ends with %, px, em, etc.
18
    fn is_likely_offset(s: &str) -> bool {
18
        if !s.contains(|c: char| c.is_ascii_digit()) {
            return false;
18
        }
        // Check if it ends with a known unit or %
18
        let units = [
18
            "%", "px", "em", "rem", "ex", "ch", "vw", "vh", "vmin", "vmax", "cm", "mm", "in", "pt",
18
            "pc", "deg", "rad", "grad", "turn",
18
        ];
78
        units.iter().any(|u| s.ends_with(u))
18
    }
    /// Parses the `from <angle> at <position>` part of a conic gradient.
5
    fn parse_conic_first_item<'a>(
5
        input: &'a str,
5
    ) -> Result<Option<(AngleValue, StyleBackgroundPosition)>, CssConicGradientParseError<'a>> {
5
        let input = input.trim();
5
        if !input.starts_with("from") {
3
            return Ok(None);
2
        }
2
        let mut parts = input["from".len()..].trim().split("at");
2
        let angle_part = parts
2
            .next()
2
            .ok_or(CssConicGradientParseError::NoAngle(input))?
2
            .trim();
2
        let angle = parse_angle_value(angle_part)?;
2
        let position = match parts.next() {
            Some(pos_part) => parse_style_background_position(pos_part.trim())?,
2
            None => StyleBackgroundPosition::default(),
        };
2
        Ok(Some((angle, position)))
5
    }
    // -- Normalization Functions --
    macro_rules! impl_get_normalized_stops {
        (
            fn $fn_name:ident($input_stop:ty) -> Vec<$output_stop:ident>,
            pos_type = $pos_ty:ty,
            default_start = $default_start:expr,
            default_end = $default_end:expr,
            pos_ctor = $pos_ctor:expr,
            pos_to_f32 = $pos_to_f32:expr,
            output_field = $out_field:ident,
        ) => {
74
            fn $fn_name(stops: &[$input_stop]) -> Vec<$output_stop> {
74
                if stops.is_empty() {
                    return Vec::new();
74
                }
74
                let mut expanded: Vec<(ColorOrSystem, Option<$pos_ty>)> = Vec::new();
226
                for stop in stops {
152
                    match (stop.offset1.into_option(), stop.offset2.into_option()) {
136
                        (None, _) => {
136
                            expanded.push((stop.color, None));
136
                        }
15
                        (Some(pos1), None) => {
15
                            expanded.push((stop.color, Some(pos1)));
15
                        }
1
                        (Some(pos1), Some(pos2)) => {
1
                            expanded.push((stop.color, Some(pos1)));
1
                            expanded.push((stop.color, Some(pos2)));
1
                        }
                    }
                }
74
                if expanded.is_empty() {
                    return Vec::new();
74
                }
74
                let pos_ctor: fn(f32) -> $pos_ty = $pos_ctor;
74
                let pos_to_f32: fn(&$pos_ty) -> f32 = $pos_to_f32;
74
                if expanded[0].1.is_none() {
67
                    expanded[0].1 = Some(pos_ctor($default_start));
67
                }
74
                let last_idx = expanded.len() - 1;
74
                if expanded[last_idx].1.is_none() {
66
                    expanded[last_idx].1 = Some(pos_ctor($default_end));
66
                }
74
                let mut max_so_far: f32 = 0.0;
153
                for (_, pos) in expanded.iter_mut() {
153
                    if let Some(p) = pos {
150
                        let val = pos_to_f32(p);
150
                        if val < max_so_far {
1
                            *p = pos_ctor(max_so_far);
149
                        } else {
149
                            max_so_far = val;
149
                        }
3
                    }
                }
74
                let mut i = 0;
226
                while i < expanded.len() {
152
                    if expanded[i].1.is_none() {
2
                        let run_start = i;
2
                        let mut run_end = i;
5
                        while run_end < expanded.len() && expanded[run_end].1.is_none() {
3
                            run_end += 1;
3
                        }
2
                        let prev_pos = if run_start > 0 {
2
                            pos_to_f32(&expanded[run_start - 1].1.unwrap())
                        } else {
                            $default_start
                        };
2
                        let next_pos = if run_end < expanded.len() {
2
                            pos_to_f32(&expanded[run_end].1.unwrap())
                        } else {
                            $default_end
                        };
2
                        let run_len = run_end - run_start;
2
                        let step = (next_pos - prev_pos) / (run_len + 1) as f32;
3
                        for j in 0..run_len {
3
                            expanded[run_start + j].1 =
3
                                Some(pos_ctor(prev_pos + step * (j + 1) as f32));
3
                        }
2
                        i = run_end;
150
                    } else {
150
                        i += 1;
150
                    }
                }
74
                expanded
74
                    .into_iter()
153
                    .map(|(color, pos)| {
153
                        $output_stop {
153
                            $out_field: pos.unwrap_or(pos_ctor($default_start)),
153
                            color,
153
                        }
153
                    })
74
                    .collect()
74
            }
        };
    }
    impl_get_normalized_stops! {
        fn get_normalized_linear_stops(LinearColorStop) -> Vec<NormalizedLinearColorStop>,
        pos_type = PercentageValue,
        default_start = 0.0,
        default_end = 100.0,
272
        pos_ctor = (|v| PercentageValue::new(v)),
143
        pos_to_f32 = (|p: &PercentageValue| p.normalized() * 100.0),
        output_field = offset,
    }
    impl_get_normalized_stops! {
        fn get_normalized_radial_stops(RadialColorStop) -> Vec<NormalizedRadialColorStop>,
        pos_type = AngleValue,
        default_start = 0.0,
        default_end = 360.0,
18
        pos_ctor = (|v| AngleValue::deg(v)),
11
        pos_to_f32 = (|p: &AngleValue| p.to_degrees_raw()),
        output_field = angle,
    }
    // -- Other Background Helpers --
10
    fn parse_background_position_horizontal<'a>(
10
        input: &'a str,
10
    ) -> Result<BackgroundPositionHorizontal, CssPixelValueParseError<'a>> {
10
        Ok(match input {
10
            "left" => BackgroundPositionHorizontal::Left,
10
            "center" => BackgroundPositionHorizontal::Center,
9
            "right" => BackgroundPositionHorizontal::Right,
8
            other => BackgroundPositionHorizontal::Exact(parse_pixel_value(other)?),
        })
10
    }
8
    fn parse_background_position_vertical<'a>(
8
        input: &'a str,
8
    ) -> Result<BackgroundPositionVertical, CssPixelValueParseError<'a>> {
8
        Ok(match input {
8
            "top" => BackgroundPositionVertical::Top,
8
            "center" => BackgroundPositionVertical::Center,
8
            "bottom" => BackgroundPositionVertical::Bottom,
8
            other => BackgroundPositionVertical::Exact(parse_pixel_value(other)?),
        })
8
    }
13
    fn parse_shape<'a>(input: &'a str) -> Result<Shape, CssShapeParseError<'a>> {
13
        match input.trim() {
13
            "circle" => Ok(Shape::Circle),
9
            "ellipse" => Ok(Shape::Ellipse),
8
            _ => Err(CssShapeParseError::ShapeErr(InvalidValueErr(input))),
        }
13
    }
8
    fn parse_radial_gradient_size<'a>(
8
        input: &'a str,
8
    ) -> Result<RadialGradientSize, InvalidValueErr<'a>> {
8
        match input.trim() {
8
            "closest-side" => Ok(RadialGradientSize::ClosestSide),
7
            "closest-corner" => Ok(RadialGradientSize::ClosestCorner),
7
            "farthest-side" => Ok(RadialGradientSize::FarthestSide),
7
            "farthest-corner" => Ok(RadialGradientSize::FarthestCorner),
7
            _ => Err(InvalidValueErr(input)),
        }
8
    }
}
#[cfg(feature = "parser")]
pub use self::parser::*;
#[cfg(all(test, feature = "parser"))]
mod tests {
    use super::*;
    use crate::props::basic::{DirectionCorner, DirectionCorners};
    #[test]
1
    fn test_parse_single_background_content() {
        // Color
1
        assert_eq!(
1
            parse_style_background_content("red").unwrap(),
            StyleBackgroundContent::Color(ColorU::RED)
        );
1
        assert_eq!(
1
            parse_style_background_content("#ff00ff").unwrap(),
1
            StyleBackgroundContent::Color(ColorU::new_rgb(255, 0, 255))
        );
        // Image
1
        assert_eq!(
1
            parse_style_background_content("url(\"image.png\")").unwrap(),
1
            StyleBackgroundContent::Image("image.png".into())
        );
        // Linear Gradient
1
        let lg = parse_style_background_content("linear-gradient(to right, red, blue)").unwrap();
1
        assert!(matches!(lg, StyleBackgroundContent::LinearGradient(_)));
1
        if let StyleBackgroundContent::LinearGradient(grad) = lg {
1
            assert_eq!(grad.stops.len(), 2);
1
            assert_eq!(
                grad.direction,
                Direction::FromTo(DirectionCorners {
                    dir_from: DirectionCorner::Left,
                    dir_to: DirectionCorner::Right
                })
            );
        }
        // Radial Gradient
1
        let rg = parse_style_background_content("radial-gradient(circle, white, black)").unwrap();
1
        assert!(matches!(rg, StyleBackgroundContent::RadialGradient(_)));
1
        if let StyleBackgroundContent::RadialGradient(grad) = rg {
1
            assert_eq!(grad.stops.len(), 2);
1
            assert_eq!(grad.shape, Shape::Circle);
        }
        // Conic Gradient
1
        let cg = parse_style_background_content("conic-gradient(from 90deg, red, blue)").unwrap();
1
        assert!(matches!(cg, StyleBackgroundContent::ConicGradient(_)));
1
        if let StyleBackgroundContent::ConicGradient(grad) = cg {
1
            assert_eq!(grad.stops.len(), 2);
1
            assert_eq!(grad.angle, AngleValue::deg(90.0));
        }
1
    }
    #[test]
1
    fn test_parse_multiple_background_content() {
1
        let result =
1
            parse_style_background_content_multiple("url(foo.png), linear-gradient(red, blue)")
1
                .unwrap();
1
        assert_eq!(result.len(), 2);
1
        assert!(matches!(
1
            result.as_slice()[0],
            StyleBackgroundContent::Image(_)
        ));
1
        assert!(matches!(
1
            result.as_slice()[1],
            StyleBackgroundContent::LinearGradient(_)
        ));
1
    }
    #[test]
1
    fn test_parse_background_position() {
        // One value
1
        let result = parse_style_background_position("center").unwrap();
1
        assert_eq!(result.horizontal, BackgroundPositionHorizontal::Center);
1
        assert_eq!(result.vertical, BackgroundPositionVertical::Center);
1
        let result = parse_style_background_position("25%").unwrap();
1
        assert_eq!(
            result.horizontal,
1
            BackgroundPositionHorizontal::Exact(PixelValue::percent(25.0))
        );
1
        assert_eq!(result.vertical, BackgroundPositionVertical::Center);
        // Two values
1
        let result = parse_style_background_position("right 50px").unwrap();
1
        assert_eq!(result.horizontal, BackgroundPositionHorizontal::Right);
1
        assert_eq!(
            result.vertical,
1
            BackgroundPositionVertical::Exact(PixelValue::px(50.0))
        );
        // Four values (not supported by this parser, should fail)
1
        assert!(parse_style_background_position("left 10px top 20px").is_err());
1
    }
    #[test]
1
    fn test_parse_background_size() {
1
        assert_eq!(
1
            parse_style_background_size("contain").unwrap(),
            StyleBackgroundSize::Contain
        );
1
        assert_eq!(
1
            parse_style_background_size("cover").unwrap(),
            StyleBackgroundSize::Cover
        );
1
        assert_eq!(
1
            parse_style_background_size("50%").unwrap(),
1
            StyleBackgroundSize::ExactSize(PixelValueSize {
1
                width: PixelValue::percent(50.0),
1
                height: PixelValue::percent(50.0)
1
            })
        );
1
        assert_eq!(
1
            parse_style_background_size("100px 20em").unwrap(),
1
            StyleBackgroundSize::ExactSize(PixelValueSize {
1
                width: PixelValue::px(100.0),
1
                height: PixelValue::em(20.0)
1
            })
        );
1
        assert!(parse_style_background_size("auto").is_err());
1
    }
    #[test]
1
    fn test_parse_background_repeat() {
1
        assert_eq!(
1
            parse_style_background_repeat("repeat").unwrap(),
            StyleBackgroundRepeat::PatternRepeat
        );
1
        assert_eq!(
1
            parse_style_background_repeat("repeat-x").unwrap(),
            StyleBackgroundRepeat::RepeatX
        );
1
        assert_eq!(
1
            parse_style_background_repeat("repeat-y").unwrap(),
            StyleBackgroundRepeat::RepeatY
        );
1
        assert_eq!(
1
            parse_style_background_repeat("no-repeat").unwrap(),
            StyleBackgroundRepeat::NoRepeat
        );
1
        assert!(parse_style_background_repeat("repeat-xy").is_err());
1
    }
    // =========================================================================
    // W3C CSS Images Level 3 - Gradient Parsing Tests
    // =========================================================================
    #[test]
1
    fn test_gradient_no_position_stops() {
        // "linear-gradient(red, blue)" - no positions specified
1
        let lg = parse_style_background_content("linear-gradient(red, blue)").unwrap();
1
        if let StyleBackgroundContent::LinearGradient(grad) = lg {
1
            assert_eq!(grad.stops.len(), 2);
            // First stop should default to 0%
1
            assert!((grad.stops.as_ref()[0].offset.normalized() - 0.0).abs() < 0.001);
            // Last stop should default to 100%
1
            assert!((grad.stops.as_ref()[1].offset.normalized() - 1.0).abs() < 0.001);
        } else {
            panic!("Expected LinearGradient");
        }
1
    }
    #[test]
1
    fn test_gradient_single_position_stops() {
        // "linear-gradient(red 25%, blue 75%)" - one position per stop
1
        let lg = parse_style_background_content("linear-gradient(red 25%, blue 75%)").unwrap();
1
        if let StyleBackgroundContent::LinearGradient(grad) = lg {
1
            assert_eq!(grad.stops.len(), 2);
1
            assert!((grad.stops.as_ref()[0].offset.normalized() - 0.25).abs() < 0.001);
1
            assert!((grad.stops.as_ref()[1].offset.normalized() - 0.75).abs() < 0.001);
        } else {
            panic!("Expected LinearGradient");
        }
1
    }
    #[test]
1
    fn test_gradient_multi_position_stops() {
        // "linear-gradient(red 10% 30%, blue)" - two positions create two stops
1
        let lg = parse_style_background_content("linear-gradient(red 10% 30%, blue)").unwrap();
1
        if let StyleBackgroundContent::LinearGradient(grad) = lg {
            // Should have 3 stops: red@10%, red@30%, blue@100%
1
            assert_eq!(grad.stops.len(), 3, "Expected 3 stops for multi-position");
1
            assert!((grad.stops.as_ref()[0].offset.normalized() - 0.10).abs() < 0.001);
1
            assert!((grad.stops.as_ref()[1].offset.normalized() - 0.30).abs() < 0.001);
1
            assert!((grad.stops.as_ref()[2].offset.normalized() - 1.0).abs() < 0.001);
            // Both first two stops should have same color (red)
1
            assert_eq!(grad.stops.as_ref()[0].color, grad.stops.as_ref()[1].color);
        } else {
            panic!("Expected LinearGradient");
        }
1
    }
    #[test]
1
    fn test_gradient_three_colors_no_positions() {
        // "linear-gradient(red, green, blue)" - evenly distributed
1
        let lg = parse_style_background_content("linear-gradient(red, green, blue)").unwrap();
1
        if let StyleBackgroundContent::LinearGradient(grad) = lg {
1
            assert_eq!(grad.stops.len(), 3);
            // Positions: 0%, 50%, 100%
1
            assert!((grad.stops.as_ref()[0].offset.normalized() - 0.0).abs() < 0.001);
1
            assert!((grad.stops.as_ref()[1].offset.normalized() - 0.5).abs() < 0.001);
1
            assert!((grad.stops.as_ref()[2].offset.normalized() - 1.0).abs() < 0.001);
        } else {
            panic!("Expected LinearGradient");
        }
1
    }
    #[test]
1
    fn test_gradient_fixup_ascending_order() {
        // "linear-gradient(red 50%, blue 20%)" - blue position < red position
        // W3C says: clamp to max of previous positions
1
        let lg = parse_style_background_content("linear-gradient(red 50%, blue 20%)").unwrap();
1
        if let StyleBackgroundContent::LinearGradient(grad) = lg {
1
            assert_eq!(grad.stops.len(), 2);
            // First stop at 50%
1
            assert!((grad.stops.as_ref()[0].offset.normalized() - 0.50).abs() < 0.001);
            // Second stop clamped to 50% (not 20%)
1
            assert!((grad.stops.as_ref()[1].offset.normalized() - 0.50).abs() < 0.001);
        } else {
            panic!("Expected LinearGradient");
        }
1
    }
    #[test]
1
    fn test_gradient_distribute_unpositioned() {
        // "linear-gradient(red 0%, yellow, green, blue 100%)"
        // yellow and green should be distributed evenly between 0% and 100%
1
        let lg =
1
            parse_style_background_content("linear-gradient(red 0%, yellow, green, blue 100%)")
1
                .unwrap();
1
        if let StyleBackgroundContent::LinearGradient(grad) = lg {
1
            assert_eq!(grad.stops.len(), 4);
            // Positions: 0%, 33.3%, 66.6%, 100%
1
            assert!((grad.stops.as_ref()[0].offset.normalized() - 0.0).abs() < 0.001);
1
            assert!((grad.stops.as_ref()[1].offset.normalized() - 0.333).abs() < 0.01);
1
            assert!((grad.stops.as_ref()[2].offset.normalized() - 0.666).abs() < 0.01);
1
            assert!((grad.stops.as_ref()[3].offset.normalized() - 1.0).abs() < 0.001);
        } else {
            panic!("Expected LinearGradient");
        }
1
    }
    #[test]
1
    fn test_gradient_direction_to_corner() {
        // "linear-gradient(to top right, red, blue)"
1
        let lg =
1
            parse_style_background_content("linear-gradient(to top right, red, blue)").unwrap();
1
        if let StyleBackgroundContent::LinearGradient(grad) = lg {
1
            assert_eq!(
                grad.direction,
                Direction::FromTo(DirectionCorners {
                    dir_from: DirectionCorner::BottomLeft,
                    dir_to: DirectionCorner::TopRight
                })
            );
        } else {
            panic!("Expected LinearGradient");
        }
1
    }
    #[test]
1
    fn test_gradient_direction_angle() {
        // "linear-gradient(45deg, red, blue)"
1
        let lg = parse_style_background_content("linear-gradient(45deg, red, blue)").unwrap();
1
        if let StyleBackgroundContent::LinearGradient(grad) = lg {
1
            assert_eq!(grad.direction, Direction::Angle(AngleValue::deg(45.0)));
        } else {
            panic!("Expected LinearGradient");
        }
1
    }
    #[test]
1
    fn test_repeating_gradient() {
        // "repeating-linear-gradient(red, blue 20%)"
1
        let lg =
1
            parse_style_background_content("repeating-linear-gradient(red, blue 20%)").unwrap();
1
        if let StyleBackgroundContent::LinearGradient(grad) = lg {
1
            assert_eq!(grad.extend_mode, ExtendMode::Repeat);
        } else {
            panic!("Expected LinearGradient");
        }
1
    }
    #[test]
1
    fn test_radial_gradient_circle() {
        // "radial-gradient(circle, red, blue)"
1
        let rg = parse_style_background_content("radial-gradient(circle, red, blue)").unwrap();
1
        if let StyleBackgroundContent::RadialGradient(grad) = rg {
1
            assert_eq!(grad.shape, Shape::Circle);
1
            assert_eq!(grad.stops.len(), 2);
            // Check default position is center
1
            assert_eq!(grad.position.horizontal, BackgroundPositionHorizontal::Left);
1
            assert_eq!(grad.position.vertical, BackgroundPositionVertical::Top);
        } else {
            panic!("Expected RadialGradient");
        }
1
    }
    #[test]
1
    fn test_radial_gradient_ellipse() {
        // "radial-gradient(ellipse, red, blue)"
1
        let rg = parse_style_background_content("radial-gradient(ellipse, red, blue)").unwrap();
1
        if let StyleBackgroundContent::RadialGradient(grad) = rg {
1
            assert_eq!(grad.shape, Shape::Ellipse);
1
            assert_eq!(grad.stops.len(), 2);
        } else {
            panic!("Expected RadialGradient");
        }
1
    }
    #[test]
1
    fn test_radial_gradient_size_keywords() {
        // Test different size keywords
1
        let rg = parse_style_background_content("radial-gradient(circle closest-side, red, blue)")
1
            .unwrap();
1
        if let StyleBackgroundContent::RadialGradient(grad) = rg {
1
            assert_eq!(grad.shape, Shape::Circle);
1
            assert_eq!(grad.size, RadialGradientSize::ClosestSide);
        } else {
            panic!("Expected RadialGradient");
        }
1
    }
    #[test]
1
    fn test_radial_gradient_stop_positions() {
        // "radial-gradient(red 0%, blue 100%)"
1
        let rg = parse_style_background_content("radial-gradient(red 0%, blue 100%)").unwrap();
1
        if let StyleBackgroundContent::RadialGradient(grad) = rg {
1
            assert_eq!(grad.stops.len(), 2);
1
            assert!((grad.stops.as_ref()[0].offset.normalized() - 0.0).abs() < 0.001);
1
            assert!((grad.stops.as_ref()[1].offset.normalized() - 1.0).abs() < 0.001);
        } else {
            panic!("Expected RadialGradient");
        }
1
    }
    #[test]
1
    fn test_repeating_radial_gradient() {
1
        let rg = parse_style_background_content("repeating-radial-gradient(circle, red, blue 20%)")
1
            .unwrap();
1
        if let StyleBackgroundContent::RadialGradient(grad) = rg {
1
            assert_eq!(grad.extend_mode, ExtendMode::Repeat);
1
            assert_eq!(grad.shape, Shape::Circle);
        } else {
            panic!("Expected RadialGradient");
        }
1
    }
    #[test]
1
    fn test_conic_gradient_angle() {
        // "conic-gradient(from 45deg, red, blue)"
1
        let cg = parse_style_background_content("conic-gradient(from 45deg, red, blue)").unwrap();
1
        if let StyleBackgroundContent::ConicGradient(grad) = cg {
1
            assert_eq!(grad.angle, AngleValue::deg(45.0));
1
            assert_eq!(grad.stops.len(), 2);
        } else {
            panic!("Expected ConicGradient");
        }
1
    }
    #[test]
1
    fn test_conic_gradient_default() {
        // "conic-gradient(red, blue)" - no angle specified
1
        let cg = parse_style_background_content("conic-gradient(red, blue)").unwrap();
1
        if let StyleBackgroundContent::ConicGradient(grad) = cg {
1
            assert_eq!(grad.stops.len(), 2);
            // First stop defaults to 0deg
1
            assert!(
1
                (grad.stops.as_ref()[0].angle.to_degrees_raw() - 0.0).abs() < 0.001,
                "First stop should be 0deg, got {}",
                grad.stops.as_ref()[0].angle.to_degrees_raw()
            );
            // Last stop defaults to 360deg (use to_degrees_raw to preserve 360)
1
            assert!(
1
                (grad.stops.as_ref()[1].angle.to_degrees_raw() - 360.0).abs() < 0.001,
                "Last stop should be 360deg, got {}",
                grad.stops.as_ref()[1].angle.to_degrees_raw()
            );
        } else {
            panic!("Expected ConicGradient");
        }
1
    }
    #[test]
1
    fn test_conic_gradient_with_positions() {
        // "conic-gradient(red 0deg, blue 180deg, green 360deg)"
1
        let cg =
1
            parse_style_background_content("conic-gradient(red 0deg, blue 180deg, green 360deg)")
1
                .unwrap();
1
        if let StyleBackgroundContent::ConicGradient(grad) = cg {
1
            assert_eq!(grad.stops.len(), 3);
            // Use to_degrees_raw() to preserve 360deg
1
            assert!(
1
                (grad.stops.as_ref()[0].angle.to_degrees_raw() - 0.0).abs() < 0.001,
                "First stop should be 0deg, got {}",
                grad.stops.as_ref()[0].angle.to_degrees_raw()
            );
1
            assert!(
1
                (grad.stops.as_ref()[1].angle.to_degrees_raw() - 180.0).abs() < 0.001,
                "Second stop should be 180deg, got {}",
                grad.stops.as_ref()[1].angle.to_degrees_raw()
            );
1
            assert!(
1
                (grad.stops.as_ref()[2].angle.to_degrees_raw() - 360.0).abs() < 0.001,
                "Last stop should be 360deg, got {}",
                grad.stops.as_ref()[2].angle.to_degrees_raw()
            );
        } else {
            panic!("Expected ConicGradient");
        }
1
    }
    #[test]
1
    fn test_repeating_conic_gradient() {
1
        let cg =
1
            parse_style_background_content("repeating-conic-gradient(red, blue 30deg)").unwrap();
1
        if let StyleBackgroundContent::ConicGradient(grad) = cg {
1
            assert_eq!(grad.extend_mode, ExtendMode::Repeat);
        } else {
            panic!("Expected ConicGradient");
        }
1
    }
    #[test]
1
    fn test_gradient_with_rgba_color() {
        // Test parsing gradient with rgba color (contains spaces)
1
        let lg =
1
            parse_style_background_content("linear-gradient(rgba(255,0,0,0.5), blue)").unwrap();
1
        if let StyleBackgroundContent::LinearGradient(grad) = lg {
1
            assert_eq!(grad.stops.len(), 2);
            // First color should have alpha of ~128 (0.5 * 255, may be 127 or 128 due to rounding)
1
            let first_color = grad.stops.as_ref()[0].color.to_color_u_default();
1
            assert!(first_color.a >= 127 && first_color.a <= 128);
        } else {
            panic!("Expected LinearGradient");
        }
1
    }
    #[test]
1
    fn test_gradient_with_rgba_and_position() {
        // Test parsing "rgba(0,0,0,0.5) 50%"
1
        let lg =
1
            parse_style_background_content("linear-gradient(rgba(0,0,0,0.5) 50%, white)").unwrap();
1
        if let StyleBackgroundContent::LinearGradient(grad) = lg {
1
            assert_eq!(grad.stops.len(), 2);
1
            assert!((grad.stops.as_ref()[0].offset.normalized() - 0.5).abs() < 0.001);
        } else {
            panic!("Expected LinearGradient");
        }
1
    }
    #[test]
1
    fn test_gradient_resolves_system_color_stop() {
        // A `system:accent` stop should round-trip through the parser as a
        // System variant and resolve against a populated `SystemColors` to
        // the live accent color, falling back to the supplied default when
        // the key is unset.
        use crate::props::basic::color::ColorOrSystem;
        use crate::system::SystemColors;
1
        let lg = parse_style_background_content(
1
            "linear-gradient(red, system:accent)",
        )
1
        .unwrap();
1
        let StyleBackgroundContent::LinearGradient(grad) = lg else {
            panic!("Expected LinearGradient");
        };
1
        let stops = grad.stops.as_ref();
1
        assert_eq!(stops.len(), 2);
1
        let accent_stop = &stops[1];
1
        assert!(matches!(accent_stop.color, ColorOrSystem::System(_)));
1
        let mut populated = SystemColors::default();
1
        populated.accent =
1
            crate::props::basic::color::OptionColorU::Some(ColorU::new_rgb(0, 122, 255));
1
        let resolved = accent_stop.resolve(&populated, ColorU::TRANSPARENT);
1
        assert_eq!(resolved, ColorU::new_rgb(0, 122, 255));
1
        let empty = SystemColors::default();
1
        let fallback = accent_stop.resolve(&empty, ColorU::TRANSPARENT);
1
        assert_eq!(fallback, ColorU::TRANSPARENT);
1
    }
}