Skip to main content

azul_css/props/style/
content.rs

1//! CSS properties for generated content.
2
3use alloc::string::{String, ToString};
4
5use crate::{corety::AzString, props::formatter::PrintAsCssValue};
6
7// A full implementation would have an enum for ContentPart with variants for
8// strings, counters, attributes, etc., and Content would be a Vec<ContentPart>.
9// For now, we'll just store the raw string value.
10#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11#[repr(C)]
12pub struct Content {
13    pub inner: AzString,
14}
15
16impl Default for Content {
17    fn default() -> Self {
18        Self {
19            inner: "normal".into(),
20        }
21    }
22}
23
24impl PrintAsCssValue for Content {
25    fn print_as_css_value(&self) -> String {
26        self.inner.as_str().to_string()
27    }
28}
29
30// Counter reset property - properly typed
31#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
32#[repr(C)]
33pub struct CounterReset {
34    pub counter_name: AzString,
35    pub value: i32,
36}
37
38impl CounterReset {
39    pub const fn new(counter_name: AzString, value: i32) -> Self {
40        Self {
41            counter_name,
42            value,
43        }
44    }
45
46    pub const fn none() -> Self {
47        Self {
48            counter_name: AzString::from_const_str("none"),
49            value: 0,
50        }
51    }
52
53    pub const fn list_item() -> Self {
54        Self {
55            counter_name: AzString::from_const_str("list-item"),
56            value: 0,
57        }
58    }
59}
60
61impl Default for CounterReset {
62    fn default() -> Self {
63        Self::none()
64    }
65}
66
67impl PrintAsCssValue for CounterReset {
68    fn print_as_css_value(&self) -> String {
69        if self.counter_name.as_str() == "none" {
70            "none".to_string()
71        } else {
72            alloc::format!("{} {}", self.counter_name.as_str(), self.value)
73        }
74    }
75}
76
77// Counter increment property - properly typed
78#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
79#[repr(C)]
80pub struct CounterIncrement {
81    pub counter_name: AzString,
82    pub value: i32,
83}
84
85impl CounterIncrement {
86    pub const fn new(counter_name: AzString, value: i32) -> Self {
87        Self {
88            counter_name,
89            value,
90        }
91    }
92
93    pub const fn none() -> Self {
94        Self {
95            counter_name: AzString::from_const_str("none"),
96            value: 0,
97        }
98    }
99
100    pub const fn list_item() -> Self {
101        Self {
102            counter_name: AzString::from_const_str("list-item"),
103            value: 1,
104        }
105    }
106}
107
108impl Default for CounterIncrement {
109    fn default() -> Self {
110        Self::none()
111    }
112}
113
114impl PrintAsCssValue for CounterIncrement {
115    fn print_as_css_value(&self) -> String {
116        if self.counter_name.as_str() == "none" {
117            "none".to_string()
118        } else {
119            alloc::format!("{} {}", self.counter_name.as_str(), self.value)
120        }
121    }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
125#[repr(C)]
126pub struct StringSet {
127    pub inner: AzString,
128}
129impl Default for StringSet {
130    fn default() -> Self {
131        Self {
132            inner: "none".into(),
133        }
134    }
135}
136impl PrintAsCssValue for StringSet {
137    fn print_as_css_value(&self) -> String {
138        self.inner.as_str().to_string()
139    }
140}
141
142// Formatting to Rust code
143impl crate::format_rust_code::FormatAsRustCode for Content {
144    fn format_as_rust_code(&self, _tabs: usize) -> String {
145        format!("Content {{ inner: String::from({:?}) }}", self.inner)
146    }
147}
148
149impl crate::format_rust_code::FormatAsRustCode for CounterReset {
150    fn format_as_rust_code(&self, _tabs: usize) -> String {
151        alloc::format!(
152            "CounterReset {{ counter_name: AzString::from_const_str({:?}), value: {} }}",
153            self.counter_name.as_str(),
154            self.value
155        )
156    }
157}
158
159impl crate::format_rust_code::FormatAsRustCode for CounterIncrement {
160    fn format_as_rust_code(&self, _tabs: usize) -> String {
161        alloc::format!(
162            "CounterIncrement {{ counter_name: AzString::from_const_str({:?}), value: {} }}",
163            self.counter_name.as_str(),
164            self.value
165        )
166    }
167}
168
169impl crate::format_rust_code::FormatAsRustCode for StringSet {
170    fn format_as_rust_code(&self, _tabs: usize) -> String {
171        format!("StringSet {{ inner: String::from({:?}) }}", self.inner)
172    }
173}
174
175// --- PARSERS ---
176
177#[cfg(feature = "parser")]
178mod parser {
179    use super::*;
180
181    // Simplified parsers that just take the raw string value.
182    pub fn parse_content(input: &str) -> Result<Content, ()> {
183        Ok(Content {
184            inner: input.trim().into(),
185        })
186    }
187
188    pub fn parse_counter_reset(input: &str) -> Result<CounterReset, ()> {
189        let trimmed = input.trim();
190
191        if trimmed == "none" {
192            return Ok(CounterReset::none());
193        }
194
195        // Parse "counter-name value" format
196        // e.g., "list-item 0", "section 1", or just "list-item" (defaults to 0)
197        let parts: Vec<&str> = trimmed.split_whitespace().collect();
198
199        if parts.is_empty() {
200            return Err(());
201        }
202
203        let counter_name = parts[0].into();
204        let value = if parts.len() > 1 {
205            parts[1].parse::<i32>().map_err(|_| ())?
206        } else {
207            0 // CSS spec: default reset value is 0
208        };
209
210        Ok(CounterReset::new(counter_name, value))
211    }
212
213    pub fn parse_counter_increment(input: &str) -> Result<CounterIncrement, ()> {
214        let trimmed = input.trim();
215
216        if trimmed == "none" {
217            return Ok(CounterIncrement::none());
218        }
219
220        // Parse "counter-name value" format
221        // e.g., "list-item 1", "section 2", or just "list-item" (defaults to 1)
222        let parts: Vec<&str> = trimmed.split_whitespace().collect();
223
224        if parts.is_empty() {
225            return Err(());
226        }
227
228        let counter_name = parts[0].into();
229        let value = if parts.len() > 1 {
230            parts[1].parse::<i32>().map_err(|_| ())?
231        } else {
232            1 // CSS spec: default increment value is 1
233        };
234
235        Ok(CounterIncrement::new(counter_name, value))
236    }
237
238    pub fn parse_string_set(input: &str) -> Result<StringSet, ()> {
239        Ok(StringSet {
240            inner: input.trim().into(),
241        })
242    }
243}
244
245#[cfg(feature = "parser")]
246pub use parser::*;
247
248#[cfg(all(test, feature = "parser"))]
249mod tests {
250    use super::*;
251
252    #[test]
253    fn test_simple_content_parser() {
254        assert_eq!(parse_content("'Hello'").unwrap().inner.as_str(), "'Hello'");
255
256        // Test counter-reset parsing
257        let reset = parse_counter_reset("page 1").unwrap();
258        assert_eq!(reset.counter_name.as_str(), "page");
259        assert_eq!(reset.value, 1);
260
261        let reset = parse_counter_reset("list-item 0").unwrap();
262        assert_eq!(reset.counter_name.as_str(), "list-item");
263        assert_eq!(reset.value, 0);
264
265        let reset = parse_counter_reset("none").unwrap();
266        assert_eq!(reset.counter_name.as_str(), "none");
267
268        // Test counter-increment parsing
269        let inc = parse_counter_increment("section").unwrap();
270        assert_eq!(inc.counter_name.as_str(), "section");
271        assert_eq!(inc.value, 1); // Default value
272
273        let inc = parse_counter_increment("list-item 2").unwrap();
274        assert_eq!(inc.counter_name.as_str(), "list-item");
275        assert_eq!(inc.value, 2);
276
277        assert_eq!(
278            parse_string_set("chapter-title content()")
279                .unwrap()
280                .inner
281                .as_str(),
282            "chapter-title content()"
283        );
284    }
285}