spreadsheet-maker 0.2.0

A tool for mount spreadsheets
Documentation
use std::{collections::HashMap, fs};

use image_builder::{Color, Image, Position, Size, Text};

const DEFAULT_COLUMN_WIDTH: u32 = 65;
const DEFAULT_ROW_HEIGHT: u32 = 20;

fn format_adapter(value: String) -> &'static str {
    let value: &'static str = Box::leak(value.into_boxed_str());
    value
}

pub struct Cell {
    pub column: u32,
    pub row: u32,
    pub content: String,
    pub color: Option<[u8; 4]>,
}
struct Column {
    pub width: u32,
    pub font_size: Option<u32>,
    pub custom_font: Option<String>,
}
struct Row {
    pub height: u32,
    pub color: Option<[u8; 4]>,
    pub font_size: Option<u32>,
    pub custom_font: Option<String>,
}

struct InternalCell {
    pub content: String,
    pub color: Option<[u8; 4]>,
    pub font_size: Option<u32>,
    pub custom_font: Option<String>,
}
pub struct Spreadsheet {
    title: String,
    margin: i32,
    columns: Vec<Column>,
    rows: Vec<Row>,
    content: HashMap<(u32, u32), InternalCell>,
}
impl Spreadsheet {
    pub fn new(title: String) -> Spreadsheet {
        Spreadsheet {
            title,
            margin: 0,
            columns: Vec::new(),
            rows: Vec::new(),
            content: HashMap::new(),
        }
    }
    fn add_column(&mut self, amount: u32) {
        if amount < 1 {
            panic!("A quantidade de colunas não pode ser menor ou igual a 0");
        }
        for _ in 0..amount {
            self.columns.push(Column {
                width: DEFAULT_COLUMN_WIDTH,
                font_size: None,
                custom_font: None,
            });
        }
    }
    fn add_row(&mut self, amount: u32) {
        if amount < 1 {
            panic!("A quantidade de linhas não pode ser menor ou igual a 0");
        }
        for _ in 0..amount {
            self.rows.push(Row {
                height: DEFAULT_ROW_HEIGHT,
                color: None,
                font_size: None,
                custom_font: None,
            });
        }
    }
    pub fn set_margin(&mut self, margin: i32) {
        self.margin = margin;
    }
    pub fn set_column_width(&mut self, number: usize, new_width: u32) {
        self.columns[number - 1].width = new_width;
    }
    pub fn set_column_font_size(&mut self, number: usize, new_size: u32) {
        self.columns[number - 1].font_size = Some(new_size);
    }
    pub fn set_column_custom_font(&mut self, number: usize, new_font: &str) {
        self.columns[number - 1].custom_font = Some(String::from(new_font));
    }
    pub fn set_row_height(&mut self, number: usize, new_height: u32) {
        self.rows[number - 1].height = new_height;
    }
    pub fn set_row_font_size(&mut self, number: usize, new_size: u32) {
        self.rows[number - 1].font_size = Some(new_size);
    }
    pub fn set_row_custom_font(&mut self, number: usize, new_font: &str) {
        self.rows[number - 1].custom_font = Some(String::from(new_font));
    }
    pub fn set_row_color(&mut self, number: usize, new_color: Option<[u8; 4]>) {
        self.rows[number - 1].color = new_color;
    }
    fn valid_cell(&mut self, cell: &Cell) {
        if cell.column < 1 || cell.row < 1 {
            panic!("Tentativa de criar célula na coluna ou linha menor que 1")
        }
        if (self.columns.len() as u32) < cell.column {
            self.add_column(cell.column - self.columns.len() as u32);
        }
        if (self.rows.len() as u32) < cell.row {
            self.add_row(cell.row - self.rows.len() as u32);
        }
    }
    pub fn set_cell_color(&mut self, position: (u32, u32), new_color: Option<[u8; 4]>) {
        let position = (position.0 - 1, position.1 - 1);
        if let Some(cell) = self.content.get(&position) {
            self.content.insert(
                position,
                InternalCell {
                    content: cell.content.clone(),
                    color: new_color,
                    font_size: cell.font_size,
                    custom_font: cell.custom_font.clone(),
                },
            );
        };
    }
    pub fn set_cell_font_size(&mut self, position: (u32, u32), new_size: u32) {
        let position = (position.0 - 1, position.1 - 1);
        if let Some(cell) = self.content.get(&position) {
            self.content.insert(
                position,
                InternalCell {
                    content: cell.content.clone(),
                    color: cell.color,
                    font_size: Some(new_size),
                    custom_font: cell.custom_font.clone(),
                },
            );
        };
    }
    pub fn set_cell_custom_font(&mut self, position: (u32, u32), new_font: &str) {
        let position = (position.0 - 1, position.1 - 1);
        if let Some(cell) = self.content.get(&position) {
            self.content.insert(
                position,
                InternalCell {
                    content: cell.content.clone(),
                    color: cell.color,
                    font_size: cell.font_size,
                    custom_font: Some(String::from(new_font)),
                },
            );
        };
    }
    pub fn set_cell(&mut self, cell: Cell) {
        self.valid_cell(&cell);
        self.content.insert(
            (cell.column - 1, cell.row - 1),
            InternalCell {
                content: cell.content,
                color: None,
                font_size: None,
                custom_font: None,
            },
        );
    }
    pub fn add_in_cell(&mut self, cell: Cell) {
        self.valid_cell(&cell);
        let index_cell = (cell.column - 1, cell.row - 1);
        match self.content.get(&index_cell) {
            Some(old_cell) => {
                let old_value: u32 = old_cell.content.parse().unwrap();
                let new_part: u32 = cell.content.parse().unwrap();
                self.content.insert(
                    index_cell,
                    InternalCell {
                        content: (old_value + new_part).to_string(),
                        color: old_cell.color,
                        font_size: old_cell.font_size,
                        custom_font: old_cell.custom_font.clone(),
                    },
                );
            }
            None => {
                self.content.insert(
                    index_cell,
                    InternalCell {
                        content: cell.content,
                        color: cell.color,
                        font_size: None,
                        custom_font: None,
                    },
                );
            }
        };
    }
    pub fn get_cell_value(&self, position: (u32, u32)) -> &String {
        let cell = self.content.get(&(position.0 - 1, position.1 - 1)).unwrap();
        &cell.content
    }
    pub fn save_csv(&self, output_path: String) -> Result<(), &'static str> {
        let mut csv = String::new();
        csv.push_str(format!("{}\n", self.title).as_str());
        for (r, _) in self.rows.iter().enumerate() {
            for (c, _) in self.columns.iter().enumerate() {
                if c != 0 {
                    csv.push(';')
                }
                if let Some(cell) = self.content.get(&(c as u32, r as u32)) {
                    csv.push_str(&cell.content)
                }
            }
            csv.push_str(" \n");
        }

