Browse Source

fix: set the width correctly using measureText in editor (#6162)

Aakansha Doshi 2 years ago
parent
commit
e41ea9562b
3 changed files with 20 additions and 14 deletions
  1. 1 1
      src/element/textElement.test.ts
  2. 7 9
      src/element/textElement.ts
  3. 12 4
      src/element/textWysiwyg.tsx

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

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

+ 7 - 9
src/element/textElement.ts

@@ -271,12 +271,11 @@ 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(Math.min(textWidth, maxWidth) + 1)}px`;
-
+    // since we are adding a span of width 1px later
+    container.style.maxWidth = `${maxWidth + 1}px`;
     container.style.overflow = "hidden";
     container.style.wordBreak = "break-word";
     container.style.lineHeight = `${String(lineHeight)}px`;
@@ -293,11 +292,7 @@ export const measureText = (
   container.appendChild(span);
   // Baseline is important for positioning text on canvas
   const baseline = span.offsetTop + span.offsetHeight;
-  // Since span adds 1px extra width to the container
-  let width = container.offsetWidth;
-  if (maxWidth && textWidth > maxWidth) {
-    width = width - 1;
-  }
+  const width = container.offsetWidth;
   const height = container.offsetHeight;
   document.body.removeChild(container);
   if (isTestEnv()) {
@@ -332,8 +327,11 @@ const getLineWidth = (text: string, font: FontString) => {
   if (isTestEnv()) {
     return metrics.width * 10;
   }
+  // Since measureText behaves differently in different browsers
+  // OS so considering a adjustment factor of 0.2
+  const adjustmentFactor = 0.2;
 
-  return metrics.width;
+  return metrics.width + adjustmentFactor;
 };
 
 export const getTextWidth = (text: string, font: FontString) => {

+ 12 - 4
src/element/textWysiwyg.tsx

@@ -142,11 +142,11 @@ export const textWysiwyg = ({
     const appState = app.state;
     const updatedTextElement =
       Scene.getScene(element)?.getElement<ExcalidrawTextElement>(id);
+
     if (!updatedTextElement) {
       return;
     }
     const { textAlign, verticalAlign } = updatedTextElement;
-
     const approxLineHeight = getApproxLineHeight(
       getFontString(updatedTextElement),
     );
@@ -161,6 +161,7 @@ export const textWysiwyg = ({
       // Set to element height by default since that's
       // what is going to be used for unbounded text
       let textElementHeight = updatedTextElement.height;
+
       if (container && updatedTextElement.containerId) {
         if (isArrowElement(container)) {
           const boundTextCoords =
@@ -206,7 +207,6 @@ export const textWysiwyg = ({
         maxHeight = getMaxContainerHeight(container);
 
         // autogrow container height if text exceeds
-
         if (!isArrowElement(container) && textElementHeight > maxHeight) {
           const diff = Math.min(
             textElementHeight - maxHeight,
@@ -276,7 +276,6 @@ export const textWysiwyg = ({
       // Make sure text editor height doesn't go beyond viewport
       const editorMaxHeight =
         (appState.height - viewportY) / appState.zoom.value;
-
       Object.assign(editable.style, {
         font: getFontString(updatedTextElement),
         // must be defined *after* font ¯\_(ツ)_/¯
@@ -395,11 +394,12 @@ export const textWysiwyg = ({
       // first line as well as setting height to "auto"
       // doubles the height as soon as user starts typing
       if (isBoundToContainer(element) && lines > 1) {
+        const container = getContainerElement(element);
+
         let height = "auto";
         editable.style.height = "0px";
         let heightSet = false;
         if (lines === 2) {
-          const container = getContainerElement(element);
           const actualLineCount = wrapText(
             editable.value,
             font,
@@ -416,6 +416,14 @@ export const textWysiwyg = ({
             heightSet = true;
           }
         }
+        const wrappedText = wrapText(
+          normalizeText(editable.value),
+          font,
+          getMaxContainerWidth(container!),
+        );
+        const width = getTextWidth(wrappedText, font);
+        editable.style.width = `${width}px`;
+
         if (!heightSet) {
           editable.style.height = `${editable.scrollHeight}px`;
         }