1
//! CSS properties for border style, width, and color.
2

            
3
use alloc::string::{String, ToString};
4
use core::fmt;
5
use crate::corety::AzString;
6

            
7
#[cfg(feature = "parser")]
8
use crate::props::basic::{color::parse_css_color, pixel::parse_pixel_value};
9
use crate::{
10
    css::PrintAsCssValue,
11
    props::{
12
        basic::{
13
            color::{ColorU, CssColorParseError, CssColorParseErrorOwned},
14
            pixel::{
15
                CssPixelValueParseError, CssPixelValueParseErrorOwned, PixelValue,
16
                MEDIUM_BORDER_THICKNESS, THICK_BORDER_THICKNESS, THIN_BORDER_THICKNESS,
17
            },
18
        },
19
        macros::PixelValueTaker,
20
    },
21
};
22

            
23
/// Style of a `border`: solid, double, dash, ridge, etc.
24
#[derive(Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Hash)]
25
#[repr(C)]
26
// +spec:box-model:28fad6 - Border style variants including groove/ridge/inset/outset for separated/collapsing border models
27
#[derive(Default)]
28
pub enum BorderStyle {
29
    #[default]
30
    None,
31
    Solid,
32
    Double,
33
    Dotted,
34
    Dashed,
35
    Hidden,
36
    Groove,
37
    Ridge,
38
    Inset,
39
    Outset,
40
}
41

            
42

            
43
impl fmt::Display for BorderStyle {
44
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
45
        write!(
46
            f,
47
            "{}",
48
            match self {
49
                Self::None => "none",
50
                Self::Solid => "solid",
51
                Self::Double => "double",
52
                Self::Dotted => "dotted",
53
                Self::Dashed => "dashed",
54
                Self::Hidden => "hidden",
55
                Self::Groove => "groove",
56
                Self::Ridge => "ridge",
57
                Self::Inset => "inset",
58
                Self::Outset => "outset",
59
            }
60
        )
61
    }
62
}
63

            
64
impl PrintAsCssValue for BorderStyle {
65
    fn print_as_css_value(&self) -> String {
66
        self.to_string()
67
    }
68
}
69

            
70
/// Internal macro to reduce boilerplate for defining border-top, -right, -bottom, -left properties.
71
macro_rules! define_border_side_property {
72
    // For types that have a simple inner value and can be formatted with Display
73
    ($struct_name:ident, $inner_type:ty, $default:expr) => {
74
        #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
75
        #[repr(C)]
76
        pub struct $struct_name {
77
            pub inner: $inner_type,
78
        }
79
        impl ::core::fmt::Debug for $struct_name {
80
            fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
81
                write!(f, "{}", self.inner)
82
            }
83
        }
84
        impl Default for $struct_name {
85
            fn default() -> Self {
86
                Self { inner: $default }
87
            }
88
        }
89
    };
90
    // Specialization for ColorU
91
    ($struct_name:ident,ColorU) => {
92
        #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
93
        #[repr(C)]
94
        pub struct $struct_name {
95
            pub inner: ColorU,
96
        }
97
        impl ::core::fmt::Debug for $struct_name {
98
            fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
99
                write!(f, "{}", self.inner.to_hash())
100
            }
101
        }
102
        // The default border color is 'currentcolor', but for simplicity we default to BLACK.
103
        // The style property resolver should handle the 'currentcolor' logic.
104
        impl Default for $struct_name {
105
            fn default() -> Self {
106
                Self {
107
                    inner: ColorU::BLACK,
108
                }
109
            }
110
        }
111
        impl $struct_name {
112
            pub fn interpolate(&self, other: &Self, t: f32) -> Self {
113
                Self {
114
                    inner: self.inner.interpolate(&other.inner, t),
115
                }
116
            }
117
        }
118
    };
119
    // Specialization for PixelValue (border-width)
120
    ($struct_name:ident,PixelValue, $default:expr) => {
121
        #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
122
        #[repr(C)]
123
        pub struct $struct_name {
124
            pub inner: PixelValue,
125
        }
126
        impl ::core::fmt::Debug for $struct_name {
127
            fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
128
                write!(f, "{}", self.inner)
129
            }
130
        }
131
        impl Default for $struct_name {
132
            fn default() -> Self {
133
                Self { inner: $default }
134
            }
135
        }
