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

This commit is contained in:
Aakansha Doshi 2023-03-28 13:48:34 +05:30
parent 83383977f5
commit fc80fd15dc
3 changed files with 85 additions and 4 deletions

View file

@ -289,6 +289,43 @@ export const measureText = (
return { width, height }; 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 * To get unitless line-height (if unknown) we can calculate it by dividing
* height-per-line by fontSize. * height-per-line by fontSize.

View file

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

View file

@ -200,6 +200,16 @@ const drawImagePlaceholder = (
size, 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 = ( const drawElementOnCanvas = (
element: NonDeletedExcalidrawElement, element: NonDeletedExcalidrawElement,
@ -274,6 +284,29 @@ const drawElementOnCanvas = (
context.canvas.setAttribute("dir", rtl ? "rtl" : "ltr"); context.canvas.setAttribute("dir", rtl ? "rtl" : "ltr");
context.save(); context.save();
context.font = getFontString(element); 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.fillStyle = element.strokeColor;
context.textAlign = element.textAlign as CanvasTextAlign; context.textAlign = element.textAlign as CanvasTextAlign;
@ -286,18 +319,17 @@ const drawElementOnCanvas = (
: element.textAlign === "right" : element.textAlign === "right"
? element.width ? element.width
: 0; : 0;
context.textBaseline = "bottom";
const lineHeightPx = getLineHeightInPx( const lineHeightPx = getLineHeightInPx(
element.fontSize, element.fontSize,
element.lineHeight, element.lineHeight,
); );
const verticalOffset = 10;
for (let index = 0; index < lines.length; index++) { for (let index = 0; index < lines.length; index++) {
context.fillText( context.fillText(
lines[index], lines[index],
horizontalOffset, horizontalOffset,
(index + 1) * lineHeightPx, (index + 1) * lineHeightPx - verticalOffset,
); );
} }
context.restore(); context.restore();