From af3b93c4103d1c9e42f1ab79d8087d4551e0a9bd Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Fri, 23 Dec 2022 21:45:49 +0530 Subject: [PATCH] fix: use canvas measureText to calculate width in measureText (#6030) * fix: use canvas measureText to calculate width in measureText * calculate multiline width correctly using canvas measure text and rename functions * set correct width when pasting in bound container * take existing value + new pasted * remove debugger :p * fix snaps --- src/element/textElement.test.ts | 2 +- src/element/textElement.ts | 32 +++++++++++++------ src/element/textWysiwyg.test.tsx | 6 ++-- src/element/textWysiwyg.tsx | 28 ++++++++++++++++ .../data/__snapshots__/restore.test.ts.snap | 2 +- src/tests/linearElementEditor.test.tsx | 4 +-- 6 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/element/textElement.test.ts b/src/element/textElement.test.ts index c9027205..c61db05f 100644 --- a/src/element/textElement.test.ts +++ b/src/element/textElement.test.ts @@ -157,7 +157,7 @@ describe("Test measureText", () => { expect(res.container).toMatchInlineSnapshot(`
maxWidth) { + width = width - 1; + } const height = container.offsetHeight; document.body.removeChild(container); if (isTestEnv()) { @@ -312,7 +318,7 @@ export const getApproxLineHeight = (font: FontString) => { }; let canvas: HTMLCanvasElement | undefined; -const getTextWidth = (text: string, font: FontString) => { +const getLineWidth = (text: string, font: FontString) => { if (!canvas) { canvas = document.createElement("canvas"); } @@ -330,10 +336,18 @@ const getTextWidth = (text: string, font: FontString) => { return metrics.width; }; +export const getTextWidth = (text: string, font: FontString) => { + const lines = text.split("\n"); + let width = 0; + lines.forEach((line) => { + width = Math.max(width, getLineWidth(line, font)); + }); + return width; +}; export const wrapText = (text: string, font: FontString, maxWidth: number) => { const lines: Array = []; const originalLines = text.split("\n"); - const spaceWidth = getTextWidth(" ", font); + const spaceWidth = getLineWidth(" ", font); const push = (str: string) => { if (str.trim()) { @@ -351,7 +365,7 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => { let index = 0; while (index < words.length) { - const currentWordWidth = getTextWidth(words[index], font); + const currentWordWidth = getLineWidth(words[index], font); // Start breaking longer words exceeding max width if (currentWordWidth >= maxWidth) { @@ -400,7 +414,7 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => { // Start appending words in a line till max width reached while (currentLineWidthTillNow < maxWidth && index < words.length) { const word = words[index]; - currentLineWidthTillNow = getTextWidth(currentLine + word, font); + currentLineWidthTillNow = getLineWidth(currentLine + word, font); if (currentLineWidthTillNow >= maxWidth) { push(currentLine); @@ -448,7 +462,7 @@ export const charWidth = (() => { cachedCharWidth[font] = []; } if (!cachedCharWidth[font][ascii]) { - const width = getTextWidth(char, font); + const width = getLineWidth(char, font); cachedCharWidth[font][ascii] = width; } @@ -508,7 +522,7 @@ export const getApproxCharsToFitInWidth = (font: FontString, width: number) => { while (widthTillNow <= width) { const batch = dummyText.substr(index, index + batchLength); str += batch; - widthTillNow += getTextWidth(str, font); + widthTillNow += getLineWidth(str, font); if (index === dummyText.length - 1) { index = 0; } @@ -517,7 +531,7 @@ export const getApproxCharsToFitInWidth = (font: FontString, width: number) => { while (widthTillNow > width) { str = str.substr(0, str.length - 1); - widthTillNow = getTextWidth(str, font); + widthTillNow = getLineWidth(str, font); } return str.length; }; diff --git a/src/element/textWysiwyg.test.tsx b/src/element/textWysiwyg.test.tsx index 59ed2829..cb24386e 100644 --- a/src/element/textWysiwyg.test.tsx +++ b/src/element/textWysiwyg.test.tsx @@ -862,7 +862,7 @@ describe("textWysiwyg", () => { resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]); expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(` Array [ - 110.5, + 110, 17, ] `); @@ -910,7 +910,7 @@ describe("textWysiwyg", () => { resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]); expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(` Array [ - 426, + 425, -539, ] `); @@ -1026,7 +1026,7 @@ describe("textWysiwyg", () => { mouse.up(rectangle.x + 100, rectangle.y + 50); expect(rectangle.x).toBe(80); expect(rectangle.y).toBe(85); - expect(text.x).toBe(90.5); + expect(text.x).toBe(90); expect(text.y).toBe(90); Keyboard.withModifierKeys({ ctrl: true }, () => { diff --git a/src/element/textWysiwyg.tsx b/src/element/textWysiwyg.tsx index 3f462325..9cb96138 100644 --- a/src/element/textWysiwyg.tsx +++ b/src/element/textWysiwyg.tsx @@ -28,6 +28,7 @@ import { getContainerDims, getContainerElement, getTextElementAngle, + getTextWidth, normalizeText, wrapText, } from "./textElement"; @@ -39,6 +40,7 @@ import { actionZoomIn, actionZoomOut } from "../actions/actionCanvas"; import App from "../components/App"; import { getMaxContainerHeight, getMaxContainerWidth } from "./newElement"; import { LinearElementEditor } from "./linearElementEditor"; +import { parseClipboard } from "../clipboard"; const getTransform = ( width: number, @@ -348,6 +350,32 @@ export const textWysiwyg = ({ updateWysiwygStyle(); if (onChange) { + editable.onpaste = async (event) => { + const clipboardData = await parseClipboard(event, true); + if (!clipboardData.text) { + return; + } + const data = normalizeText(clipboardData.text); + if (!data) { + return; + } + const container = getContainerElement(element); + + const font = getFontString({ + fontSize: app.state.currentItemFontSize, + fontFamily: app.state.currentItemFontFamily, + }); + if (container) { + const wrappedText = wrapText( + `${editable.value}${data}`, + font, + getMaxContainerWidth(container), + ); + const width = getTextWidth(wrappedText, font); + editable.style.width = `${width}px`; + } + }; + editable.oninput = () => { const updatedTextElement = Scene.getScene(element)?.getElement( id, diff --git a/src/tests/data/__snapshots__/restore.test.ts.snap b/src/tests/data/__snapshots__/restore.test.ts.snap index 444bc0ea..20cf1e7d 100644 --- a/src/tests/data/__snapshots__/restore.test.ts.snap +++ b/src/tests/data/__snapshots__/restore.test.ts.snap @@ -312,7 +312,7 @@ Object { "versionNonce": 0, "verticalAlign": "middle", "width": 100, - "x": 0.5, + "x": 0, "y": 0, } `; diff --git a/src/tests/linearElementEditor.test.tsx b/src/tests/linearElementEditor.test.tsx index 68d97171..af9b76a1 100644 --- a/src/tests/linearElementEditor.test.tsx +++ b/src/tests/linearElementEditor.test.tsx @@ -1027,7 +1027,7 @@ describe("Test Linear Elements", () => { expect(getBoundTextElementPosition(container, textElement)) .toMatchInlineSnapshot(` Object { - "x": 387.5, + "x": 387, "y": 70, } `); @@ -1086,7 +1086,7 @@ describe("Test Linear Elements", () => { expect(getBoundTextElementPosition(container, textElement)) .toMatchInlineSnapshot(` Object { - "x": 190.5, + "x": 190, "y": 20, } `);