1
//! CSS properties for multi-column layout.
2
//!
3
//! Covers `column-count`, `column-width`, `column-span`, `column-fill`,
4
//! `column-rule-width`, `column-rule-style`, and `column-rule-color`.
5
//! Types are consumed via the `CssProperty` enum in the CSS property system.
6

            
7
use alloc::string::{String, ToString};
8
use core::num::ParseIntError;
9

            
10
use crate::props::{
11
    basic::{
12
        color::{parse_css_color, ColorU, CssColorParseError, CssColorParseErrorOwned},
13
        pixel::{
14
            parse_pixel_value, CssPixelValueParseError, CssPixelValueParseErrorOwned, PixelValue,
15
        },
16
    },
17
    formatter::PrintAsCssValue,
18
    style::border::{
19
        parse_border_style, BorderStyle, CssBorderStyleParseError, CssBorderStyleParseErrorOwned,
20
    },
21
};
22

            
23
// --- column-count ---
24

            
25
/// CSS `column-count` property: specifies the number of columns in a multi-column layout.
26
///
27
/// Values: `auto` or a positive integer.
28
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
29
#[repr(C, u8)]
30
#[derive(Default)]
31
pub enum ColumnCount {
32
    #[default]
33
    Auto,
34
    Integer(u32),
35
}
36

            
37

            
38
impl PrintAsCssValue for ColumnCount {
39
    fn print_as_css_value(&self) -> String {
40
        match self {
41
            Self::Auto => "auto".to_string(),
42
            Self::Integer(i) => i.to_string(),
43
        }
44
    }
45
}
46

            
47
// --- column-width ---
48

            
49
/// CSS `column-width` property: specifies the optimal width of columns.
50
///
51
/// Values: `auto` or a length value (e.g. `200px`, `15em`).
52
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
53
#[repr(C, u8)]
54
#[derive(Default)]
55
pub enum ColumnWidth {
56
    #[default]
57
    Auto,
58
    Length(PixelValue),
59
}
60

            
61

            
62
impl PrintAsCssValue for ColumnWidth {
63
    fn print_as_css_value(&self) -> String {
64
        match self {
65
            Self::Auto => "auto".to_string(),
66
            Self::Length(px) => px.print_as_css_value(),
67
        }
68
    }
69
}
70

            
71
// --- column-span ---
72

            
73
/// CSS `column-span` property: whether an element spans across all columns.
74
///
75
/// Values: `none` (default) or `all`.
76
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
77
#[repr(C)]
78
#[derive(Default)]
79
pub enum ColumnSpan {
80
    #[default]
81
    None,
82
    All,
83
}
84

            
85

            
86
impl PrintAsCssValue for ColumnSpan {
87
    fn print_as_css_value(&self) -> String {
88
        String::from(match self {
89
            Self::None => "none",
90
            Self::All => "all",
91
        })
92
    }
93
}
94

            
95
// --- column-fill ---
96

            
97
/// CSS `column-fill` property: how content is distributed across columns.
98
///
99
/// Values: `balance` (default) or `auto`.
100
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
101
#[repr(C)]
102
#[derive(Default)]
103
pub enum ColumnFill {
104
    Auto,
105
    #[default]
106
    Balance,
107
}
108

            
109

            
110
impl PrintAsCssValue for ColumnFill {
111
    fn print_as_css_value(&self) -> String {
112
        String::from(match self {
113
            Self::Auto => "auto",
114
            Self::Balance => "balance",
115
        })
116
    }
117
}
118

            
119
// --- column-rule ---
120

            
121
/// CSS `column-rule-width` property: the width of the rule between columns.
122
///
123
/// Defaults to `medium` (3px).
124
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
125
#[repr(C)]
126
pub struct ColumnRuleWidth {
127
    pub inner: PixelValue,
128
}
129

            
130
impl Default for ColumnRuleWidth {
131
    fn default() -> Self {
132
        Self {
133
            inner: PixelValue::const_px(3),
134
        }
135
    }
136
}
137

            
138
impl PrintAsCssValue for ColumnRuleWidth {
139
    fn print_as_css_value(&self) -> String {
140
        self.inner.print_as_css_value()
141
    }
142
}
143

            
144
/// CSS `column-rule-style` property: the style of the rule between columns.
145
///
146
/// Uses `BorderStyle` values (e.g. `none`, `solid`, `dotted`).
147
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
148
#[repr(C)]
149
pub struct ColumnRuleStyle {
150
    pub inner: BorderStyle,
151
}
152

            
153
impl Default for ColumnRuleStyle {
154
    fn default() -> Self {
155
        Self {
156
            inner: BorderStyle::None,
157
        }
158
    }
159
}
160

            
161
impl PrintAsCssValue for ColumnRuleStyle {
162
    fn print_as_css_value(&self) -> String {
163
        self.inner.print_as_css_value()
164
    }
165
}
166

            
167
/// CSS `column-rule-color` property: the color of the rule between columns.
168
///
169
/// Per the CSS spec this should default to `currentcolor`, but currently
170
/// defaults to black as `currentcolor` requires a resolved-value pass at
171
/// layout time.
172
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
173
#[repr(C)]
174
pub struct ColumnRuleColor {
175
    pub inner: ColorU,
176
}
177

            
178
impl Default for ColumnRuleColor {
179
    fn default() -> Self {
180
        // NOTE: should be `currentcolor` per CSS spec, see doc comment on type
181
        Self {
182
            inner: ColorU::BLACK,
183
        }
184
    }
185
}
186

            
187
impl PrintAsCssValue for ColumnRuleColor {
188
    fn print_as_css_value(&self) -> String {
189
        self.inner.to_hash()
190
    }
191
}
192

            
193
// Formatting to Rust code
194
impl crate::format_rust_code::FormatAsRustCode for ColumnCount {
195
    fn format_as_rust_code(&self, _tabs: usize) -> String {
196
        match self {
197
            ColumnCount::Auto => String::from("ColumnCount::Auto"),
198
            ColumnCount::Integer(i) => format!("ColumnCount::Integer({})", i),
199
        }
200
    }
201
}
202

            
203
impl crate::format_rust_code::FormatAsRustCode for ColumnWidth {
204
    fn format_as_rust_code(&self, _tabs: usize) -> String {
205
        match self {
206
            ColumnWidth::Auto => String::from("ColumnWidth::Auto"),
207
            ColumnWidth::Length(px) => format!(
208
                "ColumnWidth::Length({})",
209
                crate::format_rust_code::format_pixel_value(px)
210
            ),
211
        }
212
    }
213
}
214

            
215
impl crate::format_rust_code::FormatAsRustCode for ColumnSpan {
216
    fn format_as_rust_code(&self, _tabs: usize) -> String {
217
        match self {
218
            ColumnSpan::None => String::from("ColumnSpan::None"),
219
            ColumnSpan::All => String::from("ColumnSpan::All"),
220
        }
221
    }
222
}
223

            
224
impl crate::format_rust_code::FormatAsRustCode for ColumnFill {
225
    fn format_as_rust_code(&self, _tabs: usize) -> String {
226
        match self {
227
            ColumnFill::Auto => String::from("ColumnFill::Auto"),
228
            ColumnFill::Balance => String::from("ColumnFill::Balance"),
229
        }
230
    }
231
}
232

            
233
impl crate::format_rust_code::FormatAsRustCode for ColumnRuleWidth {
234
    fn format_as_rust_code(&self, _tabs: usize) -> String {
235
        format!(
236
            "ColumnRuleWidth {{ inner: {} }}",
237
            crate::format_rust_code::format_pixel_value(&self.inner)
238
        )
239
    }
240
}
241

            
242
impl crate::format_rust_code::FormatAsRustCode for ColumnRuleStyle {
243
    fn format_as_rust_code(&self, tabs: usize) -> String {
244
        format!(
245
            "ColumnRuleStyle {{ inner: {} }}",
246
            self.inner.format_as_rust_code(tabs)
247
        )
248
    }
249
}
250

            
251
impl crate::format_rust_code::FormatAsRustCode for ColumnRuleColor {
252
    fn format_as_rust_code(&self, _tabs: usize) -> String {
253
        format!(
254
            "ColumnRuleColor {{ inner: {} }}",
255
            crate::format_rust_code::format_color_value(&self.inner)
256
        )
257
    }
258
}
259

            
260
// --- PARSERS ---
261

            
262
#[cfg(feature = "parser")]
263
pub mod parser {
264
    use super::*;
265
    use crate::corety::AzString;
266

            
267
    // -- ColumnCount parser
268

            
269
    #[derive(Clone, PartialEq)]
270
    pub enum ColumnCountParseError<'a> {
271
        InvalidValue(&'a str),
272
        ParseInt(ParseIntError),
273
    }
274

            
275
    impl_debug_as_display!(ColumnCountParseError<'a>);
276
    impl_display! { ColumnCountParseError<'a>, {
277
        InvalidValue(v) => format!("Invalid column-count value: \"{}\"", v),
278
        ParseInt(e) => format!("Invalid integer for column-count: {}", e),
279
    }}
280

            
281
    #[derive(Debug, Clone, PartialEq)]
282
    #[repr(C, u8)]
283
    pub enum ColumnCountParseErrorOwned {
284
        InvalidValue(AzString),
285
        ParseInt(AzString),
286
    }
287

            
288
    impl<'a> ColumnCountParseError<'a> {
289
        pub fn to_contained(&self) -> ColumnCountParseErrorOwned {
290
            match self {
291
                Self::InvalidValue(s) => ColumnCountParseErrorOwned::InvalidValue(s.to_string().into()),
292
                Self::ParseInt(e) => ColumnCountParseErrorOwned::ParseInt(e.to_string().into()),
293
            }
294
        }
295
    }
296

            
297
    impl ColumnCountParseErrorOwned {
298
        pub fn to_shared<'a>(&'a self) -> ColumnCountParseError<'a> {
299
            match self {
300
                Self::InvalidValue(s) => ColumnCountParseError::InvalidValue(s),
301
                // ParseIntError cannot be reconstructed from its Display string,
302
                // so we fall back to a generic message. The original error text
303
                // is preserved in the owned `AzString` but not round-trippable.
304
                Self::ParseInt(_) => ColumnCountParseError::InvalidValue("invalid integer"),
305
            }
306
        }
