Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ criterion = { version = "0.7", features = ["html_reports"] }
name = "basic"
harness = false

[[bench]]
name = "style"
harness = false

# Example that requires the `picture` feature.
[[example]]
name = "read_picture_data"
Expand Down
140 changes: 140 additions & 0 deletions benches/style.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// SPDX-License-Identifier: MIT
//
// Copyright 2016-2025, Johann Tuffe.

//! Benchmarks for style parsing and extraction features.
//!
//! Uses `tests/styles_1M.xlsx` (1M styled cells) for realistic performance
//! measurement.
//!
//! ## Run benchmarks
//!
//! ```bash
//! cargo bench --bench style
//! ```
//!
//! ## Profiling (identify bottlenecks)
//!
//! Install samply (cross-platform, works on macOS and Linux):
//! ```bash
//! cargo install samply
//! ```
//!
//! Profile a specific benchmark:
//! ```bash
//! samply record cargo bench --bench style -- "style/worksheet_style" --profile-time 5
//! ```
//!
//! This opens Firefox Profiler with an interactive flamegraph showing where time is spent.

use calamine::{open_workbook, Reader, Xlsx};
use criterion::{criterion_group, criterion_main, Criterion, SamplingMode};
use std::fs::File;
use std::hint::black_box;
use std::io::BufReader;
use std::time::Duration;

const LARGE_FILE: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/styles_1M.xlsx");

fn configure(c: &mut Criterion) -> criterion::BenchmarkGroup<'_, criterion::measurement::WallTime> {
let mut group = c.benchmark_group("style");
group.sample_size(10);
group.warm_up_time(Duration::from_millis(100));
group.measurement_time(Duration::from_secs(15)); // Accommodate slowest benchmark (~1.2s × 10)
group.sampling_mode(SamplingMode::Flat); // 1 iteration per sample for slow benchmarks
group
}

fn bench_style_parsing(c: &mut Criterion) {
if !std::path::Path::new(LARGE_FILE).exists() {
eprintln!("ERROR: tests/styles_1M.xlsx not found.");
return;
}

let mut group = configure(c);

// Core style parsing
group.bench_function("worksheet_style", |b| {
b.iter(|| {
let mut excel: Xlsx<BufReader<File>> =
open_workbook(LARGE_FILE).expect("cannot open file");
black_box(excel.worksheet_style("Sheet 1").unwrap())
})
});

// Layout parsing (column widths, row heights)
group.bench_function("worksheet_layout", |b| {
b.iter(|| {
let mut excel: Xlsx<BufReader<File>> =
open_workbook(LARGE_FILE).expect("cannot open file");
black_box(excel.worksheet_layout("Sheet 1").unwrap())
})
});

// Range parsing (cell values only, no styles)
group.bench_function("worksheet_range", |b| {
b.iter(|| {
let mut excel: Xlsx<BufReader<File>> =
open_workbook(LARGE_FILE).expect("cannot open file");
black_box(excel.worksheet_range("Sheet 1").unwrap())
})
});

// Combined range + style (common real-world usage)
group.bench_function("range_and_style", |b| {
b.iter(|| {
let mut excel: Xlsx<BufReader<File>> =
open_workbook(LARGE_FILE).expect("cannot open file");
let range = excel.worksheet_range("Sheet 1").unwrap();
let style = excel.worksheet_style("Sheet 1").unwrap();
black_box((range.cells().count(), style.cells().count()))
})
});

// Cell-by-cell iteration via cells_reader
group.bench_function("cells_reader", |b| {
b.iter(|| {
let mut excel: Xlsx<BufReader<File>> =
open_workbook(LARGE_FILE).expect("cannot open file");
let mut reader = excel.worksheet_cells_reader("Sheet 1").unwrap();
let mut count = 0usize;
while let Ok(Some(_)) = reader.next_cell() {
count += 1;
}
black_box(count)
})
});

// Iterate and access ALL style properties
group.bench_function("iterate_all_properties", |b| {
b.iter(|| {
let mut excel: Xlsx<BufReader<File>> =
open_workbook(LARGE_FILE).expect("cannot open file");
let styles = excel.worksheet_style("Sheet 1").unwrap();
let mut count = 0usize;
for (_, _, style) in styles.cells() {
if style.get_font().is_some() {
count += 1;
}
if style.get_fill().is_some() {
count += 1;
}
if style.borders.is_some() {
count += 1;
}
if style.get_alignment().is_some() {
count += 1;
}
if style.get_number_format().is_some() {
count += 1;
}
}
black_box(count)
})
});

group.finish();
}

