1use std::fs::File;
2use std::io::Write;
3use std::path::Path;
4use svg::{
5 svg_draw_barres, svg_draw_bg, svg_draw_finger, svg_draw_min_fret, svg_draw_note, svg_draw_title,
6};
7use tera::{Context as TeraContext, Tera};
8use types::{Chord, GuitarString, Hand};
9use utils::{get_filename, get_palette};
10
11mod svg;
12pub mod types;
13mod utils;
14
15fn generate_svg(chord_settings: Chord) -> std::result::Result<String, Box<dyn std::error::Error>> {
16 let string_space = 40;
17 let margin = 30;
18
19 let palette = get_palette(chord_settings.mode);
20
21 let total_strings = 5;
23
24 let mut fingers = "".to_string();
25 for (i, finger) in chord_settings.fingers.iter().enumerate() {
26 let string: GuitarString = if chord_settings.hand == Hand::Right {
27 i.into()
28 } else {
29 (total_strings - i).into()
30 };
31 fingers += &svg_draw_finger(finger, string, &string_space, &palette);
32 }
33
34 let lowest_fret: &i32 = chord_settings
35 .frets
36 .iter()
37 .filter(|fret| **fret > 0)
38 .min()
39 .unwrap_or(&0);
40
41 let show_nut = (chord_settings.frets.contains(&0) && lowest_fret < &3)
42 || chord_settings.frets.contains(&1);
43 let nut_width = if show_nut { 9 } else { 2 };
44 let nut_shape = if show_nut { "round" } else { "butt" };
45
46 let mut notes = "".to_string();
47 for (i, note) in chord_settings.frets.iter().enumerate() {
48 if note != &0 {
49 let string: GuitarString = if chord_settings.hand == Hand::Right {
50 i.into()
51 } else {
52 (total_strings - i).into()
53 };
54 notes += &svg_draw_note(note, string, &string_space, lowest_fret, &palette);
55 }
56 }
57
58 let mut min_fret_marker = "".to_string();
59 if *lowest_fret > 2 || *lowest_fret > 1 && !show_nut {
60 min_fret_marker = svg_draw_min_fret(lowest_fret, &string_space, &palette);
61 }
62
63 let chord_title = svg_draw_title(&chord_settings, &palette);
64 let barres = match chord_settings.barres {
67 Some(barres) => svg_draw_barres(
68 &barres[0],
69 &chord_settings.frets,
70 &string_space,
71 &lowest_fret,
72 &palette,
73 ),
74 None => String::from(""),
75 };
76
77 let mut context = TeraContext::new();
78 context.insert("name", &chord_title);
79 context.insert("padding", &margin);
80 context.insert("nutWidth", &nut_width);
81 context.insert("nutShape", &nut_shape);
82 context.insert("fingers", &fingers);
83 context.insert("notes", ¬es);
84 context.insert("minFret", &min_fret_marker);
85 context.insert("foreground", &palette.fg);
86 context.insert(
87 "background",
88 &svg_draw_bg(chord_settings.use_background, &palette),
89 );
90 context.insert("barres", &barres);
91
92 match Tera::one_off(include_str!("../templates/chord.svg"), &context, false) {
93 Ok(result) => Ok(result),
94 Err(e) => {
95 println!("{:?}", e);
96 Err(Box::new(e))
97 }
98 }
99}
100
101pub fn render_svg(
102 chord_settings: Chord,
103 output_dir: &str,
104) -> Result<u64, Box<dyn std::error::Error>> {
105 let hashed_title = get_filename(&chord_settings);
106
107 match generate_svg(chord_settings) {
108 Ok(result) => {
109 let path = Path::new(output_dir).join(format!("{}.svg", hashed_title));
110 let mut output = File::create(path)?;
111 write!(output, "{}", result)?;
112 Ok(hashed_title)
113 }
114
115 Err(e) => {
116 println!("Failed to create SVG: {:?}", e);
117 Err(e)
118 }
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use crate::{
125 generate_svg,
126 types::{Chord, Hand, Mode},
127 };
128
129 #[test]
130 fn should_render_svg_correctly() {
131 let title = String::from("Hendrix");
132 let chord = Chord {
133 title: Some(&title),
134 frets: vec![-1, 7, 6, 7, 8, -1],
135 fingers: vec!["x", "2", "1", "3", "4", "x"],
136 ..Default::default()
137 };
138 let image = generate_svg(chord);
139 let expected = std::fs::read_to_string("fixtures/8429847222939097413.svg")
140 .expect("couldn't open fixture");
141 assert_eq!(image.unwrap(), expected);
142
143 let title = String::from("E");
144 let chord = Chord {
145 title: Some(&title),
146 frets: vec![0, 2, 2, 1, 0, 0],
147 fingers: vec!["0", "2", "3", "1", "0", "0"],
148 ..Default::default()
149 };
150 let image = generate_svg(chord);
151 let expected = std::fs::read_to_string("fixtures/6264984114944231516.svg")
152 .expect("couldn't open fixture");
153 assert_eq!(image.unwrap(), expected);
154
155 let title = String::from("C");
156 let suffix = String::from("°7");
157 let chord = Chord {
158 title: Some(&title),
159 frets: vec![-1, 3, 4, 2, 3, -1],
160 suffix: Some(&suffix),
161 fingers: vec!["x", "2", "3", "1", "4", "x"],
162 ..Default::default()
163 };
164 let image = generate_svg(chord);
165 let expected = std::fs::read_to_string("fixtures/14747384251356703516.svg")
166 .expect("couldn't open fixture");
167 assert_eq!(image.unwrap(), expected);
168
169 let title = String::from("E9");
170 let chord = Chord {
171 title: Some(&title),
172 frets: vec![-1, 7, 6, 7, 7, 7],
173 fingers: vec!["x", "2", "1", "3", "3", "3"],
174 ..Default::default()
175 };
176 let image = generate_svg(chord);
177 let expected = std::fs::read_to_string("fixtures/1119813197946994512.svg")
178 .expect("couldn't open fixture");
179 assert_eq!(image.unwrap(), expected);
180
181 let title = String::from("D7");
182 let chord = Chord {
183 title: Some(&title),
184 frets: vec![10, 12, 10, 11, 10, 10],
185 fingers: vec!["1", "3", "1", "2", "1", "1"],
186 ..Default::default()
187 };
188 let image = generate_svg(chord);
189 let expected = std::fs::read_to_string("fixtures/14132601197530680953.svg")
190 .expect("couldn't open fixture");
191 assert_eq!(image.unwrap(), expected);
192
193 let title = String::from("Bond");
194 let chord = Chord {
195 title: Some(&title),
196 frets: vec![0, 10, 9, 8, 7, -1],
197 fingers: vec!["0", "4", "3", "2", "1", "x"],
198 hand: Hand::Right,
199 ..Default::default()
200 };
201 let image = generate_svg(chord);
202 let expected = std::fs::read_to_string("fixtures/14960314899480148130.svg")
203 .expect("couldn't open fixture");
204 assert_eq!(image.unwrap(), expected);
205 }
206
207 #[test]
208 fn should_render_barres() {
209 let suffix = String::from("9");
211 let title = String::from("B");
212 let chord = Chord {
213 title: Some(&title),
214 frets: vec![-1, 9, 8, 9, 9, 9],
215 fingers: vec!["x", "2", "1", "3", "3", "3"],
216 suffix: Some(&suffix),
217 barres: Some(vec![9]),
218 ..Default::default()
219 };
220 let image = generate_svg(chord);
221 let expected = std::fs::read_to_string("fixtures/9333158008996547180.svg")
222 .expect("couldn't open fixture");
223 assert_eq!(image.unwrap(), expected);
224
225 let title = String::from("C");
226 let suffix = String::from("m");
227 let chord = Chord {
228 title: Some(&title),
229 suffix: Some(&suffix),
230 frets: vec![-1, 3, 5, 5, 4, 3],
231 fingers: vec!["x", "1", "3", "4", "2", "1"],
232 barres: Some(vec![3]),
233 ..Default::default()
234 };
235 let image = generate_svg(chord);
236 let expected = std::fs::read_to_string("fixtures/12230973133991337290.svg")
237 .expect("couldn't open fixture");
238 assert_eq!(image.unwrap(), expected);
239 }
240
241 #[test]
242 fn should_render_barre_at_nut() {
243 let title = String::from("F");
244 let chord = Chord {
245 title: Some(&title),
246 frets: vec![1, 3, 3, 2, 1, 1],
247 fingers: vec!["1", "3", "4", "2", "1", "1"],
248 barres: Some(vec![1]),
249 ..Default::default()
250 };
251 let image = generate_svg(chord);
252 let expected = std::fs::read_to_string("fixtures/18011157745197688127.svg")
253 .expect("couldn't open fixture");
254 assert_eq!(image.unwrap(), expected);
255 }
256
257 #[test]
258 fn should_not_show_nut_when_min_is_higher_than_3() {
259 let title = String::from("D");
261 let suffix = String::from("m69");
262 let chord = Chord {
263 title: Some(&title),
264 frets: vec![-1, 5, 3, 4, 5, 0],
265 fingers: vec!["x", "3", "1", "2", "4", "0"],
266 suffix: Some(&suffix),
267 ..Default::default()
268 };
269 let image = generate_svg(chord);
270 let expected = std::fs::read_to_string("fixtures/13801489451752273984.svg")
271 .expect("couldn't open fixture");
272 assert_eq!(image.unwrap(), expected);
273 }
274
275 #[test]
276 fn should_render_bg() {
277 let title = String::from("E♭");
279 let suffix = String::from("7");
280
281 let chord = Chord {
282 title: Some(&title),
283 suffix: Some(&suffix),
284 frets: vec![-1, 6, 5, 6, -1, -1],
285 fingers: vec!["x", "2", "1", "3", "x", "x"],
286 ..Default::default()
287 };
288 let image = generate_svg(chord);
289 let expected = std::fs::read_to_string("fixtures/14385705171269355287.svg")
290 .expect("couldn't open fixture");
291 assert_eq!(image.unwrap(), expected);
292
293 let chord = Chord {
295 title: Some(&title),
296 suffix: Some(&suffix),
297 frets: vec![-1, 6, 5, 6, -1, -1],
298 fingers: vec!["x", "2", "1", "3", "x", "x"],
299 hand: Hand::Left,
300 ..Default::default()
301 };
302 let image = generate_svg(chord);
303 let expected = std::fs::read_to_string("fixtures/16884747429378599218.svg")
304 .expect("couldn't open fixture");
305 assert_eq!(image.unwrap(), expected);
306
307 let chord = Chord {
309 title: Some(&title),
310 suffix: Some(&suffix),
311 frets: vec![-1, 6, 5, 6, -1, -1],
312 fingers: vec!["x", "2", "1", "3", "x", "x"],
313 use_background: true,
314 ..Default::default()
315 };
316 let image = generate_svg(chord);
317 let expected = std::fs::read_to_string("fixtures/2476955617190468140.svg")
318 .expect("couldn't open fixture");
319 assert_eq!(image.unwrap(), expected);
320
321 let chord = Chord {
323 title: Some(&title),
324 suffix: Some(&suffix),
325 frets: vec![-1, 6, 5, 6, -1, -1],
326 fingers: vec!["x", "2", "1", "3", "x", "x"],
327 hand: Hand::Left,
328 use_background: true,
329 ..Default::default()
330 };
331 let image = generate_svg(chord);
332 let expected = std::fs::read_to_string("fixtures/3472658043626440162.svg")
333 .expect("couldn't open fixture");
334 assert_eq!(image.unwrap(), expected);
335 }
336
337 #[test]
338 fn should_use_dark_mode() {
339 let title = String::from("E♭");
341 let suffix = String::from("7");
342
343 let chord = Chord {
344 title: Some(&title),
345 suffix: Some(&suffix),
346 frets: vec![-1, 6, 5, 6, -1, -1],
347 fingers: vec!["x", "2", "1", "3", "x", "x"],
348 ..Default::default()
349 };
350 let image = generate_svg(chord);
351 let expected = std::fs::read_to_string("fixtures/14385705171269355287.svg")
352 .expect("couldn't open fixture");
353 assert_eq!(image.unwrap(), expected);
354
355 let chord = Chord {
357 title: Some(&title),
358 suffix: Some(&suffix),
359 frets: vec![-1, 6, 5, 6, -1, -1],
360 fingers: vec!["x", "2", "1", "3", "x", "x"],
361 use_background: true,
362 ..Default::default()
363 };
364 let image = generate_svg(chord);
365 let expected = std::fs::read_to_string("fixtures/2476955617190468140.svg")
366 .expect("couldn't open fixture");
367 assert_eq!(image.unwrap(), expected);
368
369 let chord = Chord {
371 title: Some(&title),
372 suffix: Some(&suffix),
373 frets: vec![-1, 6, 5, 6, -1, -1],
374 fingers: vec!["x", "2", "1", "3", "x", "x"],
375 mode: Mode::Dark,
376 ..Default::default()
377 };
378 let image = generate_svg(chord);
379 let expected = std::fs::read_to_string("fixtures/13340061165425864902.svg")
380 .expect("couldn't open fixture");
381 assert_eq!(image.unwrap(), expected);
382
383 let chord = Chord {
385 title: Some(&title),
386 suffix: Some(&suffix),
387 frets: vec![-1, 6, 5, 6, -1, -1],
388 fingers: vec!["x", "2", "1", "3", "x", "x"],
389 use_background: true,
390 mode: Mode::Dark,
391 ..Default::default()
392 };
393 let image = generate_svg(chord);
394 let expected = std::fs::read_to_string("fixtures/1048205031866609166.svg")
395 .expect("couldn't open fixture");
396 assert_eq!(image.unwrap(), expected);
397 }
398
399 #[test]
400 fn should_render_left_handed() {
401 let title = String::from("A");
402 let chord = Chord {
403 title: Some(&title),
404 frets: vec![-1, 0, 2, 2, 2, 0],
405 fingers: vec!["x", "0", "2", "1", "3", "0"],
406 hand: Hand::Left,
407 ..Default::default()
408 };
409 let image = generate_svg(chord);
410 let expected = std::fs::read_to_string("fixtures/left/12943706944351374242.svg")
411 .expect("couldn't open fixture");
412 assert_eq!(image.unwrap(), expected);
413
414 let title = String::from("Hendrix");
415 let chord = Chord {
416 title: Some(&title),
417 frets: vec![-1, 7, 6, 7, 8, -1],
418 fingers: vec!["x", "2", "1", "3", "4", "x"],
419 hand: Hand::Left,
420 ..Default::default()
421 };
422 let image = generate_svg(chord);
423 let expected = std::fs::read_to_string("fixtures/left/12438538594686784945.svg")
424 .expect("couldn't open fixture");
425 assert_eq!(image.unwrap(), expected);
426 }
427}
428
429