Fixes text jumping on creation (#266)
* Fixes text jumping on creation * Do not remove node on ESC * Fixed typo
This commit is contained in:
parent
2122a9cf9f
commit
37934c0f8b
@ -22,7 +22,7 @@ export function textWysiwyg({
|
|||||||
Object.assign(input.style, {
|
Object.assign(input.style, {
|
||||||
color: strokeColor,
|
color: strokeColor,
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: y - 8 + "px",
|
top: y + "px",
|
||||||
left: x + "px",
|
left: x + "px",
|
||||||
transform: "translate(-50%, -50%)",
|
transform: "translate(-50%, -50%)",
|
||||||
boxShadow: "none",
|
boxShadow: "none",
|
||||||
@ -36,6 +36,11 @@ export function textWysiwyg({
|
|||||||
input.onkeydown = ev => {
|
input.onkeydown = ev => {
|
||||||
if (ev.key === KEYS.ESCAPE) {
|
if (ev.key === KEYS.ESCAPE) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
if (initText) {
|
||||||
|
input.value = initText;
|
||||||
|
handleSubmit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
cleanup();
|
cleanup();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -5,5 +5,7 @@ export type ExcalidrawTextElement = ExcalidrawElement & {
|
|||||||
type: "text";
|
type: "text";
|
||||||
font: string;
|
font: string;
|
||||||
text: string;
|
text: string;
|
||||||
actualBoundingBoxAscent: number;
|
// for backward compatibility
|
||||||
|
actualBoundingBoxAscent?: number;
|
||||||
|
baseline: number;
|
||||||
};
|
};
|
||||||
|
@ -34,13 +34,12 @@ import { renderScene } from "./renderer";
|
|||||||
import { AppState } from "./types";
|
import { AppState } from "./types";
|
||||||
import { ExcalidrawElement, ExcalidrawTextElement } from "./element/types";
|
import { ExcalidrawElement, ExcalidrawTextElement } from "./element/types";
|
||||||
|
|
||||||
import { getDateTime, isInputLike } from "./utils";
|
import { getDateTime, isInputLike, measureText } from "./utils";
|
||||||
|
|
||||||
import { ButtonSelect } from "./components/ButtonSelect";
|
import { ButtonSelect } from "./components/ButtonSelect";
|
||||||
import { findShapeByKey, shapesShortcutKeys } from "./shapes";
|
import { findShapeByKey, shapesShortcutKeys } from "./shapes";
|
||||||
import { createHistory } from "./history";
|
import { createHistory } from "./history";
|
||||||
|
|
||||||
import "./styles.scss";
|
|
||||||
import ContextMenu from "./components/ContextMenu";
|
import ContextMenu from "./components/ContextMenu";
|
||||||
import { PanelTools } from "./components/panels/PanelTools";
|
import { PanelTools } from "./components/panels/PanelTools";
|
||||||
import { PanelSelection } from "./components/panels/PanelSelection";
|
import { PanelSelection } from "./components/panels/PanelSelection";
|
||||||
@ -48,6 +47,8 @@ import { PanelColor } from "./components/panels/PanelColor";
|
|||||||
import { PanelExport } from "./components/panels/PanelExport";
|
import { PanelExport } from "./components/panels/PanelExport";
|
||||||
import { PanelCanvas } from "./components/panels/PanelCanvas";
|
import { PanelCanvas } from "./components/panels/PanelCanvas";
|
||||||
|
|
||||||
|
import "./styles.scss";
|
||||||
|
|
||||||
const { elements } = createScene();
|
const { elements } = createScene();
|
||||||
const { history } = createHistory();
|
const { history } = createHistory();
|
||||||
const DEFAULT_PROJECT_NAME = `excalidraw-${getDateTime()}`;
|
const DEFAULT_PROJECT_NAME = `excalidraw-${getDateTime()}`;
|
||||||
@ -94,23 +95,16 @@ function addTextElement(
|
|||||||
if (text === null || text === "") {
|
if (text === null || text === "") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const metrics = measureText(text, font);
|
||||||
element.text = text;
|
element.text = text;
|
||||||
element.font = font;
|
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
|
// Center the text
|
||||||
element.x -= width / 2;
|
element.x -= metrics.width / 2;
|
||||||
element.y -= actualBoundingBoxAscent;
|
element.y -= metrics.height / 2;
|
||||||
element.width = width;
|
element.width = metrics.width;
|
||||||
element.height = height;
|
element.height = metrics.height;
|
||||||
|
element.baseline = metrics.baseline;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -965,8 +959,7 @@ class App extends React.Component<{}, AppState> {
|
|||||||
Object.assign(element, elementAtPosition);
|
Object.assign(element, elementAtPosition);
|
||||||
// x and y will change after calling addTextElement function
|
// x and y will change after calling addTextElement function
|
||||||
element.x = elementAtPosition.x + elementAtPosition.width / 2;
|
element.x = elementAtPosition.x + elementAtPosition.width / 2;
|
||||||
element.y =
|
element.y = elementAtPosition.y + elementAtPosition.height / 2;
|
||||||
elementAtPosition.y + elementAtPosition.actualBoundingBoxAscent;
|
|
||||||
initText = elementAtPosition.text;
|
initText = elementAtPosition.text;
|
||||||
textX =
|
textX =
|
||||||
this.state.scrollX +
|
this.state.scrollX +
|
||||||
@ -977,7 +970,7 @@ class App extends React.Component<{}, AppState> {
|
|||||||
this.state.scrollY +
|
this.state.scrollY +
|
||||||
elementAtPosition.y +
|
elementAtPosition.y +
|
||||||
CANVAS_WINDOW_OFFSET_TOP +
|
CANVAS_WINDOW_OFFSET_TOP +
|
||||||
elementAtPosition.actualBoundingBoxAscent;
|
elementAtPosition.height / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
textWysiwyg({
|
textWysiwyg({
|
||||||
@ -1063,6 +1056,5 @@ const rootElement = document.getElementById("root");
|
|||||||
ReactDOM.render(<App />, rootElement);
|
ReactDOM.render(<App />, rootElement);
|
||||||
const canvas = document.getElementById("canvas") as HTMLCanvasElement;
|
const canvas = document.getElementById("canvas") as HTMLCanvasElement;
|
||||||
const rc = rough.canvas(canvas);
|
const rc = rough.canvas(canvas);
|
||||||
const context = canvas.getContext("2d")!;
|
|
||||||
|
|
||||||
ReactDOM.render(<App />, rootElement);
|
ReactDOM.render(<App />, rootElement);
|
||||||
|
@ -131,11 +131,14 @@ export function renderElement(
|
|||||||
context.fillText(
|
context.fillText(
|
||||||
element.text,
|
element.text,
|
||||||
element.x + scrollX,
|
element.x + scrollX,
|
||||||
element.y + element.actualBoundingBoxAscent + scrollY
|
element.y +
|
||||||
|
scrollY +
|
||||||
|
(element.baseline || element.actualBoundingBoxAscent || 0)
|
||||||
);
|
);
|
||||||
context.fillStyle = fillStyle;
|
context.fillStyle = fillStyle;
|
||||||
context.font = font;
|
context.font = font;
|
||||||
context.globalAlpha = 1;
|
context.globalAlpha = 1;
|
||||||
|
console.log(element);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Unimplemented type " + element.type);
|
throw new Error("Unimplemented type " + element.type);
|
||||||
}
|
}
|
||||||
|
27
src/utils.ts
27
src/utils.ts
@ -23,3 +23,30 @@ export function isInputLike(
|
|||||||
target instanceof HTMLSelectElement
|
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 };
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user