criterion_group!(benches, bench_style_parsing);
criterion_main!(benches);
2 changes: 2 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ This directory contains some example of Calamine usage.
- `read_hyperlinks.rs`: Reads the hyperlinks defined in an XLSX worksheet,
either by sheet name or by sheet index.
- `read_picture_data.rs`: Reads pictures and their metadata from an XLSX file.
- `read_row_and_column_dimensions.rs`: Reads the column widths and row heights
(the worksheet layout) from an XLSX file.

### Serialization examples

Expand Down
1 change: 1 addition & 0 deletions examples/excel_to_csv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ fn write_to_csv<W: Write>(output_file: &mut W, range: &Range<Data>) -> std::io::
Data::String(s) | Data::DateTimeIso(s) | Data::DurationIso(s) => {
write!(output_file, "{s}")
}
Data::RichText(r) => write!(output_file, "{}", r.plain_text()),
}?;

// Write the field separator except for the last column.
Expand Down
81 changes: 81 additions & 0 deletions examples/read_row_and_column_dimensions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: MIT
//
// Copyright 2016-2025, Johann Tuffe.

use calamine::{open_workbook, Reader, Xlsx};

/// Example demonstrating how to capture column widths and row heights from Excel files

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Give this example a better name like read_row_and_column_dimensions.rs. Also add it to the examples/README.md file.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to examples/read_row_and_column_dimensions.rs and added it to examples/README.md.

fn main() -> Result<(), Box<dyn std::error::Error>> {
// Open an Excel file
let path = "tests/styles.xlsx";
let mut workbook: Xlsx<_> = open_workbook(path)?;

// Get the first sheet name
let sheet_names = workbook.sheet_names();
if let Some(sheet_name) = sheet_names.first() {
println!("Getting layout information for sheet: {}", sheet_name);

// Get the worksheet layout information (column widths and row heights)
let layout = workbook.worksheet_layout(sheet_name)?;

// Display default dimensions
if let Some(default_col_width) = layout.default_column_width {
println!("Default column width: {} characters", default_col_width);
}
if let Some(default_row_height) = layout.default_row_height {
println!("Default row height: {} points", default_row_height);
}

// Display custom column widths
if !layout.column_widths.is_empty() {
println!("\nCustom column widths:");
for col_width in layout.column_widths.values() {
println!(
" Column {}: {} characters (custom: {}, hidden: {}, best_fit: {})",
col_width.column,
col_width.width,
col_width.custom_width,
col_width.hidden,
col_width.best_fit
);
}
}

// Display custom row heights
if !layout.row_heights.is_empty() {
println!("\nCustom row heights:");
for row_height in layout.row_heights.values() {
println!(
" Row {}: {} points (custom: {}, hidden: {})",
row_height.row, row_height.height, row_height.custom_height, row_height.hidden
);
}
}

// Example of using the helper methods
println!("\nExample queries:");
let effective_width_0 = layout.get_effective_column_width(0);
let effective_height_0 = layout.get_effective_row_height(0);
println!(
"Effective width of column 0: {} characters",
effective_width_0
);
println!("Effective height of row 0: {} points", effective_height_0);

// Check if a specific column has custom width
if let Some(col_width) = layout.get_column_width(0) {
println!("Column 0 has custom width: {}", col_width.width);
} else {
println!("Column 0 uses default width");
}

// Check if layout has any custom dimensions
if layout.has_custom_dimensions() {
println!("This worksheet has custom column widths or row heights");
} else {
println!("This worksheet uses all default dimensions");
}
}

Ok(())
}
92 changes: 92 additions & 0 deletions examples/style.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: MIT
//
// Copyright 2016-2025, Johann Tuffe.

use calamine::{Cell, Color, Data, Font, FontWeight, Style};

