use std::collections::BTreeMap;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Color {
pub alpha: u8,
pub red: u8,
pub green: u8,
pub blue: u8,
}
impl Color {
pub fn new(alpha: u8, red: u8, green: u8, blue: u8) -> Self {
Self {
alpha,
red,
green,
blue,
}
}
pub fn rgb(red: u8, green: u8, blue: u8) -> Self {
Self::new(255, red, green, blue)
}
pub fn from_argb(argb: u32) -> Self {
Self {
alpha: ((argb >> 24) & 0xFF) as u8,
red: ((argb >> 16) & 0xFF) as u8,
green: ((argb >> 8) & 0xFF) as u8,
blue: (argb & 0xFF) as u8,
}
}
pub fn to_argb(&self) -> u32 {
((self.alpha as u32) << 24)
| ((self.red as u32) << 16)
| ((self.green as u32) << 8)
| (self.blue as u32)
}
pub fn is_black(&self) -> bool {
self.red == 0 && self.green == 0 && self.blue == 0
}
pub fn is_white(&self) -> bool {
self.red == 255 && self.green == 255 && self.blue == 255
}
}
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{:02X}{:02X}{:02X}", self.red, self.green, self.blue)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum BorderStyle {
#[default]
None,
Thin,
Medium,
Thick,
Double,
Hair,
Dashed,
Dotted,
MediumDashed,
DashDot,
DashDotDot,
SlantDashDot,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Border {
pub style: BorderStyle,
pub color: Option<Color>,
}
impl Border {
pub fn new(style: BorderStyle) -> Self {
Self { style, color: None }
}
pub fn with_color(style: BorderStyle, color: Color) -> Self {
Self {
style,
color: Some(color),
}
}
pub fn is_visible(&self) -> bool {
self.style != BorderStyle::None
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Borders {
pub left: Border,
pub right: Border,
pub top: Border,
pub bottom: Border,
pub diagonal_down: Border,
pub diagonal_up: Border,
}
impl Borders {
pub fn new() -> Self {
Self::default()
}
pub fn has_visible_borders(&self) -> bool {
self.left.is_visible()
|| self.right.is_visible()
|| self.top.is_visible()
|| self.bottom.is_visible()
|| self.diagonal_down.is_visible()
|| self.diagonal_up.is_visible()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum FontWeight {
#[default]
Normal,
Bold,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum FontStyle {
#[default]
Normal,
Italic,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum UnderlineStyle {
#[default]
None,
Single,
Double,
SingleAccounting,
DoubleAccounting,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Font {
pub name: Option<String>,
pub size: Option<f64>,
pub weight: FontWeight,
pub style: FontStyle,
pub underline: UnderlineStyle,
pub strikethrough: bool,
pub color: Option<Color>,
pub family: Option<String>,
}
impl Font {
pub fn new() -> Self {
Self::default()
}
pub fn with_name(mut self, name: String) -> Self {
self.name = Some(name);
self
}
pub fn with_size(mut self, size: f64) -> Self {
self.size = Some(size);
self
}
pub fn with_weight(mut self, weight: FontWeight) -> Self {
self.weight = weight;
self
}
pub fn with_style(mut self, style: FontStyle) -> Self {
self.style = style;
self
}
pub fn with_underline(mut self, underline: UnderlineStyle) -> Self {
self.underline = underline;
self
}
pub fn with_strikethrough(mut self, strikethrough: bool) -> Self {
self.strikethrough = strikethrough;
self
}
pub fn with_color(mut self, color: Color) -> Self {
self.color = Some(color);
self
}
pub fn with_family(mut self, family: String) -> Self {
self.family = Some(family);
self
}
pub fn is_bold(&self) -> bool {
self.weight == FontWeight::Bold
}
pub fn is_italic(&self) -> bool {
self.style == FontStyle::Italic
}
pub fn has_underline(&self) -> bool {
self.underline != UnderlineStyle::None
}
pub fn has_strikethrough(&self) -> bool {
self.strikethrough
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct TextRun {
pub text: String,
pub font: Option<Font>,
}
impl TextRun {
pub fn new(text: String) -> Self {
Self { text, font: None }
}
pub fn with_font(text: String, font: Font) -> Self {
Self {
text,
font: Some(font),
}
}
pub fn has_formatting(&self) -> bool {
self.font.is_some()
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct RichText {
pub runs: Vec<TextRun>,
}
impl RichText {
pub fn new() -> Self {
Self::default()
}
pub fn from_runs(runs: Vec<TextRun>) -> Self {
Self { runs }
}
pub fn push(&mut self, run: TextRun) {
self.runs.push(run);
}
pub fn push_text(&mut self, text: String) {
self.runs.push(TextRun::new(text));
}
pub fn push_formatted(&mut self, text: String, font: Font) {
self.runs.push(TextRun::with_font(text, font));
}
pub fn plain_text(&self) -> String {
self.runs.iter().map(|r| r.text.as_str()).collect()
}
pub fn is_empty(&self) -> bool {
self.runs.is_empty() || self.runs.iter().all(|r| r.text.is_empty())
}
pub fn len(&self) -> usize {
self.runs.len()
}
pub fn has_formatting(&self) -> bool {
self.runs.iter().any(|r| r.has_formatting())
}
pub fn iter(&self) -> impl Iterator<Item = &TextRun> {
self.runs.iter()
}
}
impl std::fmt::Display for RichText {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.plain_text())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum HorizontalAlignment {
Left,
Center,
Right,
Justify,
Distributed,
Fill,
#[default]
General,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum VerticalAlignment {
Top,
Center,
#[default]
Bottom,
Justify,
Distributed,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum TextRotation {
#[default]
None,
Degrees(u16),
Stacked,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Alignment {
pub horizontal: HorizontalAlignment,
pub vertical: VerticalAlignment,
pub text_rotation: TextRotation,
pub wrap_text: bool,
pub indent: Option<u8>,
pub shrink_to_fit: bool,
}
impl Alignment {
pub fn new() -> Self {
Self::default()
}
pub fn with_horizontal(mut self, horizontal: HorizontalAlignment) -> Self {
self.horizontal = horizontal;
self
}
pub fn with_vertical(mut self, vertical: VerticalAlignment) -> Self {
self.vertical = vertical;
self
}
pub fn with_text_rotation(mut self, rotation: TextRotation) -> Self {
self.text_rotation = rotation;
self
}
pub fn with_wrap_text(mut self, wrap: bool) -> Self {
self.wrap_text = wrap;
self
}
pub fn with_indent(mut self, indent: u8) -> Self {
self.indent = Some(indent);
self
}
pub fn with_shrink_to_fit(mut self, shrink: bool) -> Self {
self.shrink_to_fit = shrink;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum FillPattern {
#[default]
None,
Solid,
DarkGray,
MediumGray,
LightGray,
Gray125,
Gray0625,
DarkHorizontal,
DarkVertical,
DarkDown,
DarkUp,
DarkGrid,
DarkTrellis,
LightHorizontal,
LightVertical,
LightDown,
LightUp,
LightGrid,
LightTrellis,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Fill {
pub pattern: FillPattern,
pub foreground_color: Option<Color>,
pub background_color: Option<Color>,
}
impl Fill {
pub fn new() -> Self {
Self::default()
}
pub fn solid(color: Color) -> Self {
Self {
pattern: FillPattern::Solid,
foreground_color: Some(color),
background_color: None,
}
}
pub fn with_pattern(mut self, pattern: FillPattern) -> Self {
self.pattern = pattern;
self
}
pub fn with_foreground_color(mut self, color: Color) -> Self {
self.foreground_color = Some(color);
self
}
pub fn with_background_color(mut self, color: Color) -> Self {
self.background_color = Some(color);
self
}
pub fn is_visible(&self) -> bool {
self.pattern != FillPattern::None
}
pub fn get_color(&self) -> Option<Color> {
self.foreground_color.or(self.background_color)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct NumberFormat {
pub format_code: String,
pub format_id: Option<u32>,
}
impl NumberFormat {
pub fn new(format_code: String) -> Self {
Self {
format_code,
format_id: None,
}
}
pub fn with_id(mut self, format_id: u32) -> Self {
self.format_id = Some(format_id);
self
}
}
impl Default for NumberFormat {
fn default() -> Self {
Self {
format_code: "General".to_string(),
format_id: None,
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Protection {
pub locked: bool,
pub hidden: bool,
}
impl Protection {
pub fn new() -> Self {
Self::default()
}
pub fn with_locked(mut self, locked: bool) -> Self {
self.locked = locked;
self
}
pub fn with_hidden(mut self, hidden: bool) -> Self {
self.hidden = hidden;
self
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ColumnWidth {
pub column: u32,
pub width: f64,
pub custom_width: bool,
pub hidden: bool,
pub best_fit: bool,
}
impl ColumnWidth {
pub fn new(column: u32, width: f64) -> Self {
Self {
column,
width,
custom_width: false,
hidden: false,
best_fit: false,
}
}
pub fn with_custom_width(mut self, custom: bool) -> Self {
self.custom_width = custom;
self
}
pub fn with_hidden(mut self, hidden: bool) -> Self {
self.hidden = hidden;
self
}
pub fn with_best_fit(mut self, best_fit: bool) -> Self {
self.best_fit = best_fit;
self
}
pub fn is_visible(&self) -> bool {
!self.hidden
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RowHeight {
pub row: u32,
pub height: f64,
pub custom_height: bool,
pub hidden: bool,
pub thick_top: bool,
pub thick_bottom: bool,
}
impl RowHeight {
pub fn new(row: u32, height: f64) -> Self {
Self {
row,
height,
custom_height: false,
hidden: false,
thick_top: false,
thick_bottom: false,
}
}
pub fn with_custom_height(mut self, custom: bool) -> Self {
self.custom_height = custom;
self
}
pub fn with_hidden(mut self, hidden: bool) -> Self {
self.hidden = hidden;
self
}
pub fn with_thick_top(mut self, thick_top: bool) -> Self {
self.thick_top = thick_top;
self
}
pub fn with_thick_bottom(mut self, thick_bottom: bool) -> Self {
self.thick_bottom = thick_bottom;
self
}
pub fn is_visible(&self) -> bool {
!self.hidden
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct WorksheetLayout {
pub column_widths: BTreeMap<u32, ColumnWidth>,
pub row_heights: BTreeMap<u32, RowHeight>,
pub default_column_width: Option<f64>,
pub default_row_height: Option<f64>,
}
impl WorksheetLayout {
pub fn new() -> Self {
Self::default()
}
pub fn add_column_width(mut self, column_width: ColumnWidth) -> Self {
self.column_widths.insert(column_width.column, column_width);
self
}
pub fn add_row_height(mut self, row_height: RowHeight) -> Self {
self.row_heights.insert(row_height.row, row_height);
self
}
pub fn with_default_column_width(mut self, width: f64) -> Self {
self.default_column_width = Some(width);
self
}
pub fn with_default_row_height(mut self, height: f64) -> Self {
self.default_row_height = Some(height);
self
}
pub fn get_column_width(&self, column: u32) -> Option<&ColumnWidth> {
self.column_widths.get(&column)
}
pub fn get_row_height(&self, row: u32) -> Option<&RowHeight> {
self.row_heights.get(&row)
}
pub fn get_effective_column_width(&self, column: u32) -> f64 {
self.get_column_width(column)
.map(|cw| cw.width)
.or(self.default_column_width)
.unwrap_or(8.43)
}
pub fn get_effective_row_height(&self, row: u32) -> f64 {
self.get_row_height(row)
.map(|rh| rh.height)
.or(self.default_row_height)
.unwrap_or(15.0)
}
pub fn has_custom_dimensions(&self) -> bool {
!self.column_widths.is_empty()
|| !self.row_heights.is_empty()
|| self.default_column_width.is_some()
|| self.default_row_height.is_some()
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Style {
pub font: Option<Font>,
pub fill: Option<Fill>,
pub borders: Option<Borders>,
pub alignment: Option<Alignment>,
pub number_format: Option<NumberFormat>,
pub protection: Option<Protection>,
pub style_id: Option<u32>,
}
impl Style {
pub fn new() -> Self {
Self::default()
}
pub fn with_font(mut self, font: Font) -> Self {
self.font = Some(font);
self
}
pub fn with_fill(mut self, fill: Fill) -> Self {
self.fill = Some(fill);
self
}
pub fn with_borders(mut self, borders: Borders) -> Self {
self.borders = Some(borders);
self
}
pub fn with_alignment(mut self, alignment: Alignment) -> Self {
self.alignment = Some(alignment);
self
}
pub fn with_number_format(mut self, number_format: NumberFormat) -> Self {
self.number_format = Some(number_format);
self
}
pub fn with_protection(mut self, protection: Protection) -> Self {
self.protection = Some(protection);
self
}
pub fn with_style_id(mut self, style_id: u32) -> Self {
self.style_id = Some(style_id);
self
}
pub fn get_font(&self) -> Option<&Font> {
self.font.as_ref()
}
pub fn get_fill(&self) -> Option<&Fill> {
self.fill.as_ref()
}
pub fn get_borders(&self) -> Option<&Borders> {
self.borders.as_ref()
}
pub fn get_alignment(&self) -> Option<&Alignment> {
self.alignment.as_ref()
}
pub fn get_number_format(&self) -> Option<&NumberFormat> {
self.number_format.as_ref()
}
pub fn get_protection(&self) -> Option<&Protection> {
self.protection.as_ref()
}
pub fn is_empty(&self) -> bool {
self.font.is_none()
&& self.fill.is_none()
&& self.borders.is_none()
&& self.alignment.is_none()
&& self.number_format.is_none()
&& self.protection.is_none()
}
pub fn has_visible_properties(&self) -> bool {
(self
.font
.as_ref()
.is_some_and(|f| f.color.is_some() || f.is_bold() || f.is_italic()))
|| (self.fill.as_ref().is_some_and(|f| f.is_visible()))
|| (self
.borders
.as_ref()
.is_some_and(|b| b.has_visible_borders()))
|| (self.alignment.as_ref().is_some_and(|a| {
a.horizontal != HorizontalAlignment::General
|| a.vertical != VerticalAlignment::Bottom
|| a.text_rotation != TextRotation::None
|| a.wrap_text
|| a.indent.is_some()
|| a.shrink_to_fit
}))
}
}
#[derive(Debug, Clone)]
struct StyleRun {
style_id: u16,
count: u32,
}
#[derive(Debug, Clone, Default)]
pub struct StyleRange {
start: (u32, u32),
end: (u32, u32),
palette: Vec<Style>,
runs: Vec<StyleRun>,
total_cells: u64,
}
impl StyleRange {
pub fn empty() -> Self {
Self::default()
}
pub fn from_style_ids(cells: Vec<(u32, u32, usize)>, palette: Vec<Style>) -> Self {
if cells.is_empty() {
return Self::empty();
}
let mut row_start = u32::MAX;
let mut row_end = 0;
let mut col_start = u32::MAX;
let mut col_end = 0;
for (r, c, _) in &cells {
row_start = row_start.min(*r);
row_end = row_end.max(*r);
col_start = col_start.min(*c);
col_end = col_end.max(*c);
}
let width = (col_end - col_start + 1) as usize;
let height = (row_end - row_start + 1) as usize;
let total_cells = (width * height) as u64;
let mut style_ids = vec![0u16; width * height];
for (r, c, style_id) in cells {
let row = (r - row_start) as usize;
let col = (c - col_start) as usize;
let idx = row * width + col;
style_ids[idx] = style_id.min(u16::MAX as usize) as u16;
}
let mut runs = Vec::new();
if !style_ids.is_empty() {
let mut current_style = style_ids[0];
let mut count = 1u32;
for &style_id in &style_ids[1..] {
if style_id == current_style {
count += 1;
} else {
runs.push(StyleRun {
style_id: current_style,
count,
});
current_style = style_id;
count = 1;
}
}
runs.push(StyleRun {
style_id: current_style,
count,
});
}
runs.shrink_to_fit();
StyleRange {
start: (row_start, col_start),
end: (row_end, col_end),
palette,
runs,
total_cells,
}
}
pub fn from_sparse(cells: Vec<(u32, u32, Style)>) -> Self {
if cells.is_empty() {
return Self::empty();
}
let mut row_start = u32::MAX;
let mut row_end = 0;
let mut col_start = u32::MAX;
let mut col_end = 0;
for (r, c, _) in &cells {
row_start = row_start.min(*r);
row_end = row_end.max(*r);
col_start = col_start.min(*c);
col_end = col_end.max(*c);
}
let width = (col_end - col_start + 1) as usize;
let height = (row_end - row_start + 1) as usize;
let total_cells = (width * height) as u64;
let mut palette: Vec<Style> = vec![Style::default()]; let mut style_to_id: std::collections::HashMap<u32, u16> = std::collections::HashMap::new();
let mut style_ids = vec![0u16; width * height];
for (r, c, style) in cells {
let row = (r - row_start) as usize;
let col = (c - col_start) as usize;
let idx = row * width + col;
if style.is_empty() {
continue; }
let excel_style_id = style.style_id.unwrap_or_else(|| {
palette.len() as u32
});
let style_id = if let Some(&id) = style_to_id.get(&excel_style_id) {
id
} else {
let id = palette.len() as u16;
palette.push(style);
style_to_id.insert(excel_style_id, id);
id
};
style_ids[idx] = style_id;
}
let mut runs = Vec::new();
if !style_ids.is_empty() {
let mut current_style = style_ids[0];
let mut count = 1u32;
for &style_id in &style_ids[1..] {
if style_id == current_style {
count += 1;
} else {
runs.push(StyleRun {
style_id: current_style,
count,
});
current_style = style_id;
count = 1;
}
}
runs.push(StyleRun {
style_id: current_style,
count,
});
}
runs.shrink_to_fit();
palette.shrink_to_fit();
StyleRange {
start: (row_start, col_start),
end: (row_end, col_end),
palette,
runs,
total_cells,
}
}
pub fn start(&self) -> Option<(u32, u32)> {
if self.is_empty() {
None
} else {
Some(self.start)
}
}
pub fn end(&self) -> Option<(u32, u32)> {
if self.is_empty() {
None
} else {
Some(self.end)
}
}
pub fn is_empty(&self) -> bool {
self.runs.is_empty()
}
pub fn width(&self) -> usize {
if self.is_empty() {
0
} else {
(self.end.1 - self.start.1 + 1) as usize
}
}
pub fn height(&self) -> usize {
if self.is_empty() {
0
} else {
(self.end.0 - self.start.0 + 1) as usize
}
}
pub fn get(&self, pos: (usize, usize)) -> Option<&Style> {
let width = self.width();
let height = self.height();
if pos.0 >= height || pos.1 >= width {
return None;
}
let linear_idx = pos.0 * width + pos.1;
let style_id = self.style_id_at(linear_idx)?;
self.palette.get(style_id as usize)
}
fn style_id_at(&self, linear_idx: usize) -> Option<u16> {
let mut offset = 0usize;
for run in &self.runs {
let run_end = offset + run.count as usize;
if linear_idx < run_end {
return Some(run.style_id);
}
offset = run_end;
}
None
}
pub fn cells(&self) -> StyleRangeCells<'_> {
StyleRangeCells {
range: self,
run_idx: 0,
run_offset: 0,
linear_idx: 0,
}
}
pub fn unique_style_count(&self) -> usize {
self.palette.len().saturating_sub(1)
}
pub fn run_count(&self) -> usize {
self.runs.len()
}
pub fn compression_ratio(&self) -> f64 {
if self.runs.is_empty() {
0.0
} else {
self.total_cells as f64 / self.runs.len() as f64
}
}
}
pub struct StyleRangeCells<'a> {
range: &'a StyleRange,
run_idx: usize,
run_offset: u32,
linear_idx: u64,
}
impl<'a> Iterator for StyleRangeCells<'a> {
type Item = (usize, usize, &'a Style);
fn next(&mut self) -> Option<Self::Item> {
if self.run_idx >= self.range.runs.len() {
return None;
}
let width = self.range.width();
if width == 0 {
return None;
}
let row = (self.linear_idx / width as u64) as usize;
let col = (self.linear_idx % width as u64) as usize;
let run = &self.range.runs[self.run_idx];
let style = self.range.palette.get(run.style_id as usize)?;
self.linear_idx += 1;
self.run_offset += 1;
if self.run_offset >= run.count {
self.run_idx += 1;
self.run_offset = 0;
}
Some((row, col, style))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_color() {
let color = Color::rgb(255, 0, 128);
assert_eq!(color.red, 255);
assert_eq!(color.green, 0);
assert_eq!(color.blue, 128);
assert_eq!(color.alpha, 255);
assert_eq!(color.to_string(), "#FF0080");
}
#[test]
fn test_font() {
let font = Font::new()
.with_name("Arial".to_string())
.with_size(12.0)
.with_weight(FontWeight::Bold)
.with_color(Color::rgb(255, 0, 0));
assert_eq!(font.name, Some("Arial".to_string()));
assert_eq!(font.size, Some(12.0));
assert!(font.is_bold());
assert_eq!(font.color, Some(Color::rgb(255, 0, 0)));
}
#[test]
fn test_style() {
let style = Style::new()
.with_font(Font::new().with_name("Arial".to_string()))
.with_fill(Fill::solid(Color::rgb(255, 255, 0)));
assert!(!style.is_empty());
assert!(style.get_font().is_some());
assert!(style.get_fill().is_some());
}
#[test]
fn test_border_with_color() {
let border = Border::with_color(BorderStyle::Thin, Color::rgb(255, 0, 0));
assert_eq!(border.style, BorderStyle::Thin);
assert_eq!(border.color, Some(Color::rgb(255, 0, 0)));
assert!(border.is_visible());
}
#[test]
fn test_border_without_color() {
let border = Border::new(BorderStyle::Medium);
assert_eq!(border.style, BorderStyle::Medium);
assert_eq!(border.color, None);
assert!(border.is_visible());
}
#[test]
fn test_borders_with_mixed_colors() {
let mut borders = Borders::new();
borders.left = Border::with_color(BorderStyle::Thin, Color::rgb(255, 0, 0));
borders.right = Border::new(BorderStyle::Medium);
borders.top = Border::with_color(BorderStyle::Thick, Color::rgb(0, 255, 0));
assert_eq!(borders.left.color, Some(Color::rgb(255, 0, 0)));
assert_eq!(borders.right.color, None);
assert_eq!(borders.top.color, Some(Color::rgb(0, 255, 0)));
assert!(borders.has_visible_borders());
}
#[test]
fn test_column_width() {
let column_width = ColumnWidth::new(5, 12.5)
.with_custom_width(true)
.with_hidden(false)
.with_best_fit(true);
assert_eq!(column_width.column, 5);
assert_eq!(column_width.width, 12.5);
assert!(column_width.custom_width);
assert!(!column_width.hidden);
assert!(column_width.best_fit);
assert!(column_width.is_visible());
}
#[test]
fn test_row_height() {
let row_height = RowHeight::new(10, 20.0)
.with_custom_height(true)
.with_hidden(false)
.with_thick_top(true)
.with_thick_bottom(false);
assert_eq!(row_height.row, 10);
assert_eq!(row_height.height, 20.0);
assert!(row_height.custom_height);
assert!(!row_height.hidden);
assert!(row_height.thick_top);
assert!(!row_height.thick_bottom);
assert!(row_height.is_visible());
}
#[test]
fn test_worksheet_layout() {
let layout = WorksheetLayout::new()
.add_column_width(ColumnWidth::new(0, 10.0))
.add_column_width(ColumnWidth::new(1, 15.0))
.add_row_height(RowHeight::new(0, 18.0))
.add_row_height(RowHeight::new(1, 22.0))
.with_default_column_width(8.43)
.with_default_row_height(15.0);
assert_eq!(layout.column_widths.len(), 2);
assert_eq!(layout.row_heights.len(), 2);
assert_eq!(layout.default_column_width, Some(8.43));
assert_eq!(layout.default_row_height, Some(15.0));
assert!(layout.has_custom_dimensions());
let col_width = layout.get_column_width(0).unwrap();
assert_eq!(col_width.width, 10.0);
let row_height = layout.get_row_height(1).unwrap();
assert_eq!(row_height.height, 22.0);
assert_eq!(layout.get_effective_column_width(0), 10.0); assert_eq!(layout.get_effective_column_width(5), 8.43); assert_eq!(layout.get_effective_row_height(0), 18.0); assert_eq!(layout.get_effective_row_height(5), 15.0); }
#[test]
fn test_worksheet_layout_defaults() {
let layout = WorksheetLayout::new();
assert!(!layout.has_custom_dimensions());
assert_eq!(layout.get_effective_column_width(0), 8.43); assert_eq!(layout.get_effective_row_height(0), 15.0); }
}