136
        impl_pixel_value!($struct_name);
137
        impl PixelValueTaker for $struct_name {
138
            fn from_pixel_value(inner: PixelValue) -> Self {
139
                Self { inner }
140
            }
141
        }
142
    };
143
}
144

            
145
// --- Individual Property Structs ---
146

            
147
// +spec:box-model:8c49fe - Border style properties (none, solid, double, dashed, etc.) and border color defaulting to element's color
148
// Border Style (border-*-style)
149
/// CSS `border-top-style` property (e.g. `solid`, `dashed`, `none`).
150
define_border_side_property!(StyleBorderTopStyle, BorderStyle, BorderStyle::None);
151
/// CSS `border-right-style` property (e.g. `solid`, `dashed`, `none`).
152
define_border_side_property!(StyleBorderRightStyle, BorderStyle, BorderStyle::None);
153
/// CSS `border-bottom-style` property (e.g. `solid`, `dashed`, `none`).
154
define_border_side_property!(StyleBorderBottomStyle, BorderStyle, BorderStyle::None);
155
/// CSS `border-left-style` property (e.g. `solid`, `dashed`, `none`).
156
define_border_side_property!(StyleBorderLeftStyle, BorderStyle, BorderStyle::None);
157

            
158
// Formatting implementations for border side style values
159
impl crate::format_rust_code::FormatAsRustCode for StyleBorderTopStyle {
160
    fn format_as_rust_code(&self, tabs: usize) -> String {
161
        format!(
162
            "StyleBorderTopStyle {{ inner: {} }}",
163
            &self.inner.format_as_rust_code(tabs)
164
        )
165
    }
166
}
167

            
168
impl crate::format_rust_code::FormatAsRustCode for StyleBorderRightStyle {
169
    fn format_as_rust_code(&self, tabs: usize) -> String {
170
        format!(
171
            "StyleBorderRightStyle {{ inner: {} }}",
172
            &self.inner.format_as_rust_code(tabs)
173
        )
174
    }
175
}
176

            
177
impl crate::format_rust_code::FormatAsRustCode for StyleBorderLeftStyle {
178
    fn format_as_rust_code(&self, tabs: usize) -> String {
179
        format!(
180
            "StyleBorderLeftStyle {{ inner: {} }}",
181
            &self.inner.format_as_rust_code(tabs)
182
        )
183
    }
184
}
185

            
186
impl crate::format_rust_code::FormatAsRustCode for StyleBorderBottomStyle {
187
    fn format_as_rust_code(&self, tabs: usize) -> String {
188
        format!(
189
            "StyleBorderBottomStyle {{ inner: {} }}",
190
            &self.inner.format_as_rust_code(tabs)
191
        )
192
    }
193
}
194

            
195
// Border Color (border-*-color)
196
/// CSS `border-top-color` property. Defaults to `ColorU::BLACK`.
197
define_border_side_property!(StyleBorderTopColor, ColorU);
198
/// CSS `border-right-color` property. Defaults to `ColorU::BLACK`.
199
define_border_side_property!(StyleBorderRightColor, ColorU);
200
/// CSS `border-bottom-color` property. Defaults to `ColorU::BLACK`.
201
define_border_side_property!(StyleBorderBottomColor, ColorU);
202
/// CSS `border-left-color` property. Defaults to `ColorU::BLACK`.
203
define_border_side_property!(StyleBorderLeftColor, ColorU);
204

            
205
// Border Width (border-*-width)
206
// The default width is 'medium', which corresponds to 3px.
207
// Import from pixel.rs for consistency.
208
/// CSS `border-top-width` property. Defaults to `MEDIUM_BORDER_THICKNESS` (3px).
209
define_border_side_property!(LayoutBorderTopWidth, PixelValue, MEDIUM_BORDER_THICKNESS);
210
/// CSS `border-right-width` property. Defaults to `MEDIUM_BORDER_THICKNESS` (3px).
211
define_border_side_property!(LayoutBorderRightWidth, PixelValue, MEDIUM_BORDER_THICKNESS);
212
/// CSS `border-bottom-width` property. Defaults to `MEDIUM_BORDER_THICKNESS` (3px).
213
define_border_side_property!(LayoutBorderBottomWidth, PixelValue, MEDIUM_BORDER_THICKNESS);
214
/// CSS `border-left-width` property. Defaults to `MEDIUM_BORDER_THICKNESS` (3px).
215
define_border_side_property!(LayoutBorderLeftWidth, PixelValue, MEDIUM_BORDER_THICKNESS);
216

            
217
macro_rules! impl_border_width_helpers {
218
    ($($t:ty),+) => { $(
219
        impl $t {
220
            pub fn interpolate(&self, other: &Self, t: f32) -> Self {
221
                Self { inner: self.inner.interpolate(&other.inner, t) }
222
            }
223
            pub const fn const_px(value: isize) -> Self {
224
                Self { inner: PixelValue::const_px(value) }
225
            }
226
        }
227
    )+ };
228
}
229

            
230
impl_border_width_helpers!(
231
    LayoutBorderTopWidth,
232
    LayoutBorderRightWidth,
233
    LayoutBorderBottomWidth,
234
    LayoutBorderLeftWidth
235
);
236

            
237
/// Represents the three components of a border shorthand property, used as an intermediate
238
/// representation during parsing.
239
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
240
pub struct StyleBorderSide {
241
    pub border_width: PixelValue,
242
    pub border_style: BorderStyle,
243
    pub border_color: ColorU,
244
}
245

            
246
// --- PARSERS ---
247

            
248
// -- BorderStyle Parser --
249

            
250
#[cfg(feature = "parser")]
251
#[derive(Clone, PartialEq)]
252
pub enum CssBorderStyleParseError<'a> {
253
    InvalidStyle(&'a str),
254
}
255

            
256
#[cfg(feature = "parser")]
257
impl_debug_as_display!(CssBorderStyleParseError<'a>);
258
#[cfg(feature = "parser")]
259
impl_display! { CssBorderStyleParseError<'a>, {
260
    InvalidStyle(val) => format!("Invalid border style: \"{}\"", val),
261
}}
262

            
263
#[cfg(feature = "parser")]
264
#[derive(Debug, Clone, PartialEq)]
265
#[repr(C, u8)]
266
pub enum CssBorderStyleParseErrorOwned {
267
    InvalidStyle(AzString),
268
}
269

            
270
#[cfg(feature = "parser")]
271
impl<'a> CssBorderStyleParseError<'a> {
272
    pub fn to_contained(&self) -> CssBorderStyleParseErrorOwned {
273
        match self {
274
            CssBorderStyleParseError::InvalidStyle(s) => {
275
                CssBorderStyleParseErrorOwned::InvalidStyle(s.to_string().into())
276
            }
277
        }
278
    }
279
}
280

            
281
#[cfg(feature = "parser")]
282
impl CssBorderStyleParseErrorOwned {
283
    pub fn to_shared<'a>(&'a self) -> CssBorderStyleParseError<'a> {
284
        match self {
285
            CssBorderStyleParseErrorOwned::InvalidStyle(s) => {
286
                CssBorderStyleParseError::InvalidStyle(s.as_str())
287
            }
288
        }
289
    }
290
}
291

            
292
#[cfg(feature = "parser")]
293
502
pub fn parse_border_style<'a>(input: &'a str) -> Result<BorderStyle, CssBorderStyleParseError<'a>> {
294
502
    match input.trim() {
295
502
        "none" => Ok(BorderStyle::None),
296
501
        "solid" => Ok(BorderStyle::Solid),
297
14
        "double" => Ok(BorderStyle::Double),
298
13
        "dotted" => Ok(BorderStyle::Dotted),
299
10
        "dashed" => Ok(BorderStyle::Dashed),
300
9
        "hidden" => Ok(BorderStyle::Hidden),
301
9
        "groove" => Ok(BorderStyle::Groove),
302
8
        "ridge" => Ok(BorderStyle::Ridge),
303
7
        "inset" => Ok(BorderStyle::Inset),
304
6
        "outset" => Ok(BorderStyle::Outset),
305
6
        _ => Err(CssBorderStyleParseError::InvalidStyle(input)),
306
    }
307
502
}
308

            
309
// -- Shorthand Parser (for `border`, `border-top`, etc.) --
310

            
311
#[cfg(feature = "parser")]
312
#[derive(Clone, PartialEq)]
313
pub enum CssBorderSideParseError<'a> {
314
    InvalidDeclaration(&'a str),
315
    Width(CssPixelValueParseError<'a>),
316
    Style(CssBorderStyleParseError<'a>),
317
    Color(CssColorParseError<'a>),
318
}
319

            
320
#[cfg(feature = "parser")]
321
impl_debug_as_display!(CssBorderSideParseError<'a>);
322
#[cfg(feature = "parser")]
323
impl_display! { CssBorderSideParseError<'a>, {
324
    InvalidDeclaration(e) => format!("Invalid border declaration: \"{}\"", e),
325
    Width(e) => format!("Invalid border-width component: {}", e),
326
    Style(e) => format!("Invalid border-style component: {}", e),
327
    Color(e) => format!("Invalid border-color component: {}", e),
328
}}
329

            
330
#[cfg(feature = "parser")]
331
impl_from!(CssPixelValueParseError<'a>, CssBorderSideParseError::Width);
332
#[cfg(feature = "parser")]
333
impl_from!(CssBorderStyleParseError<'a>, CssBorderSideParseError::Style);
334
#[cfg(feature = "parser")]
335
impl_from!(CssColorParseError<'a>, CssBorderSideParseError::Color);
336

            
337
#[cfg(feature = "parser")]
338
#[derive(Debug, Clone, PartialEq)]
339
#[repr(C, u8)]
340
pub enum CssBorderSideParseErrorOwned {
341
    InvalidDeclaration(AzString),
342
    Width(CssPixelValueParseErrorOwned),
343
    Style(CssBorderStyleParseErrorOwned),
344
    Color(CssColorParseErrorOwned),
345
}
346

            
347
#[cfg(feature = "parser")]
348
impl<'a> CssBorderSideParseError<'a> {
349
    pub fn to_contained(&self) -> CssBorderSideParseErrorOwned {
350
        match self {
351
            CssBorderSideParseError::InvalidDeclaration(s) => {
352
                CssBorderSideParseErrorOwned::InvalidDeclaration(s.to_string().into())
353
            }
354
            CssBorderSideParseError::Width(e) => {
355
                CssBorderSideParseErrorOwned::Width(e.to_contained())
356
            }
357
            CssBorderSideParseError::Style(e) => {
358
                CssBorderSideParseErrorOwned::Style(e.to_contained())
359
            }
360
            CssBorderSideParseError::Color(e) => {
361
                CssBorderSideParseErrorOwned::Color(e.to_contained())
362
            }
363
        }
364
    }
365
}
366

            
367
#[cfg(feature = "parser")]
368
impl CssBorderSideParseErrorOwned {
369
    pub fn to_shared<'a>(&'a self) -> CssBorderSideParseError<'a> {
370
        match self {
371
            CssBorderSideParseErrorOwned::InvalidDeclaration(s) => {
372
                CssBorderSideParseError::InvalidDeclaration(s.as_str())
373
            }
374
            CssBorderSideParseErrorOwned::Width(e) => CssBorderSideParseError::Width(e.to_shared()),
375
            CssBorderSideParseErrorOwned::Style(e) => CssBorderSideParseError::Style(e.to_shared()),
376
            CssBorderSideParseErrorOwned::Color(e) => CssBorderSideParseError::Color(e.to_shared()),
377
        }
378
    }
379
}
380

            
381
// Type alias for compatibility with old code
382
#[cfg(feature = "parser")]
383
pub type CssBorderParseError<'a> = CssBorderSideParseError<'a>;
384

            
385
/// Newtype wrapper around `CssBorderSideParseErrorOwned` for the `border` shorthand.
386
#[cfg(feature = "parser")]
387
#[derive(Debug, Clone, PartialEq)]
388
#[repr(C)]
389
pub struct CssBorderParseErrorOwned {
390
    pub inner: CssBorderSideParseErrorOwned,
391
}
392

            
393
#[cfg(feature = "parser")]
394
impl From<CssBorderSideParseErrorOwned> for CssBorderParseErrorOwned {
395
    fn from(v: CssBorderSideParseErrorOwned) -> Self {
396
        Self { inner: v }
397
    }
398
}
399

            
400
/// Parses a border shorthand property such as "1px solid red".
401
/// Handles any order of components and applies defaults for missing values.
402
#[cfg(feature = "parser")]
403
495
fn parse_border_side<'a>(
404
495
    input: &'a str,
405
495
) -> Result<StyleBorderSide, CssBorderSideParseError<'a>> {
406
495
    let mut width = None;
407
495
    let mut style = None;
408
495
    let mut color = None;
409

            
410
495
    if input.trim().is_empty() {
411
1
        return Err(CssBorderSideParseError::InvalidDeclaration(input));
412
494
    }
413

            
414
1454
    for part in input.split_whitespace() {
415
        // Try to parse as a width.
416
1454
        if width.is_none() {
417
518
            if let Ok(w) = parse_border_width_value(part) {
418
471
                width = Some(w);
419
471
                continue;
420
47
            }
421
936
        }
422

            
423
        // Try to parse as a style.
424
983
        if style.is_none() {
425
495
            if let Ok(s) = parse_border_style(part) {
426
490
                style = Some(s);
427
490
                continue;
428
5
            }
429
488
        }
430

            
431
        // Try to parse as a color.
432
493
        if color.is_none() {
433
492
            if let Ok(c) = parse_css_color(part) {
434
489
                color = Some(c);
435
489
                continue;
436
3
            }
437
1
        }
438

            
439
        // If we get here, the part didn't match anything, or a value was specified twice.
440
4
        return Err(CssBorderSideParseError::InvalidDeclaration(input));
441
    }
442

            
443
490
    Ok(StyleBorderSide {
444
490
        border_width: width.unwrap_or(MEDIUM_BORDER_THICKNESS),
445
490
        border_style: style.unwrap_or(BorderStyle::None),
446
490
        border_color: color.unwrap_or(ColorU::BLACK),
447
490
    })
448
495
}
449

            
450
// --- Individual Property Parsers ---
451

            
452
#[cfg(feature = "parser")]
453
519
fn parse_border_width_value<'a>(
454
519
    input: &'a str,
455
519
) -> Result<PixelValue, CssPixelValueParseError<'a>> {
456
519
    match input.trim() {
457
519
        "thin" => Ok(THIN_BORDER_THICKNESS),
458
519
        "medium" => Ok(MEDIUM_BORDER_THICKNESS),
459
519
        "thick" => Ok(THICK_BORDER_THICKNESS),
460
518
        _ => parse_pixel_value(input),
461
    }
462
519
}
463

            
464
#[cfg(feature = "parser")]
465
1
pub fn parse_border_top_width<'a>(
466
1
    input: &'a str,
