mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 10:36:22 +08:00
108 lines
3.3 KiB
TypeScript
108 lines
3.3 KiB
TypeScript
import { Utils } from '@tldraw/core';
|
|
import Vec from '@tldraw/vec';
|
|
import getStroke, { getStrokePoints } from 'perfect-freehand';
|
|
import type { ShapeStyles } from '@toeverything/components/board-types';
|
|
import { getShapeStyle } from '../shared';
|
|
|
|
function getRectangleDrawPoints(
|
|
id: string,
|
|
style: ShapeStyles,
|
|
size: number[]
|
|
) {
|
|
const styles = getShapeStyle(style);
|
|
|
|
const getRandom = Utils.rng(id);
|
|
|
|
const sw = styles.strokeWidth;
|
|
|
|
// Dimensions
|
|
const w = Math.max(0, size[0]);
|
|
const h = Math.max(0, size[1]);
|
|
|
|
// Random corner offsets
|
|
const offsets = Array.from(Array(4)).map(() => {
|
|
return [getRandom() * sw * 0.75, getRandom() * sw * 0.75];
|
|
});
|
|
|
|
// Corners
|
|
const tl = Vec.add([sw / 2, sw / 2], offsets[0]);
|
|
const tr = Vec.add([w - sw / 2, sw / 2], offsets[1]);
|
|
const br = Vec.add([w - sw / 2, h - sw / 2], offsets[2]);
|
|
const bl = Vec.add([sw / 2, h - sw / 2], offsets[3]);
|
|
|
|
// Which side to start drawing first
|
|
const rm = Math.round(Math.abs(getRandom() * 2 * 4));
|
|
|
|
// Corner radii
|
|
const rx = Math.min(w / 4, sw * 2);
|
|
const ry = Math.min(h / 4, sw * 2);
|
|
|
|
// Number of points per side
|
|
const px = Math.max(8, Math.floor(w / 16));
|
|
const py = Math.max(8, Math.floor(h / 16));
|
|
|
|
// Inset each line by the corner radii and let the freehand algo
|
|
// interpolate points for the corners.
|
|
const lines = Utils.rotateArray(
|
|
[
|
|
Vec.pointsBetween(Vec.add(tl, [rx, 0]), Vec.sub(tr, [rx, 0]), px),
|
|
Vec.pointsBetween(Vec.add(tr, [0, ry]), Vec.sub(br, [0, ry]), py),
|
|
Vec.pointsBetween(Vec.sub(br, [rx, 0]), Vec.add(bl, [rx, 0]), px),
|
|
Vec.pointsBetween(Vec.sub(bl, [0, ry]), Vec.add(tl, [0, ry]), py),
|
|
],
|
|
rm
|
|
);
|
|
|
|
// For the final points, include the first half of the first line again,
|
|
// so that the line wraps around and avoids ending on a sharp corner.
|
|
// This has a bit of finesse and magic—if you change the points between
|
|
// function, then you'll likely need to change this one too.
|
|
|
|
const points = [...lines.flat(), ...lines[0]].slice(
|
|
5,
|
|
Math.floor((rm % 2 === 0 ? px : py) / -2) + 3
|
|
);
|
|
|
|
return {
|
|
points,
|
|
};
|
|
}
|
|
|
|
function getDrawStrokeInfo(id: string, style: ShapeStyles, size: number[]) {
|
|
const { points } = getRectangleDrawPoints(id, style, size);
|
|
const { strokeWidth } = getShapeStyle(style);
|
|
const options = {
|
|
size: strokeWidth,
|
|
thinning: 0.65,
|
|
streamline: 0.3,
|
|
smoothing: 1,
|
|
simulatePressure: false,
|
|
last: true,
|
|
};
|
|
return { points, options };
|
|
}
|
|
|
|
export function getRectanglePath(
|
|
id: string,
|
|
style: ShapeStyles,
|
|
size: number[]
|
|
) {
|
|
const { points, options } = getDrawStrokeInfo(id, style, size);
|
|
const stroke = getStroke(points, options);
|
|
return Utils.getSvgPathFromStroke(stroke);
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
export function getRectangleIndicatorPathTDSnapshot(
|
|
id: string,
|
|
style: ShapeStyles,
|
|
size: number[]
|
|
) {
|
|
const { points, options } = getDrawStrokeInfo(id, style, size);
|
|
const strokePoints = getStrokePoints(points, options);
|
|
return Utils.getSvgPathFromStroke(
|
|
strokePoints.map(pt => pt.point.slice(0, 2)),
|
|
false
|
|
);
|
|
}
|