1
//! CSS Counter Support
2
//!
3
//! Implements CSS counters for ordered lists and generated content as per CSS spec.
4
//! Counters are cached per-node in the LayoutCache and computed during layout traversal.
5

            
6
use alloc::string::String;
7

            
8
use azul_css::props::style::lists::StyleListStyleType;
9

            
10
/// Formats a counter value into a string based on the list style type.
11
///
12
/// Implements CSS counter styles for various numbering systems.
13
#[must_use]
14
4884
pub fn format_counter(value: i32, style: StyleListStyleType) -> String {
15
4884
    match style {
16
220
        StyleListStyleType::None => String::new(),
17
220
        StyleListStyleType::Disc => "•".to_string(),
18
132
        StyleListStyleType::Circle => "◦".to_string(),
19
132
        StyleListStyleType::Square => "▪".to_string(),
20
484
        StyleListStyleType::Decimal => value.to_string(),
21
396
        StyleListStyleType::DecimalLeadingZero => format!("{:02}", value),
22
616
        StyleListStyleType::LowerAlpha => to_alphabetic(value as u32, false),
23
352
        StyleListStyleType::UpperAlpha => to_alphabetic(value as u32, true),
24
1540
        StyleListStyleType::LowerRoman => to_roman(value as u32, false),
25
396
        StyleListStyleType::UpperRoman => to_roman(value as u32, true),
26
264
        StyleListStyleType::LowerGreek => to_greek(value as u32, false),
27
132
        StyleListStyleType::UpperGreek => to_greek(value as u32, true),
28
    }
29
4884
}
30

            
31
// --- Formatting Helpers ---
32

            
33
/// Converts a number to alphabetic representation (a, b, c, ..., z, aa, ab, ...).
34
///
35
/// This implements the CSS `lower-alpha` and `upper-alpha` counter styles.
36
968
fn to_alphabetic(mut num: u32, uppercase: bool) -> String {
37
968
    if num == 0 {
38
44
        return String::new();
39
924
    }
40

            
41
924
    let mut result = String::new();
42
924
    let base = if uppercase { b'A' } else { b'a' };
43

            
44
2200
    while num > 0 {
45
1276
        let remainder = ((num - 1) % 26) as u8;
46
1276
        result.insert(0, (base + remainder) as char);
47
1276
        num = (num - 1) / 26;
48
1276
    }
49

            
50
924
    result
51
968
}
52

            
53
/// Converts a number to Roman numeral representation.
54
///
55
/// This implements the CSS `lower-roman` and `upper-roman` counter styles.
56
1936
fn to_roman(mut num: u32, uppercase: bool) -> String {
57
1936
    if num == 0 {
58
44
        return "0".to_string();
59
1892
    }
60
    const MAX_ROMAN: u32 = 3999;
61
1892
    if num > MAX_ROMAN {
62
        // Roman numerals traditionally don't go beyond 3999
63
88
        return num.to_string();
64
1804
    }
65

            
66
1804
    let values = [
67
1804
        (1000, "M", "m"),
68
1804
        (900, "CM", "cm"),
69
1804
        (500, "D", "d"),
70
1804
        (400, "CD", "cd"),
71
1804
        (100, "C", "c"),
72
1804
        (90, "XC", "xc"),
73
1804
        (50, "L", "l"),
74
1804
        (40, "XL", "xl"),
75
1804
        (10, "X", "x"),
76
1804
        (9, "IX", "ix"),
77
1804
        (5, "V", "v"),
78
1804
        (4, "IV", "iv"),
79
1804
        (1, "I", "i"),
80
1804
    ];
81

            
82
1804
    let mut result = String::new();
83
25256
    for (value, upper, lower) in &values {
84
26620
        while num >= *value {
85
3168
            result.push_str(if uppercase { upper } else { lower });
86
3168
            num -= *value;
87
        }
88
    }
89

            
90
1804
    result
91
1936
}
92

            
93
/// Converts a number to Greek letter representation.
94
///
95
/// This implements the CSS `lower-greek` and `upper-greek` counter styles.
96
/// Supports α, β, γ, ... (24 letters of Greek alphabet).
97
396
fn to_greek(num: u32, uppercase: bool) -> String {
98
396
    if num == 0 {
99
44
        return String::new();
100
352
    }
101

            
102
    // Greek lowercase letters α-ω (24 letters, omitting archaic letters)
103
352
    let greek_lower = [
104
352
        'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', 'ρ', 'σ',
105
352
        'τ', 'υ', 'φ', 'χ', 'ψ', 'ω',
106
352
    ];
107

            
108
352
    let greek_upper = [
109
352
        'Α', 'Β', 'Γ', 'Δ', 'Ε', 'Ζ', 'Η', 'Θ', 'Ι', 'Κ', 'Λ', 'Μ', 'Ν', 'Ξ', 'Ο', 'Π', 'Ρ', 'Σ',
110
352
        'Τ', 'Υ', 'Φ', 'Χ', 'Ψ', 'Ω',
111
352
    ];
112

            
113
352
    let letters = if uppercase {
114
132
        &greek_upper
115
    } else {
116
220
        &greek_lower
117
    };
118

            
119
352
    if num <= 24 {
120
352
        letters[(num - 1) as usize].to_string()
121
    } else {
122
        // For numbers > 24, fall back to decimal
123
        num.to_string()
124
    }
125
396
}