307
    }
308

            
309
4
    pub fn parse_column_count<'a>(
310
4
        input: &'a str,
311
4
    ) -> Result<ColumnCount, ColumnCountParseError<'a>> {
312
4
        let trimmed = input.trim();
313
4
        if trimmed == "auto" {
314
1
            return Ok(ColumnCount::Auto);
315
3
        }
316
3
        let val: u32 = trimmed
317
3
            .parse()
318
3
            .map_err(ColumnCountParseError::ParseInt)?;
319
1
        Ok(ColumnCount::Integer(val))
320
4
    }
321

            
322
    // -- ColumnWidth parser
323

            
324
    #[derive(Clone, PartialEq)]
325
    pub enum ColumnWidthParseError<'a> {
326
        InvalidValue(&'a str),
327
        PixelValue(CssPixelValueParseError<'a>),
328
    }
329

            
330
    impl_debug_as_display!(ColumnWidthParseError<'a>);
331
    impl_display! { ColumnWidthParseError<'a>, {
332
        InvalidValue(v) => format!("Invalid column-width value: \"{}\"", v),
333
        PixelValue(e) => format!("{}", e),
334
    }}
335
    impl_from! { CssPixelValueParseError<'a>, ColumnWidthParseError::PixelValue }
336

            
337
    #[derive(Debug, Clone, PartialEq)]
338
    #[repr(C, u8)]
339
    pub enum ColumnWidthParseErrorOwned {
340
        InvalidValue(AzString),
341
        PixelValue(CssPixelValueParseErrorOwned),
342
    }
343

            
344
    impl<'a> ColumnWidthParseError<'a> {
345
        pub fn to_contained(&self) -> ColumnWidthParseErrorOwned {
346
            match self {
347
                Self::InvalidValue(s) => ColumnWidthParseErrorOwned::InvalidValue(s.to_string().into()),
348
                Self::PixelValue(e) => ColumnWidthParseErrorOwned::PixelValue(e.to_contained()),
349
            }
350
        }
351
    }
352

            
353
    impl ColumnWidthParseErrorOwned {
354
        pub fn to_shared<'a>(&'a self) -> ColumnWidthParseError<'a> {
355
            match self {
356
                Self::InvalidValue(s) => ColumnWidthParseError::InvalidValue(s),
357
                Self::PixelValue(e) => ColumnWidthParseError::PixelValue(e.to_shared()),
358
            }
359
        }
360
    }