467
1
) -> Result<LayoutBorderTopWidth, CssPixelValueParseError<'a>> {
468
1
    parse_border_width_value(input).map(|inner| LayoutBorderTopWidth { inner })
469
1
}
470

            
471
#[cfg(feature = "parser")]
472
pub fn parse_border_right_width<'a>(
473
    input: &'a str,
474
) -> Result<LayoutBorderRightWidth, CssPixelValueParseError<'a>> {
475
    parse_border_width_value(input).map(|inner| LayoutBorderRightWidth { inner })
476
}
477

            
478
#[cfg(feature = "parser")]
479
pub fn parse_border_bottom_width<'a>(
480
    input: &'a str,
481
) -> Result<LayoutBorderBottomWidth, CssPixelValueParseError<'a>> {
482
    parse_border_width_value(input).map(|inner| LayoutBorderBottomWidth { inner })
483
}
484

            
485
#[cfg(feature = "parser")]
486
pub fn parse_border_left_width<'a>(
487
    input: &'a str,
488
) -> Result<LayoutBorderLeftWidth, CssPixelValueParseError<'a>> {
489
    parse_border_width_value(input).map(|inner| LayoutBorderLeftWidth { inner })
490
}
491

            
492
#[cfg(feature = "parser")]
493
pub fn parse_border_top_style<'a>(
494
    input: &'a str,
