1#[derive(Clone, Debug, Copy, PartialEq, Eq)]
7pub enum GradientType {
8 Linear,
10 Radial,
12 Rectangular,
14 Path,
16}
17
18impl GradientType {
19 pub fn xml_value(&self) -> &'static str {
21 match self {
22 GradientType::Linear => "lin",
23 GradientType::Radial => "circle",
24 GradientType::Rectangular => "rect",
25 GradientType::Path => "path",
26 }
27 }
28}
29
30#[derive(Clone, Debug, Copy, PartialEq, Eq)]
32pub enum GradientDirection {
33 Horizontal,
35 Vertical,
37 DiagonalDown,
39 DiagonalUp,
41 Custom(u32),
43}
44
45impl GradientDirection {
46 pub fn angle(&self) -> u32 {
48 match self {
49 GradientDirection::Horizontal => 0,
50 GradientDirection::Vertical => 5400000,
51 GradientDirection::DiagonalDown => 2700000,
52 GradientDirection::DiagonalUp => 18900000,
53 GradientDirection::Custom(deg) => deg * 60000,
54 }
55 }
56}
57
58#[derive(Clone, Debug)]
60pub struct GradientStop {
61 pub position: u32,
63 pub color: String,
65 pub transparency: Option<u32>,
67}
68
69impl GradientStop {
70 pub fn new(position: u32, color: &str) -> Self {
72 GradientStop {
73 position: position.min(100000),
74 color: color.trim_start_matches('#').to_uppercase(),
75 transparency: None,
76 }
77 }
78
79 pub fn start(color: &str) -> Self {
81 Self::new(0, color)
82 }
83
84 pub fn middle(color: &str) -> Self {
86 Self::new(50000, color)
87 }
88
89 pub fn end(color: &str) -> Self {
91 Self::new(100000, color)
92 }
93
94 pub fn with_transparency(mut self, percent: u32) -> Self {
96 self.transparency = Some(percent.min(100) * 1000);
97 self
98 }
99}
100
101#[derive(Clone, Debug)]
103pub struct GradientFill {
104 pub gradient_type: GradientType,
106 pub direction: GradientDirection,
108 pub stops: Vec<GradientStop>,
110 pub rotate_with_shape: bool,
112}
113
114impl GradientFill {
115 pub fn new(gradient_type: GradientType) -> Self {
117 GradientFill {
118 gradient_type,
119 direction: GradientDirection::Vertical,
120 stops: Vec::new(),
121 rotate_with_shape: true,
122 }
123 }
124
125 pub fn linear(direction: GradientDirection) -> Self {
127 let mut fill = Self::new(GradientType::Linear);
128 fill.direction = direction;
129 fill
130 }
131
132 pub fn radial() -> Self {
134 Self::new(GradientType::Radial)
135 }
136
137 pub fn two_color(start_color: &str, end_color: &str) -> Self {
139 Self::linear(GradientDirection::Vertical)
140 .add_stop(GradientStop::start(start_color))
141 .add_stop(GradientStop::end(end_color))
142 }
143
144 pub fn three_color(start_color: &str, middle_color: &str, end_color: &str) -> Self {
146 Self::linear(GradientDirection::Vertical)
147 .add_stop(GradientStop::start(start_color))
148 .add_stop(GradientStop::middle(middle_color))
149 .add_stop(GradientStop::end(end_color))
150 }
151
152 pub fn add_stop(mut self, stop: GradientStop) -> Self {
154 self.stops.push(stop);
155 self
156 }
157
158 pub fn with_direction(mut self, direction: GradientDirection) -> Self {
160 self.direction = direction;
161 self
162 }
163
164 pub fn with_rotate(mut self, rotate: bool) -> Self {
166 self.rotate_with_shape = rotate;
167 self
168 }
169
170 pub fn sorted(mut self) -> Self {
172 self.stops.sort_by_key(|s| s.position);
173 self
174 }
175}
176
177pub struct PresetGradients;
179
180impl PresetGradients {
181 pub fn blue() -> GradientFill {
183 GradientFill::two_color("0066CC", "003366")
184 }
185
186 pub fn green() -> GradientFill {
188 GradientFill::two_color("00CC66", "006633")
189 }
190
191 pub fn red() -> GradientFill {
193 GradientFill::two_color("CC0000", "660000")
194 }
195
196 pub fn orange() -> GradientFill {
198 GradientFill::two_color("FF9900", "CC6600")
199 }
200
201 pub fn purple() -> GradientFill {
203 GradientFill::two_color("9933CC", "660099")
204 }
205
206 pub fn gray() -> GradientFill {
208 GradientFill::two_color("999999", "333333")
209 }
210
211 pub fn sunrise() -> GradientFill {
213 GradientFill::three_color("FF6600", "FFCC00", "FFFF66")
214 }
215
216 pub fn ocean() -> GradientFill {
218 GradientFill::three_color("003366", "0066CC", "66CCFF")
219 }
220
221 pub fn forest() -> GradientFill {
223 GradientFill::three_color("003300", "006600", "66CC66")
224 }
225
226 pub fn rainbow() -> GradientFill {
228 GradientFill::linear(GradientDirection::Horizontal)
229 .add_stop(GradientStop::new(0, "FF0000"))
230 .add_stop(GradientStop::new(17000, "FF9900"))
231 .add_stop(GradientStop::new(33000, "FFFF00"))
232 .add_stop(GradientStop::new(50000, "00FF00"))
233 .add_stop(GradientStop::new(67000, "0000FF"))
234 .add_stop(GradientStop::new(83000, "9900FF"))
235 .add_stop(GradientStop::new(100000, "FF00FF"))
236 }
237}
238
239pub fn generate_gradient_fill_xml(gradient: &GradientFill) -> String {
241 let mut xml = String::from(r#"<a:gradFill rotWithShape=""#);
242 xml.push_str(if gradient.rotate_with_shape { "1" } else { "0" });
243 xml.push_str(r#"">"#);
244
245 xml.push_str("<a:gsLst>");
247 for stop in &gradient.stops {
248 xml.push_str(&format!(
249 r#"<a:gs pos="{}"><a:srgbClr val="{}""#,
250 stop.position, stop.color
251 ));
252
253 if let Some(alpha) = stop.transparency {
254 xml.push_str(&format!(r#"><a:alpha val="{}"/></a:srgbClr>"#, 100000 - alpha));
255 } else {
256 xml.push_str("/>");
257 }
258
259 xml.push_str("</a:gs>");
260 }
261 xml.push_str("</a:gsLst>");
262
263 match gradient.gradient_type {
265 GradientType::Linear => {
266 xml.push_str(&format!(
267 r#"<a:lin ang="{}" scaled="1"/>"#,
268 gradient.direction.angle()
269 ));
270 }
271 GradientType::Radial | GradientType::Rectangular | GradientType::Path => {
272 xml.push_str(&format!(
273 r#"<a:path path="{}"><a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path>"#,
274 gradient.gradient_type.xml_value()
275 ));
276 }
277 }
278
279 xml.push_str("</a:gradFill>");
280 xml
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn test_gradient_type_xml() {
289 assert_eq!(GradientType::Linear.xml_value(), "lin");
290 assert_eq!(GradientType::Radial.xml_value(), "circle");
291 }
292
293 #[test]
294 fn test_gradient_direction_angle() {
295 assert_eq!(GradientDirection::Horizontal.angle(), 0);
296 assert_eq!(GradientDirection::Vertical.angle(), 5400000);
297 assert_eq!(GradientDirection::Custom(45).angle(), 2700000);
298 }
299
300 #[test]
301 fn test_gradient_stop() {
302 let stop = GradientStop::new(50000, "#FF0000");
303 assert_eq!(stop.position, 50000);
304 assert_eq!(stop.color, "FF0000");
305 }
306
307 #[test]
308 fn test_gradient_stop_with_transparency() {
309 let stop = GradientStop::new(0, "000000").with_transparency(50);
310 assert_eq!(stop.transparency, Some(50000));
311 }
312
313 #[test]
314 fn test_two_color_gradient() {
315 let gradient = GradientFill::two_color("FF0000", "0000FF");
316 assert_eq!(gradient.stops.len(), 2);
317 assert_eq!(gradient.stops[0].color, "FF0000");
318 assert_eq!(gradient.stops[1].color, "0000FF");
319 }
320
321 #[test]
322 fn test_three_color_gradient() {
323 let gradient = GradientFill::three_color("FF0000", "00FF00", "0000FF");
324 assert_eq!(gradient.stops.len(), 3);
325 }
326
327 #[test]
328 fn test_preset_gradients() {
329 let blue = PresetGradients::blue();
330 assert_eq!(blue.stops.len(), 2);
331
332 let rainbow = PresetGradients::rainbow();
333 assert_eq!(rainbow.stops.len(), 7);
334 }
335
336 #[test]
337 fn test_generate_gradient_xml() {
338 let gradient = GradientFill::two_color("FF0000", "0000FF");
339 let xml = generate_gradient_fill_xml(&gradient);
340 assert!(xml.contains("gradFill"));
341 assert!(xml.contains("gsLst"));
342 assert!(xml.contains("FF0000"));
343 assert!(xml.contains("0000FF"));
344 }
345
346 #[test]
347 fn test_radial_gradient_xml() {
348 let gradient = GradientFill::radial()
349 .add_stop(GradientStop::start("FFFFFF"))
350 .add_stop(GradientStop::end("000000"));
351 let xml = generate_gradient_fill_xml(&gradient);
352 assert!(xml.contains("path"));
353 assert!(xml.contains("circle"));
354 }
355}