361

            
362
4
    pub fn parse_column_width<'a>(
363
4
        input: &'a str,
364
4
    ) -> Result<ColumnWidth, ColumnWidthParseError<'a>> {
365
4
        let trimmed = input.trim();
366
4
        if trimmed == "auto" {
367
1
            return Ok(ColumnWidth::Auto);
368
3
        }
369
3
        Ok(ColumnWidth::Length(parse_pixel_value(trimmed)?))
370
4
    }
371

            
372
    // -- Other column parsers...
373
    macro_rules! define_simple_column_parser {
374
        (
375
            $fn_name:ident,
376
            $struct_name:ident,
377
            $error_name:ident,
378
            $error_owned_name:ident,
379
            $prop_name:expr,
380
            $($val:expr => $variant:path),+
381
        ) => {
382
            #[derive(Clone, PartialEq)]
383
            pub enum $error_name<'a> {
384
                InvalidValue(&'a str),
385
            }
386

            
387
            impl_debug_as_display!($error_name<'a>);
388
            impl_display! { $error_name<'a>, {
389
                InvalidValue(v) => format!("Invalid {} value: \"{}\"", $prop_name, v),
390
            }}
391

            
392
            #[derive(Debug, Clone, PartialEq)]
393
            #[repr(C, u8)]
394
            pub enum $error_owned_name {
395
                InvalidValue(AzString),
396
            }
397

            
398
            impl<'a> $error_name<'a> {
399
                pub fn to_contained(&self) -> $error_owned_name {
400
                    match self {
401
                        Self::InvalidValue(s) => $error_owned_name::InvalidValue(s.to_string().into()),
402
                    }
403
                }
404
            }
405

            
406
            impl $error_owned_name {
407
                pub fn to_shared<'a>(&'a self) -> $error_name<'a> {
408
                    match self {
409
                        Self::InvalidValue(s) => $error_name::InvalidValue(s.as_str()),
410
                    }
411
                }
412
            }