495
) -> Result<StyleBorderTopStyle, CssBorderStyleParseError<'a>> {
496
    parse_border_style(input).map(|inner| StyleBorderTopStyle { inner })
497
}
498
#[cfg(feature = "parser")]
499
pub fn parse_border_right_style<'a>(
500
    input: &'a str,
501
) -> Result<StyleBorderRightStyle, CssBorderStyleParseError<'a>> {
502
    parse_border_style(input).map(|inner| StyleBorderRightStyle { inner })
503
}
504
#[cfg(feature = "parser")]
505
pub fn parse_border_bottom_style<'a>(
506
    input: &'a str,
507
) -> Result<StyleBorderBottomStyle, CssBorderStyleParseError<'a>> {
508
    parse_border_style(input).map(|inner| StyleBorderBottomStyle { inner })
509
}
510
#[cfg(feature = "parser")]
511
1
pub fn parse_border_left_style<'a>(
512
1
    input: &'a str,
513
1
) -> Result<StyleBorderLeftStyle, CssBorderStyleParseError<'a>> {
514
1
    parse_border_style(input).map(|inner| StyleBorderLeftStyle { inner })
515
1
}
516

            
517
#[cfg(feature = "parser")]
518
pub fn parse_border_top_color<'a>(
519
    input: &'a str,
