diff --git a/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx b/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx
index 9dd50fe..ff78123 100644
--- a/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx
+++ b/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx
@@ -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;
@@ -81,6 +83,7 @@ type ToolbarProperties = {
strike: boolean;
horizontalAlign: HorizontalAlignment;
verticalAlign: VerticalAlignment;
+ wrapText: boolean;
canEdit: boolean;
numFmt: string;
showGridLines: boolean;
@@ -206,6 +209,30 @@ function Toolbar(properties: ToolbarProperties) {
+ {
+ properties.onIncreaseFontSize(-1);
+ }}
+ title={t("toolbar.decrease_font_size")}
+ >
+
+
+ {properties.fontSize}
+ {
+ properties.onIncreaseFontSize(1);
+ }}
+ title={t("toolbar.increase_font_size")}
+ >
+
+
+
-
- {
- properties.onIncreaseFontSize(-1);
- }}
- title={t("toolbar.decrease_font_size")}
- >
-
-
- {properties.fontSize}
- {
- properties.onIncreaseFontSize(1);
- }}
- title={t("toolbar.increase_font_size")}
- >
-
-
-
+ {
+ properties.onToggleWrapText(!properties.wrapText);
+ }}
+ disabled={!canEdit}
+ title={t("toolbar.wrap_text")}
+ >
+
+
{
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}
@@ -639,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())}
diff --git a/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts b/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts
index 31656b7..19a40d5 100644
--- a/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts
+++ b/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts
@@ -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;
diff --git a/webapp/IronCalc/src/locale/en_us.json b/webapp/IronCalc/src/locale/en_us.json
index 60ae6b9..11cc0c7 100644
--- a/webapp/IronCalc/src/locale/en_us.json
+++ b/webapp/IronCalc/src/locale/en_us.json
@@ -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",