use crate::ir::style::{Color, ColorStop, Gradient, RadialShape};
pub fn parse_gradient(s: &str) -> Result<Gradient, String> {
let s = s.trim();
if s.starts_with("linear-gradient(") && s.ends_with(')') {
parse_linear_gradient(s)
} else if s.starts_with("radial-gradient(") && s.ends_with(')') {
parse_radial_gradient(s)
} else {
Err(format!(
"Invalid gradient format: '{}'. Expected linear-gradient(...) or radial-gradient(...)",
s
))
}
}
fn parse_linear_gradient(s: &str) -> Result<Gradient, String> {
let inner = &s[16..s.len() - 1]; let parts: Vec<&str> = inner.split(',').collect();
if parts.len() < 2 {
return Err("Linear gradient requires at least angle and one color stop".to_string());
}
let angle = parse_angle(parts[0])?;
let stops = parse_color_stops(&parts[1..])?;
Ok(Gradient::Linear { angle, stops })
}
fn parse_radial_gradient(s: &str) -> Result<Gradient, String> {
let inner = &s[16..s.len() - 1]; let parts: Vec<&str> = inner.split(',').collect();
if parts.len() < 2 {
return Err("Radial gradient requires at least shape and one color stop".to_string());
}
let shape = parse_shape(parts[0])?;
let stops = parse_color_stops(&parts[1..])?;
Ok(Gradient::Radial { shape, stops })
}
pub fn parse_angle(s: &str) -> Result<f32, String> {
let s = s.trim();
if let Some(num) = s.strip_suffix("deg") {
let value: f32 = num
.parse()
.map_err(|_| format!("Invalid degree value: {}", s))?;
Ok(value % 360.0)
} else if let Some(num) = s.strip_suffix("rad") {
let value: f32 = num
.parse()
.map_err(|_| format!("Invalid radian value: {}", s))?;
Ok(value * 180.0 / std::f32::consts::PI)
} else if let Some(num) = s.strip_suffix("turn") {
let value: f32 = num
.parse()
.map_err(|_| format!("Invalid turn value: {}", s))?;
Ok(value * 360.0)
} else {
let value: f32 = s.parse().map_err(|_| format!("Invalid angle: {}", s))?;
Ok(value)
}
}
fn parse_shape(s: &str) -> Result<RadialShape, String> {
match s.trim().to_lowercase().as_str() {
"circle" => Ok(RadialShape::Circle),
"ellipse" => Ok(RadialShape::Ellipse),
_ => Err(format!(
"Invalid radial shape: '{}'. Expected circle or ellipse",
s
)),
}
}
pub fn parse_color_stops(parts: &[&str]) -> Result<Vec<ColorStop>, String> {
let mut stops = Vec::new();
for part in parts {
let part = part.trim();
let stop = parse_color_stop(part)?;
stops.push(stop);
}
Ok(stops)
}
pub fn parse_color_stop(s: &str) -> Result<ColorStop, String> {
let parts: Vec<&str> = s.split_whitespace().collect();
if parts.is_empty() {
return Err("Empty color stop".to_string());
}
let color_str = parts[0];
let color = Color::parse(color_str)?;
let offset = if parts.len() > 1 {
let offset_str = parts[1];
if let Some(num) = offset_str.strip_suffix('%') {
let value: f32 = num
.parse()
.map_err(|_| format!("Invalid offset: {}", offset_str))?;
value / 100.0
} else {
let value: f32 = offset_str
.parse()
.map_err(|_| format!("Invalid offset: {}", offset_str))?;
value
}
} else {
0.0
};
Ok(ColorStop { color, offset })
}