use paperforge_core::{Color, Rect};
use crate::element::Align;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BorderStyle {
None,
Solid,
Dashed,
Dotted,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Border {
pub top: BorderStyle,
pub bottom: BorderStyle,
pub left: BorderStyle,
pub right: BorderStyle,
pub width: f64,
pub color: Color,
}
impl Default for Border {
fn default() -> Self {
Self {
top: BorderStyle::None,
bottom: BorderStyle::None,
left: BorderStyle::None,
right: BorderStyle::None,
width: 1.0,
color: Color::black(),
}
}
}
#[derive(Debug, Clone)]
pub struct Cell {
pub text: String,
pub align: Align,
pub font_size: f64,
pub color: Color,
pub border: Border,
pub bg_color: Option<Color>,
pub padding: f64,
pub col_span: u32,
pub row_span: u32,
}
impl Cell {
pub fn new(text: &str) -> Self {
Self {
text: text.to_string(),
align: Align::Left,
font_size: 11.0,
color: Color::black(),
border: Border::default(),
bg_color: None,
padding: 5.0,
col_span: 1,
row_span: 1,
}
}
pub fn align(mut self, align: Align) -> Self {
self.align = align;
self
}
pub fn font_size(mut self, size: f64) -> Self {
self.font_size = size;
self
}
pub fn color(mut self, color: Color) -> Self {
self.color = color;
self
}
pub fn border(mut self, border: Border) -> Self {
self.border = border;
self
}
pub fn bg_color(mut self, color: Color) -> Self {
self.bg_color = Some(color);
self
}
pub fn padding(mut self, padding: f64) -> Self {
self.padding = padding;
self
}
pub fn col_span(mut self, span: u32) -> Self {
self.col_span = span;
self
}
pub fn row_span(mut self, span: u32) -> Self {
self.row_span = span;
self
}
}
impl Default for Cell {
fn default() -> Self {
Self::new("")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum HeaderPosition {
#[default]
Top,
Bottom,
None,
}
#[derive(Debug, Clone)]
pub struct TableConfig {
pub headers: Vec<Cell>,
pub rows: Vec<Vec<Cell>>,
pub column_widths: Vec<f64>,
pub row_height: f64,
pub header_position: HeaderPosition,
pub border: Border,
pub header_bg: Option<Color>,
pub width: f64,
pub x: f64,
pub y: f64,
}
impl TableConfig {
pub fn new() -> Self {
Self {
headers: Vec::new(),
rows: Vec::new(),
column_widths: Vec::new(),
row_height: 20.0,
header_position: HeaderPosition::Top,
border: Border::default(),
header_bg: None,
width: 400.0,
x: 50.0,
y: 700.0,
}
}
pub fn headers(mut self, headers: Vec<Cell>) -> Self {
self.headers = headers;
self
}
pub fn header_labels(mut self, labels: &[&str]) -> Self {
self.headers = labels.iter().map(|s| Cell::new(s)).collect();
self
}
pub fn rows(mut self, rows: Vec<Vec<Cell>>) -> Self {
self.rows = rows;
self
}
pub fn row(mut self, cells: Vec<Cell>) -> Self {
self.rows.push(cells);
self
}
pub fn column_widths(mut self, widths: Vec<f64>) -> Self {
self.column_widths = widths;
self
}
pub fn auto_width(self, total_width: f64, n_cols: usize) -> Self {
let width = total_width / n_cols as f64;
let column_widths = vec![width; n_cols];
TableConfig {
column_widths,
..self
}
}
pub fn row_height(mut self, height: f64) -> Self {
self.row_height = height;
self
}
pub fn header_position(mut self, pos: HeaderPosition) -> Self {
self.header_position = pos;
self
}
pub fn border(mut self, border: Border) -> Self {
self.border = border;
self
}
pub fn header_bg(mut self, color: Color) -> Self {
self.header_bg = Some(color);
self
}
pub fn position(mut self, x: f64, y: f64) -> Self {
self.x = x;
self.y = y;
self
}
pub fn width(mut self, w: f64) -> Self {
self.width = w;
self
}
pub fn compute_widths(&self) -> Vec<f64> {
if !self.column_widths.is_empty() {
return self.column_widths.clone();
}
let n_cols = self
.headers
.len()
.max(self.rows.iter().map(|r| r.len()).max().unwrap_or(0));
if n_cols == 0 {
return Vec::new();
}
vec![self.width / n_cols as f64; n_cols]
}
pub fn column_count(&self) -> usize {
let header_cols = self.headers.len();
let max_row_cols = self.rows.iter().map(|r| r.len()).max().unwrap_or(0);
header_cols.max(max_row_cols)
}
pub fn cell_bounds(&self, row: usize, col: usize) -> Option<Rect> {
let widths = self.compute_widths();
let n_cols = widths.len();
if col >= n_cols {
return None;
}
let x = self.x + (0..col).map(|i| widths[i]).sum::<f64>();
let y = self.y - (row as f64) * self.row_height;
Some(Rect::new(x, y, widths[col], self.row_height))
}
}
impl Default for TableConfig {
fn default() -> Self {
Self::new()
}
}
pub struct TableBuilder {
table: TableConfig,
}
impl TableBuilder {
pub fn new() -> Self {
Self {
table: TableConfig::new(),
}
}
pub fn headers(mut self, header_labels: &[&str]) -> Self {
self.table.headers = header_labels.iter().map(|s| Cell::new(s)).collect();
self
}
pub fn header_cells(mut self, cells: Vec<Cell>) -> Self {
self.table.headers = cells;
self
}
pub fn row(mut self, cells: Vec<Cell>) -> Self {
self.table.rows.push(cells);
self
}
pub fn row_str(mut self, cells: &[&str]) -> Self {
self.table
.rows
.push(cells.iter().map(|s| Cell::new(s)).collect());
self
}
pub fn column_widths(mut self, widths: Vec<f64>) -> Self {
self.table.column_widths = widths;
self
}
pub fn row_height(mut self, height: f64) -> Self {
self.table.row_height = height;
self
}
pub fn border(mut self, border: Border) -> Self {
self.table.border = border;
self
}
pub fn header_bg(mut self, color: Color) -> Self {
self.table.header_bg = Some(color);
self
}
pub fn position(mut self, x: f64, y: f64) -> Self {
self.table.x = x;
self.table.y = y;
self
}
pub fn width(mut self, w: f64) -> Self {
self.table.width = w;
self
}
pub fn header_position(mut self, pos: HeaderPosition) -> Self {
self.table.header_position = pos;
self
}
pub fn build(self) -> TableConfig {
let widths = self.table.compute_widths();
if self.table.column_widths.is_empty() && widths.is_empty() {
let mut t = self.table.clone();
t.column_widths = vec![];
return t;
}
self.table
}
}
impl Default for TableBuilder {
fn default() -> Self {
Self::new()
}
}
pub fn all_border(style: BorderStyle, width: f64, color: Color) -> Border {
Border {
top: style,
bottom: style,
left: style,
right: style,
width,
color,
}
}
pub fn grid_border(color: Color) -> Border {
all_border(BorderStyle::Solid, 0.5, color)
}
pub fn outer_border(color: Color) -> Border {
Border {
top: BorderStyle::Solid,
bottom: BorderStyle::Solid,
left: BorderStyle::Solid,
right: BorderStyle::Solid,
width: 1.0,
color,
}
}