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
3885
pub fn format_counter(value: i32, style: StyleListStyleType) -> String {
15
3885
    match style {
16
175
        StyleListStyleType::None => String::new(),
17
175
        StyleListStyleType::Disc => "•".to_string(),
18
105
        StyleListStyleType::Circle => "◦".to_string(),
19
105
        StyleListStyleType::Square => "▪".to_string(),
20
385
        StyleListStyleType::Decimal => value.to_string(),
21
315
        StyleListStyleType::DecimalLeadingZero => format!("{:02}", value),
22
490
        StyleListStyleType::LowerAlpha => to_alphabetic(value as u32, false),
23
280
        StyleListStyleType::UpperAlpha => to_alphabetic(value as u32, true),
24
1225
        StyleListStyleType::LowerRoman => to_roman(value as u32, false),
25
315
        StyleListStyleType::UpperRoman => to_roman(value as u32, true),
26
210
        StyleListStyleType::LowerGreek => to_greek(value as u32, false),
27
105
        StyleListStyleType::UpperGreek => to_greek(value as u32, true),
28
    }
29
3885
}
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
770
fn to_alphabetic(mut num: u32, uppercase: bool) -> String {
37
770
    if num == 0 {
38
35
        return String::new();
39
735
    }
40

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

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

            
50
735
    result
51
770
}
52

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

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

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

            
90
1435
    result
91
1540
}
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
315
fn to_greek(num: u32, uppercase: bool) -> String {
98
315
    if num == 0 {
99
35
        return String::new();
100
280
    }
101

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

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

            
113
280
    let letters = if uppercase {
114
105
        &greek_upper
115
    } else {
116
175
        &greek_lower
117
    };
118

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