use crate::graphics::Color;
use crate::text::Font;
use crate::CoordinateSystem;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum BorderStyle {
None,
#[default]
Solid,
Dashed,
Dotted,
Double,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum CellAlignment {
#[default]
Left,
Center,
Right,
Justify,
}
#[derive(Debug, Clone, Copy)]
pub struct Padding {
pub top: f64,
pub right: f64,
pub bottom: f64,
pub left: f64,
}
impl Padding {
pub fn new(top: f64, right: f64, bottom: f64, left: f64) -> Self {
Self {
top,
right,
bottom,
left,
}
}
pub fn uniform(padding: f64) -> Self {
Self {
top: padding,
right: padding,
bottom: padding,
left: padding,
}
}
pub fn symmetric(horizontal: f64, vertical: f64) -> Self {
Self {
top: vertical,
right: horizontal,
bottom: vertical,
left: horizontal,
}
}
pub fn individual(top: f64, right: f64, bottom: f64, left: f64) -> Self {
Self {
top,
right,
bottom,
left,
}
}
pub fn horizontal_total(&self) -> f64 {
self.left + self.right
}
pub fn vertical_total(&self) -> f64 {
self.top + self.bottom
}
pub fn pad_vertically(&self, coordinate_system: &CoordinateSystem, y: f64) -> f64 {
let mut padded = y;
match coordinate_system {
CoordinateSystem::PdfStandard | CoordinateSystem::Custom(_) => {
padded -= self.top;
padded += self.bottom;
}
CoordinateSystem::ScreenSpace => {
padded += self.top;
padded -= self.bottom;
}
}
padded
}
pub fn pad_horizontally(&self, x: f64) -> f64 {
let mut padded = x;
padded -= self.right;
padded += self.left;
padded
}
}
impl Default for Padding {
fn default() -> Self {
Self::uniform(4.0)
}
}
#[derive(Debug, Clone)]
pub struct CellStyle {
pub background_color: Option<Color>,
pub text_color: Option<Color>,
pub font: Option<Font>,
pub font_size: Option<f64>,
pub padding: Padding,
pub alignment: CellAlignment,
pub border: BorderConfiguration,
pub border_style: BorderStyle,
pub text_wrap: bool,
pub min_height: Option<f64>,
pub max_height: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct BorderConfiguration {
pub top: BorderEdge,
pub right: BorderEdge,
pub bottom: BorderEdge,
pub left: BorderEdge,
}
#[derive(Debug, Clone)]
pub struct BorderEdge {
pub style: BorderStyle,
pub width: f64,
pub color: Color,
}
impl BorderEdge {
pub fn new(style: BorderStyle, width: f64, color: Color) -> Self {
Self {
style,
width,
color,
}
}
pub fn solid(width: f64) -> Self {
Self::new(BorderStyle::Solid, width, Color::black())
}
pub fn dashed(width: f64, color: Color) -> Self {
Self::new(BorderStyle::Dashed, width, color)
}
pub fn dotted(width: f64, color: Color) -> Self {
Self::new(BorderStyle::Dotted, width, color)
}
pub fn none() -> Self {
Self::new(BorderStyle::None, 0.0, Color::black())
}
}
impl Default for BorderEdge {
fn default() -> Self {
Self::solid(1.0)
}
}
impl BorderConfiguration {
pub fn new() -> Self {
Self {
top: BorderEdge::default(),
right: BorderEdge::default(),
bottom: BorderEdge::default(),
left: BorderEdge::default(),
}
}
pub fn uniform(edge: BorderEdge) -> Self {
Self {
top: edge.clone(),
right: edge.clone(),
bottom: edge.clone(),
left: edge,
}
}
pub fn edges(top: bool, right: bool, bottom: bool, left: bool) -> Self {
let solid_edge = BorderEdge::solid(1.0);
let no_edge = BorderEdge::none();
Self {
top: if top {
solid_edge.clone()
} else {
no_edge.clone()
},
right: if right {
solid_edge.clone()
} else {
no_edge.clone()
},
bottom: if bottom {
solid_edge.clone()
} else {
no_edge.clone()
},
left: if left { solid_edge } else { no_edge },
}
}
pub fn none() -> Self {
let no_edge = BorderEdge::none();
Self {
top: no_edge.clone(),
right: no_edge.clone(),
bottom: no_edge.clone(),
left: no_edge,
}
}
}
impl Default for BorderConfiguration {
fn default() -> Self {
Self::new()
}
}
impl CellStyle {
pub fn new() -> Self {
Self {
background_color: None,
text_color: Some(Color::black()),
font: Some(Font::Helvetica),
font_size: Some(12.0),
padding: Padding::default(),
alignment: CellAlignment::Left,
border: BorderConfiguration::default(),
border_style: BorderStyle::Solid,
text_wrap: true,
min_height: None,
max_height: None,
}
}
pub fn background_color(mut self, color: Color) -> Self {
self.background_color = Some(color);
self
}
pub fn text_color(mut self, color: Color) -> Self {
self.text_color = Some(color);
self
}
pub fn font(mut self, font: Font) -> Self {
self.font = Some(font);
self
}
pub fn font_size(mut self, size: f64) -> Self {
self.font_size = Some(size);
self
}
pub fn padding(mut self, padding: Padding) -> Self {
self.padding = padding;
self
}
pub fn alignment(mut self, alignment: CellAlignment) -> Self {
self.alignment = alignment;
self
}
pub fn border_config(mut self, border: BorderConfiguration) -> Self {
self.border = border;
self
}
pub fn border(mut self, style: BorderStyle, width: f64, color: Color) -> Self {
self.border_style = style;
self.border = BorderConfiguration::uniform(BorderEdge::new(style, width, color));
self
}
pub fn text_wrap(mut self, wrap: bool) -> Self {
self.text_wrap = wrap;
self
}
pub fn min_height(mut self, height: f64) -> Self {
self.min_height = Some(height);
self
}
pub fn max_height(mut self, height: f64) -> Self {
self.max_height = Some(height);
self
}
pub fn header() -> Self {
Self::new()
.font(Font::HelveticaBold)
.font_size(14.0)
.alignment(CellAlignment::Center)
.background_color(Color::rgb(0.9, 0.9, 0.9))
.padding(Padding::uniform(8.0))
}
pub fn data() -> Self {
Self::new()
.font(Font::Helvetica)
.font_size(12.0)
.alignment(CellAlignment::Left)
.padding(Padding::uniform(6.0))
}
pub fn numeric() -> Self {
Self::new()
.font(Font::Courier)
.font_size(11.0)
.alignment(CellAlignment::Right)
.padding(Padding::uniform(6.0))
}
pub fn alternating() -> Self {
Self::data().background_color(Color::rgb(0.97, 0.97, 0.97))
}
}
impl Default for CellStyle {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pad_vertically() {
let cs = CoordinateSystem::PdfStandard;
let padding = Padding::new(6.0, 0.0, 2.0, 2.0);
assert_eq!(padding.pad_vertically(&cs, 100.0), 96.0);
assert_eq!(padding.pad_vertically(&cs, 50.0), 46.0);
let cs = CoordinateSystem::ScreenSpace;
let padding = Padding::new(6.0, 0.0, 2.0, 2.0);
assert_eq!(padding.pad_vertically(&cs, 100.0), 104.0);
assert_eq!(padding.pad_vertically(&cs, 50.0), 54.0);
}
#[test]
fn test_pad_horizontally() {
let padding = Padding::new(6.0, 12.0, 2.0, 2.0);
assert_eq!(padding.pad_horizontally(100.0), 90.0);
assert_eq!(padding.pad_horizontally(50.0), 40.0);
}
#[test]
fn test_border_style_default() {
let style = BorderStyle::default();
assert_eq!(style, BorderStyle::Solid);
}
#[test]
fn test_cell_alignment_default() {
let alignment = CellAlignment::default();
assert_eq!(alignment, CellAlignment::Left);
}
#[test]
fn test_padding_uniform() {
let padding = Padding::uniform(10.0);
assert_eq!(padding.top, 10.0);
assert_eq!(padding.right, 10.0);
assert_eq!(padding.bottom, 10.0);
assert_eq!(padding.left, 10.0);
}
#[test]
fn test_padding_symmetric() {
let padding = Padding::symmetric(5.0, 10.0);
assert_eq!(padding.top, 10.0);
assert_eq!(padding.right, 5.0);
assert_eq!(padding.bottom, 10.0);
assert_eq!(padding.left, 5.0);
}
#[test]
fn test_padding_individual() {
let padding = Padding::individual(1.0, 2.0, 3.0, 4.0);
assert_eq!(padding.top, 1.0);
assert_eq!(padding.right, 2.0);
assert_eq!(padding.bottom, 3.0);
assert_eq!(padding.left, 4.0);
}
#[test]
fn test_padding_totals() {
let padding = Padding::new(5.0, 10.0, 15.0, 20.0);
assert_eq!(padding.horizontal_total(), 30.0);
assert_eq!(padding.vertical_total(), 20.0);
}
#[test]
fn test_padding_default() {
let padding = Padding::default();
assert_eq!(padding.top, 4.0);
assert_eq!(padding.horizontal_total(), 8.0);
}
#[test]
fn test_border_edge_new() {
let edge = BorderEdge::new(BorderStyle::Dashed, 2.0, Color::red());
assert_eq!(edge.style, BorderStyle::Dashed);
assert_eq!(edge.width, 2.0);
}
#[test]
fn test_border_edge_solid() {
let edge = BorderEdge::solid(1.5);
assert_eq!(edge.style, BorderStyle::Solid);
assert_eq!(edge.width, 1.5);
}
#[test]
fn test_border_edge_dashed() {
let edge = BorderEdge::dashed(1.0, Color::blue());
assert_eq!(edge.style, BorderStyle::Dashed);
}
#[test]
fn test_border_edge_dotted() {
let edge = BorderEdge::dotted(0.5, Color::green());
assert_eq!(edge.style, BorderStyle::Dotted);
}
#[test]
fn test_border_edge_none() {
let edge = BorderEdge::none();
assert_eq!(edge.style, BorderStyle::None);
assert_eq!(edge.width, 0.0);
}
#[test]
fn test_border_edge_default() {
let edge = BorderEdge::default();
assert_eq!(edge.style, BorderStyle::Solid);
assert_eq!(edge.width, 1.0);
}
#[test]
fn test_border_configuration_new() {
let config = BorderConfiguration::new();
assert_eq!(config.top.style, BorderStyle::Solid);
assert_eq!(config.right.style, BorderStyle::Solid);
assert_eq!(config.bottom.style, BorderStyle::Solid);
assert_eq!(config.left.style, BorderStyle::Solid);
}
#[test]
fn test_border_configuration_uniform() {
let edge = BorderEdge::dashed(2.0, Color::red());
let config = BorderConfiguration::uniform(edge);
assert_eq!(config.top.style, BorderStyle::Dashed);
assert_eq!(config.right.width, 2.0);
assert_eq!(config.bottom.style, BorderStyle::Dashed);
}
#[test]
fn test_border_configuration_edges() {
let config = BorderConfiguration::edges(true, false, true, false);
assert_eq!(config.top.style, BorderStyle::Solid);
assert_eq!(config.right.style, BorderStyle::None);
assert_eq!(config.bottom.style, BorderStyle::Solid);
assert_eq!(config.left.style, BorderStyle::None);
}
#[test]
fn test_border_configuration_none() {
let config = BorderConfiguration::none();
assert_eq!(config.top.style, BorderStyle::None);
assert_eq!(config.right.style, BorderStyle::None);
assert_eq!(config.bottom.style, BorderStyle::None);
assert_eq!(config.left.style, BorderStyle::None);
}
#[test]
fn test_border_configuration_default() {
let config = BorderConfiguration::default();
assert_eq!(config.top.style, BorderStyle::Solid);
}
#[test]
fn test_cell_style_new() {
let style = CellStyle::new();
assert!(style.background_color.is_none());
assert!(style.text_color.is_some());
assert_eq!(style.font_size, Some(12.0));
assert!(style.text_wrap);
}
#[test]
fn test_cell_style_background_color() {
let style = CellStyle::new().background_color(Color::yellow());
assert!(style.background_color.is_some());
}
#[test]
fn test_cell_style_text_color() {
let style = CellStyle::new().text_color(Color::blue());
assert!(style.text_color.is_some());
}
#[test]
fn test_cell_style_font() {
let style = CellStyle::new().font(Font::CourierBold);
assert_eq!(style.font, Some(Font::CourierBold));
}
#[test]
fn test_cell_style_font_size() {
let style = CellStyle::new().font_size(18.0);
assert_eq!(style.font_size, Some(18.0));
}
#[test]
fn test_cell_style_padding() {
let style = CellStyle::new().padding(Padding::uniform(20.0));
assert_eq!(style.padding.top, 20.0);
}
#[test]
fn test_cell_style_alignment() {
let style = CellStyle::new().alignment(CellAlignment::Center);
assert_eq!(style.alignment, CellAlignment::Center);
}
#[test]
fn test_cell_style_border_config() {
let config = BorderConfiguration::none();
let style = CellStyle::new().border_config(config);
assert_eq!(style.border.top.style, BorderStyle::None);
}
#[test]
fn test_cell_style_border() {
let style = CellStyle::new().border(BorderStyle::Dashed, 2.0, Color::red());
assert_eq!(style.border_style, BorderStyle::Dashed);
assert_eq!(style.border.top.width, 2.0);
}
#[test]
fn test_cell_style_text_wrap() {
let style = CellStyle::new().text_wrap(false);
assert!(!style.text_wrap);
}
#[test]
fn test_cell_style_min_height() {
let style = CellStyle::new().min_height(50.0);
assert_eq!(style.min_height, Some(50.0));
}
#[test]
fn test_cell_style_max_height() {
let style = CellStyle::new().max_height(100.0);
assert_eq!(style.max_height, Some(100.0));
}
#[test]
fn test_cell_style_header() {
let style = CellStyle::header();
assert_eq!(style.font, Some(Font::HelveticaBold));
assert_eq!(style.font_size, Some(14.0));
assert_eq!(style.alignment, CellAlignment::Center);
assert!(style.background_color.is_some());
}
#[test]
fn test_cell_style_data() {
let style = CellStyle::data();
assert_eq!(style.font, Some(Font::Helvetica));
assert_eq!(style.font_size, Some(12.0));
assert_eq!(style.alignment, CellAlignment::Left);
}
#[test]
fn test_cell_style_numeric() {
let style = CellStyle::numeric();
assert_eq!(style.font, Some(Font::Courier));
assert_eq!(style.font_size, Some(11.0));
assert_eq!(style.alignment, CellAlignment::Right);
}
#[test]
fn test_cell_style_alternating() {
let style = CellStyle::alternating();
assert!(style.background_color.is_some());
assert_eq!(style.alignment, CellAlignment::Left);
}
#[test]
fn test_cell_style_default() {
let style = CellStyle::default();
assert_eq!(style.font_size, Some(12.0));
}
#[test]
fn test_border_style_variants() {
let styles = vec![
BorderStyle::None,
BorderStyle::Solid,
BorderStyle::Dashed,
BorderStyle::Dotted,
BorderStyle::Double,
];
for style in styles {
let cloned = style;
assert_eq!(style, cloned);
}
}
#[test]
fn test_cell_alignment_variants() {
let alignments = vec![
CellAlignment::Left,
CellAlignment::Center,
CellAlignment::Right,
CellAlignment::Justify,
];
for alignment in alignments {
let cloned = alignment;
assert_eq!(alignment, cloned);
}
}
}