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(())
}
}