use crate::api::layout::TextAlign;
use crate::api::Color;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum VerticalAlign {
#[default]
Top,
Center,
Bottom,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum BorderLine {
#[default]
Solid,
Dashed,
Dotted,
}
#[derive(Debug, Clone)]
pub struct CellStyle {
pub padding: [f64; 4],
pub borders: [bool; 4],
pub border_widths: [f64; 4],
pub border_colors: [Color; 4],
pub border_lines: [BorderLine; 4],
pub background_color: Option<Color>,
pub text_color: Color,
pub align: TextAlign,
pub valign: VerticalAlign,
pub font: Option<String>,
pub font_size: Option<f64>,
pub overflow: TextOverflow,
pub min_width: Option<f64>,
pub max_width: Option<f64>,
pub single_line: bool,
}
impl Default for CellStyle {
fn default() -> Self {
Self {
padding: [5.0, 5.0, 5.0, 5.0],
borders: [true, true, true, true],
border_widths: [0.5, 0.5, 0.5, 0.5],
border_colors: [Color::BLACK, Color::BLACK, Color::BLACK, Color::BLACK],
border_lines: [BorderLine::Solid; 4],
background_color: None,
text_color: Color::BLACK,
align: TextAlign::Left,
valign: VerticalAlign::Top,
font: None,
font_size: None,
overflow: TextOverflow::Truncate,
min_width: None,
max_width: None,
single_line: false,
}
}
}
impl CellStyle {
pub fn new() -> Self {
Self::default()
}
pub fn padding(mut self, value: f64) -> Self {
self.padding = [value, value, value, value];
self
}
pub fn padding_vh(mut self, vertical: f64, horizontal: f64) -> Self {
self.padding = [vertical, horizontal, vertical, horizontal];
self
}
pub fn padding_trbl(mut self, top: f64, right: f64, bottom: f64, left: f64) -> Self {
self.padding = [top, right, bottom, left];
self
}
pub fn background(mut self, color: Color) -> Self {
self.background_color = Some(color);
self
}
pub fn color(mut self, color: Color) -> Self {
self.text_color = color;
self
}
pub fn align(mut self, align: TextAlign) -> Self {
self.align = align;
self
}
pub fn valign(mut self, valign: VerticalAlign) -> Self {
self.valign = valign;
self
}
pub fn border_width(mut self, width: f64) -> Self {
self.border_widths = [width, width, width, width];
self
}
pub fn border_color(mut self, color: Color) -> Self {
self.border_colors = [color, color, color, color];
self
}
pub fn no_borders(mut self) -> Self {
self.borders = [false, false, false, false];
self
}
pub fn all_borders(mut self) -> Self {
self.borders = [true, true, true, true];
self
}
pub fn top_border(mut self, enabled: bool) -> Self {
self.borders[0] = enabled;
self
}
pub fn right_border(mut self, enabled: bool) -> Self {
self.borders[1] = enabled;
self
}
pub fn bottom_border(mut self, enabled: bool) -> Self {
self.borders[2] = enabled;
self
}
pub fn left_border(mut self, enabled: bool) -> Self {
self.borders[3] = enabled;
self
}
pub fn border_line(mut self, line: BorderLine) -> Self {
self.border_lines = [line, line, line, line];
self
}
pub fn border_lines_trbl(
mut self,
top: BorderLine,
right: BorderLine,
bottom: BorderLine,
left: BorderLine,
) -> Self {
self.border_lines = [top, right, bottom, left];
self
}
pub fn border_widths_trbl(mut self, top: f64, right: f64, bottom: f64, left: f64) -> Self {
self.border_widths = [top, right, bottom, left];
self
}
pub fn font(mut self, name: impl Into<String>) -> Self {
self.font = Some(name.into());
self
}
pub fn font_size(mut self, size: f64) -> Self {
self.font_size = Some(size);
self
}
pub fn horizontal_padding(&self) -> f64 {
self.padding[1] + self.padding[3]
}
pub fn vertical_padding(&self) -> f64 {
self.padding[0] + self.padding[2]
}
pub fn overflow(mut self, overflow: TextOverflow) -> Self {
self.overflow = overflow;
self
}
pub fn min_width(mut self, width: f64) -> Self {
self.min_width = Some(width);
self
}
pub fn max_width(mut self, width: f64) -> Self {
self.max_width = Some(width);
self
}
pub fn single_line(mut self, single: bool) -> Self {
self.single_line = single;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum TextOverflow {
#[default]
Truncate,
ShrinkToFit(f64),
Expand,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum ImageFit {
#[default]
Contain,
Cover,
Fill,
None,
}
#[derive(Debug, Clone)]
pub struct ImageContent {
pub data: Vec<u8>,
pub width: Option<f64>,
pub height: Option<f64>,
pub fit: ImageFit,
pub scale: f64,
}
impl ImageContent {
pub fn new(data: Vec<u8>) -> Self {
Self {
data,
width: None,
height: None,
fit: ImageFit::Contain,
scale: 1.0,
}
}
pub fn fit(mut self, fit: ImageFit) -> Self {
self.fit = fit;
self
}
pub fn scale(mut self, scale: f64) -> Self {
self.scale = scale;
self
}
pub fn dimensions(mut self, width: f64, height: f64) -> Self {
self.width = Some(width);
self.height = Some(height);
self
}
}
#[derive(Debug, Clone, Default)]
pub enum CellContent {
Text(String),
Image(ImageContent),
Subtable(SubtableData),
#[default]
Empty,
}
#[derive(Debug, Clone)]
pub struct SubtableData {
pub rows: Vec<Vec<String>>,
pub column_widths: Option<Vec<f64>>,
}
impl From<&str> for CellContent {
fn from(s: &str) -> Self {
CellContent::Text(s.to_string())
}
}
impl From<String> for CellContent {
fn from(s: String) -> Self {
CellContent::Text(s)
}
}
#[derive(Debug, Clone)]
pub struct Cell {
pub content: CellContent,
pub row: usize,
pub column: usize,
pub colspan: usize,
pub rowspan: usize,
pub style: CellStyle,
pub(crate) x: f64,
pub(crate) y: f64,
pub(crate) width: f64,
pub(crate) height: f64,
}
impl Cell {
pub fn new(content: impl Into<CellContent>) -> Self {
Self {
content: content.into(),
row: 0,
column: 0,
colspan: 1,
rowspan: 1,
style: CellStyle::default(),
x: 0.0,
y: 0.0,
width: 0.0,
height: 0.0,
}
}
pub fn empty() -> Self {
Self::new(CellContent::Empty)
}
pub fn image(data: Vec<u8>) -> Self {
Self {
content: CellContent::Image(ImageContent::new(data)),
row: 0,
column: 0,
colspan: 1,
rowspan: 1,
style: CellStyle::default(),
x: 0.0,
y: 0.0,
width: 0.0,
height: 0.0,
}
}
pub fn image_with(content: ImageContent) -> Self {
Self {
content: CellContent::Image(content),
row: 0,
column: 0,
colspan: 1,
rowspan: 1,
style: CellStyle::default(),
x: 0.0,
y: 0.0,
width: 0.0,
height: 0.0,
}
}
pub fn subtable(rows: Vec<Vec<String>>) -> Self {
Self {
content: CellContent::Subtable(SubtableData {
rows,
column_widths: None,
}),
row: 0,
column: 0,
colspan: 1,
rowspan: 1,
style: CellStyle::default().padding(0.0), x: 0.0,
y: 0.0,
width: 0.0,
height: 0.0,
}
}
pub fn subtable_with_widths(rows: Vec<Vec<String>>, column_widths: Vec<f64>) -> Self {
Self {
content: CellContent::Subtable(SubtableData {
rows,
column_widths: Some(column_widths),
}),
row: 0,
column: 0,
colspan: 1,
rowspan: 1,
style: CellStyle::default().padding(0.0),
x: 0.0,
y: 0.0,
width: 0.0,
height: 0.0,
}
}
pub fn colspan(mut self, span: usize) -> Self {
self.colspan = span.max(1);
self
}
pub fn rowspan(mut self, span: usize) -> Self {
self.rowspan = span.max(1);
self
}
pub fn style(mut self, style: CellStyle) -> Self {
self.style = style;
self
}
pub fn background(mut self, color: Color) -> Self {
self.style.background_color = Some(color);
self
}
pub fn color(mut self, color: Color) -> Self {
self.style.text_color = color;
self
}
pub fn align(mut self, align: TextAlign) -> Self {
self.style.align = align;
self
}
pub fn content_width(&self) -> f64 {
(self.width - self.style.horizontal_padding()).max(0.0)
}
pub fn content_height(&self) -> f64 {
(self.height - self.style.vertical_padding()).max(0.0)
}
pub fn content_origin(&self) -> [f64; 2] {
[
self.x + self.style.padding[3], self.y - self.style.padding[0], ]
}
pub fn is_spanning(&self) -> bool {
self.colspan > 1 || self.rowspan > 1
}
pub fn text(&self) -> Option<&str> {
match &self.content {
CellContent::Text(s) => Some(s),
CellContent::Empty | CellContent::Image(_) | CellContent::Subtable(_) => None,
}
}
}
pub trait IntoCell {
fn into_cell(self) -> Cell;
}
impl IntoCell for Cell {
fn into_cell(self) -> Cell {
self
}
}
impl IntoCell for &str {
fn into_cell(self) -> Cell {
Cell::new(self)
}
}
impl IntoCell for String {
fn into_cell(self) -> Cell {
Cell::new(self)
}
}
impl IntoCell for &String {
fn into_cell(self) -> Cell {
Cell::new(self.clone())
}
}
macro_rules! impl_into_cell_for_numeric {
($($t:ty),*) => {
$(
impl IntoCell for $t {
fn into_cell(self) -> Cell {
Cell::new(self.to_string())
}
}
)*
};
}
impl_into_cell_for_numeric!(
i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64
);
pub struct CellSelection<'a> {
indices: Vec<usize>,
cells: &'a mut [Cell],
}
impl<'a> CellSelection<'a> {
pub fn new(cells: &'a mut [Cell], indices: Vec<usize>) -> Self {
Self { indices, cells }
}
fn max_row(&self) -> usize {
self.cells.iter().map(|c| c.row + 1).max().unwrap_or(0)
}
fn max_column(&self) -> usize {
self.cells.iter().map(|c| c.column + 1).max().unwrap_or(0)
}
pub fn rows(self, rows: impl RangeBoundsExt) -> Self {
let max_row = self.max_row();
let rows_range = rows.to_range(max_row);
let indices = self
.indices
.into_iter()
.filter(|&i| rows_range.contains(&self.cells[i].row))
.collect();
Self {
indices,
cells: self.cells,
}
}
pub fn columns(self, cols: impl RangeBoundsExt) -> Self {
let max_col = self.max_column();
let cols_range = cols.to_range(max_col);
let indices = self
.indices
.into_iter()
.filter(|&i| cols_range.contains(&self.cells[i].column))
.collect();
Self {
indices,
cells: self.cells,
}
}
pub fn style(self, style: CellStyle) -> Self {
for &i in &self.indices {
self.cells[i].style = style.clone();
}
self
}
pub fn background_color(self, color: Color) -> Self {
for &i in &self.indices {
self.cells[i].style.background_color = Some(color);
}
self
}
pub fn text_color(self, color: Color) -> Self {
for &i in &self.indices {
self.cells[i].style.text_color = color;
}
self
}
pub fn align(self, align: TextAlign) -> Self {
for &i in &self.indices {
self.cells[i].style.align = align;
}
self
}
pub fn valign(self, valign: VerticalAlign) -> Self {
for &i in &self.indices {
self.cells[i].style.valign = valign;
}
self
}
pub fn font(self, font: &str, size: Option<f64>) -> Self {
for &i in &self.indices {
self.cells[i].style.font = Some(font.to_string());
if let Some(s) = size {
self.cells[i].style.font_size = Some(s);
}
}
self
}
pub fn padding(self, padding: f64) -> Self {
for &i in &self.indices {
self.cells[i].style.padding = [padding; 4];
}
self
}
pub fn no_borders(self) -> Self {
for &i in &self.indices {
self.cells[i].style.borders = [false; 4];
}
self
}
pub fn border_width(self, width: f64) -> Self {
for &i in &self.indices {
self.cells[i].style.border_widths = [width; 4];
}
self
}
pub fn border_color(self, color: Color) -> Self {
for &i in &self.indices {
self.cells[i].style.border_colors = [color; 4];
}
self
}
pub fn each<F>(self, mut f: F) -> Self
where
F: FnMut(&mut Cell),
{
for &i in &self.indices {
f(&mut self.cells[i]);
}
self
}
}
pub trait RangeBoundsExt {
fn to_range(self, max: usize) -> std::ops::Range<usize>;
}
fn resolve_index(idx: isize, max: usize) -> usize {
if idx >= 0 {
(idx as usize).min(max)
} else {
let positive = max as isize + idx;
if positive < 0 {
0
} else {
positive as usize
}
}
}
impl RangeBoundsExt for usize {
fn to_range(self, _max: usize) -> std::ops::Range<usize> {
self..self + 1
}
}
impl RangeBoundsExt for isize {
fn to_range(self, max: usize) -> std::ops::Range<usize> {
let idx = resolve_index(self, max);
idx..idx + 1
}
}
impl RangeBoundsExt for i32 {
fn to_range(self, max: usize) -> std::ops::Range<usize> {
let idx = resolve_index(self as isize, max);
idx..idx + 1
}
}
impl RangeBoundsExt for std::ops::Range<usize> {
fn to_range(self, _max: usize) -> std::ops::Range<usize> {
self
}
}
impl RangeBoundsExt for std::ops::Range<isize> {
fn to_range(self, max: usize) -> std::ops::Range<usize> {
let start = resolve_index(self.start, max);
let end = resolve_index(self.end, max);
start..end
}
}
impl RangeBoundsExt for std::ops::Range<i32> {
fn to_range(self, max: usize) -> std::ops::Range<usize> {
let start = resolve_index(self.start as isize, max);
let end = resolve_index(self.end as isize, max);
start..end
}
}
impl RangeBoundsExt for std::ops::RangeFrom<usize> {
fn to_range(self, max: usize) -> std::ops::Range<usize> {
self.start..max
}
}
impl RangeBoundsExt for std::ops::RangeFrom<isize> {
fn to_range(self, max: usize) -> std::ops::Range<usize> {
let start = resolve_index(self.start, max);
start..max
}
}
impl RangeBoundsExt for std::ops::RangeTo<usize> {
fn to_range(self, _max: usize) -> std::ops::Range<usize> {
0..self.end
}
}
impl RangeBoundsExt for std::ops::RangeTo<isize> {
fn to_range(self, max: usize) -> std::ops::Range<usize> {
let end = resolve_index(self.end, max);
0..end
}
}
impl RangeBoundsExt for std::ops::RangeFull {
fn to_range(self, max: usize) -> std::ops::Range<usize> {
0..max
}
}
impl RangeBoundsExt for std::ops::RangeInclusive<usize> {
fn to_range(self, _max: usize) -> std::ops::Range<usize> {
*self.start()..*self.end() + 1
}
}
impl RangeBoundsExt for std::ops::RangeInclusive<isize> {
fn to_range(self, max: usize) -> std::ops::Range<usize> {
let start = resolve_index(*self.start(), max);
let end = resolve_index(*self.end(), max);
start..end + 1
}
}