#![warn(missing_docs)]
mod tests;
use std::{collections::HashMap, fmt, hash::Hash, sync::OnceLock};
use crate::Color;
#[derive(Debug, Clone, Eq)]
pub struct Format {
pub(crate) dxf_index: u32,
pub(crate) font_index: u16,
pub(crate) fill_index: u16,
pub(crate) border_index: u16,
pub(crate) has_font: bool,
pub(crate) has_fill: bool,
pub(crate) has_border: bool,
pub(crate) num_format: String,
pub(crate) num_format_index: u16,
pub(crate) font: Font,
pub(crate) alignment: Alignment,
pub(crate) borders: Border,
pub(crate) fill: Fill,
pub(crate) hidden: bool,
pub(crate) locked: bool,
pub(crate) checkbox: bool,
pub(crate) quote_prefix: bool,
pub(crate) is_dxf_format: bool,
}
impl Hash for Format {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.font.hash(state);
self.alignment.hash(state);
self.borders.hash(state);
self.fill.hash(state);
self.num_format.hash(state);
self.num_format_index.hash(state);
self.hidden.hash(state);
self.locked.hash(state);
self.checkbox.hash(state);
self.quote_prefix.hash(state);
}
}
impl PartialEq for Format {
fn eq(&self, other: &Self) -> bool {
self.font == other.font
&& self.alignment == other.alignment
&& self.borders == other.borders
&& self.fill == other.fill
&& self.num_format == other.num_format
&& self.num_format_index == other.num_format_index
&& self.hidden == other.hidden
&& self.locked == other.locked
&& self.checkbox == other.checkbox
&& self.quote_prefix == other.quote_prefix
}
}
impl Default for Format {
fn default() -> Self {
Self::new()
}
}
impl Format {
pub fn new() -> Format {
Format {
dxf_index: 0,
font_index: 0,
fill_index: 0,
border_index: 0,
has_font: false,
has_fill: false,
has_border: false,
is_dxf_format: false,
num_format: String::new(),
num_format_index: 0,
fill: Fill::default(),
font: Font::default(),
borders: Border::default(),
alignment: Alignment::default(),
locked: true,
hidden: false,
checkbox: false,
quote_prefix: false,
}
}
pub fn merge(&self, other: &Format) -> Format {
let mut copy = self.clone();
copy.merge_with(other);
copy
}
pub fn set_num_format(mut self, num_format: impl Into<String>) -> Format {
self.num_format = num_format.into();
self
}
pub fn set_num_format_index(mut self, num_format_index: u8) -> Format {
self.num_format_index = u16::from(num_format_index);
let num_formats = HashMap::from([
(1, "0"),
(2, "0.00"),
(3, "#,##0"),
(4, "#,##0.00"),
(5, "($#,##0_);($#,##0)"),
(6, "($#,##0_);[Red]($#,##0)"),
(7, "($#,##0.00_);($#,##0.00)"),
(8, "($#,##0.00_);[Red]($#,##0.00)"),
(9, "0%"),
(10, "0.00%"),
(11, "0.00E+00"),
(12, "# ?/?"),
(13, "# ??/??"),
(14, "m/d/yy"),
(15, "d-mmm-yy"),
(16, "d-mmm"),
(17, "mmm-yy"),
(18, "h:mm AM/PM"),
(19, "h:mm:ss AM/PM"),
(20, "h:mm"),
(21, "h:mm:ss"),
(22, "m/d/yy h:mm"),
(37, "(#,##0_);(#,##0)"),
(38, "(#,##0_);[Red](#,##0)"),
(39, "(#,##0.00_);(#,##0.00)"),
(40, "(#,##0.00_);[Red](#,##0.00)"),
(41, "_(* #,##0_);_(* (#,##0);_(* \"-\"_);_(_)"),
(42, "_($* #,##0_);_($* (#,##0);_($* \"-\"_);_(_)"),
(43, "_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(_)"),
(44, "_($* #,##0.00_);_($* (#,##0.00);_($* \"-\"??_);_(_)"),
(45, "mm:ss"),
(46, "[h]:mm:ss"),
(47, "mm:ss.0"),
(48, "##0.0E+0"),
(49, "@"),
]);
if let Some(num_format) = num_formats.get(&num_format_index) {
self.num_format = (*num_format).to_string();
}
self
}
pub fn set_bold(mut self) -> Format {
self.font.bold = true;
self
}
pub fn set_italic(mut self) -> Format {
self.font.italic = true;
self
}
pub fn set_font_color(mut self, color: impl Into<Color>) -> Format {
let color = color.into();
if color.is_valid() {
self.font.color = color;
}
self
}
pub fn set_font_name(mut self, font_name: impl Into<String>) -> Format {
self.font.name = font_name.into();
self.font.scheme = FontScheme::None;
self
}
pub fn set_font_size<T>(mut self, font_size: T) -> Format
where
T: Into<f64>,
{
self.font.size = font_size.into().to_string();
self
}
pub fn set_font_scheme(mut self, scheme: FontScheme) -> Format {
self.font.scheme = scheme;
self
}
pub fn set_font_family(mut self, font_family: u8) -> Format {
self.font.family = font_family;
self
}
pub fn set_font_charset(mut self, font_charset: u8) -> Format {
self.font.charset = font_charset;
self
}
pub fn set_underline(mut self, underline: FormatUnderline) -> Format {
self.font.underline = underline;
self
}
pub fn set_font_strikethrough(mut self) -> Format {
self.font.strikethrough = true;
self
}
pub fn set_font_script(mut self, font_script: FormatScript) -> Format {
self.font.script = font_script;
self
}
pub fn set_align(mut self, align: FormatAlign) -> Format {
match align {
FormatAlign::General => {
self.alignment.horizontal = FormatAlign::General;
self.alignment.vertical = FormatAlign::General;
}
FormatAlign::Center
| FormatAlign::CenterAcross
| FormatAlign::Distributed
| FormatAlign::Fill
| FormatAlign::Justify
| FormatAlign::Left
| FormatAlign::Right => {
self.alignment.horizontal = align;
}
FormatAlign::Bottom
| FormatAlign::Top
| FormatAlign::VerticalCenter
| FormatAlign::VerticalDistributed
| FormatAlign::VerticalJustify => {
self.alignment.vertical = align;
}
}
self
}
pub fn set_text_wrap(mut self) -> Format {
self.alignment.text_wrap = true;
self
}
pub fn set_indent(mut self, indent: u8) -> Format {
self.alignment.indent = indent;
self
}
pub fn set_rotation(mut self, rotation: i16) -> Format {
match rotation {
270 => self.alignment.rotation = 255,
-90..=-1 => self.alignment.rotation = -rotation + 90,
0..=90 => self.alignment.rotation = rotation,
_ => eprintln!("Rotation outside range: -90 <= angle <= 90."),
}
self
}
pub fn set_reading_direction(mut self, reading_direction: u8) -> Format {
if reading_direction > 2 {
eprintln!("Reading direction must be 0, 1 or 2.");
return self;
}
self.alignment.reading_direction = reading_direction;
self
}
pub fn set_shrink(mut self) -> Format {
self.alignment.shrink = true;
self
}
pub fn set_pattern(mut self, pattern: FormatPattern) -> Format {
self.fill.pattern = pattern;
self
}
pub fn set_background_color(mut self, color: impl Into<Color>) -> Format {
let color = color.into();
if color.is_valid() {
self.fill.background_color = color;
}
self
}
pub fn set_foreground_color(mut self, color: impl Into<Color>) -> Format {
let color = color.into();
if color.is_valid() {
self.fill.foreground_color = color;
}
self
}
pub fn set_border(mut self, border: FormatBorder) -> Format {
self.borders.top_style = border;
self.borders.left_style = border;
self.borders.right_style = border;
self.borders.bottom_style = border;
self
}
pub fn set_border_color(mut self, color: impl Into<Color>) -> Format {
let color = color.into();
if !color.is_valid() {
return self;
}
self.borders.top_color = color;
self.borders.left_color = color;
self.borders.right_color = color;
self.borders.bottom_color = color;
self
}
pub fn set_border_top(mut self, border: FormatBorder) -> Format {
self.borders.top_style = border;
self
}
pub fn set_border_top_color(mut self, color: impl Into<Color>) -> Format {
let color = color.into();
if color.is_valid() {
self.borders.top_color = color;
}
self
}
pub fn set_border_bottom(mut self, border: FormatBorder) -> Format {
self.borders.bottom_style = border;
self
}
pub fn set_border_bottom_color(mut self, color: impl Into<Color>) -> Format {
let color = color.into();
if color.is_valid() {
self.borders.bottom_color = color;
}
self
}
pub fn set_border_left(mut self, border: FormatBorder) -> Format {
self.borders.left_style = border;
self
}
pub fn set_border_left_color(mut self, color: impl Into<Color>) -> Format {
let color = color.into();
if color.is_valid() {
self.borders.left_color = color;
}
self
}
pub fn set_border_right(mut self, border: FormatBorder) -> Format {
self.borders.right_style = border;
self
}
pub fn set_border_right_color(mut self, color: impl Into<Color>) -> Format {
let color = color.into();
if color.is_valid() {
self.borders.right_color = color;
}
self
}
pub fn set_border_diagonal(mut self, border: FormatBorder) -> Format {
self.borders.diagonal_style = border;
self
}
pub fn set_border_diagonal_color(mut self, color: impl Into<Color>) -> Format {
let color = color.into();
if color.is_valid() {
self.borders.diagonal_color = color;
}
self
}
pub fn set_border_diagonal_type(mut self, border_type: FormatDiagonalBorder) -> Format {
self.borders.diagonal_type = border_type;
self
}
pub fn set_hyperlink(mut self) -> Format {
self.font.is_hyperlink = true;
self.font.color = Color::Theme(10, 0);
self.font.underline = FormatUnderline::Single;
self.font.scheme = FontScheme::None;
self
}
pub fn set_unlocked(mut self) -> Format {
self.locked = false;
self
}
pub fn set_hidden(mut self) -> Format {
self.hidden = true;
self
}
pub fn set_quote_prefix(mut self) -> Format {
self.quote_prefix = true;
self
}
pub fn set_checkbox(mut self) -> Format {
self.checkbox = true;
self
}
pub fn unset_bold(mut self) -> Format {
self.font.bold = false;
self
}
pub fn unset_italic(mut self) -> Format {
self.font.italic = false;
self
}
pub fn unset_font_strikethrough(mut self) -> Format {
self.font.strikethrough = false;
self
}
pub fn unset_text_wrap(mut self) -> Format {
self.alignment.text_wrap = false;
self
}
pub fn unset_shrink(mut self) -> Format {
self.alignment.shrink = false;
self
}
pub fn set_locked(mut self) -> Format {
self.locked = true;
self
}
pub fn unset_hidden(mut self) -> Format {
self.hidden = false;
self
}
pub fn unset_hyperlink_style(mut self) -> Format {
self.font.is_hyperlink = true;
self
}
pub fn unset_quote_prefix(mut self) -> Format {
self.quote_prefix = false;
self
}
pub fn unset_checkbox(mut self) -> Format {
self.checkbox = false;
self
}
pub(crate) fn set_font_index(&mut self, font_index: u16, has_font: bool) {
self.font_index = font_index;
self.has_font = has_font;
}
pub(crate) fn has_dxf_font(&self) -> bool {
self.font.bold
|| self.font.italic
|| self.font.underline != FormatUnderline::None
|| self.font.strikethrough
|| !self.font.color.is_auto_or_default()
}
pub(crate) fn has_dxf_fill(&self) -> bool {
self.fill.pattern != FormatPattern::None
|| !self.fill.background_color.is_auto_or_default()
|| !self.fill.foreground_color.is_auto_or_default()
}
pub(crate) fn set_fill_index(&mut self, fill_index: u16, has_fill: bool) {
self.fill_index = fill_index;
self.has_fill = has_fill;
}
pub(crate) fn set_border_index(&mut self, border_index: u16, has_border: bool) {
self.border_index = border_index;
self.has_border = has_border;
}
pub(crate) fn set_num_format_index_u16(&mut self, num_format_index: u16) {
self.num_format_index = num_format_index;
}
pub(crate) fn has_alignment(&self) -> bool {
self.alignment.horizontal != FormatAlign::General
|| !(self.alignment.vertical == FormatAlign::General
|| self.alignment.vertical == FormatAlign::Bottom)
|| self.alignment.indent != 0
|| self.alignment.rotation != 0
|| self.alignment.text_wrap
|| self.alignment.shrink
|| self.alignment.reading_direction != 0
}
pub(crate) fn apply_alignment(&self) -> bool {
self.alignment.horizontal != FormatAlign::General
|| self.alignment.vertical != FormatAlign::General
|| self.alignment.indent != 0
|| self.alignment.rotation != 0
|| self.alignment.text_wrap
|| self.alignment.shrink
|| self.alignment.reading_direction != 0
}
pub(crate) fn has_protection(&self) -> bool {
self.hidden || !self.locked
}
pub(crate) fn has_checkbox(&self) -> bool {
self.checkbox
}
pub(crate) fn is_default(&self) -> bool {
static DEFAULT_STATE: OnceLock<Format> = OnceLock::new();
let default_state = DEFAULT_STATE.get_or_init(Format::default);
self == default_state
}
fn merge_with(&mut self, other: &Format) {
self.hidden |= other.hidden;
self.checkbox |= other.checkbox;
self.quote_prefix |= other.quote_prefix;
self.locked &= other.locked;
if self.num_format.is_empty() {
self.num_format.clone_from(&other.num_format);
}
if self.num_format_index == 0 {
self.num_format_index = other.num_format_index;
}
self.merge_fill(&other.fill);
self.merge_font(&other.font);
self.merge_border(&other.borders);
self.merge_alignment(other.alignment);
}
fn merge_fill(&mut self, other: &Fill) {
if self.fill.foreground_color == Color::Default {
self.fill.foreground_color = other.foreground_color;
}
if self.fill.background_color == Color::Default {
self.fill.background_color = other.background_color;
}
if self.fill.pattern == FormatPattern::None {
self.fill.pattern = other.pattern;
}
}
fn merge_font(&mut self, other: &Font) {
let default = Font::default();
self.font.bold |= other.bold;
self.font.italic |= other.italic;
self.font.extend |= other.extend;
self.font.condense |= other.condense;
self.font.strikethrough |= other.strikethrough;
if self.font.family == default.family {
self.font.family = other.family;
}
if self.font.charset == default.charset {
self.font.charset = other.charset;
}
if self.font.name == default.name {
self.font.name.clone_from(&other.name);
}
if self.font.size == default.size {
self.font.size.clone_from(&other.size);
}
if self.font.scheme == default.scheme {
self.font.scheme.clone_from(&other.scheme);
}
if self.font.color == Color::Default {
self.font.color = other.color;
}
if self.font.script == FormatScript::None {
self.font.script = other.script;
}
if self.font.underline == FormatUnderline::None {
self.font.underline = other.underline;
}
}
fn merge_border(&mut self, other: &Border) {
if other.is_default() {
return;
}
let default = Border::default();
if self.borders.bottom_style == default.bottom_style {
self.borders.bottom_style = other.bottom_style;
}
if self.borders.top_style == default.top_style {
self.borders.top_style = other.top_style;
}
if self.borders.left_style == default.left_style {
self.borders.left_style = other.left_style;
}
if self.borders.right_style == default.right_style {
self.borders.right_style = other.right_style;
}
if self.borders.diagonal_style == default.diagonal_style {
self.borders.diagonal_style = other.diagonal_style;
}
if self.borders.bottom_color == Color::Default {
self.borders.bottom_color = other.bottom_color;
}
if self.borders.top_color == Color::Default {
self.borders.top_color = other.top_color;
}
if self.borders.left_color == Color::Default {
self.borders.left_color = other.left_color;
}
if self.borders.right_color == Color::Default {
self.borders.right_color = other.right_color;
}
if self.borders.diagonal_color == Color::Default {
self.borders.diagonal_color = other.diagonal_color;
}
if self.borders.diagonal_type == FormatDiagonalBorder::None {
self.borders.diagonal_type = other.diagonal_type;
}
}
fn merge_alignment(&mut self, other: Alignment) {
let default = Alignment::default();
self.alignment.shrink |= other.shrink;
self.alignment.text_wrap |= other.text_wrap;
if self.alignment.indent == default.indent {
self.alignment.indent = other.indent;
}
if self.alignment.rotation == default.rotation {
self.alignment.rotation = other.rotation;
}
if self.alignment.reading_direction == default.reading_direction {
self.alignment.reading_direction = other.reading_direction;
}
if self.alignment.horizontal == FormatAlign::General {
self.alignment.horizontal = other.horizontal;
}
if self.alignment.vertical == FormatAlign::General {
self.alignment.vertical = other.vertical;
}
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
pub(crate) struct Alignment {
pub(crate) horizontal: FormatAlign,
pub(crate) vertical: FormatAlign,
pub(crate) text_wrap: bool,
pub(crate) rotation: i16,
pub(crate) indent: u8,
pub(crate) shrink: bool,
pub(crate) reading_direction: u8,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Default)]
pub(crate) struct Border {
pub(crate) bottom_style: FormatBorder,
pub(crate) top_style: FormatBorder,
pub(crate) left_style: FormatBorder,
pub(crate) right_style: FormatBorder,
pub(crate) bottom_color: Color,
pub(crate) top_color: Color,
pub(crate) left_color: Color,
pub(crate) right_color: Color,
pub(crate) diagonal_style: FormatBorder,
pub(crate) diagonal_color: Color,
pub(crate) diagonal_type: FormatDiagonalBorder,
}
impl Border {
pub(crate) fn is_default(&self) -> bool {
static DEFAULT_STATE: OnceLock<Border> = OnceLock::new();
let default_state = DEFAULT_STATE.get_or_init(Border::default);
self == default_state
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub(crate) struct Font {
pub(crate) bold: bool,
pub(crate) italic: bool,
pub(crate) underline: FormatUnderline,
pub(crate) name: String,
pub(crate) size: String,
pub(crate) color: Color,
pub(crate) strikethrough: bool,
pub(crate) script: FormatScript,
pub(crate) family: u8,
pub(crate) charset: u8,
pub(crate) scheme: FontScheme,
pub(crate) condense: bool,
pub(crate) extend: bool,
pub(crate) is_hyperlink: bool,
}
impl Default for Font {
fn default() -> Self {
Self {
name: "Calibri".to_string(),
size: "11".to_string(),
family: 2,
scheme: FontScheme::Body,
bold: Default::default(),
italic: Default::default(),
underline: FormatUnderline::default(),
color: Color::default(),
strikethrough: Default::default(),
script: FormatScript::default(),
charset: Default::default(),
condense: Default::default(),
extend: Default::default(),
is_hyperlink: Default::default(),
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Default)]
pub(crate) struct Fill {
pub(crate) foreground_color: Color,
pub(crate) background_color: Color,
pub(crate) pattern: FormatPattern,
}
impl From<&Format> for Format {
fn from(value: &Format) -> Format {
(*value).clone()
}
}
impl From<&str> for Format {
fn from(value: &str) -> Format {
Format::new().set_num_format(value)
}
}
impl From<&String> for Format {
fn from(value: &String) -> Format {
Format::new().set_num_format(value)
}
}
impl From<String> for Format {
fn from(value: String) -> Format {
Format::new().set_num_format(value)
}
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Default)]
pub enum FormatPattern {
#[default]
None,
Solid,
MediumGray,
DarkGray,
LightGray,
DarkHorizontal,
DarkVertical,
DarkDown,
DarkUp,
DarkGrid,
DarkTrellis,
LightHorizontal,
LightVertical,
LightDown,
LightUp,
LightGrid,
LightTrellis,
Gray125,
Gray0625,
}
impl fmt::Display for FormatPattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "none"),
Self::Solid => write!(f, "solid"),
Self::DarkUp => write!(f, "darkUp"),
Self::LightUp => write!(f, "lightUp"),
Self::Gray125 => write!(f, "gray125"),
Self::DarkGray => write!(f, "darkGray"),
Self::DarkDown => write!(f, "darkDown"),
Self::DarkGrid => write!(f, "darkGrid"),
Self::Gray0625 => write!(f, "gray0625"),
Self::LightGray => write!(f, "lightGray"),
Self::LightDown => write!(f, "lightDown"),
Self::LightGrid => write!(f, "lightGrid"),
Self::MediumGray => write!(f, "mediumGray"),
Self::DarkTrellis => write!(f, "darkTrellis"),
Self::DarkVertical => write!(f, "darkVertical"),
Self::LightTrellis => write!(f, "lightTrellis"),
Self::LightVertical => write!(f, "lightVertical"),
Self::DarkHorizontal => write!(f, "darkHorizontal"),
Self::LightHorizontal => write!(f, "lightHorizontal"),
}
}
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Default)]
pub enum FormatBorder {
#[default]
None,
Thin,
Medium,
Dashed,
Dotted,
Thick,
Double,
Hair,
MediumDashed,
DashDot,
MediumDashDot,
DashDotDot,
MediumDashDotDot,
SlantDashDot,
}
impl fmt::Display for FormatBorder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "none"),
Self::Thin => write!(f, "thin"),
Self::Hair => write!(f, "hair"),
Self::Thick => write!(f, "thick"),
Self::Medium => write!(f, "medium"),
Self::Dashed => write!(f, "dashed"),
Self::Dotted => write!(f, "dotted"),
Self::Double => write!(f, "double"),
Self::DashDot => write!(f, "dashDot"),
Self::DashDotDot => write!(f, "dashDotDot"),
Self::MediumDashed => write!(f, "mediumDashed"),
Self::SlantDashDot => write!(f, "slantDashDot"),
Self::MediumDashDot => write!(f, "mediumDashDot"),
Self::MediumDashDotDot => write!(f, "mediumDashDotDot"),
}
}
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Default)]
pub enum FormatDiagonalBorder {
#[default]
None,
BorderUp,
BorderDown,
BorderUpDown,
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Default)]
pub enum FormatUnderline {
#[default]
None,
Single,
Double,
SingleAccounting,
DoubleAccounting,
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Default)]
pub enum FormatScript {
#[default]
None,
Superscript,
Subscript,
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Default)]
pub enum FormatAlign {
#[default]
General,
Left,
Center,
Right,
Fill,
Justify,
CenterAcross,
Distributed,
Top,
Bottom,
VerticalCenter,
VerticalJustify,
VerticalDistributed,
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Default)]
pub enum FontScheme {
#[default]
Body,
Headings,
None,
}