520
) -> Result<StyleBorderTopColor, CssColorParseError<'a>> {
521
    parse_css_color(input).map(|inner| StyleBorderTopColor { inner })
522
}
523
#[cfg(feature = "parser")]
524
1
pub fn parse_border_right_color<'a>(
525
1
    input: &'a str,
526
1
) -> Result<StyleBorderRightColor, CssColorParseError<'a>> {
527
1
    parse_css_color(input).map(|inner| StyleBorderRightColor { inner })
528
1
}
529
#[cfg(feature = "parser")]
530
pub fn parse_border_bottom_color<'a>(
531
    input: &'a str,
532
) -> Result<StyleBorderBottomColor, CssColorParseError<'a>> {
533
    parse_css_color(input).map(|inner| StyleBorderBottomColor { inner })
534
}
535
#[cfg(feature = "parser")]
536
pub fn parse_border_left_color<'a>(
537
    input: &'a str,
538
) -> Result<StyleBorderLeftColor, CssColorParseError<'a>> {
539
    parse_css_color(input).map(|inner| StyleBorderLeftColor { inner })
540
}
541

            
542
// --- Border Color Shorthand ---
543

            
544
/// Parsed result of `border-color` shorthand (1-4 color values)
545
#[cfg(feature = "parser")]
546
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
547
pub struct StyleBorderColors {
548
    pub top: ColorU,
549
    pub right: ColorU,
550
    pub bottom: ColorU,
551
    pub left: ColorU,
552
}
553

            
554
/// Parses `border-color` shorthand: 1-4 color values
555
/// - 1 value: all sides
556
/// - 2 values: top/bottom, left/right
557
/// - 3 values: top, left/right, bottom
558
/// - 4 values: top, right, bottom, left
559
#[cfg(feature = "parser")]
560
pub fn parse_style_border_color<'a>(
561
    input: &'a str,
562
) -> Result<StyleBorderColors, CssColorParseError<'a>> {
563
    let input = input.trim();
564
    let parts: Vec<&str> = input.split_whitespace().collect();
565

            
566
    match parts.len() {
567
        1 => {
568
            let color = parse_css_color(parts[0])?;
569
            Ok(StyleBorderColors {
570
                top: color,
571
                right: color,
572
                bottom: color,
573
                left: color,
574
            })
575
        }
576
        2 => {
577
            let top_bottom = parse_css_color(parts[0])?;
578
            let left_right = parse_css_color(parts[1])?;
579
            Ok(StyleBorderColors {
580
                top: top_bottom,
581
                right: left_right,
582
                bottom: top_bottom,
583
                left: left_right,
584
            })
585
        }
586
        3 => {
587
            let top = parse_css_color(parts[0])?;
588
            let left_right = parse_css_color(parts[1])?;
589
            let bottom = parse_css_color(parts[2])?;
590
            Ok(StyleBorderColors {
591
                top,
592
                right: left_right,
593
                bottom,
594
                left: left_right,
595
            })
596
        }
597
        4 => {
598
            let top = parse_css_color(parts[0])?;
599
            let right = parse_css_color(parts[1])?;
600
            let bottom = parse_css_color(parts[2])?;
601
            let left = parse_css_color(parts[3])?;
602
            Ok(StyleBorderColors {
603
                top,
604
                right,
605
                bottom,
606
                left,
607
            })
608
        }
609
        _ => Err(CssColorParseError::InvalidColor(input)),
610
    }
