Compare commits

...

4 Commits

Author SHA1 Message Date
Nicolás Hatcher
5c13f241c6 FIX: Fixes for the CI builds 2025-02-28 12:00:54 +01:00
Nicolás Hatcher
26b20eea43 UPDATE: Bump versions to 0.5 2025-02-28 01:00:50 +01:00
Nicolás Hatcher
b62256963a UPDATE: Adds wrapping! 2025-02-28 00:29:44 +01:00
Nicolás Hatcher
4f627b4363 FIX: More sensible decrease/increase font-size 2025-02-28 00:29:44 +01:00
16 changed files with 145 additions and 52 deletions

View File

@@ -32,7 +32,7 @@ jobs:
manylinux: auto
working-directory: bindings/python
- name: Upload wheels
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: wheels
path: bindings/python/dist
@@ -56,7 +56,7 @@ jobs:
sccache: 'true'
working-directory: bindings/python
- name: Upload wheels
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: wheels
path: bindings/python/dist
@@ -79,7 +79,7 @@ jobs:
sccache: 'true'
working-directory: bindings/python
- name: Upload wheels
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: wheels
path: bindings/python/dist
@@ -95,7 +95,7 @@ jobs:
args: --out dist
working-directory: bindings/python
- name: Upload sdist
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: wheels
path: bindings/python/dist

10
Cargo.lock generated
View File