413

            
414
6
            pub fn $fn_name<'a>(input: &'a str) -> Result<$struct_name, $error_name<'a>> {
415
6
                match input.trim() {
416
4
                    $( $val => Ok($variant), )+
417
2
                    _ => Err($error_name::InvalidValue(input)),
418
                }
419
6
            }
420
        };
421
    }
422

            
423
    define_simple_column_parser!(
424
        parse_column_span,
425
        ColumnSpan,
426
        ColumnSpanParseError,
427
        ColumnSpanParseErrorOwned,
428
        "column-span",
429
        "none" => ColumnSpan::None,
430
        "all" => ColumnSpan::All
431
    );
432

            
433
    define_simple_column_parser!(
434
        parse_column_fill,
435
        ColumnFill,
436
        ColumnFillParseError,
437
        ColumnFillParseErrorOwned,
438
        "column-fill",
439
        "auto" => ColumnFill::Auto,
440
        "balance" => ColumnFill::Balance
441
    );
442

            
443
    // Parsers for column-rule-*
444

            
445
    #[derive(Clone, PartialEq)]
446
    pub enum ColumnRuleWidthParseError<'a> {
447
        Pixel(CssPixelValueParseError<'a>),
448
    }
449
    impl_debug_as_display!(ColumnRuleWidthParseError<'a>);
450
    impl_display! { ColumnRuleWidthParseError<'a>, { Pixel(e) => format!("{}", e) }}
451
    impl_from! { CssPixelValueParseError<'a>, ColumnRuleWidthParseError::Pixel }
452
    #[derive(Debug, Clone, PartialEq)]
453
    #[repr(C, u8)]
454
    pub enum ColumnRuleWidthParseErrorOwned {
455
        Pixel(CssPixelValueParseErrorOwned),
456
    }
457
    impl<'a> ColumnRuleWidthParseError<'a> {
458
        pub fn to_contained(&self) -> ColumnRuleWidthParseErrorOwned {
459
            match self {
460
                ColumnRuleWidthParseError::Pixel(e) => {
461
                    ColumnRuleWidthParseErrorOwned::Pixel(e.to_contained())
462
                }
463
            }
464
        }
465
    }
466
    impl ColumnRuleWidthParseErrorOwned {
467
        pub fn to_shared<'a>(&'a self) -> ColumnRuleWidthParseError<'a> {
468
            match self {
469
                ColumnRuleWidthParseErrorOwned::Pixel(e) => {
470
                    ColumnRuleWidthParseError::Pixel(e.to_shared())
471
                }
472
            }
473
        }
474
    }
475
1
    pub fn parse_column_rule_width<'a>(
476
1
        input: &'a str,
477
1
    ) -> Result<ColumnRuleWidth, ColumnRuleWidthParseError<'a>> {