fn main() -> Result<(), Box<dyn std::error::Error>> {
// Example of creating a cell with style
let style = Style::new().with_font(
Font::new()
.with_name("Arial".to_string())
.with_size(12.0)
.with_weight(FontWeight::Bold)
.with_color(Color::rgb(255, 0, 0)),
);

let cell = Cell::with_style((0, 0), Data::String("Hello World".to_string()), style);

println!("Created cell with style:");
if let Some(cell_style) = cell.get_style() {
if let Some(font) = cell_style.get_font() {
println!(
" Font: {} (size: {})",
font.name.as_deref().unwrap_or("Unknown"),
font.size.unwrap_or(0.0)
);
println!(" Bold: {}", font.is_bold());
if let Some(color) = font.color {
println!(" Color: {}", color);
}
}
}

// Example of creating CellData with style
use calamine::CellData;

let cell_data = CellData::with_style(
Data::Int(42),
Style::new().with_font(Font::new().with_weight(FontWeight::Bold)),
);

println!("\nCreated CellData with style:");
if cell_data.has_style() {
if let Some(style) = cell_data.get_style() {
if let Some(font) = style.get_font() {
println!(" Bold: {}", font.is_bold());
}
}
}

// Example of creating a more complex style
let complex_style = Style::new()
.with_font(
Font::new()
.with_name("Times New Roman".to_string())
.with_size(14.0)
.with_weight(FontWeight::Bold)
.with_color(Color::rgb(0, 0, 255)),
)
.with_fill(calamine::Fill::solid(Color::rgb(255, 255, 0)))
.with_borders(calamine::Borders::new());

let styled_cell = Cell::with_style((1, 1), Data::Float(42.0), complex_style);

println!("\nCreated cell with complex style:");
if let Some(style) = styled_cell.get_style() {
if let Some(font) = style.get_font() {
println!(
" Font: {} (size: {})",
font.name.as_deref().unwrap_or("Unknown"),
font.size.unwrap_or(0.0)
);
println!(" Bold: {}", font.is_bold());
if let Some(color) = font.color {
println!(" Font color: {}", color);
}
}

if let Some(fill) = style.get_fill() {
if fill.is_visible() {
println!(" Has fill");
if let Some(color) = fill.get_color() {
println!(" Fill color: {}", color);
}
}
}
}

println!("\nStyle system is working correctly!");

Ok(())
}
20 changes: 19 additions & 1 deletion src/auto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::vba::VbaProject;
use crate::Picture;
use crate::{
open_workbook, open_workbook_from_rs, Data, DataRef, HeaderRow, Metadata, Ods, Range, Reader,
ReaderRef, Xls, Xlsb, Xlsx,
ReaderRef, StyleRange, WorksheetLayout, Xls, Xlsb, Xlsx,
};

use std::fs::File;
Expand Down Expand Up @@ -147,6 +147,24 @@ where
}
}

fn worksheet_style(&mut self, name: &str) -> Result<StyleRange, Self::Error> {
match self {
Sheets::Xls(ref mut e) => e.worksheet_style(name).map_err(Error::Xls),
Sheets::Xlsx(ref mut e) => e.worksheet_style(name).map_err(Error::Xlsx),
Sheets::Xlsb(ref mut e) => e.worksheet_style(name).map_err(Error::Xlsb),
Sheets::Ods(ref mut e) => e.worksheet_style(name).map_err(Error::Ods),
}
}

fn worksheet_layout(&mut self, name: &str) -> Result<WorksheetLayout, Self::Error> {
match self {
Sheets::Xls(ref mut e) => e.worksheet_layout(name).map_err(Error::Xls),
Sheets::Xlsx(ref mut e) => e.worksheet_layout(name).map_err(Error::Xlsx),
Sheets::Xlsb(ref mut e) => e.worksheet_layout(name).map_err(Error::Xlsb),
Sheets::Ods(ref mut e) => e.worksheet_layout(name).map_err(Error::Ods),
}
}

fn worksheets(&mut self) -> Vec<(String, Range<Data>)> {
match self {
Sheets::Xls(e) => e.worksheets(),
Expand Down
Loading
Loading