Ver Fonte

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
Aakansha Doshi há 2 anos atrás
pai
commit
af3b93c410

+ 1 - 1
src/element/textElement.test.ts

@@ -157,7 +157,7 @@ describe("Test measureText", () => {
 
     expect(res.container).toMatchInlineSnapshot(`
       <div
-        style="position: absolute; white-space: pre-wrap; font: Emoji 20px 20px; min-height: 1em; width: 191px; overflow: hidden; word-break: break-word; line-height: 0px;"
+        style="position: absolute; white-space: pre-wrap; font: Emoji 20px 20px; min-height: 1em; width: 111px; overflow: hidden; word-break: break-word; line-height: 0px;"
       >
         <span
           style="display: inline-block; overflow: hidden; width: 1px; height: 1px;"

+ 23 - 9
src/element/textElement.ts

@@ -271,9 +271,12 @@ export const measureText = (
   container.style.whiteSpace = "pre";
   container.style.font = font;
   container.style.minHeight = "1em";
+  const textWidth = getTextWidth(text, font);
+
   if (maxWidth) {
     const lineHeight = getApproxLineHeight(font);
-    container.style.width = `${String(maxWidth + 1)}px`;
+    container.style.width = `${String(Math.min(textWidth, maxWidth) + 1)}px`;
+
     container.style.overflow = "hidden";
     container.style.wordBreak = "break-word";
     container.style.lineHeight = `${String(lineHeight)}px`;
@@ -291,7 +294,10 @@ export const measureText = (
   // Baseline is important for positioning text on canvas
   const baseline = span.offsetTop + span.offsetHeight;
   // Since span adds 1px extra width to the container
-  const width = container.offsetWidth - 1;
+  let width = container.offsetWidth;
+  if (maxWidth && textWidth > 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<string> = [];
   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;
 };

+ 3 - 3
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 }, () => {

+ 28 - 0
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,

+ 1 - 1
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,
 }
 `;

+ 2 - 2
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,
         }
       `);