478
        Ok(ColumnRuleWidth {
479
1
            inner: parse_pixel_value(input)?,
480
        })
481
1
    }
482

            
483
    #[derive(Clone, PartialEq)]
484
    pub enum ColumnRuleStyleParseError<'a> {
485
        Style(CssBorderStyleParseError<'a>),
486
    }
487
    impl_debug_as_display!(ColumnRuleStyleParseError<'a>);
488
    impl_display! { ColumnRuleStyleParseError<'a>, { Style(e) => format!("{}", e) }}
489
    impl_from! { CssBorderStyleParseError<'a>, ColumnRuleStyleParseError::Style }
490
    #[derive(Debug, Clone, PartialEq)]
491
    #[repr(C, u8)]
492
    pub enum ColumnRuleStyleParseErrorOwned {
493
        Style(CssBorderStyleParseErrorOwned),
494
    }
495
    impl<'a> ColumnRuleStyleParseError<'a> {
496
        pub fn to_contained(&self) -> ColumnRuleStyleParseErrorOwned {
497
            match self {
498
                ColumnRuleStyleParseError::Style(e) => {
499
                    ColumnRuleStyleParseErrorOwned::Style(e.to_contained())
500
                }
501
            }
502
        }
503
    }
504
    impl ColumnRuleStyleParseErrorOwned {
505
        pub fn to_shared<'a>(&'a self) -> ColumnRuleStyleParseError<'a> {
506
            match self {
507
                ColumnRuleStyleParseErrorOwned::Style(e) => {
508
                    ColumnRuleStyleParseError::Style(e.to_shared())
509
                }
510
            }
511
        }
512
    }
513
1
    pub fn parse_column_rule_style<'a>(
514
1
        input: &'a str,
515
1
    ) -> Result<ColumnRuleStyle, ColumnRuleStyleParseError<'a>> {
516
        Ok(ColumnRuleStyle {
517
1
            inner: parse_border_style(input)?,
518
        })
519
1
    }
520

            
521
    #[derive(Clone, PartialEq)]
522
    pub enum ColumnRuleColorParseError<'a> {
523
        Color(CssColorParseError<'a>),
524
    }
