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
1 change: 1 addition & 0 deletions packages/o-spreadsheet-engine/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export const DEFAULT_STYLE = {
rotation: 0,
} satisfies Required<Style>;

export const ROTATION_EPSILON = 0.001;
export const DEFAULT_VERTICAL_ALIGN = DEFAULT_STYLE.verticalAlign;
export const DEFAULT_WRAPPING_MODE = DEFAULT_STYLE.wrapping;

Expand Down
6 changes: 6 additions & 0 deletions packages/o-spreadsheet-engine/src/helpers/text_helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,12 @@ export function computeRotationPosition(
} else {
if (center) {
x -= sh / 2;
y -= height / 2;
if (rotateTowardCellCenter) {
y += sh;
} else {
y -= sh;
}
} else if (rotateTowardCellCenter) {
x -= sh;
y -= sw / 2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
XLSXImportData,
XLSXVerticalAlignment,
} from "../../types/xlsx";
import { rotationFromXLSX } from "../helpers/content_helpers";
import { arrayToObject } from "../helpers/misc";
import { WarningTypes, XLSXImportWarningManager } from "../helpers/xlsx_parser_error_manager";
import { convertColor } from "./color_conversion";
Expand Down Expand Up @@ -98,6 +99,9 @@ export function convertStyle(
align: styleStruct.alignment?.horizontal
? H_ALIGNMENT_CONVERSION_MAP[styleStruct.alignment.horizontal]
: undefined,
rotation: styleStruct.alignment?.textRotation
? rotationFromXLSX(styleStruct.alignment.textRotation)
: undefined,
// In xlsx fills, bgColor is the color of the fill, and fgColor is the color of the pattern above the background, except in solid fills
fillColor:
styleStruct.fillStyle?.patternType === "solid"
Expand Down
3 changes: 3 additions & 0 deletions packages/o-spreadsheet-engine/src/xlsx/functions/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ export function addStyles(styles: XLSXStyle[]): XMLString {
if (style.alignment && style.alignment.wrapText) {
alignAttrs.push(["wrapText", "1"]);
}
if (style.alignment && style.alignment.textRotation) {
alignAttrs.push(["textRotation", style.alignment.textRotation]);
}

if (alignAttrs.length > 0) {
attributes.push(["applyAlignment", "1"]); // for Libre Office
Expand Down
24 changes: 24 additions & 0 deletions packages/o-spreadsheet-engine/src/xlsx/helpers/content_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export function extractStyle(
? V_ALIGNMENT_EXPORT_CONVERSION_MAP[style.verticalAlign]
: undefined,
wrapText: style.wrapping === "wrap" || content?.includes(NEWLINE) ? true : undefined,
textRotation: style.rotation ? rotationToXLSX(style.rotation) : undefined,
},
};

Expand All @@ -149,6 +150,28 @@ export function extractStyle(
return styles;
}

function rotationToXLSX(rad: number): number {
let deg = Math.round((-rad / Math.PI) * 180) % 180;
if (deg > 90) {
deg -= 180;
} else if (deg < -90) {
deg += 180;
}
if (deg >= 0) {
return deg;
} else {
return 90 - deg;
}
}

export function rotationFromXLSX(deg: number): number {
if (deg <= 90) {
return -(deg / 180) * Math.PI;
} else {
return (-(90 - deg) / 180) * Math.PI;
}
}

export function normalizeStyle(construct: XLSXStructure, styles: ExtractedStyle): number {
// Normalize this
const numFmtId = convertFormat(styles["numFmt"], construct.numFmts);
Expand All @@ -161,6 +184,7 @@ export function normalizeStyle(construct: XLSXStructure, styles: ExtractedStyle)
vertical: styles.alignment.vertical,
horizontal: styles.alignment.horizontal,
wrapText: styles.alignment.wrapText,
textRotation: styles.alignment.textRotation,
},
} as XLSXStyle;

Expand Down
59 changes: 32 additions & 27 deletions src/actions/format_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
DEFAULT_VERTICAL_ALIGN,
DEFAULT_WRAPPING_MODE,
FONT_SIZES,
ROTATION_EPSILON,
} from "@odoo/o-spreadsheet-engine/constants";
import { parseLiteral } from "@odoo/o-spreadsheet-engine/helpers/cells/cell_evaluation";
import { getDateTimeFormat } from "@odoo/o-spreadsheet-engine/helpers/locale";
Expand Down Expand Up @@ -208,39 +209,52 @@ export const formatRotation: ActionSpec = {
icon: (env) => getRotationIcon(env),
};

function setRotation(env: SpreadsheetChildEnv, rotation: number) {
rotation = Math.trunc(rotation / ROTATION_EPSILON) * ROTATION_EPSILON;
setStyle(env, { rotation });
}

function currentRotationEqual(env: SpreadsheetChildEnv, rotation: number): boolean {
const current = env.model.getters.getCurrentStyle().rotation;
if (current === undefined) {
return rotation === 0;
}
return Math.abs(current - rotation) < ROTATION_EPSILON;
}

export const formatNoRotation: ActionSpec = {
name: _t("No rotation"),
execute: (env) => setStyle(env, { rotation: 0 }),
icon: "o-spreadsheet-Icon.ROTATION-0",
isActive: (env) => env.model.getters.getCurrentStyle().rotation === 0,
isActive: (env) => currentRotationEqual(env, 0),
};

export const formatRotation45: ActionSpec = {
name: _t("45° rotation"),
execute: (env) => setStyle(env, { rotation: Math.PI / 4 }),
execute: (env) => setRotation(env, Math.PI / 4),
icon: "o-spreadsheet-Icon.ROTATION-45",
isActive: (env) => env.model.getters.getCurrentStyle().rotation === Math.PI / 4,
isActive: (env) => currentRotationEqual(env, Math.PI / 4),
};

export const formatRotation90: ActionSpec = {
name: _t("90° rotation"),
execute: (env) => setStyle(env, { rotation: Math.PI / 2 }),
execute: (env) => setRotation(env, Math.PI / 2),
icon: "o-spreadsheet-Icon.ROTATION-90",
isActive: (env) => env.model.getters.getCurrentStyle().rotation === Math.PI / 2,
isActive: (env) => currentRotationEqual(env, Math.PI / 2),
};

export const formatRotation270: ActionSpec = {
name: _t("-90° rotation"),
execute: (env) => setStyle(env, { rotation: -Math.PI / 2 }),
execute: (env) => setRotation(env, -Math.PI / 2),
icon: "o-spreadsheet-Icon.ROTATION-270",
isActive: (env) => env.model.getters.getCurrentStyle().rotation === -Math.PI / 2,
isActive: (env) => currentRotationEqual(env, -Math.PI / 2),
};

export const formatRotation315: ActionSpec = {
name: _t("-45° rotation"),
execute: (env) => setStyle(env, { rotation: -Math.PI / 4 }),
execute: (env) => setRotation(env, -Math.PI / 4),
icon: "o-spreadsheet-Icon.ROTATION-315",
isActive: (env) => env.model.getters.getCurrentStyle().rotation === -Math.PI / 4,
isActive: (env) => currentRotationEqual(env, -Math.PI / 4),
};

export const formatStrikethrough: ActionSpec = {
Expand Down Expand Up @@ -436,11 +450,6 @@ function getWrappingMode(env: SpreadsheetChildEnv): Wrapping {
return DEFAULT_WRAPPING_MODE;
}

function getRotation(env: SpreadsheetChildEnv): number {
const style = env.model.getters.getCurrentStyle();
return style.rotation ?? 0;
}

function getHorizontalAlignmentIcon(env: SpreadsheetChildEnv) {
const horizontalAlign = getHorizontalAlign(env);

Expand Down Expand Up @@ -481,18 +490,14 @@ function getWrapModeIcon(env: SpreadsheetChildEnv) {
}

function getRotationIcon(env: SpreadsheetChildEnv) {
const rotation = getRotation(env);

switch (rotation) {
case Math.PI / 2:
return "o-spreadsheet-Icon.ROTATION-90";
case -Math.PI / 2:
return "o-spreadsheet-Icon.ROTATION-270";
case Math.PI / 4:
return "o-spreadsheet-Icon.ROTATION-45";
case -Math.PI / 4:
return "o-spreadsheet-Icon.ROTATION-315";
default:
return "o-spreadsheet-Icon.ROTATION-0";
if (currentRotationEqual(env, Math.PI / 2)) {
return "o-spreadsheet-Icon.ROTATION-90";
} else if (currentRotationEqual(env, -Math.PI / 2)) {
return "o-spreadsheet-Icon.ROTATION-270";
} else if (currentRotationEqual(env, Math.PI / 4)) {
return "o-spreadsheet-Icon.ROTATION-45";
} else if (currentRotationEqual(env, -Math.PI / 4)) {
return "o-spreadsheet-Icon.ROTATION-315";
}
return "o-spreadsheet-Icon.ROTATION-0";
}
4 changes: 2 additions & 2 deletions tests/helpers/text_helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ describe("computeRotationPosition", () => {
test.each([Math.PI / 2, Math.PI / 3, Math.PI / 4])("Positive Rotation", (rotation) => {
const sin = Math.sin(rotation);
const newX = textBox.x + (sin * textHeight) / 2;
const newY = textBox.y;
const newY = textBox.y - textHeight / 2 + sin * textHeight;

expect(computeRotationPosition(textBox, { ...style, rotation })).toMatchObject({
x: expect.toBeCloseTo(rotate(newX, newY, rotation).x),
Expand All @@ -171,7 +171,7 @@ describe("computeRotationPosition", () => {
test.each([-Math.PI / 2, -Math.PI / 3, -Math.PI / 4])("Negative Rotation", (rotation) => {
const sin = Math.sin(rotation);
const newX = textBox.x + (sin * textHeight) / 2;
const newY = textBox.y;
const newY = textBox.y - textHeight / 2 - sin * textHeight;
expect(computeRotationPosition(textBox, { ...style, rotation })).toMatchObject({
x: expect.toBeCloseTo(rotate(newX, newY, rotation).x),
y: expect.toBeCloseTo(rotate(newX, newY, rotation).y),
Expand Down
Loading