dampen_core/parser/
gradient.rs1use crate::ir::style::{Color, ColorStop, Gradient, RadialShape};
6
7pub fn parse_gradient(s: &str) -> Result<Gradient, String> {
20 let s = s.trim();
21
22 if s.starts_with("linear-gradient(") && s.ends_with(')') {
23 parse_linear_gradient(s)
24 } else if s.starts_with("radial-gradient(") && s.ends_with(')') {
25 parse_radial_gradient(s)
26 } else {
27 Err(format!(
28 "Invalid gradient format: '{}'. Expected linear-gradient(...) or radial-gradient(...)",
29 s
30 ))
31 }
32}
33
34fn parse_linear_gradient(s: &str) -> Result<Gradient, String> {
36 let inner = &s[16..s.len() - 1]; let parts: Vec<&str> = inner.split(',').collect();
38
39 if parts.len() < 2 {
40 return Err("Linear gradient requires at least angle and one color stop".to_string());
41 }
42
43 let angle = parse_angle(parts[0])?;
44 let stops = parse_color_stops(&parts[1..])?;
45
46 Ok(Gradient::Linear { angle, stops })
47}
48
49fn parse_radial_gradient(s: &str) -> Result<Gradient, String> {
51 let inner = &s[16..s.len() - 1]; let parts: Vec<&str> = inner.split(',').collect();
53
54 if parts.len() < 2 {
55 return Err("Radial gradient requires at least shape and one color stop".to_string());
56 }
57
58 let shape = parse_shape(parts[0])?;
59 let stops = parse_color_stops(&parts[1..])?;
60
61 Ok(Gradient::Radial { shape, stops })
62}
63
64pub fn parse_angle(s: &str) -> Result<f32, String> {
66 let s = s.trim();
67
68 if let Some(num) = s.strip_suffix("deg") {
69 let value: f32 = num
70 .parse()
71 .map_err(|_| format!("Invalid degree value: {}", s))?;
72 Ok(value % 360.0)
73 } else if let Some(num) = s.strip_suffix("rad") {
74 let value: f32 = num
75 .parse()
76 .map_err(|_| format!("Invalid radian value: {}", s))?;
77 Ok(value * 180.0 / std::f32::consts::PI)
78 } else if let Some(num) = s.strip_suffix("turn") {
79 let value: f32 = num
80 .parse()
81 .map_err(|_| format!("Invalid turn value: {}", s))?;
82 Ok(value * 360.0)
83 } else {
84 let value: f32 = s.parse().map_err(|_| format!("Invalid angle: {}", s))?;
86 Ok(value)
87 }
88}
89
90fn parse_shape(s: &str) -> Result<RadialShape, String> {
92 match s.trim().to_lowercase().as_str() {
93 "circle" => Ok(RadialShape::Circle),
94 "ellipse" => Ok(RadialShape::Ellipse),
95 _ => Err(format!(
96 "Invalid radial shape: '{}'. Expected circle or ellipse",
97 s
98 )),
99 }
100}
101
102pub fn parse_color_stops(parts: &[&str]) -> Result<Vec<ColorStop>, String> {
104 let mut stops = Vec::new();
105
106 for part in parts {
107 let part = part.trim();
108 let stop = parse_color_stop(part)?;
109 stops.push(stop);
110 }
111
112 Ok(stops)
113}
114
115pub fn parse_color_stop(s: &str) -> Result<ColorStop, String> {
117 let parts: Vec<&str> = s.split_whitespace().collect();
119
120 if parts.is_empty() {
121 return Err("Empty color stop".to_string());
122 }
123
124 let color_str = parts[0];
125 let color = Color::parse(color_str)?;
126
127 let offset = if parts.len() > 1 {
129 let offset_str = parts[1];
130 if let Some(num) = offset_str.strip_suffix('%') {
131 let value: f32 = num
132 .parse()
133 .map_err(|_| format!("Invalid offset: {}", offset_str))?;
134 value / 100.0
135 } else {
136 let value: f32 = offset_str
137 .parse()
138 .map_err(|_| format!("Invalid offset: {}", offset_str))?;
139 value
140 }
141 } else {
142 0.0
145 };
146
147 Ok(ColorStop { color, offset })
148}