From 6b628bb1a63b05a30f5f5d71f21c3899f38b7eb5 Mon Sep 17 00:00:00 2001 From: Daishi Kato Date: Sun, 17 May 2020 23:01:35 +0900 Subject: [PATCH] fix: resize non solid lines/arrows/draws (#1608) --- src/element/bounds.ts | 17 ++-- src/renderer/renderElement.ts | 163 +++++++++++++++------------------- 2 files changed, 83 insertions(+), 97 deletions(-) diff --git a/src/element/bounds.ts b/src/element/bounds.ts index b50e73af..11d6f426 100644 --- a/src/element/bounds.ts +++ b/src/element/bounds.ts @@ -1,9 +1,12 @@ import { ExcalidrawElement, ExcalidrawLinearElement } from "./types"; import { rotate } from "../math"; import rough from "roughjs/bin/rough"; -import { Drawable, Op, Options } from "roughjs/bin/core"; +import { Drawable, Op } from "roughjs/bin/core"; import { Point } from "../types"; -import { getShapeForElement } from "../renderer/renderElement"; +import { + getShapeForElement, + generateRoughOptions, +} from "../renderer/renderElement"; import { isLinearElement } from "./typeChecks"; import { rescalePoints } from "../points"; @@ -323,13 +326,11 @@ export const getResizedElementAbsoluteCoords = ( rescalePoints(1, nextHeight, element.points), ); - const options: Options = { - strokeWidth: element.strokeWidth, - roughness: element.roughness, - seed: element.seed, - }; const gen = rough.generator(); - const curve = gen.curve(points as [number, number][], options); + const curve = gen.curve( + points as [number, number][], + generateRoughOptions(element), + ); const ops = getCurvePathOps(curve); const [minX, minY, maxX, maxY] = getMinMaxXYFromCurvePathOps(ops); return [ diff --git a/src/renderer/renderElement.ts b/src/renderer/renderElement.ts index cd90fc56..2af0ca04 100644 --- a/src/renderer/renderElement.ts +++ b/src/renderer/renderElement.ts @@ -152,6 +152,70 @@ export function invalidateShapeForElement(element: ExcalidrawElement) { shapeCache.delete(element); } +export function generateRoughOptions(element: ExcalidrawElement): Options { + const options: Options = { + seed: element.seed, + strokeLineDash: + element.strokeStyle === "dashed" + ? DASHARRAY_DASHED + : element.strokeStyle === "dotted" + ? DASHARRAY_DOTTED + : undefined, + // for non-solid strokes, disable multiStroke because it tends to make + // dashes/dots overlay each other + disableMultiStroke: element.strokeStyle !== "solid", + // for non-solid strokes, increase the width a bit to make it visually + // similar to solid strokes, because we're also disabling multiStroke + strokeWidth: + element.strokeStyle !== "solid" + ? element.strokeWidth + 0.5 + : element.strokeWidth, + // when increasing strokeWidth, we must explicitly set fillWeight and + // hachureGap because if not specified, roughjs uses strokeWidth to + // calculate them (and we don't want the fills to be modified) + fillWeight: element.strokeWidth / 2, + hachureGap: element.strokeWidth * 4, + roughness: element.roughness, + stroke: element.strokeColor, + }; + + switch (element.type) { + case "rectangle": + case "diamond": + case "ellipse": { + options.fillStyle = element.fillStyle; + options.fill = + element.backgroundColor === "transparent" + ? undefined + : element.backgroundColor; + if (element.type === "ellipse") { + options.curveFitting = 1; + } + return options; + } + case "line": + case "draw": + case "arrow": { + // If shape is a line and is a closed shape, + // fill the shape if a color is set. + if (element.type === "line" || element.type === "draw") { + if (isPathALoop(element.points)) { + options.fillStyle = element.fillStyle; + options.fill = + element.backgroundColor === "transparent" + ? undefined + : element.backgroundColor; + } + } + + return options; + } + default: { + throw new Error(`Unimplemented type ${element.type}`); + } + } +} + function generateElement( element: NonDeletedExcalidrawElement, generator: RoughGenerator, @@ -161,44 +225,15 @@ function generateElement( if (!shape) { elementWithCanvasCache.delete(element); - const strokeLineDash = - element.strokeStyle === "dashed" - ? DASHARRAY_DASHED - : element.strokeStyle === "dotted" - ? DASHARRAY_DOTTED - : undefined; - // for non-solid strokes, disable multiStroke because it tends to make - // dashes/dots overlay each other - const disableMultiStroke = element.strokeStyle !== "solid"; - // for non-solid strokes, increase the width a bit to make it visually - // similar to solid strokes, because we're also disabling multiStroke - const strokeWidth = - element.strokeStyle !== "solid" - ? element.strokeWidth + 0.5 - : element.strokeWidth; - // when increasing strokeWidth, we must explicitly set fillWeight and - // hachureGap because if not specified, roughjs uses strokeWidth to - // calculate them (and we don't want the fills to be modified) - const fillWeight = element.strokeWidth / 2; - const hachureGap = element.strokeWidth * 4; - switch (element.type) { case "rectangle": - shape = generator.rectangle(0, 0, element.width, element.height, { - strokeWidth, - fillWeight, - hachureGap, - strokeLineDash, - disableMultiStroke, - stroke: element.strokeColor, - fill: - element.backgroundColor === "transparent" - ? undefined - : element.backgroundColor, - fillStyle: element.fillStyle, - roughness: element.roughness, - seed: element.seed, - }); + shape = generator.rectangle( + 0, + 0, + element.width, + element.height, + generateRoughOptions(element), + ); break; case "diamond": { @@ -219,21 +254,7 @@ function generateElement( [bottomX, bottomY], [leftX, leftY], ], - { - strokeWidth, - fillWeight, - hachureGap, - strokeLineDash, - disableMultiStroke, - stroke: element.strokeColor, - fill: - element.backgroundColor === "transparent" - ? undefined - : element.backgroundColor, - fillStyle: element.fillStyle, - roughness: element.roughness, - seed: element.seed, - }, + generateRoughOptions(element), ); break; } @@ -243,54 +264,18 @@ function generateElement( element.height / 2, element.width, element.height, - { - strokeWidth, - fillWeight, - hachureGap, - strokeLineDash, - disableMultiStroke, - stroke: element.strokeColor, - fill: - element.backgroundColor === "transparent" - ? undefined - : element.backgroundColor, - fillStyle: element.fillStyle, - roughness: element.roughness, - seed: element.seed, - curveFitting: 1, - }, + generateRoughOptions(element), ); break; case "line": case "draw": case "arrow": { - const options: Options = { - strokeWidth, - fillWeight, - hachureGap, - strokeLineDash, - disableMultiStroke, - stroke: element.strokeColor, - seed: element.seed, - roughness: element.roughness, - }; + const options = generateRoughOptions(element); // points array can be empty in the beginning, so it is important to add // initial position to it const points = element.points.length ? element.points : [[0, 0]]; - // If shape is a line and is a closed shape, - // fill the shape if a color is set. - if (element.type === "line" || element.type === "draw") { - if (isPathALoop(element.points)) { - options.fillStyle = element.fillStyle; - options.fill = - element.backgroundColor === "transparent" - ? undefined - : element.backgroundColor; - } - } - // curve is always the first element // this simplifies finding the curve for an element shape = [generator.curve(points as [number, number][], options)];