dampen_core/parser/
style_parser.rs

1//! Style attribute parsing
2//!
3//! This module provides parsers for individual style attributes.
4
5use crate::ir::layout::{Alignment, Justification, Length, Padding};
6use crate::ir::style::{
7    Background, Border, BorderRadius, BorderStyle, Color, Shadow, StyleProperties, Transform,
8};
9use crate::parser::gradient::parse_gradient;
10
11/// Parse color attribute
12pub fn parse_color_attr(s: &str) -> Result<Color, String> {
13    Color::parse(s)
14}
15
16/// Parse length attribute (width, height, etc.)
17pub fn parse_length_attr(s: &str) -> Result<Length, String> {
18    Length::parse(s)
19}
20
21/// Parse padding attribute
22pub fn parse_padding_attr(s: &str) -> Result<Padding, String> {
23    Padding::parse(s)
24}
25
26/// Parse shadow attribute
27pub fn parse_shadow_attr(s: &str) -> Result<Shadow, String> {
28    Shadow::parse(s)
29}
30
31/// Parse background attribute (color, gradient, or image)
32pub fn parse_background_attr(s: &str) -> Result<Background, String> {
33    let s = s.trim();
34
35    // Check for gradient
36    if s.starts_with("linear-gradient(") || s.starts_with("radial-gradient(") {
37        let gradient = parse_gradient(s)?;
38        return Ok(Background::Gradient(gradient));
39    }
40
41    // Check for image
42    if s.starts_with("url(") && s.ends_with(')') {
43        let path = &s[4..s.len() - 1];
44        return Ok(Background::Image {
45            path: path.to_string(),
46            fit: crate::ir::style::ImageFit::Cover,
47        });
48    }
49
50    // Otherwise, parse as color
51    let color = Color::parse(s)?;
52    Ok(Background::Color(color))
53}
54
55/// Parse border width
56pub fn parse_border_width(s: &str) -> Result<f32, String> {
57    s.parse()
58        .map_err(|_| format!("Invalid border width: {}", s))
59}
60
61/// Parse border color
62pub fn parse_border_color(s: &str) -> Result<Color, String> {
63    Color::parse(s)
64}
65
66/// Parse border radius
67pub fn parse_border_radius(s: &str) -> Result<BorderRadius, String> {
68    BorderRadius::parse(s)
69}
70
71/// Parse border style
72pub fn parse_border_style(s: &str) -> Result<BorderStyle, String> {
73    BorderStyle::parse(s)
74}
75
76/// Parse opacity
77pub fn parse_opacity(s: &str) -> Result<f32, String> {
78    let value: f32 = s.parse().map_err(|_| format!("Invalid opacity: {}", s))?;
79    if !(0.0..=1.0).contains(&value) {
80        return Err(format!("Opacity must be 0.0-1.0, got {}", value));
81    }
82    Ok(value)
83}
84
85/// Parse transform
86pub fn parse_transform(s: &str) -> Result<Transform, String> {
87    Transform::parse(s)
88}
89
90/// Parse alignment
91pub fn parse_alignment(s: &str) -> Result<Alignment, String> {
92    Alignment::parse(s)
93}
94
95/// Parse justification
96pub fn parse_justification(s: &str) -> Result<Justification, String> {
97    Justification::parse(s)
98}
99
100/// Parse spacing (must be non-negative)
101pub fn parse_spacing(s: &str) -> Result<f32, String> {
102    let value: f32 = s.parse().map_err(|_| format!("Invalid spacing: {}", s))?;
103    if value < 0.0 {
104        return Err(format!("Spacing must be non-negative, got {}", value));
105    }
106    Ok(value)
107}
108
109/// Parse min/max constraints
110pub fn parse_constraint(s: &str) -> Result<f32, String> {
111    let value: f32 = s
112        .parse()
113        .map_err(|_| format!("Invalid constraint value: {}", s))?;
114    if value < 0.0 {
115        return Err(format!("Constraint must be non-negative, got {}", value));
116    }
117    Ok(value)
118}
119
120/// Parse fill_portion value
121pub fn parse_fill_portion(s: &str) -> Result<u8, String> {
122    let value: u8 = s
123        .parse()
124        .map_err(|_| format!("Invalid fill_portion: {}", s))?;
125    if value == 0 {
126        return Err(format!("fill_portion must be 1-255, got {}", value));
127    }
128    Ok(value)
129}
130
131/// Parse percentage value
132pub fn parse_percentage(s: &str) -> Result<f32, String> {
133    if !s.ends_with('%') {
134        return Err(format!("Percentage must end with '%', got {}", s));
135    }
136    let num = &s[..s.len() - 1];
137    let value: f32 = num
138        .parse()
139        .map_err(|_| format!("Invalid percentage: {}", s))?;
140    if !(0.0..=100.0).contains(&value) {
141        return Err(format!("Percentage must be 0.0-100.0, got {}", value));
142    }
143    Ok(value)
144}
145
146/// Parse float attribute value
147pub fn parse_float_attr(s: &str, attr_name: &str) -> Result<f32, String> {
148    s.parse()
149        .map_err(|_| format!("Invalid float value for {}: '{}'", attr_name, s))
150}
151
152/// Parse int attribute value
153pub fn parse_int_attr(s: &str, attr_name: &str) -> Result<i32, String> {
154    s.parse()
155        .map_err(|_| format!("Invalid int value for {}: '{}'", attr_name, s))
156}
157
158/// Build StyleProperties from individual attribute values
159pub fn build_style_properties(
160    background: Option<Background>,
161    color: Option<Color>,
162    border: Option<Border>,
163    shadow: Option<Shadow>,
164    opacity: Option<f32>,
165    transform: Option<Transform>,
166) -> Result<StyleProperties, String> {
167    let style = StyleProperties {
168        background,
169        color,
170        border,
171        shadow,
172        opacity,
173        transform,
174    };
175
176    style.validate()?;
177    Ok(style)
178}
179
180/// Build Border from individual attribute values
181pub fn build_border(
182    width: Option<f32>,
183    color: Option<Color>,
184    radius: Option<BorderRadius>,
185    style: Option<BorderStyle>,
186) -> Result<Option<Border>, String> {
187    if width.is_none() && color.is_none() && radius.is_none() && style.is_none() {
188        return Ok(None);
189    }
190
191    let border = Border {
192        width: width.unwrap_or(0.0),
193        color: color.unwrap_or(Color {
194            r: 0.0,
195            g: 0.0,
196            b: 0.0,
197            a: 1.0,
198        }),
199        radius: radius.unwrap_or(BorderRadius {
200            top_left: 0.0,
201            top_right: 0.0,
202            bottom_right: 0.0,
203            bottom_left: 0.0,
204        }),
205        style: style.unwrap_or(BorderStyle::Solid),
206    };
207
208    border.validate()?;
209    Ok(Some(border))
210}