@@ -414,7 +414,7 @@ dependencies = [
[[package]]
name = "ironcalc"
version = "0.3.0"
version = "0.5.0"
dependencies = [
"bitcode",
"chrono",
@@ -430,7 +430,7 @@ dependencies = [
[[package]]
name = "ironcalc_base"
version = "0.3.0"
version = "0.5.0"
dependencies = [
"bitcode",
"chrono",
@@ -448,7 +448,7 @@ dependencies = [
[[package]]
name = "ironcalc_nodejs"
version = "0.3.1"
version = "0.5.0"
dependencies = [
"ironcalc",
"napi",
@@ -784,7 +784,7 @@ dependencies = [
[[package]]
name = "pyroncalc"
version = "0.3.0"
version = "0.5.0"
dependencies = [
"ironcalc",
"pyo3",
@@ -1070,7 +1070,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm"
version = "0.3.2"
version = "0.5.0"
dependencies = [
"ironcalc_base",
"serde",

View File

@@ -77,7 +77,7 @@ And visit <http://0.0.0.0:8000/ironcalc/>
Add the dependency to `Cargo.toml`:
```toml
[dependencies]
ironcalc = { git = "https://github.com/ironcalc/IronCalc", version = "0.1"}
ironcalc = { git = "https://github.com/ironcalc/IronCalc", version = "0.5"}
```
And then use this code in `main.rs`:

View File

@@ -1,6 +1,6 @@
[package]
name = "ironcalc_base"
version = "0.3.0"
version = "0.5.0"
authors = ["Nicolás Hatcher <nicolas@theuniverse.today>"]
edition = "2021"
homepage = "https://www.ironcalc.com"

View File

@@ -1,7 +1,7 @@
[package]
edition = "2021"
name = "ironcalc_nodejs"
version = "0.3.1"
version = "0.5.0"
[lib]
crate-type = ["cdylib"]
@@ -10,7 +10,7 @@ crate-type = ["cdylib"]
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
napi = { version = "2.12.2", default-features = false, features = ["napi4", "serde-json"] }
napi-derive = "2.12.2"
ironcalc = { path = "../../xlsx", version = "0.3.0" }
ironcalc = { path = "../../xlsx", version = "0.5.0" }
serde = { version = "1.0", features = ["derive"] }
[build-dependencies]

View File

@@ -1,6 +1,6 @@
{
"name": "@ironcalc/nodejs",
"version": "0.3.1",
"version": "0.5.1",
"main": "index.js",
"types": "index.d.ts",
"napi": {

View File

@@ -1,6 +1,6 @@
[package]
name = "pyroncalc"
version = "0.3.0"
version = "0.5.0"
edition = "2021"
@@ -12,7 +12,7 @@ crate-type = ["cdylib"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
xlsx = { package= "ironcalc", path = "../../xlsx", version = "0.3.0" }
xlsx = { package= "ironcalc", path = "../../xlsx", version = "0.5.0" }
pyo3 = { version = "0.23", features = ["extension-module"] }

View File

@@ -1,6 +1,6 @@
[project]
name = "ironcalc"
version = "0.3.0"
version = "0.5.0"
description = "Create, edit and evaluate Excel spreadsheets"
requires-python = ">=3.10"
keywords = [

View File

@@ -1,6 +1,6 @@
[package]
name = "wasm"
version = "0.3.2"
version = "0.5.0"
authors = ["Nicolas Hatcher <nicolas@theuniverse.today>"]
description = "IronCalc Web bindings"
license = "MIT/Apache-2.0"
@@ -14,7 +14,7 @@ crate-type = ["cdylib"]
# Uses `../ironcalc/base` when used locally, and uses
# the inicated version from crates.io when published.
# https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#multiple-locations
ironcalc_base = { path = "../../base", version = "0.3", features = ["use_regex_lite"] }
ironcalc_base = { path = "../../base", version = "0.5", features = ["use_regex_lite"] }
serde = { version = "1.0", features = ["derive"] }
wasm-bindgen = "0.2.92"
serde-wasm-bindgen = "0.4"

View File

@@ -7,8 +7,6 @@ import type {
import { styled } from "@mui/material/styles";
import type {} from "@mui/system";
import {
AArrowDown,
AArrowUp,
AlignCenter,
AlignLeft,
AlignRight,
@@ -22,9 +20,11 @@ import {
Grid2x2X,
ImageDown,
Italic,
Minus,
PaintBucket,
PaintRoller,
Percent,
Plus,
Redo2,
RemoveFormatting,
Strikethrough,
@@ -32,6 +32,7 @@ import {
Type,
Underline,
Undo2,
WrapText,
} from "lucide-react";
import { useRef, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -64,6 +65,7 @@ type ToolbarProperties = {
onToggleStrike: (v: boolean) => void;
onToggleHorizontalAlign: (v: string) => void;
onToggleVerticalAlign: (v: string) => void;
onToggleWrapText: (v: boolean) => void;
onCopyStyles: () => void;
onTextColorPicked: (hex: string) => void;
onFillColorPicked: (hex: string) => void;
@@ -74,12 +76,14 @@ type ToolbarProperties = {
onDownloadPNG: () => void;
fillColor: string;
fontColor: string;
fontSize: number;
bold: boolean;
underline: boolean;
italic: boolean;
strike: boolean;
horizontalAlign: HorizontalAlignment;
verticalAlign: VerticalAlignment;
wrapText: boolean;
canEdit: boolean;
numFmt: string;
showGridLines: boolean;
@@ -205,6 +209,30 @@ function Toolbar(properties: ToolbarProperties) {
</StyledButton>
</FormatMenu>
<Divider />
<StyledButton
type="button"
$pressed={false}
disabled={!canEdit}
onClick={() => {
properties.onIncreaseFontSize(-1);
}}
title={t("toolbar.decrease_font_size")}
>
<Minus />
</StyledButton>
<FontSizeBox>{properties.fontSize}</FontSizeBox>
<StyledButton
type="button"
$pressed={false}
disabled={!canEdit}
onClick={() => {
properties.onIncreaseFontSize(1);
}}
title={t("toolbar.increase_font_size")}
>
<Plus />
</StyledButton>
<Divider />
<StyledButton
type="button"
$pressed={properties.bold}
@@ -253,28 +281,6 @@ function Toolbar(properties: ToolbarProperties) {
>
<Type />
</StyledButton>
<StyledButton
type="button"
$pressed={false}
disabled={!canEdit}
onClick={() => {
properties.onIncreaseFontSize(1);
}}
title={t("toolbar.increase_font_size")}
>
<AArrowUp />
</StyledButton>
<StyledButton
type="button"
$pressed={false}
disabled={!canEdit}
onClick={() => {
properties.onIncreaseFontSize(-1);
}}
title={t("toolbar.decrease_font_size")}
>
<AArrowDown />
</StyledButton>
<StyledButton
type="button"
$pressed={false}
@@ -363,6 +369,17 @@ function Toolbar(properties: ToolbarProperties) {
>
<ArrowDownToLine />
</StyledButton>
<StyledButton
type="button"
$pressed={properties.wrapText === true}
onClick={() => {
properties.onToggleWrapText(!properties.wrapText);
}}
disabled={!canEdit}
title={t("toolbar.wrap_text")}
>
<WrapText />
</StyledButton>
<Divider />
<StyledButton
@@ -534,4 +551,16 @@ const Divider = styled("div")({
margin: "0px 12px",
});
const FontSizeBox = styled("div")({
width: "24px",
height: "24px",
lineHeight: "24px",
textAlign: "center",
fontFamily: "Inter",
fontSize: "11px",
border: `1px solid ${theme.palette.grey["300"]}`,
borderRadius: "4px",
minWidth: "24px",
});
export default Toolbar;

View File

@@ -112,6 +112,10 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
updateRangeStyle("alignment.vertical", value);
};
const onToggleWrapText = (value: boolean) => {
updateRangeStyle("alignment.wrap_text", `${value}`);
};
const onTextColorPicked = (hex: string) => {
updateRangeStyle("font.color", hex);
};
@@ -532,6 +536,7 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
onToggleStrike={onToggleStrike}
onToggleHorizontalAlign={onToggleHorizontalAlign}
onToggleVerticalAlign={onToggleVerticalAlign}
onToggleWrapText={onToggleWrapText}
onCopyStyles={onCopyStyles}
onTextColorPicked={onTextColorPicked}
onFillColorPicked={onFillColorPicked}
@@ -628,6 +633,7 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
}}
fillColor={style.fill.fg_color || "#FFFFFF"}
fontColor={style.font.color}
fontSize={style.font.sz}
bold={style.font.b}
underline={style.font.u}
italic={style.font.i}
@@ -638,6 +644,7 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
verticalAlign={
style.alignment?.vertical ? style.alignment.vertical : "bottom"
}
wrapText={style.alignment?.wrap_text || false}
canEdit={true}
numFmt={style.num_fmt}
showGridLines={model.getShowGridLines(model.getSelectedSheet())}

View File

@@ -70,6 +70,52 @@ function hexToRGBA10Percent(colorHex: string): string {
return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
}
/**
* Splits the given text into multiple lines. If `wrapText` is true, it applies word-wrapping
* based on the specified canvas context, maximum width, and horizontal padding.
*
* - First, the text is split by newline characters so that explicit newlines are respected.
* - If wrapping is enabled, each line is further split into words and measured against the
* available width. Whenever adding an extra word would exceed
* this limit, a new line is started.
*
* @param text The text to split into lines.
* @param wrapText Whether to apply word-wrapping or just return text split by newlines.
* @param context The `CanvasRenderingContext2D` used for measuring text width.
* @param width The maximum width for each line.
* @returns An array of lines (strings), each fitting within the specified width if wrapping is enabled.
*/
function computeWrappedLines(
text: string,
wrapText: boolean,
context: CanvasRenderingContext2D,
width: number,
): string[] {
// Split the text into lines
const rawLines = text.split("\n");
if (!wrapText) {
// If there is no wrapping, return the raw lines
return rawLines;
}
const wrappedLines = [];
for (const line of rawLines) {
const words = line.split(" ");
let currentLine = words[0];
for (const word of words) {
const testLine = `${currentLine} ${word}`;
const textWidth = context.measureText(testLine).width;
if (textWidth < width) {
currentLine = testLine;
} else {
wrappedLines.push(currentLine);
currentLine = word;
}
}
wrappedLines.push(currentLine);
}
return wrappedLines;
}
export default class WorksheetCanvas {
sheetWidth: number;
@@ -371,6 +417,7 @@ export default class WorksheetCanvas {
if (style.alignment?.vertical) {
verticalAlign = style.alignment.vertical;
}
const wrapText = style.alignment?.wrap_text || false;
const context = this.ctx;
context.font = font;
@@ -496,9 +543,14 @@ export default class WorksheetCanvas {
context.rect(x, y, width, height);
context.clip();
// Is there any better parameter?
// Is there any better to determine the line height?
const lineHeight = fontSize * 1.5;
const lines = fullText.split("\n");
const lines = computeWrappedLines(
fullText,
wrapText,
context,
width - padding,
);
const lineCount = lines.length;
lines.forEach((text, line) => {
@@ -682,13 +734,19 @@ export default class WorksheetCanvas {
if (fullText === "") {
continue;
}
const width = this.getColumnWidth(sheet, column);
const style = this.model.getCellStyle(sheet, row, column);
const fontSize = style.font.sz;
const lineHeight = fontSize * 1.5;
let font = `${fontSize}px ${defaultCellFontFamily}`;
font = style.font.b ? `bold ${font}` : `400 ${font}`;
this.ctx.font = font;
const lines = fullText.split("\n");
const lines = computeWrappedLines(
fullText,
style.alignment?.wrap_text || false,
this.ctx,
width,
);
const lineCount = lines.length;
// This is computed so that the y position of the text is independent of the vertical alignment
const textHeight = (lineCount - 1) * lineHeight + 8 + fontSize;

View File

@@ -26,6 +26,7 @@
"vertical_align_middle": " Align middle",
"vertical_align_top": "Align top",
"selected_png": "Export Selected area as PNG",
"wrap_text": "Wrap text",
"format_menu": {
"auto": "Auto",
"number": "Number",

View File

@@ -1,6 +1,6 @@
[package]
name = "ironcalc_server"
version = "0.3.0"
version = "0.5.0"
edition = "2021"
[dependencies]

View File

@@ -1,6 +1,6 @@
[package]
name = "ironcalc"
version = "0.3.0"
version = "0.5.0"
authors = ["Nicolás Hatcher <nicolas@theuniverse.today>"]
edition = "2021"
homepage = "https://www.ironcalc.com"
@@ -20,7 +20,7 @@ thiserror = "1.0"
# Uses `../base` when used locally, and uses
# the inicated version from crates.io when published.
# https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#multiple-locations
ironcalc_base = { path = "../base", version = "0.3" }
ironcalc_base = { path = "../base", version = "0.5" }
itertools = "0.12"
chrono = "0.4"
bitcode = "0.6.0"

View File

@@ -9,11 +9,9 @@
//!
//! ```toml
//! [dependencies]
//! ironcalc = { git = "https://github.com/ironcalc/IronCalc" }
//! ironcalc = { git = "https://github.com/ironcalc/IronCalc", tag = "v0.5.0" }
//! ```
//!
//! <small> until version 0.5.0 you should use the git dependencies as stated </small>
//!
//! A simple example with some numbers, a new sheet and a formula:
//!
//!