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
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,21 @@ sha2 = "0.10"
env_logger = "0.11"
serde_derive = "1.0"
rstest = { version = "0.26", default-features = false }
rust_xlsxwriter = "0.93"
criterion = { version = "0.7", features = ["html_reports"] }

[[example]]
name = "generate_styles_1M"
path = "benches/generate_styles_1M.rs"

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

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

[features]
default = []

Expand Down
337 changes: 337 additions & 0 deletions STYLE_FEATURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,337 @@
# Style Reading Support in Calamine

This document describes the style extraction functionality in calamine for reading style information from Excel files.

## Overview

Calamine supports extracting style information from Excel files, including:
- Font properties (name, size, weight, color, etc.)
- Fill properties (background colors, patterns)
- Border properties (style, color, position)
- Alignment properties (horizontal, vertical, text rotation, etc.)
- Protection properties (locked, hidden)

## Data Structures

When reading styles from Excel files, you'll work with these data structures:

### Color
```rust
// Colors are returned when reading style information
// You can check color properties like:
if let Some(color) = font.color {
let (r, g, b, a) = color.rgba();
println!("Color RGBA: {}, {}, {}, {}", r, g, b, a);
}
```

### Font
```rust
// Font information extracted from Excel files
if let Some(font) = style.get_font() {
// Access font properties
if let Some(name) = &font.name {
println!("Font name: {}", name);
}
if let Some(size) = font.size {
println!("Font size: {}", size);
}
println!("Bold: {}", font.is_bold());
println!("Italic: {}", font.is_italic());
if let Some(color) = font.color {
println!("Font color: {}", color);
}
}
```

### Fill
```rust
// Fill information extracted from Excel files
if let Some(fill) = style.get_fill() {
if fill.is_visible() {
println!("Cell has background fill");
if let Some(color) = fill.get_color() {
println!("Fill color: {}", color);
}
if let Some(pattern) = fill.pattern {
println!("Fill pattern: {:?}", pattern);
}
}
}
```

### Borders
```rust
// Border information extracted from Excel files
if let Some(borders) = style.get_borders() {
if borders.has_visible_borders() {
println!("Cell has borders");
if borders.left.is_visible() {
println!("Left border style: {:?}", borders.left.style);
if let Some(color) = borders.left.color {
println!("Left border color: {}", color);
}
}
// Similar for right, top, bottom borders
}
}
```

### Alignment
```rust
// Alignment information extracted from Excel files
if let Some(alignment) = style.get_alignment() {
if let Some(horizontal) = alignment.horizontal {
println!("Horizontal alignment: {:?}", horizontal);
}
if let Some(vertical) = alignment.vertical {
println!("Vertical alignment: {:?}", vertical);
}
if alignment.wrap_text.unwrap_or(false) {
println!("Text wrapping enabled");
}
}
```

## Usage Examples

### Reading Styles from Excel Files

```rust
use calamine::{open_workbook, Reader, Data};

let mut workbook = open_workbook("file.xlsx")?;

if let Ok(range) = workbook.worksheet_range("Sheet1") {
for (row, col, cell) in range.cells() {
if let Some(cell_data) = cell {
if cell_data.has_style() {
if let Some(style) = cell_data.get_style() {
println!("Cell ({}, {}) has style:", row, col);

// Access font properties
if let Some(font) = style.get_font() {
println!(" Font: {}", font.name.as_deref().unwrap_or("Unknown"));
println!(" Size: {}", font.size.unwrap_or(0.0));
println!(" Bold: {}", font.is_bold());
println!(" Italic: {}", font.is_italic());
if let Some(color) = font.color {
let (r, g, b, _) = color.rgba();
println!(" Color: rgb({}, {}, {})", r, g, b);
}
}

// Access fill properties
if let Some(fill) = style.get_fill() {
if fill.is_visible() {
println!(" Has background fill");
if let Some(color) = fill.get_color() {
let (r, g, b, _) = color.rgba();
println!(" Fill color: rgb({}, {}, {})", r, g, b);
}
}
}

// Access border properties
if let Some(borders) = style.get_borders() {
if borders.has_visible_borders() {
println!(" Has borders:");
if borders.left.is_visible() {
println!(" Left: {:?}", borders.left.style);
}
if borders.right.is_visible() {
println!(" Right: {:?}", borders.right.style);
}
if borders.top.is_visible() {
println!(" Top: {:?}", borders.top.style);
}
if borders.bottom.is_visible() {
println!(" Bottom: {:?}", borders.bottom.style);
}
}
}

// Access alignment properties
if let Some(alignment) = style.get_alignment() {
if let Some(horizontal) = alignment.horizontal {
println!(" Horizontal alignment: {:?}", horizontal);
}
if let Some(vertical) = alignment.vertical {
println!(" Vertical alignment: {:?}", vertical);
}
if alignment.wrap_text.unwrap_or(false) {
println!(" Text wrapping: enabled");
}
}
}
}
}
}
}
```

