Forráskód Böngészése

fix: introduce baseline to fix the layout shift when switching to text editor

Aakansha Doshi 2 éve
szülő
commit
fc80fd15dc
3 módosított fájl, 85 hozzáadás és 4 törlés
  1. 37 0
      src/element/textElement.ts
  2. 13 1
      src/element/textWysiwyg.tsx
  3. 35 3
      src/renderer/renderElement.ts

+ 37 - 0
src/element/textElement.ts

@@ -289,6 +289,43 @@ export const measureText = (
   return { width, height };
 };
 
+export const measureBaseline = (
+  text: string,
+  font: FontString,
+  lineHeight: ExcalidrawTextElement["lineHeight"],
+  wrapInContainer?: boolean,
+) => {
+  const container = document.createElement("div");
+  container.style.position = "absolute";
+  container.style.whiteSpace = "pre";
+  container.style.font = font;
+  container.style.minHeight = "1em";
+
+  if (wrapInContainer) {
+    container.style.overflow = "hidden";
+    container.style.wordBreak = "break-word";
+    container.style.whiteSpace = "pre-wrap";
+  }
+
+  //@ts-ignore
+  container.style.lineHeight = lineHeight;
+
+  container.innerText = text;
+
+  // Baseline is important for positioning text on canvas
+  document.body.appendChild(container);
+
+  const span = document.createElement("span");
+  span.style.display = "inline-block";
+  span.style.overflow = "hidden";
+  span.style.width = "1px";
+  span.style.height = "1px";
+  container.appendChild(span);
+  const baseline = span.offsetTop + span.offsetHeight;
+  //document.body.removeChild(container);
+  return baseline;
+};
+
 /**
  * To get unitless line-height (if unknown) we can calculate it by dividing
  * height-per-line by fontSize.

+ 13 - 1
src/element/textWysiwyg.tsx

@@ -34,6 +34,7 @@ import {
   wrapText,
   getMaxContainerHeight,
   getMaxContainerWidth,
+  measureBaseline,
 } from "./textElement";
 import {
   actionDecreaseFontSize,
@@ -269,6 +270,17 @@ export const textWysiwyg = ({
       } else {
         textElementWidth += 0.5;
       }
+      const baseline = measureBaseline(
+        updatedTextElement.text,
+        getFontString(updatedTextElement),
+        updatedTextElement.lineHeight,
+        !!container,
+      );
+
+      const offset =
+        (updatedTextElement.height - baseline - 10) * appState.zoom.value;
+
+      const top = viewportY + offset;
       // Make sure text editor height doesn't go beyond viewport
       const editorMaxHeight =
         (appState.height - viewportY) / appState.zoom.value;
@@ -279,7 +291,7 @@ export const textWysiwyg = ({
         width: `${textElementWidth}px`,
         height: `${textElementHeight}px`,
         left: `${viewportX}px`,
-        top: `${viewportY}px`,
+        top: `${top}px`,
         transform: getTransform(
           textElementWidth,
           textElementHeight,

+ 35 - 3
src/renderer/renderElement.ts

@@ -200,6 +200,16 @@ const drawImagePlaceholder = (
     size,
   );
 };
+//@ts-ignore
+const drawLine = (x, y, width, height, stroke, context) => {
+  context.lineWidth = "2";
+  context.strokeStyle = stroke;
+  context.beginPath();
+  context.moveTo(x, y);
+  context.lineTo(x + width, y);
+  context.closePath();
+  context.stroke();
+};
 
 const drawElementOnCanvas = (
   element: NonDeletedExcalidrawElement,
@@ -274,6 +284,29 @@ const drawElementOnCanvas = (
         context.canvas.setAttribute("dir", rtl ? "rtl" : "ltr");
         context.save();
         context.font = getFontString(element);
+        context.textBaseline = "alphabetic";
+        const metrics = context.measureText(element.text);
+
+        // drawLine(0, 0, metrics.width, element.height, "green", context);
+
+        // drawLine(
+        //   0,
+        //   -metrics.actualBoundingBoxAscent,
+        //   metrics.width,
+        //   element.height,
+        //   "magenta",
+        //   context,
+        // );
+
+        // drawLine(
+        //   0,
+        //   metrics.actualBoundingBoxDescent,
+        //   metrics.width,
+        //   element.height,
+        //   "magenta",
+        //   context,
+        // );
+
         context.fillStyle = element.strokeColor;
         context.textAlign = element.textAlign as CanvasTextAlign;
 
@@ -286,18 +319,17 @@ const drawElementOnCanvas = (
             : element.textAlign === "right"
             ? element.width
             : 0;
-        context.textBaseline = "bottom";
-
         const lineHeightPx = getLineHeightInPx(
           element.fontSize,
           element.lineHeight,
         );
+        const verticalOffset = 10;
 
         for (let index = 0; index < lines.length; index++) {
           context.fillText(
             lines[index],
             horizontalOffset,
-            (index + 1) * lineHeightPx,
+            (index + 1) * lineHeightPx - verticalOffset,
           );
         }
         context.restore();