diff --git a/src/element/newElement.ts b/src/element/newElement.ts index c1b0f17b..4922a5b4 100644 --- a/src/element/newElement.ts +++ b/src/element/newElement.ts @@ -33,7 +33,7 @@ import { measureText, normalizeText, wrapText, - getMaxContainerWidth, + getBoundTextMaxWidth, getDefaultLineHeight, } from "./textElement"; import { @@ -310,7 +310,7 @@ export const refreshTextDimensions = ( text = wrapText( text, getFontString(textElement), - getMaxContainerWidth(container), + getBoundTextMaxWidth(container), ); } const dimensions = getAdjustedDimensions(textElement, text); diff --git a/src/element/resizeElements.ts b/src/element/resizeElements.ts index 69b8afae..67a6346b 100644 --- a/src/element/resizeElements.ts +++ b/src/element/resizeElements.ts @@ -44,10 +44,10 @@ import { getBoundTextElementId, getContainerElement, handleBindTextResize, - getMaxContainerWidth, + getBoundTextMaxWidth, getApproxMinLineHeight, measureText, - getMaxContainerHeight, + getBoundTextMaxHeight, } from "./textElement"; export const normalizeAngle = (angle: number): number => { @@ -204,7 +204,7 @@ const measureFontSizeFromWidth = ( if (hasContainer) { const container = getContainerElement(element); if (container) { - width = getMaxContainerWidth(container); + width = getBoundTextMaxWidth(container); } } const nextFontSize = element.fontSize * (nextWidth / width); @@ -435,8 +435,8 @@ export const resizeSingleElement = ( const nextFont = measureFontSizeFromWidth( boundTextElement, - getMaxContainerWidth(updatedElement), - getMaxContainerHeight(updatedElement), + getBoundTextMaxWidth(updatedElement), + getBoundTextMaxHeight(updatedElement, boundTextElement), ); if (nextFont === null) { return; @@ -718,10 +718,10 @@ const resizeMultipleElements = ( const metrics = measureFontSizeFromWidth( boundTextElement ?? (element.orig as ExcalidrawTextElement), boundTextElement - ? getMaxContainerWidth(updatedElement) + ? getBoundTextMaxWidth(updatedElement) : updatedElement.width, boundTextElement - ? getMaxContainerHeight(updatedElement) + ? getBoundTextMaxHeight(updatedElement, boundTextElement) : updatedElement.height, ); diff --git a/src/element/textElement.test.ts b/src/element/textElement.test.ts index f83eafd1..b6221336 100644 --- a/src/element/textElement.test.ts +++ b/src/element/textElement.test.ts @@ -3,15 +3,15 @@ import { API } from "../tests/helpers/api"; import { computeContainerDimensionForBoundText, getContainerCoords, - getMaxContainerWidth, - getMaxContainerHeight, + getBoundTextMaxWidth, + getBoundTextMaxHeight, wrapText, detectLineHeight, getLineHeightInPx, getDefaultLineHeight, parseTokens, } from "./textElement"; -import { FontString } from "./types"; +import { ExcalidrawTextElementWithContainer, FontString } from "./types"; describe("Test wrapText", () => { const font = "20px Cascadia, width: Segoe UI Emoji" as FontString; @@ -311,7 +311,7 @@ describe("Test measureText", () => { }); }); - describe("Test getMaxContainerWidth", () => { + describe("Test getBoundTextMaxWidth", () => { const params = { width: 178, height: 194, @@ -319,39 +319,76 @@ describe("Test measureText", () => { it("should return max width when container is rectangle", () => { const container = API.createElement({ type: "rectangle", ...params }); - expect(getMaxContainerWidth(container)).toBe(168); + expect(getBoundTextMaxWidth(container)).toBe(168); }); it("should return max width when container is ellipse", () => { const container = API.createElement({ type: "ellipse", ...params }); - expect(getMaxContainerWidth(container)).toBe(116); + expect(getBoundTextMaxWidth(container)).toBe(116); }); it("should return max width when container is diamond", () => { const container = API.createElement({ type: "diamond", ...params }); - expect(getMaxContainerWidth(container)).toBe(79); + expect(getBoundTextMaxWidth(container)).toBe(79); }); }); - describe("Test getMaxContainerHeight", () => { + describe("Test getBoundTextMaxHeight", () => { const params = { width: 178, height: 194, + id: '"container-id', }; + const boundTextElement = API.createElement({ + type: "text", + id: "text-id", + x: 560.51171875, + y: 202.033203125, + width: 154, + height: 175, + fontSize: 20, + fontFamily: 1, + text: "Excalidraw is a\nvirtual \nopensource \nwhiteboard for \nsketching \nhand-drawn like\ndiagrams", + textAlign: "center", + verticalAlign: "middle", + containerId: params.id, + }) as ExcalidrawTextElementWithContainer; + it("should return max height when container is rectangle", () => { const container = API.createElement({ type: "rectangle", ...params }); - expect(getMaxContainerHeight(container)).toBe(184); + expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(184); }); it("should return max height when container is ellipse", () => { const container = API.createElement({ type: "ellipse", ...params }); - expect(getMaxContainerHeight(container)).toBe(127); + expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(127); }); it("should return max height when container is diamond", () => { const container = API.createElement({ type: "diamond", ...params }); - expect(getMaxContainerHeight(container)).toBe(87); + expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(87); + }); + + it("should return max height when container is arrow", () => { + const container = API.createElement({ + type: "arrow", + ...params, + }); + expect(getBoundTextMaxHeight(container, boundTextElement)).toBe(194); + }); + + it("should return max height when container is arrow and height is less than threshold", () => { + const container = API.createElement({ + type: "arrow", + ...params, + height: 70, + boundElements: [{ type: "text", id: "text-id" }], + }); + + expect(getBoundTextMaxHeight(container, boundTextElement)).toBe( + boundTextElement.height, + ); }); }); }); diff --git a/src/element/textElement.ts b/src/element/textElement.ts index 339f68b9..a6d0c327 100644 --- a/src/element/textElement.ts +++ b/src/element/textElement.ts @@ -65,7 +65,7 @@ export const redrawTextBoundingBox = ( boundTextUpdates.text = textElement.text; if (container) { - maxWidth = getMaxContainerWidth(container); + maxWidth = getBoundTextMaxWidth(container); boundTextUpdates.text = wrapText( textElement.originalText, getFontString(textElement), @@ -84,7 +84,10 @@ export const redrawTextBoundingBox = ( if (container) { const containerDims = getContainerDims(container); - const maxContainerHeight = getMaxContainerHeight(container); + const maxContainerHeight = getBoundTextMaxHeight( + container, + textElement as ExcalidrawTextElementWithContainer, + ); let nextHeight = containerDims.height; if (metrics.height > maxContainerHeight) { @@ -173,8 +176,11 @@ export const handleBindTextResize = ( let nextHeight = textElement.height; let nextWidth = textElement.width; const containerDims = getContainerDims(container); - const maxWidth = getMaxContainerWidth(container); - const maxHeight = getMaxContainerHeight(container); + const maxWidth = getBoundTextMaxWidth(container); + const maxHeight = getBoundTextMaxHeight( + container, + textElement as ExcalidrawTextElementWithContainer, + ); let containerHeight = containerDims.height; let nextBaseLine = textElement.baseline; if (transformHandleType !== "n" && transformHandleType !== "s") { @@ -246,8 +252,8 @@ export const computeBoundTextPosition = ( ); } const containerCoords = getContainerCoords(container); - const maxContainerHeight = getMaxContainerHeight(container); - const maxContainerWidth = getMaxContainerWidth(container); + const maxContainerHeight = getBoundTextMaxHeight(container, boundTextElement); + const maxContainerWidth = getBoundTextMaxWidth(container); let x; let y; @@ -880,18 +886,10 @@ export const computeContainerDimensionForBoundText = ( return dimension + padding; }; -export const getMaxContainerWidth = (container: ExcalidrawElement) => { +export const getBoundTextMaxWidth = (container: ExcalidrawElement) => { const width = getContainerDims(container).width; if (isArrowElement(container)) { - const containerWidth = width - BOUND_TEXT_PADDING * 8 * 2; - if (containerWidth <= 0) { - const boundText = getBoundTextElement(container); - if (boundText) { - return boundText.width; - } - return BOUND_TEXT_PADDING * 8 * 2; - } - return containerWidth; + return width - BOUND_TEXT_PADDING * 8 * 2; } if (container.type === "ellipse") { @@ -908,16 +906,15 @@ export const getMaxContainerWidth = (container: ExcalidrawElement) => { return width - BOUND_TEXT_PADDING * 2; }; -export const getMaxContainerHeight = (container: ExcalidrawElement) => { +export const getBoundTextMaxHeight = ( + container: ExcalidrawElement, + boundTextElement: ExcalidrawTextElementWithContainer, +) => { const height = getContainerDims(container).height; if (isArrowElement(container)) { const containerHeight = height - BOUND_TEXT_PADDING * 8 * 2; if (containerHeight <= 0) { - const boundText = getBoundTextElement(container); - if (boundText) { - return boundText.height; - } - return BOUND_TEXT_PADDING * 8 * 2; + return boundTextElement.height; } return height; } diff --git a/src/element/textWysiwyg.tsx b/src/element/textWysiwyg.tsx index ef4f7c92..63bc9e4a 100644 --- a/src/element/textWysiwyg.tsx +++ b/src/element/textWysiwyg.tsx @@ -32,8 +32,8 @@ import { normalizeText, redrawTextBoundingBox, wrapText, - getMaxContainerHeight, - getMaxContainerWidth, + getBoundTextMaxHeight, + getBoundTextMaxWidth, computeContainerDimensionForBoundText, detectLineHeight, } from "./textElement"; @@ -205,8 +205,11 @@ export const textWysiwyg = ({ } } - maxWidth = getMaxContainerWidth(container); - maxHeight = getMaxContainerHeight(container); + maxWidth = getBoundTextMaxWidth(container); + maxHeight = getBoundTextMaxHeight( + container, + updatedTextElement as ExcalidrawTextElementWithContainer, + ); // autogrow container height if text exceeds if (!isArrowElement(container) && textElementHeight > maxHeight) { @@ -377,7 +380,7 @@ export const textWysiwyg = ({ const wrappedText = wrapText( `${editable.value}${data}`, font, - getMaxContainerWidth(container), + getBoundTextMaxWidth(container), ); const width = getTextWidth(wrappedText, font); editable.style.width = `${width}px`; @@ -394,7 +397,7 @@ export const textWysiwyg = ({ const wrappedText = wrapText( normalizeText(editable.value), font, - getMaxContainerWidth(container!), + getBoundTextMaxWidth(container!), ); const { width, height } = measureText( wrappedText, diff --git a/src/renderer/renderElement.ts b/src/renderer/renderElement.ts index f85c83a6..77ea1458 100644 --- a/src/renderer/renderElement.ts +++ b/src/renderer/renderElement.ts @@ -44,8 +44,8 @@ import { getContainerCoords, getContainerElement, getLineHeightInPx, - getMaxContainerHeight, - getMaxContainerWidth, + getBoundTextMaxHeight, + getBoundTextMaxWidth, } from "../element/textElement"; import { LinearElementEditor } from "../element/linearElementEditor"; @@ -868,14 +868,17 @@ const drawElementFromCanvas = ( "true" && hasBoundTextElement(element) ) { + const textElement = getBoundTextElement( + element, + ) as ExcalidrawTextElementWithContainer; const coords = getContainerCoords(element); context.strokeStyle = "#c92a2a"; context.lineWidth = 3; context.strokeRect( (coords.x + renderConfig.scrollX) * window.devicePixelRatio, (coords.y + renderConfig.scrollY) * window.devicePixelRatio, - getMaxContainerWidth(element) * window.devicePixelRatio, - getMaxContainerHeight(element) * window.devicePixelRatio, + getBoundTextMaxWidth(element) * window.devicePixelRatio, + getBoundTextMaxHeight(element, textElement) * window.devicePixelRatio, ); } } diff --git a/src/tests/linearElementEditor.test.tsx b/src/tests/linearElementEditor.test.tsx index 15fd105e..c71283a4 100644 --- a/src/tests/linearElementEditor.test.tsx +++ b/src/tests/linearElementEditor.test.tsx @@ -20,7 +20,7 @@ import { resize, rotate } from "./utils"; import { getBoundTextElementPosition, wrapText, - getMaxContainerWidth, + getBoundTextMaxWidth, } from "../element/textElement"; import * as textElementUtils from "../element/textElement"; import { ROUNDNESS, VERTICAL_ALIGN } from "../constants"; @@ -729,7 +729,7 @@ describe("Test Linear Elements", () => { type: "text", x: 0, y: 0, - text: wrapText(text, font, getMaxContainerWidth(container)), + text: wrapText(text, font, getBoundTextMaxWidth(container)), containerId: container.id, width: 30, height: 20, @@ -1149,7 +1149,7 @@ describe("Test Linear Elements", () => { expect(rect.x).toBe(400); expect(rect.y).toBe(0); expect( - wrapText(textElement.originalText, font, getMaxContainerWidth(arrow)), + wrapText(textElement.originalText, font, getBoundTextMaxWidth(arrow)), ).toMatchInlineSnapshot(` "Online whiteboard collaboration made easy" @@ -1172,7 +1172,7 @@ describe("Test Linear Elements", () => { false, ); expect( - wrapText(textElement.originalText, font, getMaxContainerWidth(arrow)), + wrapText(textElement.originalText, font, getBoundTextMaxWidth(arrow)), ).toMatchInlineSnapshot(` "Online whiteboard collaboration made