        if let Err(error) = fs::write(format!("{}/spreadsheet.csv", output_path), csv) {
            return Err(format_adapter(format!(
                "Não foi possível salvar o arquivo em {}:\n{}",
                output_path, error
            )));
        };

        Ok(())
    }
    pub fn save_png(&self, output_path: &str) -> Result<(), &'static str> {
        let title_size = 30;
        let width = self.columns.iter().fold(0, |total, col| total + col.width);
        let height = self.rows.iter().fold(0, |total, row| total + row.height) + title_size;
        let mut image = Image::new(
            width + (self.margin * 2) as u32,
            height + (self.margin * 2) as u32,
        );
        let roboto_bold = Vec::from(include_bytes!("fonts/Roboto-Bold.ttf") as &[u8]);
        image.add_custom_font("bold", roboto_bold);

        image.print_text(Text {
            content: &self.title,
            custom_font: Some("bold"),
            position: Position {
                x: self.margin,
                y: self.margin,
            },
            color: None,
            size: title_size - 5,
        });

        let mut row_position = self.margin + title_size as i32;
        let mut column_position = self.margin;
        let mut font_size = 14;
        let mut custom_font = None;
        for (r, row) in self.rows.iter().enumerate() {
            if let Some(row_font_size) = row.font_size {
                font_size = row_font_size;
            }
            if let Some(color) = row.color {
                image.print_rect(
                    Position {
                        x: column_position,
                        y: row_position + 1,
                    },
                    Size {
                        height: row.height - 2,
                        width,
                    },
                    Color::Rgba(color),
                );
            }
            if let Some(font) = &row.custom_font {
                custom_font = Some(font.as_str());
            }
            for (c, column) in self.columns.iter().enumerate() {
                if let Some(column_font_size) = column.font_size {
                    font_size = column_font_size;
                }
                if let Some(font) = &column.custom_font {
                    custom_font = Some(font.as_str());
                }
                if let Some(cell) = self.content.get(&(c as u32, r as u32)) {
                    if let Some(color) = cell.color {
                        image.print_rect(
                            Position {
                                x: column_position,
                                y: row_position + 1,
                            },
                            Size {
                                height: row.height - 2,
                                width: column.width,
                            },
                            Color::Rgba(color),
                        );
                    }
                    if let Some(cell_font_size) = cell.font_size {
                        font_size = cell_font_size;
                    }
                    if let Some(font) = &cell.custom_font {
                        custom_font = Some(font.as_str());
                    }
                    image.print_text(Text {
                        content: &cell.content,
                        size: font_size,
                        position: Position {
                            x: column_position + 5,
                            y: row_position + 3,
                        },
                        color: None,
                        custom_font,
                    });
                }
                column_position += column.width as i32;
            }
            font_size = 14;
            row_position += row.height as i32;
            column_position = self.margin;
            custom_font = None;
        }

        image.save(output_path);
        Ok(())
    }
}