1use 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
11pub fn parse_color_attr(s: &str) -> Result<Color, String> {
13 Color::parse(s)
14}
15
16pub fn parse_length_attr(s: &str) -> Result<Length, String> {
18 Length::parse(s)
19}
20
21pub fn parse_padding_attr(s: &str) -> Result<Padding, String> {
23 Padding::parse(s)
24}
25
26pub fn parse_shadow_attr(s: &str) -> Result<Shadow, String> {
28 Shadow::parse(s)
29}
30
31pub fn parse_background_attr(s: &str) -> Result<Background, String> {
33 let s = s.trim();
34
35 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 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 let color = Color::parse(s)?;
52 Ok(Background::Color(color))
53}
54
55pub fn parse_border_width(s: &str) -> Result<f32, String> {
57 s.parse()
58 .map_err(|_| format!("Invalid border width: {}", s))
59}
60
61pub fn parse_border_color(s: &str) -> Result<Color, String> {
63 Color::parse(s)
64}
65
66pub fn parse_border_radius(s: &str) -> Result<BorderRadius, String> {
68 BorderRadius::parse(s)
69}
70
71pub fn parse_border_style(s: &str) -> Result<BorderStyle, String> {
73 BorderStyle::parse(s)
74}
75
76pub 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
85pub fn parse_transform(s: &str) -> Result<Transform, String> {
87 Transform::parse(s)
88}
89
90pub fn parse_alignment(s: &str) -> Result<Alignment, String> {
92 Alignment::parse(s)
93}
94
95pub fn parse_justification(s: &str) -> Result<Justification, String> {
97 Justification::parse(s)
98}
99
100pub 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
109pub 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
120pub 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
131pub 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
146pub 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
152pub 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
158pub 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
180pub 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}