Skip to main content

azul_layout/solver3/
counters.rs

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
6use alloc::string::String;
7
8use 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]
14pub fn format_counter(value: i32, style: StyleListStyleType) -> String {
15    match style {
16        StyleListStyleType::None => String::new(),
17        StyleListStyleType::Disc => "•".to_string(),
18        StyleListStyleType::Circle => "◦".to_string(),
19        StyleListStyleType::Square => "▪".to_string(),
20        StyleListStyleType::Decimal => value.to_string(),
21        StyleListStyleType::DecimalLeadingZero => format!("{:02}", value),
22        StyleListStyleType::LowerAlpha => to_alphabetic(value as u32, false),
23        StyleListStyleType::UpperAlpha => to_alphabetic(value as u32, true),
24        StyleListStyleType::LowerRoman => to_roman(value as u32, false),
25        StyleListStyleType::UpperRoman => to_roman(value as u32, true),
26        StyleListStyleType::LowerGreek => to_greek(value as u32, false),
27        StyleListStyleType::UpperGreek => to_greek(value as u32, true),
28    }
29}
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.
36fn to_alphabetic(mut num: u32, uppercase: bool) -> String {
37    if num == 0 {
38        return String::new();
39    }
40
41    let mut result = String::new();
42    let base = if uppercase { b'A' } else { b'a' };
43
44    while num > 0 {
45        let remainder = ((num - 1) % 26) as u8;
46        result.insert(0, (base + remainder) as char);
47        num = (num - 1) / 26;
48    }
49
50    result
51}
52
53/// Converts a number to Roman numeral representation.
54///
55/// This implements the CSS `lower-roman` and `upper-roman` counter styles.
56fn to_roman(mut num: u32, uppercase: bool) -> String {
57    if num == 0 {
58        return "0".to_string();
59    }
60    const MAX_ROMAN: u32 = 3999;
61    if num > MAX_ROMAN {
62        // Roman numerals traditionally don't go beyond 3999
63        return num.to_string();
64    }
65
66    let values = [
67        (1000, "M", "m"),
68        (900, "CM", "cm"),
69        (500, "D", "d"),
70        (400, "CD", "cd"),
71        (100, "C", "c"),
72        (90, "XC", "xc"),
73        (50, "L", "l"),
74        (40, "XL", "xl"),
75        (10, "X", "x"),
76        (9, "IX", "ix"),
77        (5, "V", "v"),
78        (4, "IV", "iv"),
79        (1, "I", "i"),
80    ];
81
82    let mut result = String::new();
83    for (value, upper, lower) in &values {
84        while num >= *value {
85            result.push_str(if uppercase { upper } else { lower });
86            num -= *value;
87        }
88    }
89
90    result
91}
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).
97fn to_greek(num: u32, uppercase: bool) -> String {
98    if num == 0 {
99        return String::new();
100    }
101
102    // Greek lowercase letters α-ω (24 letters, omitting archaic letters)
103    let greek_lower = [
104        'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', 'ρ', 'σ',
105        'τ', 'υ', 'φ', 'χ', 'ψ', 'ω',
106    ];
107
108    let greek_upper = [
109        'Α', 'Β', 'Γ', 'Δ', 'Ε', 'Ζ', 'Η', 'Θ', 'Ι', 'Κ', 'Λ', 'Μ', 'Ν', 'Ξ', 'Ο', 'Π', 'Ρ', 'Σ',
110        'Τ', 'Υ', 'Φ', 'Χ', 'Ψ', 'Ω',
111    ];
112
113    let letters = if uppercase {
114        &greek_upper
115    } else {
116        &greek_lower
117    };
118
119    if num <= 24 {
120        letters[(num - 1) as usize].to_string()
121    } else {
122        // For numbers > 24, fall back to decimal
123        num.to_string()
124    }
125}