use crate::color::Color;
use crate::content::{ContentStream, TextAlign};
use crate::font::BuiltinFont;
#[derive(Debug, Clone)]
pub struct TableCell {
pub text: String,
pub font: BuiltinFont,
pub font_size: f64,
pub text_color: Color,
pub background: Option<Color>,
pub align: TextAlign,
pub padding: f64,
pub bold: bool,
pub col_span: usize,
}
impl Default for TableCell {
fn default() -> Self {
Self {
text: String::new(),
font: BuiltinFont::Helvetica,
font_size: 10.0,
text_color: Color::BLACK,
background: None,
align: TextAlign::Left,
padding: 4.0,
bold: false,
col_span: 1,
}
}
}
impl TableCell {
pub fn new(text: impl Into<String>) -> Self {
Self { text: text.into(), ..Default::default() }
}
pub fn bold(mut self) -> Self {
self.bold = true;
self.font = BuiltinFont::HelveticaBold;
self
}
pub fn font_size(mut self, size: f64) -> Self {
self.font_size = size;
self
}
pub fn color(mut self, color: Color) -> Self {
self.text_color = color;
self
}
pub fn background(mut self, color: Color) -> Self {
self.background = Some(color);
self
}
pub fn align(mut self, align: TextAlign) -> Self {
self.align = align;
self
}
pub fn col_span(mut self, span: usize) -> Self {
self.col_span = span;
self
}
}
#[derive(Debug, Clone)]
pub struct TableRow {
pub cells: Vec<TableCell>,
pub height: Option<f64>,
pub background: Option<Color>,
pub is_header: bool,
}
impl TableRow {
pub fn new(cells: Vec<TableCell>) -> Self {
Self { cells, height: None, background: None, is_header: false }
}
pub fn header(cells: Vec<TableCell>) -> Self {
let cells: Vec<TableCell> = cells.into_iter().map(|c| c.bold()).collect();
Self { cells, height: None, background: Some(Color::rgb_u8(60, 90, 160)), is_header: true }
}
pub fn height(mut self, h: f64) -> Self {
self.height = Some(h);
self
}
pub fn background(mut self, color: Color) -> Self {
self.background = Some(color);
self
}
}
pub struct TableStyle {
pub border_color: Color,
pub border_width: f64,
pub header_text_color: Color,
pub row_alt_color: Option<Color>,
pub default_row_height: f64,
}
impl Default for TableStyle {
fn default() -> Self {
Self {
border_color: Color::rgb_u8(180, 180, 180),
border_width: 0.5,
header_text_color: Color::WHITE,
row_alt_color: Some(Color::rgb_u8(245, 247, 250)),
default_row_height: 20.0,
}
}
}
pub struct Table {
pub rows: Vec<TableRow>,
pub col_widths: Vec<f64>,
pub style: TableStyle,
pub font_prefix: String,
}
impl Table {
pub fn new(col_widths: Vec<f64>) -> Self {
Self {
rows: Vec::new(),
col_widths,
style: TableStyle::default(),
font_prefix: "F".into(),
}
}
pub fn style(mut self, style: TableStyle) -> Self {
self.style = style;
self
}
pub fn add_row(&mut self, row: TableRow) -> &mut Self {
self.rows.push(row);
self
}
pub fn total_width(&self) -> f64 {
self.col_widths.iter().sum()
}
pub fn total_height(&self) -> f64 {
self.rows.iter().map(|r| r.height.unwrap_or(self.style.default_row_height)).sum()
}
pub fn render(&self, cs: &mut ContentStream, x: f64, y_top: f64, font_prefix: &str) -> f64 {
let total_w = self.total_width();
let mut cur_y = y_top;
for (row_idx, row) in self.rows.iter().enumerate() {
let row_h = row.height.unwrap_or(self.style.default_row_height);
let row_y = cur_y - row_h;
let bg = if row.is_header {
row.background.or(Some(Color::rgb_u8(60, 90, 160)))
} else if let Some(bg) = row.background {
Some(bg)
} else if row_idx % 2 == 1 {
self.style.row_alt_color
} else {
None
};
if let Some(bg_color) = bg {
cs.filled_rect(x, row_y, total_w, row_h, bg_color);
}
let mut cell_x = x;
let mut col_idx = 0;
for cell in &row.cells {
let span = cell.col_span.max(1);
let cell_w: f64 = self.col_widths[col_idx..col_idx + span.min(self.col_widths.len() - col_idx)]
.iter()
.sum();
let text_color = if row.is_header {
self.style.header_text_color
} else {
cell.text_color
};
let font_key = if cell.bold || row.is_header {
format!("{}Bold", font_prefix)
} else {
format!("{}Reg", font_prefix)
};
let font = if cell.bold || row.is_header {
BuiltinFont::HelveticaBold
} else {
cell.font
};
let text_y = row_y + (row_h - cell.font_size) / 2.0 + 1.0;
let (text_x, align) = match cell.align {
TextAlign::Left => (cell_x + cell.padding, TextAlign::Left),
TextAlign::Center => (cell_x + cell_w / 2.0, TextAlign::Center),
TextAlign::Right => (cell_x + cell_w - cell.padding, TextAlign::Right),
};
cs.draw_text(&cell.text, text_x, text_y, &font_key, font, cell.font_size, text_color, align);
if col_idx + span < self.col_widths.len() {
cs.line(
cell_x + cell_w, row_y,
cell_x + cell_w, cur_y,
self.style.border_color,
self.style.border_width,
);
}
cell_x += cell_w;
col_idx += span;
}
cs.line(x, row_y, x + total_w, row_y, self.style.border_color, self.style.border_width);
cur_y = row_y;
}
cs.stroked_rect(x, cur_y, total_w, y_top - cur_y, self.style.border_color, self.style.border_width);
cur_y
}
}