From 7edcea9a93034a1b55adeb9c7b5291b3940e540a Mon Sep 17 00:00:00 2001 From: Daishi Kato Date: Thu, 28 May 2020 07:17:15 +0900 Subject: [PATCH] feat: resize text element (#1650) * feat: resize text element * ignore small font size change that leads jankiness Co-authored-by: dwelle --- src/element/handlerRectangles.ts | 87 +++++++++++++------------ src/element/resizeElements.ts | 105 +++++++++++++++++++++++++++++++ src/element/resizeTest.ts | 5 -- src/renderer/renderScene.ts | 2 +- 4 files changed, 152 insertions(+), 47 deletions(-) diff --git a/src/element/handlerRectangles.ts b/src/element/handlerRectangles.ts index 0798c9ed..32e292a1 100644 --- a/src/element/handlerRectangles.ts +++ b/src/element/handlerRectangles.ts @@ -21,6 +21,33 @@ export const OMIT_SIDES_FOR_MULTIPLE_ELEMENTS = { rotation: true, }; +const OMIT_SIDES_FOR_TEXT_ELEMENT = { + e: true, + s: true, + n: true, + w: true, +}; + +const OMIT_SIDES_FOR_LINE_SLASH = { + e: true, + s: true, + n: true, + w: true, + nw: true, + se: true, + rotation: true, +}; + +const OMIT_SIDES_FOR_LINE_BACKSLASH = { + e: true, + s: true, + n: true, + w: true, + ne: true, + sw: true, + rotation: true, +}; + const generateHandler = ( x: number, y: number, @@ -180,13 +207,7 @@ export const handlerRectangles = ( zoom: number, pointerType: PointerType = "mouse", ) => { - const handlers = handlerRectanglesFromCoords( - getElementAbsoluteCoords(element), - element.angle, - zoom, - pointerType, - ); - + let omitSides: { [T in Sides]?: boolean } = {}; if ( element.type === "arrow" || element.type === "line" || @@ -195,43 +216,27 @@ export const handlerRectangles = ( if (element.points.length === 2) { // only check the last point because starting point is always (0,0) const [, p1] = element.points; - if (p1[0] === 0 || p1[1] === 0) { - return { - nw: handlers.nw, - se: handlers.se, - } as typeof handlers; - } - - if (p1[0] > 0 && p1[1] < 0) { - return { - ne: handlers.ne, - sw: handlers.sw, - } as typeof handlers; - } - - if (p1[0] > 0 && p1[1] > 0) { - return { - nw: handlers.nw, - se: handlers.se, - } as typeof handlers; - } - - if (p1[0] < 0 && p1[1] > 0) { - return { - ne: handlers.ne, - sw: handlers.sw, - } as typeof handlers; - } - - if (p1[0] < 0 && p1[1] < 0) { - return { - nw: handlers.nw, - se: handlers.se, - } as typeof handlers; + omitSides = OMIT_SIDES_FOR_LINE_BACKSLASH; + } else if (p1[0] > 0 && p1[1] < 0) { + omitSides = OMIT_SIDES_FOR_LINE_SLASH; + } else if (p1[0] > 0 && p1[1] > 0) { + omitSides = OMIT_SIDES_FOR_LINE_BACKSLASH; + } else if (p1[0] < 0 && p1[1] > 0) { + omitSides = OMIT_SIDES_FOR_LINE_SLASH; + } else if (p1[0] < 0 && p1[1] < 0) { + omitSides = OMIT_SIDES_FOR_LINE_BACKSLASH; } } + } else if (element.type === "text") { + omitSides = OMIT_SIDES_FOR_TEXT_ELEMENT; } - return handlers; + return handlerRectanglesFromCoords( + getElementAbsoluteCoords(element), + element.angle, + zoom, + pointerType, + omitSides, + ); }; diff --git a/src/element/resizeElements.ts b/src/element/resizeElements.ts index 20bbb3de..5b0199ef 100644 --- a/src/element/resizeElements.ts +++ b/src/element/resizeElements.ts @@ -4,6 +4,7 @@ import { rescalePoints } from "../points"; import { rotate, adjustXYWithRotation, getFlipAdjustment } from "../math"; import { ExcalidrawLinearElement, + ExcalidrawTextElement, NonDeletedExcalidrawElement, NonDeleted, } from "./types"; @@ -24,6 +25,7 @@ import { getResizeCenterPointKey, getResizeWithSidesSameLengthKey, } from "../keys"; +import { measureText, getFontString } from "../utils"; type ResizeTestType = ReturnType; @@ -55,6 +57,20 @@ export const resizeElements = ( pointerX, pointerY, ); + } else if ( + element.type === "text" && + (resizeHandle === "nw" || + resizeHandle === "ne" || + resizeHandle === "sw" || + resizeHandle === "se") + ) { + resizeSingleTextElement( + element, + resizeHandle, + getResizeCenterPointKey(event), + pointerX, + pointerY, + ); } else if (resizeHandle) { resizeSingleElement( element, @@ -188,6 +204,95 @@ const rescalePointsInElement = ( } : {}; +const resizeSingleTextElement = ( + element: NonDeleted, + resizeHandle: "nw" | "ne" | "sw" | "se", + isResizeFromCenter: boolean, + pointerX: number, + pointerY: number, +) => { + const [x1, y1, x2, y2] = getElementAbsoluteCoords(element); + const cx = (x1 + x2) / 2; + const cy = (y1 + y2) / 2; + // rotation pointer with reverse angle + const [rotatedX, rotatedY] = rotate( + pointerX, + pointerY, + cx, + cy, + -element.angle, + ); + let scale; + switch (resizeHandle) { + case "se": + scale = Math.max( + (rotatedX - x1) / (x2 - x1), + (rotatedY - y1) / (y2 - y1), + ); + break; + case "nw": + scale = Math.max( + (x2 - rotatedX) / (x2 - x1), + (y2 - rotatedY) / (y2 - y1), + ); + break; + case "ne": + scale = Math.max( + (rotatedX - x1) / (x2 - x1), + (y2 - rotatedY) / (y2 - y1), + ); + break; + case "sw": + scale = Math.max( + (x2 - rotatedX) / (x2 - x1), + (rotatedY - y1) / (y2 - y1), + ); + break; + } + if (scale > 0) { + const newFontSize = Math.max(element.fontSize * scale, 10); + const metrics = measureText( + element.text, + getFontString({ fontSize: newFontSize, fontFamily: element.fontFamily }), + ); + if ( + Math.abs(metrics.width - element.width) <= 1 || + Math.abs(metrics.height - element.height) <= 1 + ) { + // we ignore 1px change to avoid janky behavior + return; + } + const [nextX1, nextY1, nextX2, nextY2] = getResizedElementAbsoluteCoords( + element, + metrics.width, + metrics.height, + ); + const deltaX1 = (x1 - nextX1) / 2; + const deltaY1 = (y1 - nextY1) / 2; + const deltaX2 = (x2 - nextX2) / 2; + const deltaY2 = (y2 - nextY2) / 2; + const [nextElementX, nextElementY] = adjustXYWithRotation( + resizeHandle, + element.x, + element.y, + element.angle, + deltaX1, + deltaY1, + deltaX2, + deltaY2, + isResizeFromCenter, + ); + mutateElement(element, { + fontSize: newFontSize, + width: metrics.width, + height: metrics.height, + baseline: metrics.baseline, + x: nextElementX, + y: nextElementY, + }); + } +}; + const resizeSingleElement = ( element: NonDeletedExcalidrawElement, resizeHandle: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se", diff --git a/src/element/resizeTest.ts b/src/element/resizeTest.ts index 6f151f27..67fc80bf 100644 --- a/src/element/resizeTest.ts +++ b/src/element/resizeTest.ts @@ -45,11 +45,6 @@ export const resizeTest = ( return "rotation" as HandlerRectanglesRet; } - if (element.type === "text") { - // can't resize text elements - return false; - } - const filter = Object.keys(handlers).filter((key) => { const handler = handlers[key as Exclude]!; if (!handler) { diff --git a/src/renderer/renderScene.ts b/src/renderer/renderScene.ts index 13c8f615..3f2b1239 100644 --- a/src/renderer/renderScene.ts +++ b/src/renderer/renderScene.ts @@ -304,7 +304,7 @@ export const renderScene = ( handler[2], handler[3], ); - } else if (locallySelectedElements[0].type !== "text") { + } else { strokeRectWithRotation( context, handler[0],