1
//! CSS properties for generated content (`content`, `counter-reset`,
2
//! `counter-increment`, `string-set`).
3
//!
4
//! Defines [`Content`], [`CounterReset`], [`CounterIncrement`], and
5
//! [`StringSet`], which are registered as [`CssProperty`] variants.
6

            
7
use alloc::string::{String, ToString};
8

            
9
use crate::{corety::AzString, props::formatter::PrintAsCssValue};
10

            
11
/// CSS `content` property value, stored as a raw string.
12
///
13
/// Intentionally simplified: stores the unparsed CSS value rather than
14
/// a structured `ContentPart` enum. Complex values like `counter(section) ". "`
15
/// are preserved verbatim but not individually evaluated.
16
///
17
/// **Note:** Currently parsed and stored but not yet consumed by the layout
18
/// engine (e.g., for `::before`/`::after` pseudo-element generated content).
19
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20
#[repr(C)]
21
pub struct Content {
22
    pub inner: AzString,
23
}
24

            
25
impl Default for Content {
26
    fn default() -> Self {
27
        Self {
28
            inner: "normal".into(),
29
        }
30
    }
31
}
32

            
33
impl PrintAsCssValue for Content {
34
    fn print_as_css_value(&self) -> String {
35
        self.inner.as_str().to_string()
36
    }
37
}
38

            
39
/// CSS `counter-reset` property: resets a named counter to a given value.
40
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
41
#[repr(C)]
42
pub struct CounterReset {
43
    pub counter_name: AzString,
44
    pub value: i32,
45
}
46

            
47
impl CounterReset {
48
3
    pub const fn new(counter_name: AzString, value: i32) -> Self {
49
3
        Self {
50
3
            counter_name,
51
3
            value,
52
3
        }
53
3
    }
54

            
55
    pub const fn none() -> Self {
56
        Self {
57
            counter_name: AzString::from_const_str("none"),
58
            value: 0,
59
        }
60
    }
61

            
62
    pub const fn list_item() -> Self {
63
        Self {
64
            counter_name: AzString::from_const_str("list-item"),
65
            value: 0,
66
        }
67
    }
68
}
69

            
70
impl Default for CounterReset {
71
    fn default() -> Self {
72
        Self::none()
73
    }
74
}
75

            
76
impl PrintAsCssValue for CounterReset {
77
    fn print_as_css_value(&self) -> String {
78
        if self.counter_name.as_str() == "none" {
79
            "none".to_string()
80
        } else {
81
            alloc::format!("{} {}", self.counter_name.as_str(), self.value)
82
        }
83
    }
84
}
85

            
86
/// CSS `counter-increment` property: increments a named counter by a given value.
87
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
88
#[repr(C)]
89
pub struct CounterIncrement {
90
    pub counter_name: AzString,
91
    pub value: i32,
92
}
93

            
94
impl CounterIncrement {
95
2
    pub const fn new(counter_name: AzString, value: i32) -> Self {
96
2
        Self {
97
2
            counter_name,
98
2
            value,
99
2
        }
100
2
    }
101

            
102
    pub const fn none() -> Self {
103
        Self {
104
            counter_name: AzString::from_const_str("none"),
105
            value: 0,
106
        }
107
    }
108

            
109
    pub const fn list_item() -> Self {
110
        Self {
111
            counter_name: AzString::from_const_str("list-item"),
112
            value: 1,
113
        }
114
    }
115
}
116

            
117
impl Default for CounterIncrement {
118
    fn default() -> Self {
119
        Self::none()
120
    }
121
}
122

            
123
impl PrintAsCssValue for CounterIncrement {
124
    fn print_as_css_value(&self) -> String {
125
        if self.counter_name.as_str() == "none" {
126
            "none".to_string()
127
        } else {
128
            alloc::format!("{} {}", self.counter_name.as_str(), self.value)
129
        }
130
    }
131
}
132

            
133
/// CSS `string-set` property value, stored as a raw string.
134
///
135
/// **Note:** Currently parsed and stored but not yet consumed by the layout engine.
136
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
137
#[repr(C)]
138
pub struct StringSet {
139
    pub inner: AzString,
140
}
141

            
142
impl Default for StringSet {
143
    fn default() -> Self {
144
        Self {
145
            inner: "none".into(),
146
        }
147
    }
148
}
149

            
150
impl PrintAsCssValue for StringSet {
151
    fn print_as_css_value(&self) -> String {
152
        self.inner.as_str().to_string()
153
    }
154
}
155

            
156
// Formatting to Rust code
157
impl crate::format_rust_code::FormatAsRustCode for Content {
158
    fn format_as_rust_code(&self, _tabs: usize) -> String {
159
        format!("Content {{ inner: String::from({:?}) }}", self.inner)
160
    }
161
}
162

            
163
impl crate::format_rust_code::FormatAsRustCode for CounterReset {
164
    fn format_as_rust_code(&self, _tabs: usize) -> String {
165
        alloc::format!(
166
            "CounterReset {{ counter_name: AzString::from_const_str({:?}), value: {} }}",
167
            self.counter_name.as_str(),
168
            self.value
169
        )
170
    }
171
}
172

            
173
impl crate::format_rust_code::FormatAsRustCode for CounterIncrement {
174
    fn format_as_rust_code(&self, _tabs: usize) -> String {
175
        alloc::format!(
176
            "CounterIncrement {{ counter_name: AzString::from_const_str({:?}), value: {} }}",
177
            self.counter_name.as_str(),
178
            self.value
179
        )
180
    }
181
}
182

            
183
impl crate::format_rust_code::FormatAsRustCode for StringSet {
184
    fn format_as_rust_code(&self, _tabs: usize) -> String {
185
        format!("StringSet {{ inner: String::from({:?}) }}", self.inner)
186
    }
187
}
188

            
189
// --- PARSERS ---
190

            
191
#[cfg(feature = "parser")]
192
pub mod parser {
193
    use super::*;
194

            
195
    // Simplified parsers that just take the raw string value.
196
29
    pub fn parse_content(input: &str) -> Result<Content, ()> {
197
29
        Ok(Content {
198
29
            inner: input.trim().into(),
199
29
        })
200
29
    }
201

            
202
5
    fn parse_counter_name_value(input: &str, default_value: i32) -> Result<(AzString, i32), ()> {
203
5
        let trimmed = input.trim();
204

            
205
5
        if trimmed == "none" {
206
1
            return Ok((AzString::from_const_str("none"), 0));
207
4
        }
208

            
209
4
        let parts: Vec<&str> = trimmed.split_whitespace().collect();
210

            
211
4
        if parts.is_empty() {
212
            return Err(());
213
4
        }
214

            
215
4
        let counter_name = parts[0].into();
216
4
        let value = if parts.len() > 1 {
217
3
            parts[1].parse::<i32>().map_err(|_| ())?
218
        } else {
219
1
            default_value
220
        };
221

            
222
4
        Ok((counter_name, value))
223
5
    }
224

            
225
3
    pub fn parse_counter_reset(input: &str) -> Result<CounterReset, ()> {
226
3
        let (counter_name, value) = parse_counter_name_value(input, 0)?;
227
3
        Ok(CounterReset::new(counter_name, value))
228
3
    }
229

            
230
2
    pub fn parse_counter_increment(input: &str) -> Result<CounterIncrement, ()> {
231
2
        let (counter_name, value) = parse_counter_name_value(input, 1)?;
232
2
        Ok(CounterIncrement::new(counter_name, value))
233
2
    }
234

            
235
1
    pub fn parse_string_set(input: &str) -> Result<StringSet, ()> {
236
1
        Ok(StringSet {
237
1
            inner: input.trim().into(),
238
1
        })
239
1
    }
240
}
241

            
242
#[cfg(feature = "parser")]
243
pub use parser::*;
244

            
245
#[cfg(all(test, feature = "parser"))]
246
mod tests {
247
    use super::*;
248

            
249
    #[test]
250
1
    fn test_simple_content_parser() {
251
1
        assert_eq!(parse_content("'Hello'").unwrap().inner.as_str(), "'Hello'");
252

            
253
        // Test counter-reset parsing
254
1
        let reset = parse_counter_reset("page 1").unwrap();
255
1
        assert_eq!(reset.counter_name.as_str(), "page");
256
1
        assert_eq!(reset.value, 1);
257

            
258
1
        let reset = parse_counter_reset("list-item 0").unwrap();
259
1
        assert_eq!(reset.counter_name.as_str(), "list-item");
260
1
        assert_eq!(reset.value, 0);
261

            
262
1
        let reset = parse_counter_reset("none").unwrap();
263
1
        assert_eq!(reset.counter_name.as_str(), "none");
264

            
265
        // Test counter-increment parsing
266
1
        let inc = parse_counter_increment("section").unwrap();
267
1
        assert_eq!(inc.counter_name.as_str(), "section");
268
1
        assert_eq!(inc.value, 1); // Default value
269

            
270
1
        let inc = parse_counter_increment("list-item 2").unwrap();
271
1
        assert_eq!(inc.counter_name.as_str(), "list-item");
272
1
        assert_eq!(inc.value, 2);
273

            
274
1
        assert_eq!(
275
1
            parse_string_set("chapter-title content()")
276
1
                .unwrap()
277
1
                .inner
278
1
                .as_str(),
279
            "chapter-title content()"
280
        );
281
1
    }
282
}