### Checking for Specific Style Properties

```rust
use calamine::{open_workbook, Reader, FontWeight, HorizontalAlignment};

let mut workbook = open_workbook("file.xlsx")?;

if let Ok(range) = workbook.worksheet_range("Sheet1") {
for (row, col, cell) in range.cells() {
if let Some(cell_data) = cell {
if cell_data.has_style() {
if let Some(style) = cell_data.get_style() {
// Check for bold text
if let Some(font) = style.get_font() {
if font.is_bold() {
println!("Cell ({}, {}) has bold text", row, col);
}
}

// Check for center alignment
if let Some(alignment) = style.get_alignment() {
if alignment.horizontal == Some(HorizontalAlignment::Center) {
println!("Cell ({}, {}) is center-aligned", row, col);
}
}

// Check for background color
if let Some(fill) = style.get_fill() {
if fill.is_visible() {
println!("Cell ({}, {}) has background color", row, col);
}
}
}
}
}
}
}
```

### Working with CellData

```rust
use calamine::{CellData, Data};

// When iterating through cells, you get CellData which may contain style information
fn process_cell(cell_data: &CellData) {
// Check if cell has any style information
if cell_data.has_style() {
println!("Cell value: {:?}", cell_data.get_value());

if let Some(style) = cell_data.get_style() {
if !style.is_empty() {
println!("Cell has formatting");

// Process style information as shown in previous examples
if let Some(font) = style.get_font() {
if font.is_bold() {
println!("Text is bold");
}
}
}
}
}
}
```

## Supported Formats

Style extraction is supported for:
- [x] **XLSX**: Full style support including fonts, fills, borders, and alignment
- [ ] **XLSB**: Basic style support (format-based)
- [ ] **XLS**: Basic style support (format-based)
- [ ] **ODS**: Basic style support (format-based)

## Style Parsing

The style parser extracts information from the Excel styles.xml file, including:

### Font Properties
- Font name
- Font size
- Font weight (bold/normal)
- Font style (italic/normal)
- Underline style
- Strikethrough
- Font color
- Font family

### Fill Properties
- Fill pattern (solid, patterns, etc.)
- Foreground color
- Background color

### Border Properties
- Border style (thin, medium, thick, etc.)
- Border color
- Border position (left, right, top, bottom, diagonal)

### Alignment Properties
- Horizontal alignment (left, center, right, justify, etc.)
- Vertical alignment (top, center, bottom, justify, etc.)
- Text rotation
- Wrap text
- Indent level
- Shrink to fit

### Protection Properties
- Cell locked
- Cell hidden

## Limitations

1. **Theme Colors**: Theme color support is limited and may not fully match Excel's rendering
2. **Indexed Colors**: Indexed color support is basic
3. **Complex Patterns**: Some complex fill patterns may not be fully supported
4. **Conditional Formatting**: Conditional formatting styles are not extracted

## Future Enhancements

Planned improvements for style reading include:
- Full theme color support
- Conditional formatting style extraction
- Enhanced pattern support
- Better color space handling

## API Reference

### Key Types for Reading Styles

- `Style`: Complete cell style container (read-only)
- `Font`: Font properties (read-only)
- `Fill`: Fill properties (read-only)
- `Borders`: Border properties (read-only)
- `Alignment`: Alignment properties (read-only)
- `Protection`: Protection properties (read-only)
- `Color`: Color representation (read-only)
- `CellData`: Cell value with optional style information

### Key Methods for Reading Styles

- `CellData::get_style()`: Get cell style information
- `CellData::has_style()`: Check if cell has style information
- `Style::get_font()`: Get font properties
- `Style::get_fill()`: Get fill properties
- `Style::get_borders()`: Get border properties
- `Style::get_alignment()`: Get alignment properties
- `Style::is_empty()`: Check if style has any properties
- `Style::has_visible_properties()`: Check if style has visible properties
- `Font::is_bold()`: Check if font is bold
- `Font::is_italic()`: Check if font is italic
- `Fill::is_visible()`: Check if fill is visible
- `Borders::has_visible_borders()`: Check if borders are visible

## Migration Guide

The style reading functionality is backward compatible. Existing code will continue to work without changes. To add style reading support:

1. Update your cell iteration to check for styles using `has_style()`
2. Use `get_style()` to access style information
3. Access specific style properties through the getter methods
4. Check for visibility using methods like `is_visible()` and `has_visible_borders()`

## Examples

See the `examples/style.rs` file for a complete working example of style extraction and usage.
Loading