spreadsheet_maker/
lib.rs

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}