mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Refactor functions so they are not polluting the render code
This commit is contained in:
parent
e1b108cd01
commit
c265a58453
3 changed files with 445 additions and 435 deletions
|
@ -1,27 +1,19 @@
|
||||||
import rough from "roughjs/bin/rough";
|
import rough from "roughjs/bin/rough";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
rescalePoints,
|
|
||||||
arrayToMap,
|
arrayToMap,
|
||||||
invariant,
|
invariant,
|
||||||
|
rescalePoints,
|
||||||
sizeOf,
|
sizeOf,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
degreesToRadians,
|
degreesToRadians,
|
||||||
lineSegment,
|
lineSegment,
|
||||||
pointFrom,
|
|
||||||
pointDistance,
|
pointDistance,
|
||||||
|
pointFrom,
|
||||||
pointFromArray,
|
pointFromArray,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
bezierEquation,
|
|
||||||
curve,
|
|
||||||
curveTangent,
|
|
||||||
vectorNormalize,
|
|
||||||
vectorNormal,
|
|
||||||
vectorScale,
|
|
||||||
pointFromVector,
|
|
||||||
vector,
|
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import { getCurvePathOps } from "@excalidraw/utils/shape";
|
import { getCurvePathOps } from "@excalidraw/utils/shape";
|
||||||
|
@ -41,8 +33,8 @@ import type { AppState } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
import type { Mutable } from "@excalidraw/common/utility-types";
|
import type { Mutable } from "@excalidraw/common/utility-types";
|
||||||
|
|
||||||
import { ShapeCache } from "./ShapeCache";
|
|
||||||
import { generateRoughOptions } from "./Shape";
|
import { generateRoughOptions } from "./Shape";
|
||||||
|
import { ShapeCache } from "./ShapeCache";
|
||||||
import { LinearElementEditor } from "./linearElementEditor";
|
import { LinearElementEditor } from "./linearElementEditor";
|
||||||
import { getBoundTextElement, getContainerElement } from "./textElement";
|
import { getBoundTextElement, getContainerElement } from "./textElement";
|
||||||
import {
|
import {
|
||||||
|
@ -60,20 +52,20 @@ import {
|
||||||
deconstructRectanguloidElement,
|
deconstructRectanguloidElement,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
|
|
||||||
import type {
|
|
||||||
ExcalidrawElement,
|
|
||||||
ExcalidrawLinearElement,
|
|
||||||
Arrowhead,
|
|
||||||
ExcalidrawFreeDrawElement,
|
|
||||||
NonDeleted,
|
|
||||||
ExcalidrawTextElementWithContainer,
|
|
||||||
ElementsMap,
|
|
||||||
ExcalidrawRectanguloidElement,
|
|
||||||
ExcalidrawEllipseElement,
|
|
||||||
ElementsMapOrArray,
|
|
||||||
} from "./types";
|
|
||||||
import type { Drawable, Op } from "roughjs/bin/core";
|
import type { Drawable, Op } from "roughjs/bin/core";
|
||||||
import type { Point as RoughPoint } from "roughjs/bin/geometry";
|
import type { Point as RoughPoint } from "roughjs/bin/geometry";
|
||||||
|
import type {
|
||||||
|
Arrowhead,
|
||||||
|
ElementsMap,
|
||||||
|
ElementsMapOrArray,
|
||||||
|
ExcalidrawElement,
|
||||||
|
ExcalidrawEllipseElement,
|
||||||
|
ExcalidrawFreeDrawElement,
|
||||||
|
ExcalidrawLinearElement,
|
||||||
|
ExcalidrawRectanguloidElement,
|
||||||
|
ExcalidrawTextElementWithContainer,
|
||||||
|
NonDeleted,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
export type RectangleBox = {
|
export type RectangleBox = {
|
||||||
x: number;
|
x: number;
|
||||||
|
@ -1154,53 +1146,3 @@ export const doBoundsIntersect = (
|
||||||
|
|
||||||
return minX1 < maxX2 && maxX1 > minX2 && minY1 < maxY2 && maxY1 > minY2;
|
return minX1 < maxX2 && maxX1 > minX2 && minY1 < maxY2 && maxY1 > minY2;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function offsetCubicBezier(
|
|
||||||
p0: GlobalPoint,
|
|
||||||
p1: GlobalPoint,
|
|
||||||
p2: GlobalPoint,
|
|
||||||
p3: GlobalPoint,
|
|
||||||
offsetDist: number,
|
|
||||||
steps = 20,
|
|
||||||
) {
|
|
||||||
const offsetPoints = [];
|
|
||||||
|
|
||||||
for (let i = 0; i <= steps; i++) {
|
|
||||||
const t = i / steps;
|
|
||||||
const c = curve(p0, p1, p2, p3);
|
|
||||||
const point = bezierEquation(c, t);
|
|
||||||
const tangent = vectorNormalize(curveTangent(c, t));
|
|
||||||
const normal = vectorNormal(tangent);
|
|
||||||
|
|
||||||
offsetPoints.push(pointFromVector(vectorScale(normal, offsetDist), point));
|
|
||||||
}
|
|
||||||
|
|
||||||
return offsetPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function offsetQuadraticBezier(
|
|
||||||
p0: GlobalPoint,
|
|
||||||
p1: GlobalPoint,
|
|
||||||
p2: GlobalPoint,
|
|
||||||
offsetDist: number,
|
|
||||||
steps = 20,
|
|
||||||
) {
|
|
||||||
const offsetPoints = [];
|
|
||||||
|
|
||||||
for (let i = 0; i <= steps; i++) {
|
|
||||||
const t = i / steps;
|
|
||||||
const t1 = 1 - t;
|
|
||||||
const point = pointFrom<GlobalPoint>(
|
|
||||||
t1 * t1 * p0[0] + 2 * t1 * t * p1[0] + t * t * p2[0],
|
|
||||||
t1 * t1 * p0[1] + 2 * t1 * t * p1[1] + t * t * p2[1],
|
|
||||||
);
|
|
||||||
const tangentX = 2 * (1 - t) * (p1[0] - p0[0]) + 2 * t * (p2[0] - p1[0]);
|
|
||||||
const tangentY = 2 * (1 - t) * (p1[1] - p0[1]) + 2 * t * (p2[1] - p1[1]);
|
|
||||||
const tangent = vectorNormalize(vector(tangentX, tangentY));
|
|
||||||
const normal = vectorNormal(tangent);
|
|
||||||
|
|
||||||
offsetPoints.push(pointFromVector(vectorScale(normal, offsetDist), point));
|
|
||||||
}
|
|
||||||
|
|
||||||
return offsetPoints;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,30 @@
|
||||||
import { THEME, THEME_FILTER } from "@excalidraw/common";
|
import { elementCenterPoint, THEME, THEME_FILTER } from "@excalidraw/common";
|
||||||
|
|
||||||
|
import { FIXED_BINDING_DISTANCE } from "@excalidraw/element/binding";
|
||||||
|
import { getDiamondPoints } from "@excalidraw/element/bounds";
|
||||||
|
import { getCornerRadius } from "@excalidraw/element/shapes";
|
||||||
|
|
||||||
|
import {
|
||||||
|
bezierEquation,
|
||||||
|
curve,
|
||||||
|
curveTangent,
|
||||||
|
type GlobalPoint,
|
||||||
|
pointFrom,
|
||||||
|
pointFromVector,
|
||||||
|
pointRotateRads,
|
||||||
|
vector,
|
||||||
|
vectorNormal,
|
||||||
|
vectorNormalize,
|
||||||
|
vectorScale,
|
||||||
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ExcalidrawDiamondElement,
|
||||||
|
ExcalidrawRectanguloidElement,
|
||||||
|
} from "@excalidraw/element/types";
|
||||||
|
|
||||||
import type { StaticCanvasRenderConfig } from "../scene/types";
|
import type { StaticCanvasRenderConfig } from "../scene/types";
|
||||||
import type { StaticCanvasAppState, AppState } from "../types";
|
import type { AppState, StaticCanvasAppState } from "../types";
|
||||||
|
|
||||||
export const fillCircle = (
|
export const fillCircle = (
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
|
@ -72,3 +95,399 @@ export const bootstrapCanvas = ({
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function drawCatmullRomQuadraticApprox(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
points: GlobalPoint[],
|
||||||
|
segments = 20,
|
||||||
|
) {
|
||||||
|
ctx.lineTo(points[0][0], points[0][1]);
|
||||||
|
|
||||||
|
for (let i = 0; i < points.length - 1; i++) {
|
||||||
|
const p0 = points[i - 1 < 0 ? 0 : i - 1];
|
||||||
|
const p1 = points[i];
|
||||||
|
const p2 = points[i + 1 >= points.length ? points.length - 1 : i + 1];
|
||||||
|
|
||||||
|
for (let t = 0; t <= 1; t += 1 / segments) {
|
||||||
|
const t2 = t * t;
|
||||||
|
|
||||||
|
const x =
|
||||||
|
(1 - t) * (1 - t) * p0[0] + 2 * (1 - t) * t * p1[0] + t2 * p2[0];
|
||||||
|
|
||||||
|
const y =
|
||||||
|
(1 - t) * (1 - t) * p0[1] + 2 * (1 - t) * t * p1[1] + t2 * p2[1];
|
||||||
|
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawCatmullRomCubicApprox(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
points: GlobalPoint[],
|
||||||
|
segments = 20,
|
||||||
|
) {
|
||||||
|
ctx.lineTo(points[0][0], points[0][1]);
|
||||||
|
|
||||||
|
for (let i = 0; i < points.length - 1; i++) {
|
||||||
|
const p0 = points[i - 1 < 0 ? 0 : i - 1];
|
||||||
|
const p1 = points[i];
|
||||||
|
const p2 = points[i + 1 >= points.length ? points.length - 1 : i + 1];
|
||||||
|
const p3 = points[i + 2 >= points.length ? points.length - 1 : i + 2];
|
||||||
|
|
||||||
|
for (let t = 0; t <= 1; t += 1 / segments) {
|
||||||
|
const t2 = t * t;
|
||||||
|
const t3 = t2 * t;
|
||||||
|
|
||||||
|
const x =
|
||||||
|
0.5 *
|
||||||
|
(2 * p1[0] +
|
||||||
|
(-p0[0] + p2[0]) * t +
|
||||||
|
(2 * p0[0] - 5 * p1[0] + 4 * p2[0] - p3[0]) * t2 +
|
||||||
|
(-p0[0] + 3 * p1[0] - 3 * p2[0] + p3[0]) * t3);
|
||||||
|
|
||||||
|
const y =
|
||||||
|
0.5 *
|
||||||
|
(2 * p1[1] +
|
||||||
|
(-p0[1] + p2[1]) * t +
|
||||||
|
(2 * p0[1] - 5 * p1[1] + 4 * p2[1] - p3[1]) * t2 +
|
||||||
|
(-p0[1] + 3 * p1[1] - 3 * p2[1] + p3[1]) * t3);
|
||||||
|
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const drawHighlightForRectWithRotation = (
|
||||||
|
context: CanvasRenderingContext2D,
|
||||||
|
element: ExcalidrawRectanguloidElement,
|
||||||
|
padding: number,
|
||||||
|
) => {
|
||||||
|
const [x, y] = pointRotateRads(
|
||||||
|
pointFrom<GlobalPoint>(element.x, element.y),
|
||||||
|
elementCenterPoint(element),
|
||||||
|
element.angle,
|
||||||
|
);
|
||||||
|
|
||||||
|
context.save();
|
||||||
|
context.translate(x, y);
|
||||||
|
context.rotate(element.angle);
|
||||||
|
|
||||||
|
let radius = getCornerRadius(
|
||||||
|
Math.min(element.width, element.height),
|
||||||
|
element,
|
||||||
|
);
|
||||||
|
if (radius === 0) {
|
||||||
|
radius = 0.01;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.beginPath();
|
||||||
|
|
||||||
|
{
|
||||||
|
const topLeftApprox = offsetQuadraticBezier(
|
||||||
|
pointFrom(0, 0 + radius),
|
||||||
|
pointFrom(0, 0),
|
||||||
|
pointFrom(0 + radius, 0),
|
||||||
|
padding,
|
||||||
|
);
|
||||||
|
const topRightApprox = offsetQuadraticBezier(
|
||||||
|
pointFrom(element.width - radius, 0),
|
||||||
|
pointFrom(element.width, 0),
|
||||||
|
pointFrom(element.width, radius),
|
||||||
|
padding,
|
||||||
|
);
|
||||||
|
const bottomRightApprox = offsetQuadraticBezier(
|
||||||
|
pointFrom(element.width, element.height - radius),
|
||||||
|
pointFrom(element.width, element.height),
|
||||||
|
pointFrom(element.width - radius, element.height),
|
||||||
|
padding,
|
||||||
|
);
|
||||||
|
const bottomLeftApprox = offsetQuadraticBezier(
|
||||||
|
pointFrom(radius, element.height),
|
||||||
|
pointFrom(0, element.height),
|
||||||
|
pointFrom(0, element.height - radius),
|
||||||
|
padding,
|
||||||
|
);
|
||||||
|
|
||||||
|
context.moveTo(
|
||||||
|
topLeftApprox[topLeftApprox.length - 1][0],
|
||||||
|
topLeftApprox[topLeftApprox.length - 1][1],
|
||||||
|
);
|
||||||
|
context.lineTo(topRightApprox[0][0], topRightApprox[0][1]);
|
||||||
|
drawCatmullRomQuadraticApprox(context, topRightApprox);
|
||||||
|
context.lineTo(bottomRightApprox[0][0], bottomRightApprox[0][1]);
|
||||||
|
drawCatmullRomQuadraticApprox(context, bottomRightApprox);
|
||||||
|
context.lineTo(bottomLeftApprox[0][0], bottomLeftApprox[0][1]);
|
||||||
|
drawCatmullRomQuadraticApprox(context, bottomLeftApprox);
|
||||||
|
context.lineTo(topLeftApprox[0][0], topLeftApprox[0][1]);
|
||||||
|
drawCatmullRomQuadraticApprox(context, topLeftApprox);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Counter-clockwise for the cutout in the middle. We need to have an "inverse
|
||||||
|
// mask" on a filled shape for the diamond highlight, because stroking creates
|
||||||
|
// sharp inset edges on line joins < 90 degrees.
|
||||||
|
{
|
||||||
|
const topLeftApprox = offsetQuadraticBezier(
|
||||||
|
pointFrom(0 + radius, 0),
|
||||||
|
pointFrom(0, 0),
|
||||||
|
pointFrom(0, 0 + radius),
|
||||||
|
-FIXED_BINDING_DISTANCE,
|
||||||
|
);
|
||||||
|
const topRightApprox = offsetQuadraticBezier(
|
||||||
|
pointFrom(element.width, radius),
|
||||||
|
pointFrom(element.width, 0),
|
||||||
|
pointFrom(element.width - radius, 0),
|
||||||
|
-FIXED_BINDING_DISTANCE,
|
||||||
|
);
|
||||||
|
const bottomRightApprox = offsetQuadraticBezier(
|
||||||
|
pointFrom(element.width - radius, element.height),
|
||||||
|
pointFrom(element.width, element.height),
|
||||||
|
pointFrom(element.width, element.height - radius),
|
||||||
|
-FIXED_BINDING_DISTANCE,
|
||||||
|
);
|
||||||
|
const bottomLeftApprox = offsetQuadraticBezier(
|
||||||
|
pointFrom(0, element.height - radius),
|
||||||
|
pointFrom(0, element.height),
|
||||||
|
pointFrom(radius, element.height),
|
||||||
|
-FIXED_BINDING_DISTANCE,
|
||||||
|
);
|
||||||
|
|
||||||
|
context.moveTo(
|
||||||
|
topLeftApprox[topLeftApprox.length - 1][0],
|
||||||
|
topLeftApprox[topLeftApprox.length - 1][1],
|
||||||
|
);
|
||||||
|
context.lineTo(bottomLeftApprox[0][0], bottomLeftApprox[0][1]);
|
||||||
|
drawCatmullRomQuadraticApprox(context, bottomLeftApprox);
|
||||||
|
context.lineTo(bottomRightApprox[0][0], bottomRightApprox[0][1]);
|
||||||
|
drawCatmullRomQuadraticApprox(context, bottomRightApprox);
|
||||||
|
context.lineTo(topRightApprox[0][0], topRightApprox[0][1]);
|
||||||
|
drawCatmullRomQuadraticApprox(context, topRightApprox);
|
||||||
|
context.lineTo(topLeftApprox[0][0], topLeftApprox[0][1]);
|
||||||
|
drawCatmullRomQuadraticApprox(context, topLeftApprox);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.closePath();
|
||||||
|
context.fill();
|
||||||
|
|
||||||
|
context.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const strokeEllipseWithRotation = (
|
||||||
|
context: CanvasRenderingContext2D,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
cx: number,
|
||||||
|
cy: number,
|
||||||
|
angle: number,
|
||||||
|
) => {
|
||||||
|
context.beginPath();
|
||||||
|
context.ellipse(cx, cy, width / 2, height / 2, angle, 0, Math.PI * 2);
|
||||||
|
context.stroke();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const strokeRectWithRotation = (
|
||||||
|
context: CanvasRenderingContext2D,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
cx: number,
|
||||||
|
cy: number,
|
||||||
|
angle: number,
|
||||||
|
fill: boolean = false,
|
||||||
|
/** should account for zoom */
|
||||||
|
radius: number = 0,
|
||||||
|
) => {
|
||||||
|
context.save();
|
||||||
|
context.translate(cx, cy);
|
||||||
|
context.rotate(angle);
|
||||||
|
if (fill) {
|
||||||
|
context.fillRect(x - cx, y - cy, width, height);
|
||||||
|
}
|
||||||
|
if (radius && context.roundRect) {
|
||||||
|
context.beginPath();
|
||||||
|
context.roundRect(x - cx, y - cy, width, height, radius);
|
||||||
|
context.stroke();
|
||||||
|
context.closePath();
|
||||||
|
} else {
|
||||||
|
context.strokeRect(x - cx, y - cy, width, height);
|
||||||
|
}
|
||||||
|
context.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const drawHighlightForDiamondWithRotation = (
|
||||||
|
context: CanvasRenderingContext2D,
|
||||||
|
padding: number,
|
||||||
|
element: ExcalidrawDiamondElement,
|
||||||
|
) => {
|
||||||
|
const [x, y] = pointRotateRads(
|
||||||
|
pointFrom<GlobalPoint>(element.x, element.y),
|
||||||
|
elementCenterPoint(element),
|
||||||
|
element.angle,
|
||||||
|
);
|
||||||
|
context.save();
|
||||||
|
context.translate(x, y);
|
||||||
|
context.rotate(element.angle);
|
||||||
|
|
||||||
|
{
|
||||||
|
context.beginPath();
|
||||||
|
|
||||||
|
const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] =
|
||||||
|
getDiamondPoints(element);
|
||||||
|
const verticalRadius = element.roundness
|
||||||
|
? getCornerRadius(Math.abs(topX - leftX), element)
|
||||||
|
: (topX - leftX) * 0.01;
|
||||||
|
const horizontalRadius = element.roundness
|
||||||
|
? getCornerRadius(Math.abs(rightY - topY), element)
|
||||||
|
: (rightY - topY) * 0.01;
|
||||||
|
const topApprox = offsetCubicBezier(
|
||||||
|
pointFrom(topX - verticalRadius, topY + horizontalRadius),
|
||||||
|
pointFrom(topX, topY),
|
||||||
|
pointFrom(topX, topY),
|
||||||
|
pointFrom(topX + verticalRadius, topY + horizontalRadius),
|
||||||
|
padding,
|
||||||
|
);
|
||||||
|
const rightApprox = offsetCubicBezier(
|
||||||
|
pointFrom(rightX - verticalRadius, rightY - horizontalRadius),
|
||||||
|
pointFrom(rightX, rightY),
|
||||||
|
pointFrom(rightX, rightY),
|
||||||
|
pointFrom(rightX - verticalRadius, rightY + horizontalRadius),
|
||||||
|
padding,
|
||||||
|
);
|
||||||
|
const bottomApprox = offsetCubicBezier(
|
||||||
|
pointFrom(bottomX + verticalRadius, bottomY - horizontalRadius),
|
||||||
|
pointFrom(bottomX, bottomY),
|
||||||
|
pointFrom(bottomX, bottomY),
|
||||||
|
pointFrom(bottomX - verticalRadius, bottomY - horizontalRadius),
|
||||||
|
padding,
|
||||||
|
);
|
||||||
|
const leftApprox = offsetCubicBezier(
|
||||||
|
pointFrom(leftX + verticalRadius, leftY + horizontalRadius),
|
||||||
|
pointFrom(leftX, leftY),
|
||||||
|
pointFrom(leftX, leftY),
|
||||||
|
pointFrom(leftX + verticalRadius, leftY - horizontalRadius),
|
||||||
|
padding,
|
||||||
|
);
|
||||||
|
|
||||||
|
context.moveTo(
|
||||||
|
topApprox[topApprox.length - 1][0],
|
||||||
|
topApprox[topApprox.length - 1][1],
|
||||||
|
);
|
||||||
|
context.lineTo(rightApprox[0][0], rightApprox[0][1]);
|
||||||
|
drawCatmullRomCubicApprox(context, rightApprox);
|
||||||
|
context.lineTo(bottomApprox[0][0], bottomApprox[0][1]);
|
||||||
|
drawCatmullRomCubicApprox(context, bottomApprox);
|
||||||
|
context.lineTo(leftApprox[0][0], leftApprox[0][1]);
|
||||||
|
drawCatmullRomCubicApprox(context, leftApprox);
|
||||||
|
context.lineTo(topApprox[0][0], topApprox[0][1]);
|
||||||
|
drawCatmullRomCubicApprox(context, topApprox);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Counter-clockwise for the cutout in the middle. We need to have an "inverse
|
||||||
|
// mask" on a filled shape for the diamond highlight, because stroking creates
|
||||||
|
// sharp inset edges on line joins < 90 degrees.
|
||||||
|
{
|
||||||
|
const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] =
|
||||||
|
getDiamondPoints(element);
|
||||||
|
const verticalRadius = element.roundness
|
||||||
|
? getCornerRadius(Math.abs(topX - leftX), element)
|
||||||
|
: (topX - leftX) * 0.01;
|
||||||
|
const horizontalRadius = element.roundness
|
||||||
|
? getCornerRadius(Math.abs(rightY - topY), element)
|
||||||
|
: (rightY - topY) * 0.01;
|
||||||
|
const topApprox = offsetCubicBezier(
|
||||||
|
pointFrom(topX + verticalRadius, topY + horizontalRadius),
|
||||||
|
pointFrom(topX, topY),
|
||||||
|
pointFrom(topX, topY),
|
||||||
|
pointFrom(topX - verticalRadius, topY + horizontalRadius),
|
||||||
|
-FIXED_BINDING_DISTANCE,
|
||||||
|
);
|
||||||
|
const rightApprox = offsetCubicBezier(
|
||||||
|
pointFrom(rightX - verticalRadius, rightY + horizontalRadius),
|
||||||
|
pointFrom(rightX, rightY),
|
||||||
|
pointFrom(rightX, rightY),
|
||||||
|
pointFrom(rightX - verticalRadius, rightY - horizontalRadius),
|
||||||
|
-FIXED_BINDING_DISTANCE,
|
||||||
|
);
|
||||||
|
const bottomApprox = offsetCubicBezier(
|
||||||
|
pointFrom(bottomX - verticalRadius, bottomY - horizontalRadius),
|
||||||
|
pointFrom(bottomX, bottomY),
|
||||||
|
pointFrom(bottomX, bottomY),
|
||||||
|
pointFrom(bottomX + verticalRadius, bottomY - horizontalRadius),
|
||||||
|
-FIXED_BINDING_DISTANCE,
|
||||||
|
);
|
||||||
|
const leftApprox = offsetCubicBezier(
|
||||||
|
pointFrom(leftX + verticalRadius, leftY - horizontalRadius),
|
||||||
|
pointFrom(leftX, leftY),
|
||||||
|
pointFrom(leftX, leftY),
|
||||||
|
pointFrom(leftX + verticalRadius, leftY + horizontalRadius),
|
||||||
|
-FIXED_BINDING_DISTANCE,
|
||||||
|
);
|
||||||
|
|
||||||
|
context.moveTo(
|
||||||
|
topApprox[topApprox.length - 1][0],
|
||||||
|
topApprox[topApprox.length - 1][1],
|
||||||
|
);
|
||||||
|
context.lineTo(leftApprox[0][0], leftApprox[0][1]);
|
||||||
|
drawCatmullRomCubicApprox(context, leftApprox);
|
||||||
|
context.lineTo(bottomApprox[0][0], bottomApprox[0][1]);
|
||||||
|
drawCatmullRomCubicApprox(context, bottomApprox);
|
||||||
|
context.lineTo(rightApprox[0][0], rightApprox[0][1]);
|
||||||
|
drawCatmullRomCubicApprox(context, rightApprox);
|
||||||
|
context.lineTo(topApprox[0][0], topApprox[0][1]);
|
||||||
|
drawCatmullRomCubicApprox(context, topApprox);
|
||||||
|
}
|
||||||
|
context.closePath();
|
||||||
|
context.fill();
|
||||||
|
context.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
function offsetCubicBezier(
|
||||||
|
p0: GlobalPoint,
|
||||||
|
p1: GlobalPoint,
|
||||||
|
p2: GlobalPoint,
|
||||||
|
p3: GlobalPoint,
|
||||||
|
offsetDist: number,
|
||||||
|
steps = 20,
|
||||||
|
) {
|
||||||
|
const offsetPoints = [];
|
||||||
|
|
||||||
|
for (let i = 0; i <= steps; i++) {
|
||||||
|
const t = i / steps;
|
||||||
|
const c = curve(p0, p1, p2, p3);
|
||||||
|
const point = bezierEquation(c, t);
|
||||||
|
const tangent = vectorNormalize(curveTangent(c, t));
|
||||||
|
const normal = vectorNormal(tangent);
|
||||||
|
|
||||||
|
offsetPoints.push(pointFromVector(vectorScale(normal, offsetDist), point));
|
||||||
|
}
|
||||||
|
|
||||||
|
return offsetPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
function offsetQuadraticBezier(
|
||||||
|
p0: GlobalPoint,
|
||||||
|
p1: GlobalPoint,
|
||||||
|
p2: GlobalPoint,
|
||||||
|
offsetDist: number,
|
||||||
|
steps = 20,
|
||||||
|
) {
|
||||||
|
const offsetPoints = [];
|
||||||
|
|
||||||
|
for (let i = 0; i <= steps; i++) {
|
||||||
|
const t = i / steps;
|
||||||
|
const t1 = 1 - t;
|
||||||
|
const point = pointFrom<GlobalPoint>(
|
||||||
|
t1 * t1 * p0[0] + 2 * t1 * t * p1[0] + t * t * p2[0],
|
||||||
|
t1 * t1 * p0[1] + 2 * t1 * t * p1[1] + t * t * p2[1],
|
||||||
|
);
|
||||||
|
const tangentX = 2 * (1 - t) * (p1[0] - p0[0]) + 2 * t * (p2[0] - p1[0]);
|
||||||
|
const tangentY = 2 * (1 - t) * (p1[1] - p0[1]) + 2 * t * (p2[1] - p1[1]);
|
||||||
|
const tangent = vectorNormalize(vector(tangentX, tangentY));
|
||||||
|
const normal = vectorNormal(tangent);
|
||||||
|
|
||||||
|
offsetPoints.push(pointFromVector(vectorScale(normal, offsetDist), point));
|
||||||
|
}
|
||||||
|
|
||||||
|
return offsetPoints;
|
||||||
|
}
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
import oc from "open-color";
|
|
||||||
import {
|
import {
|
||||||
pointFrom,
|
pointFrom,
|
||||||
pointRotateRads,
|
|
||||||
type GlobalPoint,
|
type GlobalPoint,
|
||||||
type LocalPoint,
|
type LocalPoint,
|
||||||
type Radians,
|
type Radians,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
import oc from "open-color";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
arrayToMap,
|
||||||
DEFAULT_TRANSFORM_HANDLE_SPACING,
|
DEFAULT_TRANSFORM_HANDLE_SPACING,
|
||||||
FRAME_STYLE,
|
FRAME_STYLE,
|
||||||
THEME,
|
|
||||||
arrayToMap,
|
|
||||||
elementCenterPoint,
|
|
||||||
invariant,
|
invariant,
|
||||||
|
THEME,
|
||||||
throttleRAF,
|
throttleRAF,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
|
@ -36,23 +34,18 @@ import {
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "@excalidraw/element/typeChecks";
|
} from "@excalidraw/element/typeChecks";
|
||||||
|
|
||||||
import { getCornerRadius } from "@excalidraw/element/shapes";
|
|
||||||
|
|
||||||
import { renderSelectionElement } from "@excalidraw/element/renderElement";
|
import { renderSelectionElement } from "@excalidraw/element/renderElement";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isSelectedViaGroup,
|
|
||||||
getSelectedGroupIds,
|
|
||||||
getElementsInGroup,
|
getElementsInGroup,
|
||||||
|
getSelectedGroupIds,
|
||||||
|
isSelectedViaGroup,
|
||||||
selectGroupsFromGivenElements,
|
selectGroupsFromGivenElements,
|
||||||
} from "@excalidraw/element/groups";
|
} from "@excalidraw/element/groups";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getCommonBounds,
|
getCommonBounds,
|
||||||
getDiamondPoints,
|
|
||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
offsetCubicBezier,
|
|
||||||
offsetQuadraticBezier,
|
|
||||||
} from "@excalidraw/element/bounds";
|
} from "@excalidraw/element/bounds";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
@ -68,12 +61,10 @@ import type {
|
||||||
import type {
|
import type {
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
ExcalidrawBindableElement,
|
ExcalidrawBindableElement,
|
||||||
ExcalidrawDiamondElement,
|
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawFrameLikeElement,
|
ExcalidrawFrameLikeElement,
|
||||||
ExcalidrawImageElement,
|
ExcalidrawImageElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
ExcalidrawRectanguloidElement,
|
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
GroupId,
|
GroupId,
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
|
@ -92,8 +83,12 @@ import { getClientColor, renderRemoteCursors } from "../clients";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
bootstrapCanvas,
|
bootstrapCanvas,
|
||||||
|
drawHighlightForDiamondWithRotation,
|
||||||
|
drawHighlightForRectWithRotation,
|
||||||
fillCircle,
|
fillCircle,
|
||||||
getNormalizedCanvasDimensions,
|
getNormalizedCanvasDimensions,
|
||||||
|
strokeEllipseWithRotation,
|
||||||
|
strokeRectWithRotation,
|
||||||
} from "./helpers";
|
} from "./helpers";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
@ -166,277 +161,6 @@ const highlightPoint = <Point extends LocalPoint | GlobalPoint>(
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const drawHighlightForRectWithRotation = (
|
|
||||||
context: CanvasRenderingContext2D,
|
|
||||||
element: ExcalidrawRectanguloidElement,
|
|
||||||
padding: number,
|
|
||||||
) => {
|
|
||||||
const [x, y] = pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(element.x, element.y),
|
|
||||||
elementCenterPoint(element),
|
|
||||||
element.angle,
|
|
||||||
);
|
|
||||||
|
|
||||||
context.save();
|
|
||||||
context.translate(x, y);
|
|
||||||
context.rotate(element.angle);
|
|
||||||
|
|
||||||
let radius = getCornerRadius(
|
|
||||||
Math.min(element.width, element.height),
|
|
||||||
element,
|
|
||||||
);
|
|
||||||
if (radius === 0) {
|
|
||||||
radius = 0.01;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.beginPath();
|
|
||||||
|
|
||||||
{
|
|
||||||
const topLeftApprox = offsetQuadraticBezier(
|
|
||||||
pointFrom(0, 0 + radius),
|
|
||||||
pointFrom(0, 0),
|
|
||||||
pointFrom(0 + radius, 0),
|
|
||||||
padding,
|
|
||||||
);
|
|
||||||
const topRightApprox = offsetQuadraticBezier(
|
|
||||||
pointFrom(element.width - radius, 0),
|
|
||||||
pointFrom(element.width, 0),
|
|
||||||
pointFrom(element.width, radius),
|
|
||||||
padding,
|
|
||||||
);
|
|
||||||
const bottomRightApprox = offsetQuadraticBezier(
|
|
||||||
pointFrom(element.width, element.height - radius),
|
|
||||||
pointFrom(element.width, element.height),
|
|
||||||
pointFrom(element.width - radius, element.height),
|
|
||||||
padding,
|
|
||||||
);
|
|
||||||
const bottomLeftApprox = offsetQuadraticBezier(
|
|
||||||
pointFrom(radius, element.height),
|
|
||||||
pointFrom(0, element.height),
|
|
||||||
pointFrom(0, element.height - radius),
|
|
||||||
padding,
|
|
||||||
);
|
|
||||||
|
|
||||||
context.moveTo(
|
|
||||||
topLeftApprox[topLeftApprox.length - 1][0],
|
|
||||||
topLeftApprox[topLeftApprox.length - 1][1],
|
|
||||||
);
|
|
||||||
context.lineTo(topRightApprox[0][0], topRightApprox[0][1]);
|
|
||||||
drawCatmullRomQuadraticApprox(context, topRightApprox);
|
|
||||||
context.lineTo(bottomRightApprox[0][0], bottomRightApprox[0][1]);
|
|
||||||
drawCatmullRomQuadraticApprox(context, bottomRightApprox);
|
|
||||||
context.lineTo(bottomLeftApprox[0][0], bottomLeftApprox[0][1]);
|
|
||||||
drawCatmullRomQuadraticApprox(context, bottomLeftApprox);
|
|
||||||
context.lineTo(topLeftApprox[0][0], topLeftApprox[0][1]);
|
|
||||||
drawCatmullRomQuadraticApprox(context, topLeftApprox);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Counter-clockwise for the cutout in the middle. We need to have an "inverse
|
|
||||||
// mask" on a filled shape for the diamond highlight, because stroking creates
|
|
||||||
// sharp inset edges on line joins < 90 degrees.
|
|
||||||
{
|
|
||||||
const topLeftApprox = offsetQuadraticBezier(
|
|
||||||
pointFrom(0 + radius, 0),
|
|
||||||
pointFrom(0, 0),
|
|
||||||
pointFrom(0, 0 + radius),
|
|
||||||
-FIXED_BINDING_DISTANCE,
|
|
||||||
);
|
|
||||||
const topRightApprox = offsetQuadraticBezier(
|
|
||||||
pointFrom(element.width, radius),
|
|
||||||
pointFrom(element.width, 0),
|
|
||||||
pointFrom(element.width - radius, 0),
|
|
||||||
-FIXED_BINDING_DISTANCE,
|
|
||||||
);
|
|
||||||
const bottomRightApprox = offsetQuadraticBezier(
|
|
||||||
pointFrom(element.width - radius, element.height),
|
|
||||||
pointFrom(element.width, element.height),
|
|
||||||
pointFrom(element.width, element.height - radius),
|
|
||||||
-FIXED_BINDING_DISTANCE,
|
|
||||||
);
|
|
||||||
const bottomLeftApprox = offsetQuadraticBezier(
|
|
||||||
pointFrom(0, element.height - radius),
|
|
||||||
pointFrom(0, element.height),
|
|
||||||
pointFrom(radius, element.height),
|
|
||||||
-FIXED_BINDING_DISTANCE,
|
|
||||||
);
|
|
||||||
|
|
||||||
context.moveTo(
|
|
||||||
topLeftApprox[topLeftApprox.length - 1][0],
|
|
||||||
topLeftApprox[topLeftApprox.length - 1][1],
|
|
||||||
);
|
|
||||||
context.lineTo(bottomLeftApprox[0][0], bottomLeftApprox[0][1]);
|
|
||||||
drawCatmullRomQuadraticApprox(context, bottomLeftApprox);
|
|
||||||
context.lineTo(bottomRightApprox[0][0], bottomRightApprox[0][1]);
|
|
||||||
drawCatmullRomQuadraticApprox(context, bottomRightApprox);
|
|
||||||
context.lineTo(topRightApprox[0][0], topRightApprox[0][1]);
|
|
||||||
drawCatmullRomQuadraticApprox(context, topRightApprox);
|
|
||||||
context.lineTo(topLeftApprox[0][0], topLeftApprox[0][1]);
|
|
||||||
drawCatmullRomQuadraticApprox(context, topLeftApprox);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.closePath();
|
|
||||||
context.fill();
|
|
||||||
|
|
||||||
context.restore();
|
|
||||||
};
|
|
||||||
|
|
||||||
const strokeRectWithRotation = (
|
|
||||||
context: CanvasRenderingContext2D,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
width: number,
|
|
||||||
height: number,
|
|
||||||
cx: number,
|
|
||||||
cy: number,
|
|
||||||
angle: number,
|
|
||||||
fill: boolean = false,
|
|
||||||
/** should account for zoom */
|
|
||||||
radius: number = 0,
|
|
||||||
) => {
|
|
||||||
context.save();
|
|
||||||
context.translate(cx, cy);
|
|
||||||
context.rotate(angle);
|
|
||||||
if (fill) {
|
|
||||||
context.fillRect(x - cx, y - cy, width, height);
|
|
||||||
}
|
|
||||||
if (radius && context.roundRect) {
|
|
||||||
context.beginPath();
|
|
||||||
context.roundRect(x - cx, y - cy, width, height, radius);
|
|
||||||
context.stroke();
|
|
||||||
context.closePath();
|
|
||||||
} else {
|
|
||||||
context.strokeRect(x - cx, y - cy, width, height);
|
|
||||||
}
|
|
||||||
context.restore();
|
|
||||||
};
|
|
||||||
|
|
||||||
const drawHighlightForDiamondWithRotation = (
|
|
||||||
context: CanvasRenderingContext2D,
|
|
||||||
padding: number,
|
|
||||||
element: ExcalidrawDiamondElement,
|
|
||||||
) => {
|
|
||||||
const [x, y] = pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(element.x, element.y),
|
|
||||||
elementCenterPoint(element),
|
|
||||||
element.angle,
|
|
||||||
);
|
|
||||||
context.save();
|
|
||||||
context.translate(x, y);
|
|
||||||
context.rotate(element.angle);
|
|
||||||
|
|
||||||
{
|
|
||||||
context.beginPath();
|
|
||||||
|
|
||||||
const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] =
|
|
||||||
getDiamondPoints(element);
|
|
||||||
const verticalRadius = element.roundness
|
|
||||||
? getCornerRadius(Math.abs(topX - leftX), element)
|
|
||||||
: (topX - leftX) * 0.01;
|
|
||||||
const horizontalRadius = element.roundness
|
|
||||||
? getCornerRadius(Math.abs(rightY - topY), element)
|
|
||||||
: (rightY - topY) * 0.01;
|
|
||||||
const topApprox = offsetCubicBezier(
|
|
||||||
pointFrom(topX - verticalRadius, topY + horizontalRadius),
|
|
||||||
pointFrom(topX, topY),
|
|
||||||
pointFrom(topX, topY),
|
|
||||||
pointFrom(topX + verticalRadius, topY + horizontalRadius),
|
|
||||||
padding,
|
|
||||||
);
|
|
||||||
const rightApprox = offsetCubicBezier(
|
|
||||||
pointFrom(rightX - verticalRadius, rightY - horizontalRadius),
|
|
||||||
pointFrom(rightX, rightY),
|
|
||||||
pointFrom(rightX, rightY),
|
|
||||||
pointFrom(rightX - verticalRadius, rightY + horizontalRadius),
|
|
||||||
padding,
|
|
||||||
);
|
|
||||||
const bottomApprox = offsetCubicBezier(
|
|
||||||
pointFrom(bottomX + verticalRadius, bottomY - horizontalRadius),
|
|
||||||
pointFrom(bottomX, bottomY),
|
|
||||||
pointFrom(bottomX, bottomY),
|
|
||||||
pointFrom(bottomX - verticalRadius, bottomY - horizontalRadius),
|
|
||||||
padding,
|
|
||||||
);
|
|
||||||
const leftApprox = offsetCubicBezier(
|
|
||||||
pointFrom(leftX + verticalRadius, leftY + horizontalRadius),
|
|
||||||
pointFrom(leftX, leftY),
|
|
||||||
pointFrom(leftX, leftY),
|
|
||||||
pointFrom(leftX + verticalRadius, leftY - horizontalRadius),
|
|
||||||
padding,
|
|
||||||
);
|
|
||||||
|
|
||||||
context.moveTo(
|
|
||||||
topApprox[topApprox.length - 1][0],
|
|
||||||
topApprox[topApprox.length - 1][1],
|
|
||||||
);
|
|
||||||
context.lineTo(rightApprox[0][0], rightApprox[0][1]);
|
|
||||||
drawCatmullRomCubicApprox(context, rightApprox);
|
|
||||||
context.lineTo(bottomApprox[0][0], bottomApprox[0][1]);
|
|
||||||
drawCatmullRomCubicApprox(context, bottomApprox);
|
|
||||||
context.lineTo(leftApprox[0][0], leftApprox[0][1]);
|
|
||||||
drawCatmullRomCubicApprox(context, leftApprox);
|
|
||||||
context.lineTo(topApprox[0][0], topApprox[0][1]);
|
|
||||||
drawCatmullRomCubicApprox(context, topApprox);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Counter-clockwise for the cutout in the middle. We need to have an "inverse
|
|
||||||
// mask" on a filled shape for the diamond highlight, because stroking creates
|
|
||||||
// sharp inset edges on line joins < 90 degrees.
|
|
||||||
{
|
|
||||||
const [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY] =
|
|
||||||
getDiamondPoints(element);
|
|
||||||
const verticalRadius = element.roundness
|
|
||||||
? getCornerRadius(Math.abs(topX - leftX), element)
|
|
||||||
: (topX - leftX) * 0.01;
|
|
||||||
const horizontalRadius = element.roundness
|
|
||||||
? getCornerRadius(Math.abs(rightY - topY), element)
|
|
||||||
: (rightY - topY) * 0.01;
|
|
||||||
const topApprox = offsetCubicBezier(
|
|
||||||
pointFrom(topX + verticalRadius, topY + horizontalRadius),
|
|
||||||
pointFrom(topX, topY),
|
|
||||||
pointFrom(topX, topY),
|
|
||||||
pointFrom(topX - verticalRadius, topY + horizontalRadius),
|
|
||||||
-FIXED_BINDING_DISTANCE,
|
|
||||||
);
|
|
||||||
const rightApprox = offsetCubicBezier(
|
|
||||||
pointFrom(rightX - verticalRadius, rightY + horizontalRadius),
|
|
||||||
pointFrom(rightX, rightY),
|
|
||||||
pointFrom(rightX, rightY),
|
|
||||||
pointFrom(rightX - verticalRadius, rightY - horizontalRadius),
|
|
||||||
-FIXED_BINDING_DISTANCE,
|
|
||||||
);
|
|
||||||
const bottomApprox = offsetCubicBezier(
|
|
||||||
pointFrom(bottomX - verticalRadius, bottomY - horizontalRadius),
|
|
||||||
pointFrom(bottomX, bottomY),
|
|
||||||
pointFrom(bottomX, bottomY),
|
|
||||||
pointFrom(bottomX + verticalRadius, bottomY - horizontalRadius),
|
|
||||||
-FIXED_BINDING_DISTANCE,
|
|
||||||
);
|
|
||||||
const leftApprox = offsetCubicBezier(
|
|
||||||
pointFrom(leftX + verticalRadius, leftY - horizontalRadius),
|
|
||||||
pointFrom(leftX, leftY),
|
|
||||||
pointFrom(leftX, leftY),
|
|
||||||
pointFrom(leftX + verticalRadius, leftY + horizontalRadius),
|
|
||||||
-FIXED_BINDING_DISTANCE,
|
|
||||||
);
|
|
||||||
|
|
||||||
context.moveTo(
|
|
||||||
topApprox[topApprox.length - 1][0],
|
|
||||||
topApprox[topApprox.length - 1][1],
|
|
||||||
);
|
|
||||||
context.lineTo(leftApprox[0][0], leftApprox[0][1]);
|
|
||||||
drawCatmullRomCubicApprox(context, leftApprox);
|
|
||||||
context.lineTo(bottomApprox[0][0], bottomApprox[0][1]);
|
|
||||||
drawCatmullRomCubicApprox(context, bottomApprox);
|
|
||||||
context.lineTo(rightApprox[0][0], rightApprox[0][1]);
|
|
||||||
drawCatmullRomCubicApprox(context, rightApprox);
|
|
||||||
context.lineTo(topApprox[0][0], topApprox[0][1]);
|
|
||||||
drawCatmullRomCubicApprox(context, topApprox);
|
|
||||||
}
|
|
||||||
context.closePath();
|
|
||||||
context.fill();
|
|
||||||
context.restore();
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderSingleLinearPoint = <Point extends GlobalPoint | LocalPoint>(
|
const renderSingleLinearPoint = <Point extends GlobalPoint | LocalPoint>(
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
appState: InteractiveCanvasAppState,
|
appState: InteractiveCanvasAppState,
|
||||||
|
@ -463,19 +187,6 @@ const renderSingleLinearPoint = <Point extends GlobalPoint | LocalPoint>(
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const strokeEllipseWithRotation = (
|
|
||||||
context: CanvasRenderingContext2D,
|
|
||||||
width: number,
|
|
||||||
height: number,
|
|
||||||
cx: number,
|
|
||||||
cy: number,
|
|
||||||
angle: number,
|
|
||||||
) => {
|
|
||||||
context.beginPath();
|
|
||||||
context.ellipse(cx, cy, width / 2, height / 2, angle, 0, Math.PI * 2);
|
|
||||||
context.stroke();
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderBindingHighlightForBindableElement = (
|
const renderBindingHighlightForBindableElement = (
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
element: ExcalidrawBindableElement,
|
element: ExcalidrawBindableElement,
|
||||||
|
@ -1444,65 +1155,3 @@ export const renderInteractiveScene = <
|
||||||
renderConfig.callback(ret);
|
renderConfig.callback(ret);
|
||||||
return ret as T extends true ? void : ReturnType<U>;
|
return ret as T extends true ? void : ReturnType<U>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function drawCatmullRomQuadraticApprox(
|
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
points: GlobalPoint[],
|
|
||||||
segments = 20,
|
|
||||||
) {
|
|
||||||
ctx.lineTo(points[0][0], points[0][1]);
|
|
||||||
|
|
||||||
for (let i = 0; i < points.length - 1; i++) {
|
|
||||||
const p0 = points[i - 1 < 0 ? 0 : i - 1];
|
|
||||||
const p1 = points[i];
|
|
||||||
const p2 = points[i + 1 >= points.length ? points.length - 1 : i + 1];
|
|
||||||
|
|
||||||
for (let t = 0; t <= 1; t += 1 / segments) {
|
|
||||||
const t2 = t * t;
|
|
||||||
|
|
||||||
const x =
|
|
||||||
(1 - t) * (1 - t) * p0[0] + 2 * (1 - t) * t * p1[0] + t2 * p2[0];
|
|
||||||
|
|
||||||
const y =
|
|
||||||
(1 - t) * (1 - t) * p0[1] + 2 * (1 - t) * t * p1[1] + t2 * p2[1];
|
|
||||||
|
|
||||||
ctx.lineTo(x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawCatmullRomCubicApprox(
|
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
points: GlobalPoint[],
|
|
||||||
segments = 20,
|
|
||||||
) {
|
|
||||||
ctx.lineTo(points[0][0], points[0][1]);
|
|
||||||
|
|
||||||
for (let i = 0; i < points.length - 1; i++) {
|
|
||||||
const p0 = points[i - 1 < 0 ? 0 : i - 1];
|
|
||||||
const p1 = points[i];
|
|
||||||
const p2 = points[i + 1 >= points.length ? points.length - 1 : i + 1];
|
|
||||||
const p3 = points[i + 2 >= points.length ? points.length - 1 : i + 2];
|
|
||||||
|
|
||||||
for (let t = 0; t <= 1; t += 1 / segments) {
|
|
||||||
const t2 = t * t;
|
|
||||||
const t3 = t2 * t;
|
|
||||||
|
|
||||||
const x =
|
|
||||||
0.5 *
|
|
||||||
(2 * p1[0] +
|
|
||||||
(-p0[0] + p2[0]) * t +
|
|
||||||
(2 * p0[0] - 5 * p1[0] + 4 * p2[0] - p3[0]) * t2 +
|
|
||||||
(-p0[0] + 3 * p1[0] - 3 * p2[0] + p3[0]) * t3);
|
|
||||||
|
|
||||||
const y =
|
|
||||||
0.5 *
|
|
||||||
(2 * p1[1] +
|
|
||||||
(-p0[1] + p2[1]) * t +
|
|
||||||
(2 * p0[1] - 5 * p1[1] + 4 * p2[1] - p3[1]) * t2 +
|
|
||||||
(-p0[1] + 3 * p1[1] - 3 * p2[1] + p3[1]) * t3);
|
|
||||||
|
|
||||||
ctx.lineTo(x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue