1use std::{collections::HashMap, fs};
2
3use image_builder::{Color, Image, Position, Size, Text};
4
5const DEFAULT_COLUMN_WIDTH: u32 = 65;
6const DEFAULT_ROW_HEIGHT: u32 = 20;
7
8fn format_adapter(value: String) -> &'static str {
9 let value: &'static str = Box::leak(value.into_boxed_str());
10 value
11}
12
13pub struct Cell {
14 pub column: u32,
15 pub row: u32,
16 pub content: String,
17 pub color: Option<[u8; 4]>,
18}
19struct Column {
20 pub width: u32,
21 pub font_size: Option<u32>,
22 pub custom_font: Option<String>,
23}
24struct Row {
25 pub height: u32,
26 pub color: Option<[u8; 4]>,
27 pub font_size: Option<u32>,
28 pub custom_font: Option<String>,
29}
30
31struct InternalCell {
32 pub content: String,
33 pub color: Option<[u8; 4]>,
34 pub font_size: Option<u32>,
35 pub custom_font: Option<String>,
36}
37pub struct Spreadsheet {
38 title: String,
39 margin: i32,
40 columns: Vec<Column>,
41 rows: Vec<Row>,
42 content: HashMap<(u32, u32), InternalCell>,
43}
44impl Spreadsheet {
45 pub fn new(title: String) -> Spreadsheet {
46 Spreadsheet {
47 title,
48 margin: 0,
49 columns: Vec::new(),
50 rows: Vec::new(),
51 content: HashMap::new(),
52 }
53 }
54 fn add_column(&mut self, amount: u32) {
55 if amount < 1 {
56 panic!("A quantidade de colunas não pode ser menor ou igual a 0");
57 }
58 for _ in 0..amount {
59 self.columns.push(Column {
60 width: DEFAULT_COLUMN_WIDTH,
61 font_size: None,
62 custom_font: None,
63 });
64 }
65 }
66 fn add_row(&mut self, amount: u32) {
67 if amount < 1 {
68 panic!("A quantidade de linhas não pode ser menor ou igual a 0");
69 }
70 for _ in 0..amount {
71 self.rows.push(Row {
72 height: DEFAULT_ROW_HEIGHT,
73 color: None,
74 font_size: None,
75 custom_font: None,
76 });
77 }
78 }
79 pub fn set_margin(&mut self, margin: i32) {
80 self.margin = margin;
81 }
82 pub fn set_column_width(&mut self, number: usize, new_width: u32) {
83 self.columns[number - 1].width = new_width;
84 }
85 pub fn set_column_font_size(&mut self, number: usize, new_size: u32) {
86 self.columns[number - 1].font_size = Some(new_size);
87 }
88 pub fn set_column_custom_font(&mut self, number: usize, new_font: &str) {
89 self.columns[number - 1].custom_font = Some(String::from(new_font));
90 }
91 pub fn set_row_height(&mut self, number: usize, new_height: u32) {
92 self.rows[number - 1].height = new_height;
93 }
94 pub fn set_row_font_size(&mut self, number: usize, new_size: u32) {
95 self.rows[number - 1].font_size = Some(new_size);
96 }
97 pub fn set_row_custom_font(&mut self, number: usize, new_font: &str) {
98 self.rows[number - 1].custom_font = Some(String::from(new_font));
99 }
100 pub fn set_row_color(&mut self, number: usize, new_color: Option<[u8; 4]>) {
101 self.rows[number - 1].color = new_color;
102 }
103 fn valid_cell(&mut self, cell: &Cell) {
104 if cell.column < 1 || cell.row < 1 {
105 panic!("Tentativa de criar célula na coluna ou linha menor que 1")
106 }
107 if (self.columns.len() as u32) < cell.column {
108 self.add_column(cell.column - self.columns.len() as u32);
109 }
110 if (self.rows.len() as u32) < cell.row {
111 self.add_row(cell.row - self.rows.len() as u32);
112 }
113 }
114 pub fn set_cell_color(&mut self, position: (u32, u32), new_color: Option<[u8; 4]>) {
115 let position = (position.0 - 1, position.1 - 1);
116 if let Some(cell) = self.content.get(&position) {
117 self.content.insert(
118 position,
119 InternalCell {
120 content: cell.content.clone(),
121 color: new_color,
122 font_size: cell.font_size,
123 custom_font: cell.custom_font.clone(),
124 },
125 );
126 };
127 }
128 pub fn set_cell_font_size(&mut self, position: (u32, u32), new_size: u32) {
129 let position = (position.0 - 1, position.1 - 1);
130 if let Some(cell) = self.content.get(&position) {
131 self.content.insert(
132 position,
133 InternalCell {
134 content: cell.content.clone(),
135 color: cell.color,
136 font_size: Some(new_size),
137 custom_font: cell.custom_font.clone(),
138 },
139 );
140 };
141 }
142 pub fn set_cell_custom_font(&mut self, position: (u32, u32), new_font: &str) {
143 let position = (position.0 - 1, position.1 - 1);
144 if let Some(cell) = self.content.get(&position) {
145 self.content.insert(
146 position,
147 InternalCell {
148 content: cell.content.clone(),
149 color: cell.color,
150 font_size: cell.font_size,
151 custom_font: Some(String::from(new_font)),
152 },
153 );
154 };
155 }
156 pub fn set_cell(&mut self, cell: Cell) {
157 self.valid_cell(&cell);
158 self.content.insert(
159 (cell.column - 1, cell.row - 1),
160 InternalCell {
161 content: cell.content,
162 color: None,
163 font_size: None,
164 custom_font: None,
165 },
166 );
167 }
168 pub fn add_in_cell(&mut self, cell: Cell) {
169 self.valid_cell(&cell);
170 let index_cell = (cell.column - 1, cell.row - 1);
171 match self.content.get(&index_cell) {
172 Some(old_cell) => {
173 let old_value: u32 = old_cell.content.parse().unwrap();
174 let new_part: u32 = cell.content.parse().unwrap();
175 self.content.insert(
176 index_cell,
177 InternalCell {
178 content: (old_value + new_part).to_string(),
179 color: old_cell.color,
180 font_size: old_cell.font_size,
181 custom_font: old_cell.custom_font.clone(),
182 },
183 );
184 }
185 None => {
186 self.content.insert(
187 index_cell,
188 InternalCell {
189 content: cell.content,
190 color: cell.color,
191 font_size: None,
192 custom_font: None,
193 },
194 );
195 }
196 };
197 }
198 pub fn get_cell_value(&self, position: (u32, u32)) -> &String {
199 let cell = self.content.get(&(position.0 - 1, position.1 - 1)).unwrap();
200 &cell.content
201 }
202 pub fn save_csv(&self, output_path: String) -> Result<(), &'static str> {
203 let mut csv = String::new();
204 csv.push_str(format!("{}\n", self.title).as_str());
205 for (r, _) in self.rows.iter().enumerate() {
206 for (c, _) in self.columns.iter().enumerate() {
207 if c != 0 {
208 csv.push(';')
209 }
210 if let Some(cell) = self.content.get(&(c as u32, r as u32)) {
211 csv.push_str(&cell.content)
212 }
213 }
214 csv.push_str(" \n");
215 }
216
217 if let Err(error) = fs::write(format!("{}/spreadsheet.csv", output_path), csv) {
218 return Err(format_adapter(format!(
219 "Não foi possível salvar o arquivo em {}:\n{}",
220 output_path, error
221 )));
222 };
223
224 Ok(())
225 }
226 pub fn save_png(&self, output_path: &str) -> Result<(), &'static str> {
227 let title_size = 30;
228 let width = self.columns.iter().fold(0, |total, col| total + col.width);
229 let height = self.rows.iter().fold(0, |total, row| total + row.height) + title_size;
230 let mut image = Image::new(
231 width + (self.margin * 2) as u32,
232 height + (self.margin * 2) as u32,
233 );
234 let roboto_bold = Vec::from(include_bytes!("fonts/Roboto-Bold.ttf") as &[u8]);
235 image.add_custom_font("bold", roboto_bold);
236
237 image.print_text(Text {
238 content: &self.title,
239 custom_font: Some("bold"),
240 position: Position {
241 x: self.margin,
242 y: self.margin,
243 },
244 color: None,
245 size: title_size - 5,
246 });
247
248 let mut row_position = self.margin + title_size as i32;
249 let mut column_position = self.margin;
250 let mut font_size = 14;
251 let mut custom_font = None;
252 for (r, row) in self.rows.iter().enumerate() {
253 if let Some(row_font_size) = row.font_size {
254 font_size = row_font_size;
255 }
256 if let Some(color) = row.color {
257 image.print_rect(
258 Position {
259 x: column_position,
260 y: row_position + 1,
261 },
262 Size {
263 height: row.height - 2,
264 width,
265 },
266 Color::Rgba(color),
267 );
268 }
269 if let Some(font) = &row.custom_font {
270 custom_font = Some(font.as_str());
271 }
272 for (c, column) in self.columns.iter().enumerate() {
273 if let Some(column_font_size) = column.font_size {
274 font_size = column_font_size;
275 }
276 if let Some(font) = &column.custom_font {
277 custom_font = Some(font.as_str());
278 }
279 if let Some(cell) = self.content.get(&(c as u32, r as u32)) {
280 if let Some(color) = cell.color {
281 image.print_rect(
282 Position {
283 x: column_position,
284 y: row_position + 1,
285 },
286 Size {
287 height: row.height - 2,
288 width: column.width,
289 },
290 Color::Rgba(color),
291 );
292 }
293 if let Some(cell_font_size) = cell.font_size {
294 font_size = cell_font_size;
295 }
296 if let Some(font) = &cell.custom_font {
297 custom_font = Some(font.as_str());
298 }
299 image.print_text(Text {
300 content: &cell.content,
301 size: font_size,
302 position: Position {
303 x: column_position + 5,
304 y: row_position + 3,
305 },
306 color: None,
307 custom_font,
308 });
309 }
310 column_position += column.width as i32;
311 }
312 font_size = 14;
313 row_position += row.height as i32;
314 column_position = self.margin;
315 custom_font = None;
316 }
317
318 image.save(output_path);
319 Ok(())
320 }
321}