UPDATE: Adds a bunch of documentation and examples
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -204,7 +204,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ironcalc_base"
|
name = "ironcalc_base"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
|
|||||||
10
Makefile
10
Makefile
@@ -6,9 +6,15 @@ format:
|
|||||||
cargo fmt
|
cargo fmt
|
||||||
|
|
||||||
tests: lint
|
tests: lint
|
||||||
cargo test --verbose
|
cargo test
|
||||||
|
make remove-xlsx
|
||||||
|
|
||||||
clean:
|
remove-xlsx:
|
||||||
|
rm -f xlsx/hello-calc.xlsx
|
||||||
|
rm -f xlsx/hello-styles.xlsx
|
||||||
|
rm -f xlsx/widths-and-heights.xlsx
|
||||||
|
|
||||||
|
clean: remove-xlsx
|
||||||
cargo clean
|
cargo clean
|
||||||
rm -r -f base/target
|
rm -r -f base/target
|
||||||
rm -r -f xlsx/target
|
rm -r -f xlsx/target
|
||||||
|
|||||||
90
README.md
90
README.md
@@ -1,4 +1,4 @@
|
|||||||
# 📚 IronCalc
|
# IronCalc
|
||||||
|
|
||||||
[![MIT licensed][mit-badge]][mit-url]
|
[![MIT licensed][mit-badge]][mit-url]
|
||||||
[![Apache 2.0 licensed][apache-badge]][apache-url]
|
[![Apache 2.0 licensed][apache-badge]][apache-url]
|
||||||
@@ -33,7 +33,7 @@ Programmed in Rust, you will be able to use it from a variety of programming lan
|
|||||||
|
|
||||||
We will build different _skins_: in the terminal, as a desktop application or use it in you own web application.
|
We will build different _skins_: in the terminal, as a desktop application or use it in you own web application.
|
||||||
|
|
||||||
# 🛠️ Building
|
# Building
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo build --release
|
cargo build --release
|
||||||
@@ -41,37 +41,81 @@ cargo build --release
|
|||||||
|
|
||||||
# Testing, linting and code coverage
|
# Testing, linting and code coverage
|
||||||
|
|
||||||
Testing:
|
Test are run automatically and test coverage can always be found in [codecov](https://codecov.io/gh/ironcalc/IronCalc)
|
||||||
```bash
|
|
||||||
cargo test
|
|
||||||
```
|
|
||||||
|
|
||||||
Linting:
|
If you want to run the tests yourself:
|
||||||
```bash
|
|
||||||
make lint
|
|
||||||
```
|
|
||||||
|
|
||||||
Testing and linting:
|
|
||||||
```bash
|
```bash
|
||||||
make tests
|
make tests
|
||||||
```
|
```
|
||||||
|
|
||||||
Code coverage:
|
Note that this runs unit tests, integration tests, linter tests and formatting tests.
|
||||||
|
|
||||||
|
If you want to run the code coverage yourself:
|
||||||
```bash
|
```bash
|
||||||
make coverage
|
make coverage
|
||||||
cd target/coverage/html/
|
cd target/coverage/html/
|
||||||
python -m http.server
|
python -m http.server
|
||||||
```
|
```
|
||||||
|
|
||||||
# 🖹 API Documentation
|
# API Documentation
|
||||||
|
|
||||||
Documentation might be generated with
|
Documentation is published at: https://docs.rs/ironcalc/latest/ironcalc/
|
||||||
|
|
||||||
|
It might be generated locally
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cargo doc --no-deps
|
$ make docs
|
||||||
|
$ cd target/doc
|
||||||
|
$ python -m http.server
|
||||||
```
|
```
|
||||||
|
|
||||||
# 📝 ROADMAP
|
And visit <http://0.0.0.0:8000/ironcalc/>
|
||||||
|
|
||||||
|
# Simple example
|
||||||
|
|
||||||
|
Add the dependency to `Cargo.toml`:
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
ironcalc = { git = "https://github.com/ironcalc/IronCalc", version = "0.1"}
|
||||||
|
```
|
||||||
|
|
||||||
|
And then use this code in `main.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use ironcalc::{
|
||||||
|
base::{expressions::utils::number_to_column, model::Model},
|
||||||
|
export::save_to_xlsx,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut model = Model::new_empty("hello-calc.xlsx", "en", "UTC")?;
|
||||||
|
// Adds a square of numbers in the first sheet
|
||||||
|
for row in 1..100 {
|
||||||
|
for column in 1..100 {
|
||||||
|
let value = row * column;
|
||||||
|
model.set_user_input(0, row, column, format!("{}", value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Adds a new sheet
|
||||||
|
model.add_sheet("Calculation")?;
|
||||||
|
// column 100 is CV
|
||||||
|
let last_column = number_to_column(100).unwrap();
|
||||||
|
let formula = format!("=SUM(Sheet1!A1:{}100)", last_column);
|
||||||
|
model.set_user_input(1, 1, 1, formula);
|
||||||
|
|
||||||
|
// evaluates
|
||||||
|
model.evaluate();
|
||||||
|
|
||||||
|
// saves to disk
|
||||||
|
save_to_xlsx(&model, "hello-calc.xlsx")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See more examples in the `examples` folder of the xlsx crate.
|
||||||
|
|
||||||
|
# ROADMAP
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> This is work-in-progress. IronCalc in developed in the open. Expect things to be broken and change quickly until version 0.5
|
> This is work-in-progress. IronCalc in developed in the open. Expect things to be broken and change quickly until version 0.5
|
||||||
@@ -83,7 +127,7 @@ Major milestones:
|
|||||||
|
|
||||||
MVP stands for _Minimum Viable Product_
|
MVP stands for _Minimum Viable Product_
|
||||||
|
|
||||||
## Version 0.5 or MVP (15 March 2024)
|
## Version 0.5 or MVP (early 2024)
|
||||||
|
|
||||||
Version 0.5 includes the engine, javascript and nodejs bindings and a web application
|
Version 0.5 includes the engine, javascript and nodejs bindings and a web application
|
||||||
|
|
||||||
@@ -123,8 +167,6 @@ Minor milestones in the ROADMAD for version 1.0.0 (engine and UI):
|
|||||||
* Python bindings
|
* Python bindings
|
||||||
* Full test coverage
|
* Full test coverage
|
||||||
|
|
||||||
I will be creating issues during the first two months of 2024
|
|
||||||
|
|
||||||
# Early testing
|
# Early testing
|
||||||
|
|
||||||
An early preview of the technology running entirely in your browser:
|
An early preview of the technology running entirely in your browser:
|
||||||
@@ -132,6 +174,16 @@ An early preview of the technology running entirely in your browser:
|
|||||||
https://playground.ironcalc.com
|
https://playground.ironcalc.com
|
||||||
|
|
||||||
|
|
||||||
|
# Collaborators needed!. Call to action
|
||||||
|
|
||||||
|
We don't have a vibrant community just yet. This is the very stages of the project. But if you are passionate about code with high standards and no compromises, if you are looking for a project with high impact, if you are interested in a better, more open infrastructure for spreadsheets, whether you are a developer (rust, python, TypeScript, electron/tauri/anything else native app, React, you name it), a designer (we need a logo desperately!), an Excel power user who wants features, a business looking to integrate a MIT/Apache licensed spreadsheet in your own SaaS application join us!
|
||||||
|
|
||||||
|
The best place to start will be to join or [discord channel](https://discord.gg/zZYWfh3RHJ) or sheet me an email at hello@ironcalc.com.
|
||||||
|
|
||||||
|
Many have said it better before me:
|
||||||
|
|
||||||
|
Folks wanted for hazardous journey. Low wages, bitter cold, long hours of complete darkness. Safe return doubtful. Honour and recognition in event of success.
|
||||||
|
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ironcalc_base"
|
name = "ironcalc_base"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
authors = ["Nicolás Hatcher <nicolas@theuniverse.today>"]
|
authors = ["Nicolás Hatcher <nicolas@theuniverse.today>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
homepage = "https://www.ironcalc.com"
|
homepage = "https://www.ironcalc.com"
|
||||||
|
|||||||
19
base/examples/hello_world.rs
Normal file
19
base/examples/hello_world.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
use ironcalc_base::{cell::CellValue, model::Model};
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut model = Model::new_empty("hello-world", "en", "UTC")?;
|
||||||
|
// A1
|
||||||
|
model.set_user_input(0, 1, 1, "Hello".to_string());
|
||||||
|
// B1
|
||||||
|
model.set_user_input(0, 1, 2, "world!".to_string());
|
||||||
|
// C1
|
||||||
|
model.set_user_input(0, 1, 3, "=CONCAT(A1, \" \", B1".to_string());
|
||||||
|
// evaluates
|
||||||
|
model.evaluate();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
model.get_cell_value_by_index(0, 1, 3),
|
||||||
|
Ok(CellValue::String("Hello world!".to_string()))
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,4 +1,24 @@
|
|||||||
#![deny(clippy::unwrap_used)]
|
//! # IronCalcBase engine API
|
||||||
|
//!
|
||||||
|
//! This is the documentation for the base engine API.
|
||||||
|
//!
|
||||||
|
//! # Basic usage
|
||||||
|
//!
|
||||||
|
//! Add the dependency in Cargo.toml:
|
||||||
|
//!
|
||||||
|
//! ```toml
|
||||||
|
//! [dependencies]
|
||||||
|
//! ironcalc_base = { git = "https://github.com/ironcalc/IronCalc", version = "0.1"}
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! <small> until version 0.5.0 you should use the git dependencies as stated </small>
|
||||||
|
//!
|
||||||
|
//! In this example we use the excel function `CONCAT` to concatenate strings in cells `A1` and `B1`:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
#![doc = include_str!("../examples/hello_world.rs")]
|
||||||
|
//! ```
|
||||||
|
|
||||||
pub mod calc_result;
|
pub mod calc_result;
|
||||||
pub mod cell;
|
pub mod cell;
|
||||||
pub mod expressions;
|
pub mod expressions;
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
//! # Model
|
||||||
|
//!
|
||||||
|
//! Note that sheets are 0-indexed and rows and columns are 1-indexed.
|
||||||
|
//!
|
||||||
|
//! IronCalc is row first. A cell is referenced by (`sheet`, `row`, `column`)
|
||||||
|
//!
|
||||||
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -39,6 +47,7 @@ pub use chrono_tz::Tz;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub use crate::mock_time::get_milliseconds_since_epoch;
|
pub use crate::mock_time::get_milliseconds_since_epoch;
|
||||||
|
|
||||||
|
/// wasm implementation for time
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn get_milliseconds_since_epoch() -> i64 {
|
pub fn get_milliseconds_since_epoch() -> i64 {
|
||||||
@@ -56,55 +65,72 @@ pub fn get_milliseconds_since_epoch() -> i64 {
|
|||||||
Date::now() as i64
|
Date::now() as i64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A cell might be evaluated or being evaluated
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum CellState {
|
pub enum CellState {
|
||||||
|
/// The cell has already been evaluated
|
||||||
Evaluated,
|
Evaluated,
|
||||||
|
/// The cell is being evaluated
|
||||||
Evaluating,
|
Evaluating,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A parsed formula for a defined name
|
||||||
pub enum ParsedDefinedName {
|
pub enum ParsedDefinedName {
|
||||||
|
/// CellReference (`=C4`)
|
||||||
CellReference(CellReference),
|
CellReference(CellReference),
|
||||||
|
/// A Range (`=C4:D6`)
|
||||||
RangeReference(Range),
|
RangeReference(Range),
|
||||||
|
/// `=SomethingElse`
|
||||||
InvalidDefinedNameFormula,
|
InvalidDefinedNameFormula,
|
||||||
// TODO: Support constants in defined names
|
// TODO: Support constants in defined names
|
||||||
// TODO: Support formulas in defined names
|
// TODO: Support formulas in defined names
|
||||||
// TODO: Support tables in defined names
|
// TODO: Support tables in defined names
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A model includes:
|
/// A dynamical IronCalc model.
|
||||||
/// * A Workbook: An internal representation of and Excel workbook
|
///
|
||||||
/// * Parsed Formulas: All the formulas in the workbook are parsed here (runtime only)
|
/// Its is composed of a `Workbook`. Everything else are dynamical quantities:
|
||||||
/// * A list of cells with its status (evaluating, evaluated, not evaluated)
|
///
|
||||||
/// * A dictionary with the shared strings and their indices.
|
/// * The Locale: a parsed version of the Workbook's locale
|
||||||
/// This is an optimization for large files (~1 million rows)
|
/// * The Timezone: an object representing the Workbook's timezone
|
||||||
|
/// * The language. Note that the timezone and the locale belong to the workbook while
|
||||||
|
/// the language can be different for different users looking _at the same_ workbook.
|
||||||
|
/// * Parsed Formulas: All the formulas in the workbook are parsed here (runtime only)
|
||||||
|
/// * A list of cells with its status (evaluating, evaluated, not evaluated)
|
||||||
|
/// * A dictionary with the shared strings and their indices.
|
||||||
|
/// This is an optimization for large files (~1 million rows)
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
|
/// A Rust internal representation of an Excel workbook
|
||||||
pub workbook: Workbook,
|
pub workbook: Workbook,
|
||||||
|
/// A list of parsed formulas
|
||||||
pub parsed_formulas: Vec<Vec<Node>>,
|
pub parsed_formulas: Vec<Vec<Node>>,
|
||||||
|
/// A list of parsed defined names
|
||||||
pub parsed_defined_names: HashMap<(Option<u32>, String), ParsedDefinedName>,
|
pub parsed_defined_names: HashMap<(Option<u32>, String), ParsedDefinedName>,
|
||||||
|
/// An optimization to lookup strings faster
|
||||||
pub shared_strings: HashMap<String, usize>,
|
pub shared_strings: HashMap<String, usize>,
|
||||||
|
/// An instance of the parser
|
||||||
pub parser: Parser,
|
pub parser: Parser,
|
||||||
|
/// The list of cells with formulas that are evaluated of being evaluated
|
||||||
pub cells: HashMap<(u32, i32, i32), CellState>,
|
pub cells: HashMap<(u32, i32, i32), CellState>,
|
||||||
|
/// The locale of the model
|
||||||
pub locale: Locale,
|
pub locale: Locale,
|
||||||
|
/// Tha language used
|
||||||
pub language: Language,
|
pub language: Language,
|
||||||
|
/// The timezone used to evaluate the model
|
||||||
pub tz: Tz,
|
pub tz: Tz,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Maybe this should be the same as CellReference
|
||||||
|
/// A struct pointing to a cell
|
||||||
pub struct CellIndex {
|
pub struct CellIndex {
|
||||||
|
/// Sheet index (0-indexed)
|
||||||
pub index: u32,
|
pub index: u32,
|
||||||
|
/// Row index
|
||||||
pub row: i32,
|
pub row: i32,
|
||||||
|
/// Column index
|
||||||
pub column: i32,
|
pub column: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub struct Style {
|
|
||||||
pub alignment: Option<Alignment>,
|
|
||||||
pub num_fmt: String,
|
|
||||||
pub fill: Fill,
|
|
||||||
pub font: Font,
|
|
||||||
pub border: Border,
|
|
||||||
pub quote_prefix: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
pub(crate) fn evaluate_node_with_reference(
|
pub(crate) fn evaluate_node_with_reference(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -625,12 +651,27 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the color of the sheet tab
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use ironcalc_base::model::Model;
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
|
/// assert_eq!(model.workbook.worksheet(0)?.color, None);
|
||||||
|
/// model.set_sheet_color(0, "#DBBE29")?;
|
||||||
|
/// assert_eq!(model.workbook.worksheet(0)?.color, Some("#DBBE29".to_string()));
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
pub fn set_sheet_color(&mut self, sheet: u32, color: &str) -> Result<(), String> {
|
pub fn set_sheet_color(&mut self, sheet: u32, color: &str) -> Result<(), String> {
|
||||||
let worksheet = self.workbook.worksheet_mut(sheet)?;
|
let worksheet = self.workbook.worksheet_mut(sheet)?;
|
||||||
if color.is_empty() {
|
if color.is_empty() {
|
||||||
worksheet.color = None;
|
worksheet.color = None;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else if common::is_valid_hex_color(color) {
|
}
|
||||||
|
if common::is_valid_hex_color(color) {
|
||||||
worksheet.color = Some(color.to_string());
|
worksheet.color = Some(color.to_string());
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -677,11 +718,22 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if cell is completely empty.
|
/// Returns `true` if the cell is completely empty.
|
||||||
/// Cell with formula that evaluates to empty string is not considered empty.
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use ironcalc_base::model::Model;
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
|
/// assert_eq!(model.is_empty_cell(0, 1, 1)?, true);
|
||||||
|
/// model.set_user_input(0, 1, 1, "Attention is all you need".to_string());
|
||||||
|
/// assert_eq!(model.is_empty_cell(0, 1, 1)?, false);
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
pub fn is_empty_cell(&self, sheet: u32, row: i32, column: i32) -> Result<bool, String> {
|
pub fn is_empty_cell(&self, sheet: u32, row: i32, column: i32) -> Result<bool, String> {
|
||||||
let worksheet = self.workbook.worksheet(sheet)?;
|
self.workbook.worksheet(sheet)?.is_empty_cell(row, column)
|
||||||
worksheet.is_empty_cell(row, column)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn evaluate_cell(&mut self, cell_reference: CellReference) -> CalcResult {
|
pub(crate) fn evaluate_cell(&mut self, cell_reference: CellReference) -> CalcResult {
|
||||||
@@ -743,14 +795,48 @@ impl Model {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public API
|
|
||||||
/// Returns a model from a String representation of a workbook
|
/// Returns a model from a String representation of a workbook
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use ironcalc_base::model::Model;
|
||||||
|
/// # use ironcalc_base::cell::CellValue;
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
|
/// model.set_user_input(0, 1, 1, "Stella!".to_string());
|
||||||
|
/// let model2 = Model::from_json(&model.to_json_str())?;
|
||||||
|
/// assert_eq!(
|
||||||
|
/// model2.get_cell_value_by_index(0, 1, 1),
|
||||||
|
/// Ok(CellValue::String("Stella!".to_string()))
|
||||||
|
/// );
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
pub fn from_json(s: &str) -> Result<Model, String> {
|
pub fn from_json(s: &str) -> Result<Model, String> {
|
||||||
let workbook: Workbook =
|
let workbook: Workbook =
|
||||||
serde_json::from_str(s).map_err(|_| "Error parsing workbook".to_string())?;
|
serde_json::from_str(s).map_err(|_| "Error parsing workbook".to_string())?;
|
||||||
Model::from_workbook(workbook)
|
Model::from_workbook(workbook)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a model from a Workbook object
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use ironcalc_base::model::Model;
|
||||||
|
/// # use ironcalc_base::cell::CellValue;
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
|
/// model.set_user_input(0, 1, 1, "Stella!".to_string());
|
||||||
|
/// let model2 = Model::from_workbook(model.workbook)?;
|
||||||
|
/// assert_eq!(
|
||||||
|
/// model2.get_cell_value_by_index(0, 1, 1),
|
||||||
|
/// Ok(CellValue::String("Stella!".to_string()))
|
||||||
|
/// );
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
pub fn from_workbook(workbook: Workbook) -> Result<Model, String> {
|
pub fn from_workbook(workbook: Workbook) -> Result<Model, String> {
|
||||||
let parsed_formulas = Vec::new();
|
let parsed_formulas = Vec::new();
|
||||||
let worksheets = &workbook.worksheets;
|
let worksheets = &workbook.worksheets;
|
||||||
@@ -803,6 +889,20 @@ impl Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a reference like "Sheet1!B4" into {0, 2, 4}
|
/// Parses a reference like "Sheet1!B4" into {0, 2, 4}
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use ironcalc_base::model::Model;
|
||||||
|
/// # use ironcalc_base::calc_result::CellReference;
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
|
/// model.set_user_input(0, 1, 1, "Stella!".to_string());
|
||||||
|
/// let reference = model.parse_reference("Sheet1!D40");
|
||||||
|
/// assert_eq!(reference, Some(CellReference {sheet: 0, row: 40, column: 4}));
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
pub fn parse_reference(&self, s: &str) -> Option<CellReference> {
|
pub fn parse_reference(&self, s: &str) -> Option<CellReference> {
|
||||||
let bytes = s.as_bytes();
|
let bytes = s.as_bytes();
|
||||||
let mut sheet_name = "".to_string();
|
let mut sheet_name = "".to_string();
|
||||||
@@ -857,7 +957,23 @@ impl Model {
|
|||||||
Some(CellReference { sheet, row, column })
|
Some(CellReference { sheet, row, column })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// moves the value in area from source to target.
|
/// Moves the formula `value` from `source` (in `area`) to `target`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use ironcalc_base::model::Model;
|
||||||
|
/// # use ironcalc_base::expressions::types::{Area, CellReferenceIndex};
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
|
/// let source = CellReferenceIndex { sheet: 0, row: 3, column: 1};
|
||||||
|
/// let target = CellReferenceIndex { sheet: 0, row: 50, column: 1};
|
||||||
|
/// let area = Area { sheet: 0, row: 1, column: 1, width: 5, height: 4};
|
||||||
|
/// let result = model.move_cell_value_to_area("=B1", &source, &target, &area)?;
|
||||||
|
/// assert_eq!(&result, "=B48");
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
pub fn move_cell_value_to_area(
|
pub fn move_cell_value_to_area(
|
||||||
&mut self,
|
&mut self,
|
||||||
value: &str,
|
value: &str,
|
||||||
@@ -908,7 +1024,22 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 'Extends' the value from cell [sheet, row, column] to [target_row, target_column]
|
/// 'Extends' the value from cell (`sheet`, `row`, `column`) to (`target_row`, `target_column`) in the same sheet
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use ironcalc_base::model::Model;
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
|
/// model.set_user_input(sheet, row, column, "=B1*D4".to_string());
|
||||||
|
/// let (target_row, target_column) = (30, 1);
|
||||||
|
/// let result = model.extend_to(sheet, row, column, target_row, target_column)?;
|
||||||
|
/// assert_eq!(&result, "=B30*D33");
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
pub fn extend_to(
|
pub fn extend_to(
|
||||||
&self,
|
&self,
|
||||||
sheet: u32,
|
sheet: u32,
|
||||||
@@ -936,14 +1067,32 @@ impl Model {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 'Extends' value from cell [sheet, row, column] to [target_row, target_column]
|
/// 'Extends' the formula `value` from `source` to `target`
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use ironcalc_base::model::Model;
|
||||||
|
/// # use ironcalc_base::expressions::types::CellReferenceIndex;
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
|
/// let source = CellReferenceIndex {sheet: 0, row: 1, column: 1};
|
||||||
|
/// let target = CellReferenceIndex {sheet: 0, row: 30, column: 1};
|
||||||
|
/// let result = model.extend_copied_value("=B1*D4", &source, &target)?;
|
||||||
|
/// assert_eq!(&result, "=B30*D33");
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
pub fn extend_copied_value(
|
pub fn extend_copied_value(
|
||||||
&mut self, // FIXME: weird that it must be mutable
|
&mut self,
|
||||||
value: &str,
|
value: &str,
|
||||||
source_sheet_name: &str,
|
|
||||||
source: &CellReferenceIndex,
|
source: &CellReferenceIndex,
|
||||||
target: &CellReferenceIndex,
|
target: &CellReferenceIndex,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
|
let source_sheet_name = match self.workbook.worksheets.get(source.sheet as usize) {
|
||||||
|
Some(ws) => ws.get_name(),
|
||||||
|
None => {
|
||||||
|
return Err("Invalid worksheet index".to_owned());
|
||||||
|
}
|
||||||
|
};
|
||||||
let target_sheet_name = match self.workbook.worksheets.get(target.sheet as usize) {
|
let target_sheet_name = match self.workbook.worksheets.get(target.sheet as usize) {
|
||||||
Some(ws) => ws.get_name(),
|
Some(ws) => ws.get_name(),
|
||||||
None => {
|
None => {
|
||||||
@@ -967,6 +1116,20 @@ impl Model {
|
|||||||
Ok(value.to_string())
|
Ok(value.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the formula in (`sheet`, `row`, `column`) if any
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use ironcalc_base::model::Model;
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
|
/// model.set_user_input(sheet, row, column, "=SIN(B1*C3)+1".to_string());
|
||||||
|
/// model.evaluate();
|
||||||
|
/// let result = model.cell_formula(sheet, row, column)?;
|
||||||
|
/// assert_eq!(result, Some("=SIN(B1*C3)+1".to_string()));
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
pub fn cell_formula(
|
pub fn cell_formula(
|
||||||
&self,
|
&self,
|
||||||
sheet: u32,
|
sheet: u32,
|
||||||
@@ -1210,6 +1373,9 @@ impl Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the Excel Value (Bool, Number, String) of a cell
|
/// Gets the Excel Value (Bool, Number, String) of a cell
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// * [Model::get_cell_value_by_index()]
|
||||||
pub fn get_cell_value_by_ref(&self, cell_ref: &str) -> Result<CellValue, String> {
|
pub fn get_cell_value_by_ref(&self, cell_ref: &str) -> Result<CellValue, String> {
|
||||||
let cell_reference = match self.parse_reference(cell_ref) {
|
let cell_reference = match self.parse_reference(cell_ref) {
|
||||||
Some(c) => c,
|
Some(c) => c,
|
||||||
@@ -1222,6 +1388,10 @@ impl Model {
|
|||||||
self.get_cell_value_by_index(sheet_index, row, column)
|
self.get_cell_value_by_index(sheet_index, row, column)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the cell value for (`sheet`, `row`, `column`)
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// * [Model::formatted_cell_value()]
|
||||||
pub fn get_cell_value_by_index(
|
pub fn get_cell_value_by_index(
|
||||||
&self,
|
&self,
|
||||||
sheet_index: u32,
|
sheet_index: u32,
|
||||||
@@ -1238,6 +1408,26 @@ impl Model {
|
|||||||
Ok(cell_value)
|
Ok(cell_value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the formatted cell value for (`sheet`, `row`, `column`)
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// * [Model::get_cell_value_by_index()]
|
||||||
|
/// * [Model::get_cell_value_by_ref]
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use ironcalc_base::model::Model;
|
||||||
|
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
/// let mut model = Model::new_empty("model", "en", "UTC")?;
|
||||||
|
/// let (sheet, row, column) = (0, 1, 1);
|
||||||
|
/// model.set_user_input(sheet, row, column, "=1/3".to_string());
|
||||||
|
/// model.evaluate();
|
||||||
|
/// let result = model.formatted_cell_value(sheet, row, column)?;
|
||||||
|
/// assert_eq!(result, "0.333333333".to_string());
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
pub fn formatted_cell_value(
|
pub fn formatted_cell_value(
|
||||||
&self,
|
&self,
|
||||||
sheet_index: u32,
|
sheet_index: u32,
|
||||||
@@ -1337,6 +1527,7 @@ impl Model {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the style index for cell (`sheet`, `row`, `column`)
|
||||||
pub fn get_cell_style_index(&self, sheet: u32, row: i32, column: i32) -> i32 {
|
pub fn get_cell_style_index(&self, sheet: u32, row: i32, column: i32) -> i32 {
|
||||||
// First check the cell, then row, the column
|
// First check the cell, then row, the column
|
||||||
let cell = self
|
let cell = self
|
||||||
@@ -1370,6 +1561,7 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the style for cell (`sheet`, `row`, `column`)
|
||||||
pub fn get_style_for_cell(&self, sheet: u32, row: i32, column: i32) -> Style {
|
pub fn get_style_for_cell(&self, sheet: u32, row: i32, column: i32) -> Style {
|
||||||
self.workbook
|
self.workbook
|
||||||
.styles
|
.styles
|
||||||
@@ -1415,6 +1607,9 @@ impl Model {
|
|||||||
Ok(rows.join("\n"))
|
Ok(rows.join("\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the currency of the model.
|
||||||
|
/// Currently we only support `USD`, `EUR`, `GBP` and `JPY`
|
||||||
|
/// NB: This is not preserved in the JSON.
|
||||||
pub fn set_currency(&mut self, iso: &str) -> Result<(), &str> {
|
pub fn set_currency(&mut self, iso: &str) -> Result<(), &str> {
|
||||||
// TODO: Add a full list
|
// TODO: Add a full list
|
||||||
let symbol = if iso == "USD" {
|
let symbol = if iso == "USD" {
|
||||||
@@ -1435,6 +1630,7 @@ impl Model {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the number of frozen rows in `sheet`
|
||||||
pub fn get_frozen_rows(&self, sheet: u32) -> Result<i32, String> {
|
pub fn get_frozen_rows(&self, sheet: u32) -> Result<i32, String> {
|
||||||
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
|
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
|
||||||
Ok(worksheet.frozen_rows)
|
Ok(worksheet.frozen_rows)
|
||||||
@@ -1443,6 +1639,7 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the number of frozen columns in `sheet`
|
||||||
pub fn get_frozen_columns(&self, sheet: u32) -> Result<i32, String> {
|
pub fn get_frozen_columns(&self, sheet: u32) -> Result<i32, String> {
|
||||||
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
|
if let Some(worksheet) = self.workbook.worksheets.get(sheet as usize) {
|
||||||
Ok(worksheet.frozen_columns)
|
Ok(worksheet.frozen_columns)
|
||||||
@@ -1451,11 +1648,14 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the number of frozen rows to `frozen_rows` in the workbook.
|
||||||
|
/// Fails if `frozen`_rows` is either too small (<0) or too large (>LAST_ROW)`
|
||||||
pub fn set_frozen_rows(&mut self, sheet: u32, frozen_rows: i32) -> Result<(), String> {
|
pub fn set_frozen_rows(&mut self, sheet: u32, frozen_rows: i32) -> Result<(), String> {
|
||||||
if let Some(worksheet) = self.workbook.worksheets.get_mut(sheet as usize) {
|
if let Some(worksheet) = self.workbook.worksheets.get_mut(sheet as usize) {
|
||||||
if frozen_rows < 0 {
|
if frozen_rows < 0 {
|
||||||
return Err("Frozen rows cannot be negative".to_string());
|
return Err("Frozen rows cannot be negative".to_string());
|
||||||
} else if frozen_rows >= LAST_ROW {
|
}
|
||||||
|
if frozen_rows >= LAST_ROW {
|
||||||
return Err("Too many rows".to_string());
|
return Err("Too many rows".to_string());
|
||||||
}
|
}
|
||||||
worksheet.frozen_rows = frozen_rows;
|
worksheet.frozen_rows = frozen_rows;
|
||||||
@@ -1465,11 +1665,14 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the number of frozen columns to `frozen_column` in the workbook.
|
||||||
|
/// Fails if `frozen`_columns` is either too small (<0) or too large (>LAST_COLUMN)`
|
||||||
pub fn set_frozen_columns(&mut self, sheet: u32, frozen_columns: i32) -> Result<(), String> {
|
pub fn set_frozen_columns(&mut self, sheet: u32, frozen_columns: i32) -> Result<(), String> {
|
||||||
if let Some(worksheet) = self.workbook.worksheets.get_mut(sheet as usize) {
|
if let Some(worksheet) = self.workbook.worksheets.get_mut(sheet as usize) {
|
||||||
if frozen_columns < 0 {
|
if frozen_columns < 0 {
|
||||||
return Err("Frozen columns cannot be negative".to_string());
|
return Err("Frozen columns cannot be negative".to_string());
|
||||||
} else if frozen_columns >= LAST_COLUMN {
|
}
|
||||||
|
if frozen_columns >= LAST_COLUMN {
|
||||||
return Err("Too many columns".to_string());
|
return Err("Too many columns".to_string());
|
||||||
}
|
}
|
||||||
worksheet.frozen_columns = frozen_columns;
|
worksheet.frozen_columns = frozen_columns;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
model::{Model, Style},
|
model::Model,
|
||||||
number_format::{get_default_num_fmt_id, get_new_num_fmt_index, get_num_fmt},
|
number_format::{get_default_num_fmt_id, get_new_num_fmt_index, get_num_fmt},
|
||||||
types::{Border, CellStyles, CellXfs, Fill, Font, NumFmt, Styles},
|
types::{Border, CellStyles, CellXfs, Fill, Font, NumFmt, Style, Styles},
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Move Styles and all related types from crate::types here
|
// TODO: Move Styles and all related types from crate::types here
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
use crate::model::Style;
|
|
||||||
use crate::test::util::new_empty_model;
|
use crate::test::util::new_empty_model;
|
||||||
|
use crate::types::Style;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_model_set_cells_with_values_styles() {
|
fn test_model_set_cells_with_values_styles() {
|
||||||
|
|||||||
@@ -314,6 +314,16 @@ impl Default for Styles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct Style {
|
||||||
|
pub alignment: Option<Alignment>,
|
||||||
|
pub num_fmt: String,
|
||||||
|
pub fill: Fill,
|
||||||
|
pub font: Font,
|
||||||
|
pub border: Border,
|
||||||
|
pub quote_prefix: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct NumFmt {
|
pub struct NumFmt {
|
||||||
pub num_fmt_id: i32,
|
pub num_fmt_id: i32,
|
||||||
|
|||||||
@@ -4,18 +4,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let mut model = Model::new_empty("hello_styles", "en", "UTC")?;
|
let mut model = Model::new_empty("hello_styles", "en", "UTC")?;
|
||||||
|
|
||||||
// We are going to change styles in cell A1
|
// We are going to change styles in cell A1
|
||||||
let sheet = 0;
|
let (sheet, row, column) = (0, 1, 1);
|
||||||
let row = 1;
|
|
||||||
let column = 1;
|
|
||||||
let mut style = model.get_style_for_cell(sheet, row, column);
|
let mut style = model.get_style_for_cell(sheet, row, column);
|
||||||
style.fill.fg_color = Some("#FFEE11".to_string());
|
style.fill.fg_color = Some("#FF9011".to_string());
|
||||||
style.font.b = true;
|
style.font.b = true;
|
||||||
style.font.color = Some("#EEFF22".to_string());
|
style.font.color = Some("#E91E63".to_string());
|
||||||
model.set_cell_style(sheet, row, column, &style)?;
|
model.set_cell_style(sheet, row, column, &style)?;
|
||||||
|
|
||||||
// evaluates (unnecessary in this case)
|
|
||||||
model.evaluate();
|
|
||||||
|
|
||||||
// saves to disk
|
// saves to disk
|
||||||
save_to_xlsx(&model, "hello-styles.xlsx")?;
|
save_to_xlsx(&model, "hello-styles.xlsx")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ use ironcalc::{base::model::Model, export::save_to_xlsx};
|
|||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut model = Model::new_empty("widths-and-heights", "en", "UTC")?;
|
let mut model = Model::new_empty("widths-and-heights", "en", "UTC")?;
|
||||||
// Cell C5
|
// Cell C5
|
||||||
let column = 3;
|
let (sheet, row, column) = (0, 5, 3);
|
||||||
let row = 5;
|
|
||||||
// Make the first column 4 times as width
|
// Make the first column 4 times as width
|
||||||
let worksheet = model.workbook.worksheet_mut(0)?;
|
let worksheet = model.workbook.worksheet_mut(sheet)?;
|
||||||
let column_width = worksheet.column_width(column)? * 4.0;
|
let column_width = worksheet.column_width(column)? * 4.0;
|
||||||
worksheet.set_column_width(column, column_width)?;
|
worksheet.set_column_width(column, column_width)?;
|
||||||
|
|
||||||
@@ -14,9 +13,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let row_height = worksheet.row_height(row)? * 2.0;
|
let row_height = worksheet.row_height(row)? * 2.0;
|
||||||
worksheet.set_row_height(row, row_height)?;
|
worksheet.set_row_height(row, row_height)?;
|
||||||
|
|
||||||
// evaluates
|
|
||||||
model.evaluate();
|
|
||||||
|
|
||||||
// saves to disk
|
// saves to disk
|
||||||
save_to_xlsx(&model, "widths-and-heights.xlsx")?;
|
save_to_xlsx(&model, "widths-and-heights.xlsx")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! # IronCalc - Core API documentation
|
//! # IronCalc - Core API documentation
|
||||||
//!
|
//!
|
||||||
//! This technical API documentation in aimed at developers who want to develop bindings for a different language,
|
//! This technical API documentation is aimed at developers.
|
||||||
//! build a UI based on the engine or just use the library in a Rust program
|
//! It is used to build language bindings (like python, javascript or nodejs) or to build full fledged applications like ironCalc in the terminal or IronCalc, the Web application.
|
||||||
//!
|
//!
|
||||||
//! ## Basic usage
|
//! ## Basic usage
|
||||||
//!
|
//!
|
||||||
@@ -21,7 +21,12 @@
|
|||||||
#![doc = include_str!("../examples/hello_calc.rs")]
|
#![doc = include_str!("../examples/hello_calc.rs")]
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Styling the workbook
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! This is a collection of full fledged examples you can use as a starting point or for learning purposes.
|
||||||
|
//! You might find the code in the examples folder
|
||||||
|
//!
|
||||||
|
//! ### Styling the workbook
|
||||||
//!
|
//!
|
||||||
//! Adding colors, to cells, full columns or full rows is easy
|
//! Adding colors, to cells, full columns or full rows is easy
|
||||||
//!
|
//!
|
||||||
@@ -29,12 +34,11 @@
|
|||||||
#![doc = include_str!("../examples/hello_styles.rs")]
|
#![doc = include_str!("../examples/hello_styles.rs")]
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Changing column width and row heigh
|
//! ### Changing column width and row heigh
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
#![doc = include_str!("../examples/widths_and_heights.rs")]
|
#![doc = include_str!("../examples/widths_and_heights.rs")]
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
|
||||||
|
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://raw.githubusercontent.com/ironcalc/ironcalc/main/assets/logo.png",
|
html_logo_url = "https://raw.githubusercontent.com/ironcalc/ironcalc/main/assets/logo.png",
|
||||||
|
|||||||
Reference in New Issue
Block a user