1use crate::graph::GitGraph;
4use crate::settings::Settings;
5use svg::node::element::path::Data;
6use svg::node::element::{Circle, Line, Path};
7use svg::Document;
8
9pub fn print_svg(graph: &GitGraph, settings: &Settings) -> Result<String, String> {
11 let mut document = Document::new();
12
13 let max_idx = graph.commits.len();
14 let mut max_column = 0;
15
16 if settings.debug {
17 for branch in &graph.all_branches {
18 if let (Some(start), Some(end)) = branch.range {
19 document = document.add(bold_line(
20 start,
21 branch.visual.column.unwrap(),
22 end,
23 branch.visual.column.unwrap(),
24 "cyan",
25 ));
26 }
27 }
28 }
29
30 for (idx, info) in graph.commits.iter().enumerate() {
31 if let Some(trace) = info.branch_trace {
32 let branch = &graph.all_branches[trace];
33 let branch_color = &branch.visual.svg_color;
34
35 if branch.visual.column.unwrap() > max_column {
36 max_column = branch.visual.column.unwrap();
37 }
38
39 for p in 0..2 {
40 let parent = info.parents[p];
41 let Some(par_oid) = parent else {
42 continue;
43 };
44 let Some(par_idx) = graph.indices.get(&par_oid) else {
45 let idx_bottom = max_idx;
48 document = document.add(line(
49 idx,
50 branch.visual.column.unwrap(),
51 idx_bottom,
52 branch.visual.column.unwrap(),
53 branch_color,
54 ));
55 continue;
56 };
57 let par_info = &graph.commits[*par_idx];
58 let par_branch = &graph.all_branches[par_info.branch_trace.unwrap()];
59
60 let color = if info.is_merge {
61 &par_branch.visual.svg_color
62 } else {
63 branch_color
64 };
65
66 if branch.visual.column == par_branch.visual.column {
67 document = document.add(line(
68 idx,
69 branch.visual.column.unwrap(),
70 *par_idx,
71 par_branch.visual.column.unwrap(),
72 color,
73 ));
74 } else {
75 let split_index = super::get_deviate_index(graph, idx, *par_idx);
76 document = document.add(path(
77 idx,
78 branch.visual.column.unwrap(),
79 *par_idx,
80 par_branch.visual.column.unwrap(),
81 split_index,
82 color,
83 ));
84 }
85 }
86
87 document = document.add(commit_dot(
88 idx,
89 branch.visual.column.unwrap(),
90 branch_color,
91 !info.is_merge,
92 ));
93 }
94 }
95 let (x_max, y_max) = commit_coord(max_idx + 1, max_column + 1);
96 document = document
97 .set("viewBox", (0, 0, x_max, y_max))
98 .set("width", x_max)
99 .set("height", y_max);
100
101 let mut out: Vec<u8> = vec![];
102 svg::write(&mut out, &document).map_err(|err| err.to_string())?;
103 Ok(String::from_utf8(out).unwrap_or_else(|_| "Invalid UTF8 character.".to_string()))
104}
105
106fn commit_dot(index: usize, column: usize, color: &str, filled: bool) -> Circle {
107 let (x, y) = commit_coord(index, column);
108 Circle::new()
109 .set("cx", x)
110 .set("cy", y)
111 .set("r", 4)
112 .set("fill", if filled { color } else { "white" })
113 .set("stroke", color)
114 .set("stroke-width", 1)
115}
116
117fn line(index1: usize, column1: usize, index2: usize, column2: usize, color: &str) -> Line {
118 let (x1, y1) = commit_coord(index1, column1);
119 let (x2, y2) = commit_coord(index2, column2);
120 Line::new()
121 .set("x1", x1)
122 .set("y1", y1)
123 .set("x2", x2)
124 .set("y2", y2)
125 .set("stroke", color)
126 .set("stroke-width", 1)
127}
128
129fn bold_line(index1: usize, column1: usize, index2: usize, column2: usize, color: &str) -> Line {
130 let (x1, y1) = commit_coord(index1, column1);
131 let (x2, y2) = commit_coord(index2, column2);
132 Line::new()
133 .set("x1", x1)
134 .set("y1", y1)
135 .set("x2", x2)
136 .set("y2", y2)
137 .set("stroke", color)
138 .set("stroke-width", 5)
139}
140
141fn path(
142 index1: usize,
143 column1: usize,
144 index2: usize,
145 column2: usize,
146 split_idx: usize,
147 color: &str,
148) -> Path {
149 let c0 = commit_coord(index1, column1);
150
151 let c1 = commit_coord(split_idx, column1);
152 let c2 = commit_coord(split_idx + 1, column2);
153
154 let c3 = commit_coord(index2, column2);
155
156 let m = (0.5 * (c1.0 + c2.0), 0.5 * (c1.1 + c2.1));
157
158 let data = Data::new()
159 .move_to(c0)
160 .line_to(c1)
161 .quadratic_curve_to((c1.0, m.1, m.0, m.1))
162 .quadratic_curve_to((c2.0, m.1, c2.0, c2.1))
163 .line_to(c3);
164
165 Path::new()
166 .set("d", data)
167 .set("fill", "none")
168 .set("stroke", color)
169 .set("stroke-width", 1)
170}
171
172fn commit_coord(index: usize, column: usize) -> (f32, f32) {
173 (15.0 * (column as f32 + 1.0), 15.0 * (index as f32 + 1.0))
174}