525
    impl_debug_as_display!(ColumnRuleColorParseError<'a>);
526
    impl_display! { ColumnRuleColorParseError<'a>, { Color(e) => format!("{}", e) }}
527
    impl_from! { CssColorParseError<'a>, ColumnRuleColorParseError::Color }
528
    #[derive(Debug, Clone, PartialEq)]
529
    #[repr(C, u8)]
530
    pub enum ColumnRuleColorParseErrorOwned {
531
        Color(CssColorParseErrorOwned),
532
    }
533
    impl<'a> ColumnRuleColorParseError<'a> {
534
        pub fn to_contained(&self) -> ColumnRuleColorParseErrorOwned {
535
            match self {
536
                ColumnRuleColorParseError::Color(e) => {
537
                    ColumnRuleColorParseErrorOwned::Color(e.to_contained())
538
                }
539
            }
540
        }
541
    }
542
    impl ColumnRuleColorParseErrorOwned {
543
        pub fn to_shared<'a>(&'a self) -> ColumnRuleColorParseError<'a> {
544
            match self {
545
                ColumnRuleColorParseErrorOwned::Color(e) => {
546
                    ColumnRuleColorParseError::Color(e.to_shared())
547
                }
548
            }
549
        }
550
    }
551
1
    pub fn parse_column_rule_color<'a>(
552
1
        input: &'a str,
553
1
    ) -> Result<ColumnRuleColor, ColumnRuleColorParseError<'a>> {
554
        Ok(ColumnRuleColor {
555
1
            inner: parse_css_color(input)?,
556
        })
557
1
    }
558
}
559

            
560
#[cfg(feature = "parser")]
561
pub use parser::*;
562

            
563
#[cfg(all(test, feature = "parser"))]
564
mod tests {
565
    use super::*;
566

            
567
    #[test]
568
1
    fn test_parse_column_count() {
569
1
        assert_eq!(parse_column_count("auto").unwrap(), ColumnCount::Auto);
570
1
        assert_eq!(parse_column_count("3").unwrap(), ColumnCount::Integer(3));
571
1
        assert!(parse_column_count("none").is_err());
572
1
        assert!(parse_column_count("2.5").is_err());
573
1
    }
574

            
575
    #[test]
576
1
    fn test_parse_column_width() {
577
1
        assert_eq!(parse_column_width("auto").unwrap(), ColumnWidth::Auto);
578
1
        assert_eq!(
579
1
            parse_column_width("200px").unwrap(),
580
1
            ColumnWidth::Length(PixelValue::px(200.0))
581
        );
582
1
        assert_eq!(
583
1
            parse_column_width("15em").unwrap(),
584
1
            ColumnWidth::Length(PixelValue::em(15.0))
585
        );
586
1
        assert!(parse_column_width("50%").is_ok()); // Percentage is valid for column-width
587
1
    }
588

            
589
    #[test]
590
1
    fn test_parse_column_span() {
591
1
        assert_eq!(parse_column_span("none").unwrap(), ColumnSpan::None);
592
1
        assert_eq!(parse_column_span("all").unwrap(), ColumnSpan::All);
593
1
        assert!(parse_column_span("2").is_err());
594
1
    }
595

            
596
    #[test]
597
1
    fn test_parse_column_fill() {
598
1
        assert_eq!(parse_column_fill("auto").unwrap(), ColumnFill::Auto);
599
1
        assert_eq!(parse_column_fill("balance").unwrap(), ColumnFill::Balance);
600
1
        assert!(parse_column_fill("none").is_err());
601
1
    }
602

            
603
    #[test]
604
1
    fn test_parse_column_rule() {
605
1
        assert_eq!(
606
1
            parse_column_rule_width("5px").unwrap().inner,
607
1
            PixelValue::px(5.0)
608
        );
609
1
        assert_eq!(
610
1
            parse_column_rule_style("dotted").unwrap().inner,
611
            BorderStyle::Dotted
612
        );
613
1
        assert_eq!(parse_column_rule_color("blue").unwrap().inner, ColorU::BLUE);
614
1
    }
615
}