Ver código fonte

fix: fix trailing line whitespaces layout shift (#8714)

Marcel Mraz 10 meses atrás
pai
commit
dfaaff4432

+ 35 - 0
packages/excalidraw/element/textElement.test.ts

@@ -47,6 +47,41 @@ describe("Test wrapText", () => {
     expect(res).toBe("don't wrap this number\n99,100.99");
   });
 
+  it("should trim all trailing whitespaces", () => {
+    const text = "Hello     ";
+    const maxWidth = 50;
+    const res = wrapText(text, font, maxWidth);
+    expect(res).toBe("Hello");
+  });
+
+  it("should trim all but one trailing whitespaces", () => {
+    const text = "Hello     ";
+    const maxWidth = 60;
+    const res = wrapText(text, font, maxWidth);
+    expect(res).toBe("Hello ");
+  });
+
+  it("should keep preceding whitespaces and trim all trailing whitespaces", () => {
+    const text = "  Hello  World";
+    const maxWidth = 90;
+    const res = wrapText(text, font, maxWidth);
+    expect(res).toBe("  Hello\nWorld");
+  });
+
+  it("should keep some preceding whitespaces, trim trailing whitespaces, but kep those that fit in the trailing line", () => {
+    const text = "   Hello  World            ";
+    const maxWidth = 90;
+    const res = wrapText(text, font, maxWidth);
+    expect(res).toBe("   Hello\nWorld    ");
+  });
+
+  it("should trim keep those whitespace that fit in the trailing line", () => {
+    const text = "Hello   Wo rl  d                     ";
+    const maxWidth = 100;
+    const res = wrapText(text, font, maxWidth);
+    expect(res).toBe("Hello   Wo\nrl  d     ");
+  });
+
   it("should support multiple (multi-codepoint) emojis", () => {
     const text = "😀🗺🔥👩🏽‍🦰👨‍👩‍👧‍👦🇨🇿";
     const maxWidth = 1;

+ 35 - 2
packages/excalidraw/element/textElement.ts

@@ -681,7 +681,7 @@ const wrapLine = (
 
       lines.push(...precedingLines);
 
-      // trailing line of the wrapped word might still be joined with next token/s
+      // trailing line of the wrapped word might -still be joined with next token/s
       currentLine = trailingLine;
       currentLineWidth = getLineWidth(trailingLine, font, true);
       iterator = tokenIterator.next();
@@ -697,12 +697,45 @@ const wrapLine = (
 
   // iterator done, push the trailing line if exists
   if (currentLine) {
-    lines.push(currentLine.trimEnd());
+    const trailingLine = trimTrailingLine(currentLine, font, maxWidth);
+    lines.push(trailingLine);
   }
 
   return lines;
 };
 
+// similarly to browsers, does not trim all whitespaces, but only those exceeding the maxWidth
+const trimTrailingLine = (line: string, font: FontString, maxWidth: number) => {
+  const shouldTrimWhitespaces = getLineWidth(line, font, true) > maxWidth;
+
+  if (!shouldTrimWhitespaces) {
+    return line;
+  }
+
+  // defensively default to `trimeEnd` in case the regex does not match
+  let [, trimmedLine, whitespaces] = line.match(/^(.+?)(\s+)$/) ?? [
+    line,
+    line.trimEnd(),
+    "",
+  ];
+
+  let trimmedLineWidth = getLineWidth(trimmedLine, font, true);
+
+  for (const whitespace of Array.from(whitespaces)) {
+    const _charWidth = charWidth.calculate(whitespace, font);
+    const testLineWidth = trimmedLineWidth + _charWidth;
+
+    if (testLineWidth > maxWidth) {
+      break;
+    }
+
+    trimmedLine = trimmedLine + whitespace;
+    trimmedLineWidth = testLineWidth;
+  }
+
+  return trimmedLine;
+};
+
 export const wrapText = (
   text: string,
   font: FontString,