Skip to main content

azul_css/props/style/
content.rs

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
7use alloc::string::{String, ToString};
8
9use 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)]
21pub struct Content {
22    pub inner: AzString,
23}
24
25impl Default for Content {
26    fn default() -> Self {
27        Self {
28            inner: "normal".into(),
29        }
30    }
31}
32
33impl 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)]
42pub struct CounterReset {
43    pub counter_name: AzString,
44    pub value: i32,
45}
46
47impl CounterReset {
48    pub const fn new(counter_name: AzString, value: i32) -> Self {
49        Self {
50            counter_name,
51            value,
52        }
53    }
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
70impl Default for CounterReset {
71    fn default() -> Self {
72        Self::none()
73    }
74}
75
76impl 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)]
89pub struct CounterIncrement {
90    pub counter_name: AzString,
91    pub value: i32,
92}
93
94impl CounterIncrement {
95    pub const fn new(counter_name: AzString, value: i32) -> Self {
96        Self {
97            counter_name,
98            value,
99        }
100    }
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
117impl Default for CounterIncrement {
118    fn default() -> Self {
119        Self::none()
120    }
121}
122
123impl 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)]
138pub struct StringSet {
139    pub inner: AzString,
140}
141
142impl Default for StringSet {
143    fn default() -> Self {
144        Self {
145            inner: "none".into(),
146        }
147    }
148}
149
150impl 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
157impl 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
163impl 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
173impl 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
183impl 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")]
192pub mod parser {
193    use super::*;
194
195    // Simplified parsers that just take the raw string value.
196    pub fn parse_content(input: &str) -> Result<Content, ()> {
197        Ok(Content {
198            inner: input.trim().into(),
199        })
200    }
201
202    fn parse_counter_name_value(input: &str, default_value: i32) -> Result<(AzString, i32), ()> {
203        let trimmed = input.trim();
204
205        if trimmed == "none" {
206            return Ok((AzString::from_const_str("none"), 0));
207        }
208
209        let parts: Vec<&str> = trimmed.split_whitespace().collect();
210
211        if parts.is_empty() {
212            return Err(());
213        }
214
215        let counter_name = parts[0].into();
216        let value = if parts.len() > 1 {
217            parts[1].parse::<i32>().map_err(|_| ())?
218        } else {
219            default_value
220        };
221
222        Ok((counter_name, value))
223    }
224
225    pub fn parse_counter_reset(input: &str) -> Result<CounterReset, ()> {
226        let (counter_name, value) = parse_counter_name_value(input, 0)?;
227        Ok(CounterReset::new(counter_name, value))
228    }
229
230    pub fn parse_counter_increment(input: &str) -> Result<CounterIncrement, ()> {
231        let (counter_name, value) = parse_counter_name_value(input, 1)?;
232        Ok(CounterIncrement::new(counter_name, value))
233    }
234
235    pub fn parse_string_set(input: &str) -> Result<StringSet, ()> {
236        Ok(StringSet {
237            inner: input.trim().into(),
238        })
239    }
240}
241
242#[cfg(feature = "parser")]
243pub use parser::*;
244
245#[cfg(all(test, feature = "parser"))]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn test_simple_content_parser() {
251        assert_eq!(parse_content("'Hello'").unwrap().inner.as_str(), "'Hello'");
252
253        // Test counter-reset parsing
254        let reset = parse_counter_reset("page 1").unwrap();
255        assert_eq!(reset.counter_name.as_str(), "page");
256        assert_eq!(reset.value, 1);
257
258        let reset = parse_counter_reset("list-item 0").unwrap();
259        assert_eq!(reset.counter_name.as_str(), "list-item");
260        assert_eq!(reset.value, 0);
261
262        let reset = parse_counter_reset("none").unwrap();
263        assert_eq!(reset.counter_name.as_str(), "none");
264
265        // Test counter-increment parsing
266        let inc = parse_counter_increment("section").unwrap();
267        assert_eq!(inc.counter_name.as_str(), "section");
268        assert_eq!(inc.value, 1); // Default value
269
270        let inc = parse_counter_increment("list-item 2").unwrap();
271        assert_eq!(inc.counter_name.as_str(), "list-item");
272        assert_eq!(inc.value, 2);
273
274        assert_eq!(
275            parse_string_set("chapter-title content()")
276                .unwrap()
277                .inner
278                .as_str(),
279            "chapter-title content()"
280        );
281    }
282}