From 37934c0f8b33c189d11e2ec9ce95cebfcd0838ed Mon Sep 17 00:00:00 2001 From: Timur Khazamov Date: Thu, 9 Jan 2020 00:06:25 +0500 Subject: [PATCH] Fixes text jumping on creation (#266) * Fixes text jumping on creation * Do not remove node on ESC * Fixed typo --- src/element/textWysiwyg.tsx | 7 ++++++- src/element/types.ts | 4 +++- src/index.tsx | 32 ++++++++++++-------------------- src/renderer/renderElement.ts | 5 ++++- src/utils.ts | 27 +++++++++++++++++++++++++++ 5 files changed, 52 insertions(+), 23 deletions(-) diff --git a/src/element/textWysiwyg.tsx b/src/element/textWysiwyg.tsx index 4935754f..612a2652 100644 --- a/src/element/textWysiwyg.tsx +++ b/src/element/textWysiwyg.tsx @@ -22,7 +22,7 @@ export function textWysiwyg({ Object.assign(input.style, { color: strokeColor, position: "absolute", - top: y - 8 + "px", + top: y + "px", left: x + "px", transform: "translate(-50%, -50%)", boxShadow: "none", @@ -36,6 +36,11 @@ export function textWysiwyg({ input.onkeydown = ev => { if (ev.key === KEYS.ESCAPE) { ev.preventDefault(); + if (initText) { + input.value = initText; + handleSubmit(); + return; + } cleanup(); return; } diff --git a/src/element/types.ts b/src/element/types.ts index 1662bfdf..e350ad65 100644 --- a/src/element/types.ts +++ b/src/element/types.ts @@ -5,5 +5,7 @@ export type ExcalidrawTextElement = ExcalidrawElement & { type: "text"; font: string; text: string; - actualBoundingBoxAscent: number; + // for backward compatibility + actualBoundingBoxAscent?: number; + baseline: number; }; diff --git a/src/index.tsx b/src/index.tsx index 86413b6c..3eeecb15 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -34,13 +34,12 @@ import { renderScene } from "./renderer"; import { AppState } from "./types"; import { ExcalidrawElement, ExcalidrawTextElement } from "./element/types"; -import { getDateTime, isInputLike } from "./utils"; +import { getDateTime, isInputLike, measureText } from "./utils"; import { ButtonSelect } from "./components/ButtonSelect"; import { findShapeByKey, shapesShortcutKeys } from "./shapes"; import { createHistory } from "./history"; -import "./styles.scss"; import ContextMenu from "./components/ContextMenu"; import { PanelTools } from "./components/panels/PanelTools"; import { PanelSelection } from "./components/panels/PanelSelection"; @@ -48,6 +47,8 @@ import { PanelColor } from "./components/panels/PanelColor"; import { PanelExport } from "./components/panels/PanelExport"; import { PanelCanvas } from "./components/panels/PanelCanvas"; +import "./styles.scss"; + const { elements } = createScene(); const { history } = createHistory(); const DEFAULT_PROJECT_NAME = `excalidraw-${getDateTime()}`; @@ -94,23 +95,16 @@ function addTextElement( if (text === null || text === "") { return false; } + + const metrics = measureText(text, font); element.text = text; element.font = font; - const currentFont = context.font; - context.font = element.font; - const textMeasure = context.measureText(element.text); - const width = textMeasure.width; - const actualBoundingBoxAscent = - textMeasure.actualBoundingBoxAscent || parseInt(font); - const actualBoundingBoxDescent = textMeasure.actualBoundingBoxDescent || 0; - element.actualBoundingBoxAscent = actualBoundingBoxAscent; - context.font = currentFont; - const height = actualBoundingBoxAscent + actualBoundingBoxDescent; // Center the text - element.x -= width / 2; - element.y -= actualBoundingBoxAscent; - element.width = width; - element.height = height; + element.x -= metrics.width / 2; + element.y -= metrics.height / 2; + element.width = metrics.width; + element.height = metrics.height; + element.baseline = metrics.baseline; return true; } @@ -965,8 +959,7 @@ class App extends React.Component<{}, AppState> { Object.assign(element, elementAtPosition); // x and y will change after calling addTextElement function element.x = elementAtPosition.x + elementAtPosition.width / 2; - element.y = - elementAtPosition.y + elementAtPosition.actualBoundingBoxAscent; + element.y = elementAtPosition.y + elementAtPosition.height / 2; initText = elementAtPosition.text; textX = this.state.scrollX + @@ -977,7 +970,7 @@ class App extends React.Component<{}, AppState> { this.state.scrollY + elementAtPosition.y + CANVAS_WINDOW_OFFSET_TOP + - elementAtPosition.actualBoundingBoxAscent; + elementAtPosition.height / 2; } textWysiwyg({ @@ -1063,6 +1056,5 @@ const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement); const canvas = document.getElementById("canvas") as HTMLCanvasElement; const rc = rough.canvas(canvas); -const context = canvas.getContext("2d")!; ReactDOM.render(, rootElement); diff --git a/src/renderer/renderElement.ts b/src/renderer/renderElement.ts index 3c6b1d90..8742e876 100644 --- a/src/renderer/renderElement.ts +++ b/src/renderer/renderElement.ts @@ -131,11 +131,14 @@ export function renderElement( context.fillText( element.text, element.x + scrollX, - element.y + element.actualBoundingBoxAscent + scrollY + element.y + + scrollY + + (element.baseline || element.actualBoundingBoxAscent || 0) ); context.fillStyle = fillStyle; context.font = font; context.globalAlpha = 1; + console.log(element); } else { throw new Error("Unimplemented type " + element.type); } diff --git a/src/utils.ts b/src/utils.ts index a62ecd07..7c71cc03 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -23,3 +23,30 @@ export function isInputLike( target instanceof HTMLSelectElement ); } + +// https://github.com/grassator/canvas-text-editor/blob/master/lib/FontMetrics.js +export function measureText(text: string, font: string) { + const line = document.createElement("div"); + const body = document.body; + line.style.position = "absolute"; + line.style.whiteSpace = "nowrap"; + line.style.font = font; + body.appendChild(line); + // Now we can measure width and height of the letter + line.innerHTML = text; + const width = line.offsetWidth; + const height = line.offsetHeight; + // Now creating 1px sized item that will be aligned to baseline + // to calculate baseline shift + const span = document.createElement("span"); + span.style.display = "inline-block"; + span.style.overflow = "hidden"; + span.style.width = "1px"; + span.style.height = "1px"; + line.appendChild(span); + // Baseline is important for positioning text on canvas + const baseline = span.offsetTop + span.offsetHeight; + document.body.removeChild(line); + + return { width, height, baseline }; +}