use crate::types::{Color, Handle, LineWeight};
use bitflags::bitflags;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(i16)]
pub enum TableFlowDirection {
#[default]
Down = 0,
Up = 1,
}
impl From<i16> for TableFlowDirection {
fn from(value: i16) -> Self {
match value {
1 => Self::Up,
_ => Self::Down,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(i16)]
pub enum CellAlignment {
TopLeft = 1,
TopCenter = 2,
TopRight = 3,
MiddleLeft = 4,
#[default]
MiddleCenter = 5,
MiddleRight = 6,
BottomLeft = 7,
BottomCenter = 8,
BottomRight = 9,
}
impl From<i16> for CellAlignment {
fn from(value: i16) -> Self {
match value {
1 => Self::TopLeft,
2 => Self::TopCenter,
3 => Self::TopRight,
4 => Self::MiddleLeft,
5 => Self::MiddleCenter,
6 => Self::MiddleRight,
7 => Self::BottomLeft,
8 => Self::BottomCenter,
9 => Self::BottomRight,
_ => Self::MiddleCenter,
}
}
}
impl CellAlignment {
pub fn is_top(&self) -> bool {
matches!(self, Self::TopLeft | Self::TopCenter | Self::TopRight)
}
pub fn is_middle(&self) -> bool {
matches!(self, Self::MiddleLeft | Self::MiddleCenter | Self::MiddleRight)
}
pub fn is_bottom(&self) -> bool {
matches!(self, Self::BottomLeft | Self::BottomCenter | Self::BottomRight)
}
pub fn is_left(&self) -> bool {
matches!(self, Self::TopLeft | Self::MiddleLeft | Self::BottomLeft)
}
pub fn is_center(&self) -> bool {
matches!(self, Self::TopCenter | Self::MiddleCenter | Self::BottomCenter)
}
pub fn is_right(&self) -> bool {
matches!(self, Self::TopRight | Self::MiddleRight | Self::BottomRight)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(i16)]
pub enum TableBorderType {
#[default]
Single = 1,
Double = 2,
}
impl From<i16> for TableBorderType {
fn from(value: i16) -> Self {
match value {
2 => Self::Double,
_ => Self::Single,
}
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TableCellStylePropertyFlags: i32 {
const NONE = 0x0;
const DATA_TYPE = 0x1;
const DATA_FORMAT = 0x2;
const ROTATION = 0x4;
const BLOCK_SCALE = 0x8;
const ALIGNMENT = 0x10;
const CONTENT_COLOR = 0x20;
const TEXT_STYLE = 0x40;
const TEXT_HEIGHT = 0x80;
const AUTO_SCALE = 0x100;
const BACKGROUND_COLOR = 0x200;
const MARGIN_LEFT = 0x400;
const MARGIN_TOP = 0x800;
const MARGIN_RIGHT = 0x1000;
const MARGIN_BOTTOM = 0x2000;
const CONTENT_LAYOUT = 0x4000;
const MERGE_ALL = 0x8000;
const FLOW_DIRECTION_BOTTOM_TO_TOP = 0x10000;
const MARGIN_HORIZONTAL_SPACING = 0x20000;
const MARGIN_VERTICAL_SPACING = 0x40000;
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TableBorderPropertyFlags: i32 {
const NONE = 0x0;
const LINE_WEIGHT = 0x1;
const LINE_TYPE = 0x2;
const COLOR = 0x4;
const VISIBILITY = 0x8;
const DOUBLE_LINE_SPACING = 0x10;
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TableStyleFlags: i16 {
const NONE = 0;
const TITLE_SUPPRESSED = 1;
const HEADER_SUPPRESSED = 2;
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TableCellBorder {
pub property_flags: TableBorderPropertyFlags,
pub border_type: TableBorderType,
pub line_weight: LineWeight,
pub color: Color,
pub is_invisible: bool,
pub double_line_spacing: f64,
}
impl TableCellBorder {
pub fn new() -> Self {
TableCellBorder {
property_flags: TableBorderPropertyFlags::NONE,
border_type: TableBorderType::Single,
line_weight: LineWeight::ByBlock,
color: Color::ByBlock,
is_invisible: false,
double_line_spacing: 0.0,
}
}
pub fn invisible() -> Self {
TableCellBorder {
is_invisible: true,
..Self::new()
}
}
pub fn with_style(color: Color, line_weight: LineWeight) -> Self {
TableCellBorder {
color,
line_weight,
..Self::new()
}
}
}
impl Default for TableCellBorder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RowCellStyle {
pub text_style_name: String,
pub text_style_handle: Option<Handle>,
pub text_height: f64,
pub alignment: CellAlignment,
pub text_color: Color,
pub fill_color: Color,
pub fill_enabled: bool,
pub data_type: i32,
pub unit_type: i32,
pub format_string: String,
pub left_border: TableCellBorder,
pub right_border: TableCellBorder,
pub top_border: TableCellBorder,
pub bottom_border: TableCellBorder,
pub horizontal_inside_border: TableCellBorder,
pub vertical_inside_border: TableCellBorder,
}
impl RowCellStyle {
pub fn new() -> Self {
RowCellStyle {
text_style_name: "Standard".to_string(),
text_style_handle: None,
text_height: 0.18,
alignment: CellAlignment::MiddleCenter,
text_color: Color::ByBlock,
fill_color: Color::Index(7), fill_enabled: false,
data_type: 512,
unit_type: 0,
format_string: String::new(),
left_border: TableCellBorder::new(),
right_border: TableCellBorder::new(),
top_border: TableCellBorder::new(),
bottom_border: TableCellBorder::new(),
horizontal_inside_border: TableCellBorder::new(),
vertical_inside_border: TableCellBorder::new(),
}
}
pub fn data_row() -> Self {
RowCellStyle {
text_height: 0.18,
alignment: CellAlignment::MiddleCenter,
..Self::new()
}
}
pub fn header_row() -> Self {
RowCellStyle {
text_height: 0.25,
alignment: CellAlignment::TopCenter,
..Self::new()
}
}
pub fn title_row() -> Self {
RowCellStyle {
text_height: 0.25,
alignment: CellAlignment::TopCenter,
..Self::new()
}
}
pub fn set_all_borders(&mut self, border: TableCellBorder) {
self.left_border = border.clone();
self.right_border = border.clone();
self.top_border = border.clone();
self.bottom_border = border.clone();
self.horizontal_inside_border = border.clone();
self.vertical_inside_border = border;
}
pub fn set_all_borders_invisible(&mut self) {
self.set_all_borders(TableCellBorder::invisible());
}
}
impl Default for RowCellStyle {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TableStyle {
pub handle: Handle,
pub owner_handle: Handle,
pub name: String,
pub description: String,
pub version: i16,
pub flow_direction: TableFlowDirection,
pub flags: TableStyleFlags,
pub horizontal_margin: f64,
pub vertical_margin: f64,
pub title_suppressed: bool,
pub header_suppressed: bool,
pub data_row_style: RowCellStyle,
pub header_row_style: RowCellStyle,
pub title_row_style: RowCellStyle,
}
impl TableStyle {
pub const OBJECT_NAME: &'static str = "TABLESTYLE";
pub const SUBCLASS_MARKER: &'static str = "AcDbTableStyle";
pub const STANDARD: &'static str = "Standard";
pub fn new(name: &str) -> Self {
TableStyle {
handle: Handle::NULL,
owner_handle: Handle::NULL,
name: name.to_string(),
description: String::new(),
version: 0,
flow_direction: TableFlowDirection::Down,
flags: TableStyleFlags::NONE,
horizontal_margin: 0.06,
vertical_margin: 0.06,
title_suppressed: false,
header_suppressed: false,
data_row_style: RowCellStyle::data_row(),
header_row_style: RowCellStyle::header_row(),
title_row_style: RowCellStyle::title_row(),
}
}
pub fn standard() -> Self {
Self::new(Self::STANDARD)
}
pub fn set_margins(&mut self, margin: f64) {
self.horizontal_margin = margin;
self.vertical_margin = margin;
}
pub fn set_all_text_heights(&mut self, height: f64) {
self.data_row_style.text_height = height;
self.header_row_style.text_height = height;
self.title_row_style.text_height = height;
}
pub fn set_all_text_styles(&mut self, name: &str, handle: Option<Handle>) {
self.data_row_style.text_style_name = name.to_string();
self.data_row_style.text_style_handle = handle;
self.header_row_style.text_style_name = name.to_string();
self.header_row_style.text_style_handle = handle;
self.title_row_style.text_style_name = name.to_string();
self.title_row_style.text_style_handle = handle;
}
pub fn set_all_text_colors(&mut self, color: Color) {
self.data_row_style.text_color = color;
self.header_row_style.text_color = color;
self.title_row_style.text_color = color;
}
pub fn set_all_fill_colors(&mut self, color: Color) {
self.data_row_style.fill_color = color;
self.header_row_style.fill_color = color;
self.title_row_style.fill_color = color;
}
pub fn set_all_fill_enabled(&mut self, enabled: bool) {
self.data_row_style.fill_enabled = enabled;
self.header_row_style.fill_enabled = enabled;
self.title_row_style.fill_enabled = enabled;
}
pub fn has_title_row(&self) -> bool {
!self.title_suppressed
}
pub fn has_header_row(&self) -> bool {
!self.header_suppressed
}
pub fn set_title_visible(&mut self, visible: bool) {
self.title_suppressed = !visible;
}
pub fn set_header_visible(&mut self, visible: bool) {
self.header_suppressed = !visible;
}
}
impl Default for TableStyle {
fn default() -> Self {
Self::standard()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tablestyle_creation() {
let style = TableStyle::new("TestStyle");
assert_eq!(style.name, "TestStyle");
assert!(style.description.is_empty());
}
#[test]
fn test_standard_style() {
let style = TableStyle::standard();
assert_eq!(style.name, "Standard");
}
#[test]
fn test_default_values() {
let style = TableStyle::default();
assert!((style.horizontal_margin - 0.06).abs() < 1e-10);
assert!((style.vertical_margin - 0.06).abs() < 1e-10);
assert!(!style.title_suppressed);
assert!(!style.header_suppressed);
assert_eq!(style.flow_direction, TableFlowDirection::Down);
}
#[test]
fn test_data_row_style() {
let style = TableStyle::default();
assert!((style.data_row_style.text_height - 0.18).abs() < 1e-10);
assert_eq!(style.data_row_style.alignment, CellAlignment::MiddleCenter);
}
#[test]
fn test_header_row_style() {
let style = TableStyle::default();
assert!((style.header_row_style.text_height - 0.25).abs() < 1e-10);
assert_eq!(style.header_row_style.alignment, CellAlignment::TopCenter);
}
#[test]
fn test_title_row_style() {
let style = TableStyle::default();
assert!((style.title_row_style.text_height - 0.25).abs() < 1e-10);
}
#[test]
fn test_flow_direction_enum() {
assert_eq!(TableFlowDirection::from(0), TableFlowDirection::Down);
assert_eq!(TableFlowDirection::from(1), TableFlowDirection::Up);
}
#[test]
fn test_cell_alignment_enum() {
assert_eq!(CellAlignment::from(1), CellAlignment::TopLeft);
assert_eq!(CellAlignment::from(5), CellAlignment::MiddleCenter);
assert_eq!(CellAlignment::from(9), CellAlignment::BottomRight);
assert_eq!(CellAlignment::from(99), CellAlignment::MiddleCenter);
}
#[test]
fn test_cell_alignment_helpers() {
let top_left = CellAlignment::TopLeft;
assert!(top_left.is_top());
assert!(top_left.is_left());
assert!(!top_left.is_center());
assert!(!top_left.is_bottom());
let middle_center = CellAlignment::MiddleCenter;
assert!(middle_center.is_middle());
assert!(middle_center.is_center());
let bottom_right = CellAlignment::BottomRight;
assert!(bottom_right.is_bottom());
assert!(bottom_right.is_right());
}
#[test]
fn test_set_margins() {
let mut style = TableStyle::new("Test");
style.set_margins(0.15);
assert_eq!(style.horizontal_margin, 0.15);
assert_eq!(style.vertical_margin, 0.15);
}
#[test]
fn test_set_all_text_heights() {
let mut style = TableStyle::new("Test");
style.set_all_text_heights(0.3);
assert_eq!(style.data_row_style.text_height, 0.3);
assert_eq!(style.header_row_style.text_height, 0.3);
assert_eq!(style.title_row_style.text_height, 0.3);
}
#[test]
fn test_set_all_text_colors() {
let mut style = TableStyle::new("Test");
style.set_all_text_colors(Color::Index(1));
assert_eq!(style.data_row_style.text_color, Color::Index(1));
assert_eq!(style.header_row_style.text_color, Color::Index(1));
assert_eq!(style.title_row_style.text_color, Color::Index(1));
}
#[test]
fn test_set_all_fill_enabled() {
let mut style = TableStyle::new("Test");
style.set_all_fill_enabled(true);
assert!(style.data_row_style.fill_enabled);
assert!(style.header_row_style.fill_enabled);
assert!(style.title_row_style.fill_enabled);
}
#[test]
fn test_row_visibility() {
let mut style = TableStyle::new("Test");
assert!(style.has_title_row());
assert!(style.has_header_row());
style.set_title_visible(false);
style.set_header_visible(false);
assert!(!style.has_title_row());
assert!(!style.has_header_row());
}
#[test]
fn test_cell_border_creation() {
let border = TableCellBorder::new();
assert!(!border.is_invisible);
assert_eq!(border.border_type, TableBorderType::Single);
}
#[test]
fn test_cell_border_invisible() {
let border = TableCellBorder::invisible();
assert!(border.is_invisible);
}
#[test]
fn test_cell_border_with_style() {
let border = TableCellBorder::with_style(Color::Index(1), LineWeight::W0_50);
assert_eq!(border.color, Color::Index(1));
assert_eq!(border.line_weight, LineWeight::W0_50);
}
#[test]
fn test_set_all_borders() {
let mut style = RowCellStyle::new();
let border = TableCellBorder::with_style(Color::Index(3), LineWeight::W1_00);
style.set_all_borders(border);
assert_eq!(style.left_border.color, Color::Index(3));
assert_eq!(style.right_border.color, Color::Index(3));
assert_eq!(style.top_border.color, Color::Index(3));
assert_eq!(style.bottom_border.color, Color::Index(3));
}
#[test]
fn test_set_all_borders_invisible() {
let mut style = RowCellStyle::new();
style.set_all_borders_invisible();
assert!(style.left_border.is_invisible);
assert!(style.right_border.is_invisible);
assert!(style.top_border.is_invisible);
assert!(style.bottom_border.is_invisible);
}
#[test]
fn test_cell_style_property_flags() {
let flags = TableCellStylePropertyFlags::ALIGNMENT
| TableCellStylePropertyFlags::TEXT_HEIGHT
| TableCellStylePropertyFlags::BACKGROUND_COLOR;
assert!(flags.contains(TableCellStylePropertyFlags::ALIGNMENT));
assert!(flags.contains(TableCellStylePropertyFlags::TEXT_HEIGHT));
assert!(!flags.contains(TableCellStylePropertyFlags::ROTATION));
}
#[test]
fn test_border_property_flags() {
let flags = TableBorderPropertyFlags::COLOR | TableBorderPropertyFlags::LINE_WEIGHT;
assert!(flags.contains(TableBorderPropertyFlags::COLOR));
assert!(flags.contains(TableBorderPropertyFlags::LINE_WEIGHT));
assert!(!flags.contains(TableBorderPropertyFlags::VISIBILITY));
}
#[test]
fn test_table_style_flags() {
let flags = TableStyleFlags::TITLE_SUPPRESSED;
assert!(flags.contains(TableStyleFlags::TITLE_SUPPRESSED));
assert!(!flags.contains(TableStyleFlags::HEADER_SUPPRESSED));
}
#[test]
fn test_border_type_enum() {
assert_eq!(TableBorderType::from(1), TableBorderType::Single);
assert_eq!(TableBorderType::from(2), TableBorderType::Double);
}
}