excalidraw/src/element/newElement.ts
Daishi Kato 65be7973be
Rotation support (#1099)
* rotate rectanble with fixed angle

* rotate dashed rectangle with fixed angle

* fix rotate handler rect

* fix canvas size with rotation

* angle in element base

* fix bug in calculating canvas size

* trial only for rectangle

* hitTest for rectangle rotation

* properly resize rotated rectangle

* fix canvas size calculation

* giving up... workaround for now

* **experimental** handler to rotate rectangle

* remove rotation on copy for debugging

* update snapshots

* better rotation handler with atan2

* rotate when drawImage

* add rotation handler

* hitTest for any shapes

* fix hitTest for curved lines

* rotate text element

* rotation locking

* hint messaage for rotating

* show proper handlers on mobile (a workaround, there should be a better way)

* refactor hitTest

* support exporting png

* support exporting svg

* fix rotating curved line

* refactor drawElementFromCanvas with getElementAbsoluteCoords

* fix export png and svg

* adjust resize positions for lines (N, E, S, W)

* do not make handlers big on mobile

* Update src/locales/en.json

Alright!

Co-Authored-By: Lipis <lipiridis@gmail.com>

* do not show rotation/resizing hints on mobile

* proper calculation for N and W positions

* simplify calculation

* use "rotation" as property name for clarification (may increase bundle size)

* update snapshots excluding rotation handle

* refactor with adjustPositionWithRotation

* refactor with adjustXYWithRotation

* forgot to rename rotation

* rename internal function

* initialize element angle on restore

* rotate wysiwyg editor

* fix shift-rotate around 270deg

* improve rotation locking

* refactor adjustXYWithRotation

* avoid rotation degree becomes >=360

* refactor with generateHandler

Co-authored-by: Lipis <lipiridis@gmail.com>
Co-authored-by: dwelle <luzar.david@gmail.com>
2020-04-02 10:40:26 +02:00

156 lines
3.9 KiB
TypeScript

import {
ExcalidrawElement,
ExcalidrawTextElement,
ExcalidrawLinearElement,
ExcalidrawGenericElement,
} from "../element/types";
import { measureText } from "../utils";
import { randomInteger, randomId } from "../random";
type ElementConstructorOpts = {
x: ExcalidrawGenericElement["x"];
y: ExcalidrawGenericElement["y"];
strokeColor: ExcalidrawGenericElement["strokeColor"];
backgroundColor: ExcalidrawGenericElement["backgroundColor"];
fillStyle: ExcalidrawGenericElement["fillStyle"];
strokeWidth: ExcalidrawGenericElement["strokeWidth"];
roughness: ExcalidrawGenericElement["roughness"];
opacity: ExcalidrawGenericElement["opacity"];
width?: ExcalidrawGenericElement["width"];
height?: ExcalidrawGenericElement["height"];
angle?: ExcalidrawGenericElement["angle"];
};
function _newElementBase<T extends ExcalidrawElement>(
type: T["type"],
{
x,
y,
strokeColor,
backgroundColor,
fillStyle,
strokeWidth,
roughness,
opacity,
width = 0,
height = 0,
angle = 0,
...rest
}: ElementConstructorOpts & Partial<ExcalidrawGenericElement>,
) {
return {
id: rest.id || randomId(),
type,
x,
y,
width,
height,
angle,
strokeColor,
backgroundColor,
fillStyle,
strokeWidth,
roughness,
opacity,
seed: rest.seed ?? randomInteger(),
version: rest.version || 1,
versionNonce: rest.versionNonce ?? 0,
isDeleted: rest.isDeleted ?? false,
};
}
export function newElement(
opts: {
type: ExcalidrawGenericElement["type"];
} & ElementConstructorOpts,
): ExcalidrawGenericElement {
return _newElementBase<ExcalidrawGenericElement>(opts.type, opts);
}
export function newTextElement(
opts: {
text: string;
font: string;
} & ElementConstructorOpts,
): ExcalidrawTextElement {
const { text, font } = opts;
const metrics = measureText(text, font);
const textElement = {
..._newElementBase<ExcalidrawTextElement>("text", opts),
text: text,
font: font,
// Center the text
x: opts.x - metrics.width / 2,
y: opts.y - metrics.height / 2,
width: metrics.width,
height: metrics.height,
baseline: metrics.baseline,
};
return textElement;
}
export function newLinearElement(
opts: {
type: ExcalidrawLinearElement["type"];
lastCommittedPoint?: ExcalidrawLinearElement["lastCommittedPoint"];
} & ElementConstructorOpts,
): ExcalidrawLinearElement {
return {
..._newElementBase<ExcalidrawLinearElement>(opts.type, opts),
points: [],
lastCommittedPoint: opts.lastCommittedPoint || null,
};
}
// Simplified deep clone for the purpose of cloning ExcalidrawElement only
// (doesn't clone Date, RegExp, Map, Set, Typed arrays etc.)
//
// Adapted from https://github.com/lukeed/klona
function _duplicateElement(val: any, depth: number = 0) {
if (val == null || typeof val !== "object") {
return val;
}
if (Object.prototype.toString.call(val) === "[object Object]") {
const tmp =
typeof val.constructor === "function"
? Object.create(Object.getPrototypeOf(val))
: {};
for (const key in val) {
if (val.hasOwnProperty(key)) {
// don't copy top-level shape property, which we want to regenerate
if (depth === 0 && (key === "shape" || key === "canvas")) {
continue;
}
tmp[key] = _duplicateElement(val[key], depth + 1);
}
}
return tmp;
}
if (Array.isArray(val)) {
let k = val.length;
const arr = new Array(k);
while (k--) {
arr[k] = _duplicateElement(val[k], depth + 1);
}
return arr;
}
return val;
}
export function duplicateElement<TElement extends Mutable<ExcalidrawElement>>(
element: TElement,
overrides?: Partial<TElement>,
): TElement {
let copy: TElement = _duplicateElement(element);
copy.id = randomId();
copy.seed = randomInteger();
if (overrides) {
copy = Object.assign(copy, overrides);
}
return copy;
}