611
}
612

            
613
// --- Border Style Shorthand ---
614

            
615
/// Parsed result of `border-style` shorthand (1-4 style values)
616
#[cfg(feature = "parser")]
617
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
618
pub struct StyleBorderStyles {
619
    pub top: BorderStyle,
620
    pub right: BorderStyle,
621
    pub bottom: BorderStyle,
622
    pub left: BorderStyle,
623
}
624

            
625
/// Parses `border-style` shorthand: 1-4 style values
626
#[cfg(feature = "parser")]
627
pub fn parse_style_border_style<'a>(
628
    input: &'a str,
629
) -> Result<StyleBorderStyles, CssBorderStyleParseError<'a>> {
630
    let input = input.trim();
631
    let parts: Vec<&str> = input.split_whitespace().collect();
632

            
633
    match parts.len() {
634
        1 => {
635
            let style = parse_border_style(parts[0])?;
636
            Ok(StyleBorderStyles {
637
                top: style,
638
                right: style,
639
                bottom: style,
640
                left: style,
641
            })
642
        }
643
        2 => {
644
            let top_bottom = parse_border_style(parts[0])?;
645
            let left_right = parse_border_style(parts[1])?;
646
            Ok(StyleBorderStyles {
647
                top: top_bottom,
648
                right: left_right,
649
                bottom: top_bottom,
650
                left: left_right,
651
            })
652
        }
653
        3 => {
654
            let top = parse_border_style(parts[0])?;
655
            let left_right = parse_border_style(parts[1])?;
656
            let bottom = parse_border_style(parts[2])?;
657
            Ok(StyleBorderStyles {
658
                top,
659
                right: left_right,
660
                bottom,
661
                left: left_right,
662
            })
663
        }
664
        4 => {
665
            let top = parse_border_style(parts[0])?;
666
            let right = parse_border_style(parts[1])?;
667
            let bottom = parse_border_style(parts[2])?;
668
            let left = parse_border_style(parts[3])?;
669
            Ok(StyleBorderStyles {
670
                top,
671
                right,
672
                bottom,
673
                left,
674
            })
675
        }
676
        _ => Err(CssBorderStyleParseError::InvalidStyle(input)),
677
    }
678
}
679

            
680
// --- Border Width Shorthand ---
681

            
682
/// Parsed result of `border-width` shorthand (1-4 width values)
683
#[cfg(feature = "parser")]
684
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
685
pub struct StyleBorderWidths {
686
    pub top: PixelValue,
687
    pub right: PixelValue,
688
    pub bottom: PixelValue,
689
    pub left: PixelValue,
690
}
691

            
692
/// Parses `border-width` shorthand: 1-4 width values
693
#[cfg(feature = "parser")]
694
pub fn parse_style_border_width<'a>(
695
    input: &'a str,
696
) -> Result<StyleBorderWidths, CssPixelValueParseError<'a>> {
697
    let input = input.trim();
698
    let parts: Vec<&str> = input.split_whitespace().collect();
699

            
700
    match parts.len() {
701
        1 => {
702
            let width = parse_pixel_value(parts[0])?;
703
            Ok(StyleBorderWidths {
704
                top: width,
705
                right: width,
706
                bottom: width,
707
                left: width,
708
            })
709
        }
710
        2 => {
711
            let top_bottom = parse_pixel_value(parts[0])?;
712
            let left_right = parse_pixel_value(parts[1])?;
713
            Ok(StyleBorderWidths {
714
                top: top_bottom,
715
                right: left_right,
716
                bottom: top_bottom,
717
                left: left_right,
718
            })
719
        }
720
        3 => {
721
            let top = parse_pixel_value(parts[0])?;
722
            let left_right = parse_pixel_value(parts[1])?;
723
            let bottom = parse_pixel_value(parts[2])?;
724
            Ok(StyleBorderWidths {
725
                top,
726
                right: left_right,
727
                bottom,
728
                left: left_right,
729
            })
730
        }
731
        4 => {
732
            let top = parse_pixel_value(parts[0])?;
733
            let right = parse_pixel_value(parts[1])?;
734
            let bottom = parse_pixel_value(parts[2])?;
735
            let left = parse_pixel_value(parts[3])?;
736
            Ok(StyleBorderWidths {
737
                top,
738
                right,
739
                bottom,
740
                left,
741
            })
742
        }
743
        _ => Err(CssPixelValueParseError::InvalidPixelValue(input)),
744
    }
745
}
746

            
747
// Compatibility alias
748
#[cfg(feature = "parser")]
749
484
pub fn parse_style_border<'a>(input: &'a str) -> Result<StyleBorderSide, CssBorderParseError<'a>> {
750
484
    parse_border_side(input)
751
484
}
752

            
753
#[cfg(all(test, feature = "parser"))]
754
mod tests {
755
    use super::*;
756

            
757
    #[test]
758
1
    fn test_parse_border_style() {
759
1
        assert_eq!(parse_border_style("solid").unwrap(), BorderStyle::Solid);
760
1
        assert_eq!(parse_border_style("dotted").unwrap(), BorderStyle::Dotted);
761
1
        assert_eq!(parse_border_style("none").unwrap(), BorderStyle::None);
762
1
        assert_eq!(
763
1
            parse_border_style("  dashed  ").unwrap(),
764
            BorderStyle::Dashed
765
        );
766
1
        assert!(parse_border_style("solidd").is_err());
767
1
    }
768

            
769
    #[test]
770
1
    fn test_parse_border_side_shorthand() {
771
        // Full
772
1
        let result = parse_border_side("2px dotted #ff0000").unwrap();
773
1
        assert_eq!(result.border_width, PixelValue::px(2.0));
774
1
        assert_eq!(result.border_style, BorderStyle::Dotted);
775
1
        assert_eq!(result.border_color, ColorU::new_rgb(255, 0, 0));
776

            
777
        // Different order
778
1
        let result = parse_border_side("solid green 1em").unwrap();
779
1
        assert_eq!(result.border_width, PixelValue::em(1.0));
780
1
        assert_eq!(result.border_style, BorderStyle::Solid);
781
1
        assert_eq!(result.border_color, ColorU::new_rgb(0, 128, 0));
782

            
783
        // Missing width
784
1
        let result = parse_border_side("ridge #f0f").unwrap();
785
1
        assert_eq!(result.border_width, MEDIUM_BORDER_THICKNESS); // default
786
1
        assert_eq!(result.border_style, BorderStyle::Ridge);
787
1
        assert_eq!(result.border_color, ColorU::new_rgb(255, 0, 255));
788

            
789
        // Missing style
790
1
        let result = parse_border_side("5pt blue").unwrap();
791
1
        assert_eq!(result.border_width, PixelValue::pt(5.0));
792
1
        assert_eq!(result.border_style, BorderStyle::None); // default
793
1
        assert_eq!(result.border_color, ColorU::BLUE);
794

            
795
        // Missing color
796
1
        let result = parse_border_side("thick double").unwrap();
797
1
        assert_eq!(result.border_width, PixelValue::px(5.0));
798
1
        assert_eq!(result.border_style, BorderStyle::Double);
799
1
        assert_eq!(result.border_color, ColorU::BLACK); // default
800

            
801
        // Only one value
802
1
        let result = parse_border_side("inset").unwrap();
803
1
        assert_eq!(result.border_width, MEDIUM_BORDER_THICKNESS);
804
1
        assert_eq!(result.border_style, BorderStyle::Inset);
805
1
        assert_eq!(result.border_color, ColorU::BLACK);
806
1
    }
807

            
808
    #[test]
809
1
    fn test_parse_border_side_invalid() {
810
        // Two widths
811
1
        assert!(parse_border_side("1px 2px solid red").is_err());
812
        // Two styles
813
1
        assert!(parse_border_side("solid dashed red").is_err());
814
        // Two colors
815
1
        assert!(parse_border_side("red blue solid").is_err());
816
        // Empty
817
1
        assert!(parse_border_side("").is_err());
818
        // Unknown keyword
819
1
        assert!(parse_border_side("1px unknown red").is_err());
820
1
    }
821

            
822
    #[test]
823
1
    fn test_parse_longhand_border() {
824
1
        assert_eq!(
825
1
            parse_border_top_width("1.5em").unwrap().inner,
826
1
            PixelValue::em(1.5)
827
        );
828
1
        assert_eq!(
829
1
            parse_border_left_style("groove").unwrap().inner,
830
            BorderStyle::Groove
831
        );
832
1
        assert_eq!(
833
1
            parse_border_right_color("rgba(10, 20, 30, 0.5)")
834
1
                .unwrap()
835
                .inner,
836
1
            ColorU::new(10, 20, 30, 128)
837
        );
838
1
    }
839
}