Bounds refactor and duplication removal

This commit is contained in:
Mark Tolmacs 2024-09-27 15:58:18 +02:00
parent 7b4e989d65
commit 91b6057d9c
No known key found for this signature in database
28 changed files with 431 additions and 147 deletions

View file

@ -10,7 +10,7 @@ import {
import { serializeAsJSON } from "../../packages/excalidraw/data/json"; import { serializeAsJSON } from "../../packages/excalidraw/data/json";
import { restore } from "../../packages/excalidraw/data/restore"; import { restore } from "../../packages/excalidraw/data/restore";
import type { ImportedDataState } from "../../packages/excalidraw/data/types"; import type { ImportedDataState } from "../../packages/excalidraw/data/types";
import type { SceneBounds } from "../../packages/excalidraw/element/bounds"; import type { ViewportBounds } from "../../packages/excalidraw/element/bounds";
import { isInvisiblySmallElement } from "../../packages/excalidraw/element/sizeHelpers"; import { isInvisiblySmallElement } from "../../packages/excalidraw/element/sizeHelpers";
import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks"; import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
import type { import type {
@ -104,7 +104,7 @@ export type SocketUpdateDataSource = {
payload: { payload: {
socketId: SocketId; socketId: SocketId;
username: string; username: string;
sceneBounds: SceneBounds; sceneBounds: ViewportBounds;
}; };
}; };
IDLE_STATUS: { IDLE_STATUS: {

View file

@ -35,7 +35,7 @@ import {
isHandToolActive, isHandToolActive,
} from "../appState"; } from "../appState";
import { DEFAULT_CANVAS_BACKGROUND_PICKS } from "../colors"; import { DEFAULT_CANVAS_BACKGROUND_PICKS } from "../colors";
import type { SceneBounds } from "../element/bounds"; import type { ViewportBounds } from "../element/bounds";
import { setCursor } from "../cursor"; import { setCursor } from "../cursor";
import { StoreAction } from "../store"; import { StoreAction } from "../store";
import { clamp, point, roundToStep } from "../../math"; import { clamp, point, roundToStep } from "../../math";
@ -245,7 +245,7 @@ export const actionResetZoom = register({
}); });
const zoomValueToFitBoundsOnViewport = ( const zoomValueToFitBoundsOnViewport = (
bounds: SceneBounds, bounds: ViewportBounds,
viewportDimensions: { width: number; height: number }, viewportDimensions: { width: number; height: number },
viewportZoomFactor: number = 1, // default to 1 if not provided viewportZoomFactor: number = 1, // default to 1 if not provided
) => { ) => {
@ -271,7 +271,7 @@ export const zoomToFitBounds = ({
minZoom = -Infinity, minZoom = -Infinity,
maxZoom = Infinity, maxZoom = Infinity,
}: { }: {
bounds: SceneBounds; bounds: ViewportBounds;
canvasOffsets?: Offsets; canvasOffsets?: Offsets;
appState: Readonly<AppState>; appState: Readonly<AppState>;
/** whether to fit content to viewport (beyond >100%) */ /** whether to fit content to viewport (beyond >100%) */

View file

@ -1,6 +1,6 @@
import type { ElementsMap, ExcalidrawElement } from "./element/types"; import type { Bounds, ElementsMap, ExcalidrawElement } from "./element/types";
import { newElementWith } from "./element/mutateElement"; import { newElementWith } from "./element/mutateElement";
import { getCommonBounds, type Bounds } from "./element/bounds"; import { getCommonBounds } from "./element/bounds";
import { getMaximumGroups } from "./groups"; import { getMaximumGroups } from "./groups";
export interface Alignment { export interface Alignment {

View file

@ -1,10 +1,10 @@
import type { GlobalPoint, Radians } from "../../../math"; import type { GlobalPoint, Radians } from "../../../math";
import { point, pointRotateRads } from "../../../math"; import { point, pointRotateRads } from "../../../math";
import { MIME_TYPES } from "../../constants"; import { MIME_TYPES } from "../../constants";
import type { Bounds } from "../../element/bounds";
import { getElementAbsoluteCoords } from "../../element/bounds"; import { getElementAbsoluteCoords } from "../../element/bounds";
import { hitElementBoundingBox } from "../../element/collision"; import { hitElementBoundingBox } from "../../element/collision";
import type { import type {
Bounds,
ElementsMap, ElementsMap,
NonDeletedExcalidrawElement, NonDeletedExcalidrawElement,
} from "../../element/types"; } from "../../element/types";

View file

@ -1,5 +1,236 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Test Transform > Test arrow bindings > should bind arrows to existing shapes when start / end provided with ids 1`] = `
{
"angle": 0,
"backgroundColor": "#d8f5a2",
"boundElements": [
{
"id": "id45",
"type": "arrow",
},
{
"id": "id46",
"type": "arrow",
},
],
"customData": undefined,
"fillStyle": "solid",
"frameId": null,
"groupIds": [],
"height": 300,
"id": Any<String>,
"index": "a0",
"isDeleted": false,
"link": null,
"locked": false,
"opacity": 100,
"roughness": 1,
"roundness": null,
"seed": Any<Number>,
"strokeColor": "#66a80f",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "ellipse",
"updated": 1,
"version": 4,
"versionNonce": Any<Number>,
"width": 300,
"x": 630,
"y": 316,
}
`;
exports[`Test Transform > Test arrow bindings > should bind arrows to existing shapes when start / end provided with ids 2`] = `
{
"angle": 0,
"backgroundColor": "transparent",
"boundElements": [
{
"id": "id46",
"type": "arrow",
},
],
"customData": undefined,
"fillStyle": "solid",
"frameId": null,
"groupIds": [],
"height": 100,
"id": Any<String>,
"index": "a1",
"isDeleted": false,
"link": null,
"locked": false,
"opacity": 100,
"roughness": 1,
"roundness": null,
"seed": Any<Number>,
"strokeColor": "#9c36b5",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "diamond",
"updated": 1,
"version": 3,
"versionNonce": Any<Number>,
"width": 140,
"x": 96,
"y": 400,
}
`;
exports[`Test Transform > Test arrow bindings > should bind arrows to existing shapes when start / end provided with ids 3`] = `
{
"angle": 0,
"backgroundColor": "transparent",
"boundElements": null,
"customData": undefined,
"elbowed": false,
"endArrowhead": "arrow",
"endBinding": {
"elementId": "ellipse-1",
"fixedPoint": null,
"focus": -0.008153707962747813,
"gap": 11.562288374879595,
},
"fillStyle": "solid",
"frameId": null,
"groupIds": [],
"height": 35,
"id": Any<String>,
"index": "a2",
"isDeleted": false,
"lastCommittedPoint": null,
"link": null,
"locked": false,
"opacity": 100,
"points": [
[
0.5,
0.5,
],
[
394.5,
34.5,
],
],
"roughness": 1,
"roundness": null,
"seed": Any<Number>,
"startArrowhead": null,
"startBinding": {
"elementId": "id47",
"fixedPoint": null,
"focus": -0.08139534883720931,
"gap": 1,
},
"strokeColor": "#1864ab",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "arrow",
"updated": 1,
"version": 4,
"versionNonce": Any<Number>,
"width": 395,
"x": 247,
"y": 420,
}
`;
exports[`Test Transform > Test arrow bindings > should bind arrows to existing shapes when start / end provided with ids 4`] = `
{
"angle": 0,
"backgroundColor": "transparent",
"boundElements": null,
"customData": undefined,
"elbowed": false,
"endArrowhead": "arrow",
"endBinding": {
"elementId": "ellipse-1",
"fixedPoint": null,
"focus": 0.10666666666666667,
"gap": 3.8343264684446097,
},
"fillStyle": "solid",
"frameId": null,
"groupIds": [],
"height": 0,
"id": Any<String>,
"index": "a3",
"isDeleted": false,
"lastCommittedPoint": null,
"link": null,
"locked": false,
"opacity": 100,
"points": [
[
0.5,
0,
],
[
399.5,
0,
],
],
"roughness": 1,
"roundness": null,
"seed": Any<Number>,
"startArrowhead": null,
"startBinding": {
"elementId": "diamond-1",
"fixedPoint": null,
"focus": 0,
"gap": 5.2311437434718675,
},
"strokeColor": "#e67700",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "arrow",
"updated": 1,
"version": 4,
"versionNonce": Any<Number>,
"width": 400,
"x": 227,
"y": 450,
}
`;
exports[`Test Transform > Test arrow bindings > should bind arrows to existing shapes when start / end provided with ids 5`] = `
{
"angle": 0,
"backgroundColor": "transparent",
"boundElements": [
{
"id": "id45",
"type": "arrow",
},
],
"customData": undefined,
"fillStyle": "solid",
"frameId": null,
"groupIds": [],
"height": 300,
"id": Any<String>,
"index": "a4",
"isDeleted": false,
"link": null,
"locked": false,
"opacity": 100,
"roughness": 1,
"roundness": null,
"seed": Any<Number>,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 3,
"versionNonce": Any<Number>,
"width": 300,
"x": -53,
"y": 270,
}
`;
exports[`Test Transform > Test arrow bindings > should bind arrows to existing text elements when start / end provided with ids 1`] = ` exports[`Test Transform > Test arrow bindings > should bind arrows to existing text elements when start / end provided with ids 1`] = `
{ {
"angle": 0, "angle": 0,

View file

@ -25,9 +25,9 @@ import type {
ExcalidrawElbowArrowElement, ExcalidrawElbowArrowElement,
FixedPoint, FixedPoint,
SceneElementsMap, SceneElementsMap,
Bounds,
} from "./types"; } from "./types";
import type { Bounds } from "./bounds";
import { getCenterForBounds, getElementAbsoluteCoords } from "./bounds"; import { getCenterForBounds, getElementAbsoluteCoords } from "./bounds";
import type { AppState } from "../types"; import type { AppState } from "../types";
import { isPointOnShape } from "../../utils/collision"; import { isPointOnShape } from "../../utils/collision";

View file

@ -4,6 +4,7 @@ import type {
ExcalidrawFreeDrawElement, ExcalidrawFreeDrawElement,
ExcalidrawTextElementWithContainer, ExcalidrawTextElementWithContainer,
ElementsMap, ElementsMap,
Bounds,
} from "./types"; } from "./types";
import rough from "roughjs/bin/rough"; import rough from "roughjs/bin/rough";
import type { Point as RoughPoint } from "roughjs/bin/geometry"; import type { Point as RoughPoint } from "roughjs/bin/geometry";
@ -34,17 +35,7 @@ import { getCurvePathOps } from "../../utils/geometry/shape";
type MaybeQuadraticSolution = [number | null, number | null] | false; type MaybeQuadraticSolution = [number | null, number | null] | false;
/** export type ViewportBounds = readonly [
* x and y position of top left corner, x and y position of bottom right corner
*/
export type Bounds = readonly [
minX: number,
minY: number,
maxX: number,
maxY: number,
];
export type SceneBounds = readonly [
sceneX: number, sceneX: number,
sceneY: number, sceneY: number,
sceneX2: number, sceneX2: number,
@ -57,6 +48,7 @@ class ElementBounds {
{ {
bounds: Bounds; bounds: Bounds;
version: ExcalidrawElement["version"]; version: ExcalidrawElement["version"];
versionNonce: ExcalidrawElement["versionNonce"];
} }
>(); >();
@ -66,6 +58,7 @@ class ElementBounds {
if ( if (
cachedBounds?.version && cachedBounds?.version &&
cachedBounds.version === element.version && cachedBounds.version === element.version &&
cachedBounds?.versionNonce === element.versionNonce &&
// we don't invalidate cache when we update containers and not labels, // we don't invalidate cache when we update containers and not labels,
// which is causing problems down the line. Fix TBA. // which is causing problems down the line. Fix TBA.
!isBoundToContainer(element) !isBoundToContainer(element)
@ -76,6 +69,7 @@ class ElementBounds {
ElementBounds.boundsCache.set(element, { ElementBounds.boundsCache.set(element, {
version: element.version, version: element.version,
versionNonce: element.versionNonce,
bounds, bounds,
}); });
@ -93,7 +87,7 @@ class ElementBounds {
elementsMap, elementsMap,
); );
if (isFreeDrawElement(element)) { if (isFreeDrawElement(element)) {
const [minX, minY, maxX, maxY] = getBoundsFromPoints( const [minX, minY, maxX, maxY] = getBoundsFromFreeDrawPoints(
element.points.map(([x, y]) => element.points.map(([x, y]) =>
pointRotateRads( pointRotateRads(
point(x, y), point(x, y),
@ -177,6 +171,20 @@ class ElementBounds {
} }
} }
/**
* Get the axis-aligned bounds of the given element in global / scene coordinates
*
* @param element The element to determine the bounding box for
* @param elementsMap The elements map to retrieve attached elements (notably text label)
* @returns The axis-aligned bounding box in scene (global coordinates)
*/
export const getElementBounds = (
element: ExcalidrawElement,
elementsMap: ElementsMap,
): Bounds => {
return ElementBounds.getBounds(element, elementsMap);
};
// Scene -> Scene coords, but in x1,x2,y1,y2 format. // Scene -> Scene coords, but in x1,x2,y1,y2 format.
// //
// If the element is created from right to left, the width is going to be negative // If the element is created from right to left, the width is going to be negative
@ -224,21 +232,6 @@ export const getElementAbsoluteCoords = (
]; ];
}; };
export const getDiamondPoints = (element: ExcalidrawElement) => {
// Here we add +1 to avoid these numbers to be 0
// otherwise rough.js will throw an error complaining about it
const topX = Math.floor(element.width / 2) + 1;
const topY = 0;
const rightX = element.width;
const rightY = Math.floor(element.height / 2) + 1;
const bottomX = topX;
const bottomY = element.height;
const leftX = 0;
const leftY = rightY;
return [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY];
};
// reference: https://eliot-jones.com/2019/12/cubic-bezier-curve-bounding-boxes // reference: https://eliot-jones.com/2019/12/cubic-bezier-curve-bounding-boxes
const getBezierValueForT = ( const getBezierValueForT = (
t: number, t: number,
@ -382,7 +375,7 @@ export const getMinMaxXYFromCurvePathOps = (
return [minX, minY, maxX, maxY]; return [minX, minY, maxX, maxY];
}; };
export const getBoundsFromPoints = ( const getBoundsFromFreeDrawPoints = (
points: ExcalidrawFreeDrawElement["points"], points: ExcalidrawFreeDrawElement["points"],
): Bounds => { ): Bounds => {
let minX = Infinity; let minX = Infinity;
@ -403,7 +396,7 @@ export const getBoundsFromPoints = (
const getFreeDrawElementAbsoluteCoords = ( const getFreeDrawElementAbsoluteCoords = (
element: ExcalidrawFreeDrawElement, element: ExcalidrawFreeDrawElement,
): [number, number, number, number, number, number] => { ): [number, number, number, number, number, number] => {
const [minX, minY, maxX, maxY] = getBoundsFromPoints(element.points); const [minX, minY, maxX, maxY] = getBoundsFromFreeDrawPoints(element.points);
const x1 = minX + element.x; const x1 = minX + element.x;
const y1 = minY + element.y; const y1 = minY + element.y;
const x2 = maxX + element.x; const x2 = maxX + element.x;
@ -496,13 +489,6 @@ const getLinearElementRotatedBounds = (
return coords; return coords;
}; };
export const getElementBounds = (
element: ExcalidrawElement,
elementsMap: ElementsMap,
): Bounds => {
return ElementBounds.getBounds(element, elementsMap);
};
export const getCommonBounds = ( export const getCommonBounds = (
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
elementsMap?: ElementsMap, elementsMap?: ElementsMap,
@ -568,7 +554,7 @@ export const getResizedElementAbsoluteCoords = (
if (isFreeDrawElement(element)) { if (isFreeDrawElement(element)) {
// Free Draw // Free Draw
bounds = getBoundsFromPoints(points); bounds = getBoundsFromFreeDrawPoints(points);
} else { } else {
// Line // Line
const gen = rough.generator(); const gen = rough.generator();
@ -651,7 +637,7 @@ export const getVisibleSceneBounds = ({
width, width,
height, height,
zoom, zoom,
}: AppState): SceneBounds => { }: AppState): ViewportBounds => {
return [ return [
-scrollX, -scrollX,
-scrollY, -scrollY,

View file

@ -40,31 +40,33 @@ export const distanceToBindableElement = (
} }
}; };
/**
* Returns the distance of a point and the provided rectangular-shaped element,
* accounting for roundness and rotation
*
* @param element The rectanguloid element
* @param p The point to consider
* @returns The eucledian distance to the outline of the rectanguloid element
*/
export const distanceToRectangleElement = ( export const distanceToRectangleElement = (
element: ExcalidrawRectanguloidElement, element: ExcalidrawRectanguloidElement,
p: GlobalPoint, p: GlobalPoint,
) => { ) => {
const center = point(
element.x + element.width / 2,
element.y + element.height / 2,
);
const r = rectangle( const r = rectangle(
pointRotateRads(
point(element.x, element.y), point(element.x, element.y),
center,
radians(element.angle),
),
pointRotateRads(
point(element.x + element.width, element.y + element.height), point(element.x + element.width, element.y + element.height),
center, );
radians(element.angle), // To emulate a rotated rectangle we rotate the point in the inverse angle
), // instead. It's all the same distance-wise.
const rotatedPoint = pointRotateRads(
p,
point(element.x + element.width / 2, element.y + element.height / 2),
radians(-element.angle),
); );
const roundness = getCornerRadius( const roundness = getCornerRadius(
Math.min(element.width, element.height), Math.min(element.width, element.height),
element, element,
); );
const rotatedPoint = pointRotateRads(p, center, element.angle);
const sideDistances = [ const sideDistances = [
segment( segment(
point(r[0][0] + roundness, r[0][1]), point(r[0][0] + roundness, r[0][1]),
@ -116,10 +118,22 @@ export const distanceToRectangleElement = (
return Math.min(...[...sideDistances, ...cornerDistances]); return Math.min(...[...sideDistances, ...cornerDistances]);
}; };
const roundedCutoffSegment = ( /**
* Shortens a segment on both ends to accomodate the arc in the rounded
* diamond shape
*
* @param s The segment to shorten
* @param r The radius to shorten by
* @returns The segment shortened on both ends by the same radius
*/
const createDiamondSide = (
s: Segment<GlobalPoint>, s: Segment<GlobalPoint>,
r: number, r: number,
): Segment<GlobalPoint> => { ): Segment<GlobalPoint> => {
if (r === 0) {
return s;
}
const t = (4 * r) / Math.sqrt(2); const t = (4 * r) / Math.sqrt(2);
return segment( return segment(
@ -128,17 +142,36 @@ const roundedCutoffSegment = (
); );
}; };
const diamondArc = (left: GlobalPoint, right: GlobalPoint, r: number) => { /**
const c = point((left[0] + right[0]) / 2, left[1]); * Creates an arc for the given roundness and position by taking the start
* and end positions and determining the angle points on the hypotethical
* circle with center point between start and end and raidus equals provided
* roundness. I.e. the created arc is gobal point-aware, or "rotated" in-place.
*
* @param start
* @param end
* @param r
* @returns
*/
const createDiamondArc = (start: GlobalPoint, end: GlobalPoint, r: number) => {
const c = point((start[0] + end[0]) / 2, start[1]);
return arc( return arc(
c, c,
r, r,
radians(Math.asin((left[1] - c[1]) / r)), radians(Math.asin((start[1] - c[1]) / r)),
radians(Math.asin((right[1] - c[1]) / r)), radians(Math.asin((end[1] - c[1]) / r)),
); );
}; };
/**
* Returns the distance of a point and the provided diamond element, accounting
* for roundness and rotation
*
* @param element The diamond element
* @param p The point to consider
* @returns The eucledian distance to the outline of the diamond
*/
export const distanceToDiamondElement = ( export const distanceToDiamondElement = (
element: ExcalidrawDiamondElement, element: ExcalidrawDiamondElement,
p: GlobalPoint, p: GlobalPoint,
@ -151,31 +184,19 @@ export const distanceToDiamondElement = (
Math.min(element.width, element.height), Math.min(element.width, element.height),
element, element,
); );
const rotatedPoint = pointRotateRads(p, center, element.angle); // Rotate the point to the inverse direction to simulate the rotated diamond
const top = pointRotateRads<GlobalPoint>( // points. It's all the same distance-wise.
const rotatedPoint = pointRotateRads(p, center, radians(-element.angle));
const [top, right, bottom, left]: GlobalPoint[] = [
point(element.x + element.width / 2, element.y), point(element.x + element.width / 2, element.y),
center,
element.angle,
);
const right = pointRotateRads<GlobalPoint>(
point(element.x + element.width, element.y + element.height / 2), point(element.x + element.width, element.y + element.height / 2),
center,
element.angle,
);
const bottom = pointRotateRads<GlobalPoint>(
point(element.x + element.width / 2, element.y + element.height), point(element.x + element.width / 2, element.y + element.height),
center,
element.angle,
);
const left = pointRotateRads<GlobalPoint>(
point(element.x, element.y + element.height / 2), point(element.x, element.y + element.height / 2),
center, ];
element.angle, const topRight = createDiamondSide(segment(top, right), roundness);
); const bottomRight = createDiamondSide(segment(right, bottom), roundness);
const topRight = roundedCutoffSegment(segment(top, right), roundness); const bottomLeft = createDiamondSide(segment(bottom, left), roundness);
const bottomRight = roundedCutoffSegment(segment(right, bottom), roundness); const topLeft = createDiamondSide(segment(left, top), roundness);
const bottomLeft = roundedCutoffSegment(segment(bottom, left), roundness);
const topLeft = roundedCutoffSegment(segment(left, top), roundness);
return Math.min( return Math.min(
...[ ...[
@ -184,16 +205,24 @@ export const distanceToDiamondElement = (
), ),
...(roundness > 0 ...(roundness > 0
? [ ? [
diamondArc(topLeft[1], topRight[0], roundness), createDiamondArc(topLeft[1], topRight[0], roundness),
diamondArc(topRight[1], bottomRight[0], roundness), createDiamondArc(topRight[1], bottomRight[0], roundness),
diamondArc(bottomRight[1], bottomLeft[0], roundness), createDiamondArc(bottomRight[1], bottomLeft[0], roundness),
diamondArc(bottomLeft[1], topLeft[0], roundness), createDiamondArc(bottomLeft[1], topLeft[0], roundness),
].map((a) => arcDistanceFromPoint(a, rotatedPoint)) ].map((a) => arcDistanceFromPoint(a, rotatedPoint))
: []), : []),
], ],
); );
}; };
/**
* Returns the distance of a point and the provided ellipse element, accounting
* for roundness and rotation
*
* @param element The ellipse element
* @param p The point to consider
* @returns The eucledian distance to the outline of the ellipse
*/
export const distanceToEllipseElement = ( export const distanceToEllipseElement = (
element: ExcalidrawEllipseElement, element: ExcalidrawEllipseElement,
p: GlobalPoint, p: GlobalPoint,
@ -203,6 +232,7 @@ export const distanceToEllipseElement = (
element.y + element.height / 2, element.y + element.height / 2,
); );
return ellipseDistanceFromPoint( return ellipseDistanceFromPoint(
// Instead of rotating the ellipse, rotate the point to the inverse angle
pointRotateRads(p, center, radians(-element.angle)), pointRotateRads(p, center, radians(-element.angle)),
ellipse(center, element.width / 2, element.height / 2), ellipse(center, element.width / 2, element.height / 2),
); );

View file

@ -1,9 +1,8 @@
import { updateBoundElements } from "./binding"; import { updateBoundElements } from "./binding";
import type { Bounds } from "./bounds";
import { getCommonBounds } from "./bounds"; import { getCommonBounds } from "./bounds";
import { mutateElement } from "./mutateElement"; import { mutateElement } from "./mutateElement";
import { getPerfectElementSize } from "./sizeHelpers"; import { getPerfectElementSize } from "./sizeHelpers";
import type { NonDeletedExcalidrawElement } from "./types"; import type { Bounds, NonDeletedExcalidrawElement } from "./types";
import type { import type {
AppState, AppState,
NormalizedZoomValue, NormalizedZoomValue,

View file

@ -13,8 +13,8 @@ import {
radiansToDegrees, radiansToDegrees,
triangleIncludesPoint, triangleIncludesPoint,
} from "../../math"; } from "../../math";
import { getCenterForBounds, type Bounds } from "./bounds"; import { getCenterForBounds } from "./bounds";
import type { ExcalidrawBindableElement } from "./types"; import type { Bounds, ExcalidrawBindableElement } from "./types";
export const HEADING_RIGHT = [1, 0] as Heading; export const HEADING_RIGHT = [1, 0] as Heading;
export const HEADING_DOWN = [0, 1] as Heading; export const HEADING_DOWN = [0, 1] as Heading;

View file

@ -19,9 +19,9 @@ export {
getElementAbsoluteCoords, getElementAbsoluteCoords,
getElementBounds, getElementBounds,
getCommonBounds, getCommonBounds,
getDiamondPoints,
getClosestElementBounds, getClosestElementBounds,
} from "./bounds"; } from "./bounds";
export { getDiamondPoints } from "../scene/Shape";
export { export {
OMIT_SIDES_FOR_MULTIPLE_ELEMENTS, OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,

View file

@ -10,9 +10,9 @@ import type {
OrderedExcalidrawElement, OrderedExcalidrawElement,
FixedPointBinding, FixedPointBinding,
SceneElementsMap, SceneElementsMap,
Bounds,
} from "./types"; } from "./types";
import { getElementAbsoluteCoords, getLockedLinearCursorAlignSize } from "."; import { getElementAbsoluteCoords, getLockedLinearCursorAlignSize } from ".";
import type { Bounds } from "./bounds";
import { getElementPointsCoords, getMinMaxXYFromCurvePathOps } from "./bounds"; import { getElementPointsCoords, getMinMaxXYFromCurvePathOps } from "./bounds";
import type { import type {
AppState, AppState,

View file

@ -3,6 +3,7 @@ import type {
PointerType, PointerType,
NonDeletedExcalidrawElement, NonDeletedExcalidrawElement,
ElementsMap, ElementsMap,
Bounds,
} from "./types"; } from "./types";
import type { import type {
@ -17,7 +18,6 @@ import {
canResizeFromSides, canResizeFromSides,
} from "./transformHandles"; } from "./transformHandles";
import type { AppState, Device, Zoom } from "../types"; import type { AppState, Device, Zoom } from "../types";
import type { Bounds } from "./bounds";
import { getElementAbsoluteCoords } from "./bounds"; import { getElementAbsoluteCoords } from "./bounds";
import { SIDE_RESIZING_THRESHOLD } from "../constants"; import { SIDE_RESIZING_THRESHOLD } from "../constants";
import { isLinearElement } from "./typeChecks"; import { isLinearElement } from "./typeChecks";

View file

@ -24,7 +24,6 @@ import {
getGlobalFixedPointForBindableElement, getGlobalFixedPointForBindableElement,
snapToMid, snapToMid,
} from "./binding"; } from "./binding";
import type { Bounds } from "./bounds";
import { distanceToBindableElement } from "./distance"; import { distanceToBindableElement } from "./distance";
import type { Heading } from "./heading"; import type { Heading } from "./heading";
import { import {
@ -40,6 +39,7 @@ import type { ElementUpdate } from "./mutateElement";
import { mutateElement } from "./mutateElement"; import { mutateElement } from "./mutateElement";
import { isBindableElement, isRectanguloidElement } from "./typeChecks"; import { isBindableElement, isRectanguloidElement } from "./typeChecks";
import type { import type {
Bounds,
ExcalidrawElbowArrowElement, ExcalidrawElbowArrowElement,
NonDeletedSceneElementsMap, NonDeletedSceneElementsMap,
SceneElementsMap, SceneElementsMap,

View file

@ -1,11 +1,11 @@
import type { import type {
Bounds,
ElementsMap, ElementsMap,
ExcalidrawElement, ExcalidrawElement,
NonDeletedExcalidrawElement, NonDeletedExcalidrawElement,
PointerType, PointerType,
} from "./types"; } from "./types";
import type { Bounds } from "./bounds";
import { getElementAbsoluteCoords } from "./bounds"; import { getElementAbsoluteCoords } from "./bounds";
import type { Device, InteractiveCanvasAppState, Zoom } from "../types"; import type { Device, InteractiveCanvasAppState, Zoom } from "../types";
import { import {

View file

@ -2,7 +2,6 @@ import { ROUNDNESS } from "../constants";
import type { ElementOrToolType } from "../types"; import type { ElementOrToolType } from "../types";
import type { MarkNonNullable } from "../utility-types"; import type { MarkNonNullable } from "../utility-types";
import { assertNever } from "../utils"; import { assertNever } from "../utils";
import type { Bounds } from "./bounds";
import type { import type {
ExcalidrawElement, ExcalidrawElement,
ExcalidrawTextElement, ExcalidrawTextElement,
@ -26,6 +25,7 @@ import type {
PointBinding, PointBinding,
FixedPointBinding, FixedPointBinding,
ExcalidrawFlowchartNodeElement, ExcalidrawFlowchartNodeElement,
Bounds,
} from "./types"; } from "./types";
export const isInitializedImageElement = ( export const isInitializedImageElement = (

View file

@ -373,3 +373,13 @@ export type NonDeletedSceneElementsMap = Map<
export type ElementsMapOrArray = export type ElementsMapOrArray =
| readonly ExcalidrawElement[] | readonly ExcalidrawElement[]
| Readonly<ElementsMap>; | Readonly<ElementsMap>;
/**
* Axis-aligned bounding box (i.e. no rotation)
*/
export type Bounds = readonly [
minX: number,
minY: number,
maxX: number,
maxY: number,
];

View file

@ -25,7 +25,7 @@ import type {
import { getElementsWithinSelection, getSelectedElements } from "./scene"; import { getElementsWithinSelection, getSelectedElements } from "./scene";
import { getElementsInGroup, selectGroupsFromGivenElements } from "./groups"; import { getElementsInGroup, selectGroupsFromGivenElements } from "./groups";
import type { ExcalidrawElementsIncludingDeleted } from "./scene/Scene"; import type { ExcalidrawElementsIncludingDeleted } from "./scene/Scene";
import { elementsOverlappingBBox } from "../utils/"; import { elementsOverlappingBounds } from "../utils/";
import { import {
isFrameElement, isFrameElement,
isFrameLikeElement, isFrameLikeElement,
@ -863,7 +863,7 @@ export const getElementsOverlappingFrame = (
frame: ExcalidrawFrameLikeElement, frame: ExcalidrawFrameLikeElement,
) => { ) => {
return ( return (
elementsOverlappingBBox({ elementsOverlappingBounds({
elements, elements,
bounds: frame, bounds: frame,
type: "overlap", type: "overlap",

View file

@ -284,9 +284,9 @@ export { convertToExcalidrawElements } from "./data/transform";
export { getCommonBounds, getVisibleSceneBounds } from "./element/bounds"; export { getCommonBounds, getVisibleSceneBounds } from "./element/bounds";
export { export {
elementsOverlappingBBox, elementsOverlappingBounds as elementsOverlappingBBox,
isElementInsideBBox, isElementInsideBBox,
elementPartiallyOverlapsWithOrContainsBBox, elementPartiallyOverlapsWithOrContainsBounds as elementPartiallyOverlapsWithOrContainsBBox,
} from "../utils/withinBounds"; } from "../utils/withinBounds";
export { DiagramToCodePlugin } from "./components/DiagramToCodePlugin/DiagramToCodePlugin"; export { DiagramToCodePlugin } from "./components/DiagramToCodePlugin/DiagramToCodePlugin";

View file

@ -1,7 +1,7 @@
import type { Point as RoughPoint } from "roughjs/bin/geometry"; import type { Point as RoughPoint } from "roughjs/bin/geometry";
import type { Drawable, Options } from "roughjs/bin/core"; import type { Drawable, Options } from "roughjs/bin/core";
import type { RoughGenerator } from "roughjs/bin/generator"; import type { RoughGenerator } from "roughjs/bin/generator";
import { getArrowheadPoints, getDiamondPoints } from "../element"; import { getArrowheadPoints } from "../element";
import type { ElementShapes } from "./types"; import type { ElementShapes } from "./types";
import type { import type {
ExcalidrawElement, ExcalidrawElement,
@ -278,6 +278,21 @@ const getArrowheadShapes = (
} }
}; };
export const getDiamondPoints = (element: ExcalidrawElement) => {
// Here we add +1 to avoid these numbers to be 0
// otherwise rough.js will throw an error complaining about it
const topX = Math.floor(element.width / 2) + 1;
const topY = 0;
const rightX = element.width;
const rightY = Math.floor(element.height / 2) + 1;
const bottomX = topX;
const bottomY = element.height;
const leftX = 0;
const leftY = rightY;
return [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY];
};
/** /**
* Generates the roughjs shape for given element. * Generates the roughjs shape for given element.
* *

View file

@ -1,12 +1,12 @@
import rough from "roughjs/bin/rough"; import rough from "roughjs/bin/rough";
import type { import type {
Bounds,
ExcalidrawElement, ExcalidrawElement,
ExcalidrawFrameLikeElement, ExcalidrawFrameLikeElement,
ExcalidrawTextElement, ExcalidrawTextElement,
NonDeletedExcalidrawElement, NonDeletedExcalidrawElement,
NonDeletedSceneElementsMap, NonDeletedSceneElementsMap,
} from "../element/types"; } from "../element/types";
import type { Bounds } from "../element/bounds";
import { getCommonBounds, getElementAbsoluteCoords } from "../element/bounds"; import { getCommonBounds, getElementAbsoluteCoords } from "../element/bounds";
import { renderSceneToSvg } from "../renderer/staticSvgScene"; import { renderSceneToSvg } from "../renderer/staticSvgScene";
import { arrayToMap, getFontString, toBrandedType } from "../utils"; import { arrayToMap, getFontString, toBrandedType } from "../utils";

View file

@ -36,11 +36,11 @@ import {
ROUNDNESS, ROUNDNESS,
} from "./constants"; } from "./constants";
import { getElementAbsoluteCoords } from "./element"; import { getElementAbsoluteCoords } from "./element";
import type { Bounds } from "./element/bounds";
import { shouldTestInside } from "./element/collision"; import { shouldTestInside } from "./element/collision";
import { LinearElementEditor } from "./element/linearElementEditor"; import { LinearElementEditor } from "./element/linearElementEditor";
import { getBoundTextElement } from "./element/textElement"; import { getBoundTextElement } from "./element/textElement";
import type { import type {
Bounds,
ElementsMap, ElementsMap,
ExcalidrawElement, ExcalidrawElement,
ExcalidrawLinearElement, ExcalidrawLinearElement,

View file

@ -8,7 +8,6 @@ import {
type GlobalPoint, type GlobalPoint,
} from "../math"; } from "../math";
import { TOOL_TYPE } from "./constants"; import { TOOL_TYPE } from "./constants";
import type { Bounds } from "./element/bounds";
import { import {
getCommonBounds, getCommonBounds,
getDraggedElementsBounds, getDraggedElementsBounds,
@ -17,6 +16,7 @@ import {
import type { MaybeTransformHandleType } from "./element/transformHandles"; import type { MaybeTransformHandleType } from "./element/transformHandles";
import { isBoundToContainer, isFrameLikeElement } from "./element/typeChecks"; import { isBoundToContainer, isFrameLikeElement } from "./element/typeChecks";
import type { import type {
Bounds,
ElementsMap, ElementsMap,
ExcalidrawElement, ExcalidrawElement,
NonDeletedExcalidrawElement, NonDeletedExcalidrawElement,

View file

@ -4,11 +4,11 @@ import { render } from "./test-utils";
import { reseed } from "../random"; import { reseed } from "../random";
import { UI, Keyboard, Pointer } from "./helpers/ui"; import { UI, Keyboard, Pointer } from "./helpers/ui";
import type { import type {
Bounds,
ExcalidrawElbowArrowElement, ExcalidrawElbowArrowElement,
ExcalidrawFreeDrawElement, ExcalidrawFreeDrawElement,
ExcalidrawLinearElement, ExcalidrawLinearElement,
} from "../element/types"; } from "../element/types";
import type { Bounds } from "../element/bounds";
import { getElementPointsCoords } from "../element/bounds"; import { getElementPointsCoords } from "../element/bounds";
import { Excalidraw } from "../index"; import { Excalidraw } from "../index";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
@ -895,7 +895,7 @@ describe("multiple selection", () => {
expect(rightBoundArrow.height).toBeCloseTo(0); expect(rightBoundArrow.height).toBeCloseTo(0);
expect(rightBoundArrow.angle).toEqual(0); expect(rightBoundArrow.angle).toEqual(0);
expect(rightBoundArrow.startBinding).toBeNull(); expect(rightBoundArrow.startBinding).toBeNull();
expect(rightBoundArrow.endBinding?.gap).toBeCloseTo(8.0952); expect(rightBoundArrow.endBinding?.gap).toBeCloseTo(7.0952);
expect(rightBoundArrow.endBinding?.elementId).toBe( expect(rightBoundArrow.endBinding?.elementId).toBe(
rightArrowBinding.elementId, rightArrowBinding.elementId,
); );

View file

@ -1,7 +1,7 @@
import type { Segment } from "../math"; import type { Segment } from "../math";
import { isSegment, segment, point, type GlobalPoint } from "../math"; import { isSegment, segment, point, type GlobalPoint } from "../math";
import type { Bounds } from "./element/bounds";
import { isBounds } from "./element/typeChecks"; import { isBounds } from "./element/typeChecks";
import type { Bounds } from "./element/types";
// The global data holder to collect the debug operations // The global data holder to collect the debug operations
declare global { declare global {

View file

@ -102,6 +102,22 @@ export function pointRotateRads<Point extends GenericPoint>(
); );
} }
/**
* Rotate multiple points around a common center via the same angle in radians
*
* @param p The point array to rotate
* @param c The common center point
* @param angle The common angle to rotate by
* @returns The array of rotated points
*/
function pointsRotateRads<Point extends GenericPoint>(
p: Point[],
c: Point,
angle: Radians,
): Point[] {
return p.map((x, idx) => pointRotateRads(x, c, angle));
}
/** /**
* Roate a point by [angle] degree. * Roate a point by [angle] degree.
* *

View file

@ -1,8 +1,8 @@
import type { Bounds } from "../excalidraw/element/bounds"; import type { Bounds } from "../excalidraw/element/types";
import { API } from "../excalidraw/tests/helpers/api"; import { API } from "../excalidraw/tests/helpers/api";
import { import {
elementPartiallyOverlapsWithOrContainsBBox, elementPartiallyOverlapsWithOrContainsBounds,
elementsOverlappingBBox, elementsOverlappingBounds,
isElementInsideBBox, isElementInsideBBox,
} from "./withinBounds"; } from "./withinBounds";
@ -99,13 +99,13 @@ describe("elementPartiallyOverlapsWithOrContainsBBox()", () => {
// bbox contains element // bbox contains element
expect( expect(
elementPartiallyOverlapsWithOrContainsBBox( elementPartiallyOverlapsWithOrContainsBounds(
makeElement(0, 0, 100, 100), makeElement(0, 0, 100, 100),
bbox, bbox,
), ),
).toBe(true); ).toBe(true);
expect( expect(
elementPartiallyOverlapsWithOrContainsBBox( elementPartiallyOverlapsWithOrContainsBounds(
makeElement(10, 10, 90, 90), makeElement(10, 10, 90, 90),
bbox, bbox,
), ),
@ -113,7 +113,7 @@ describe("elementPartiallyOverlapsWithOrContainsBBox()", () => {
// element contains bbox // element contains bbox
expect( expect(
elementPartiallyOverlapsWithOrContainsBBox( elementPartiallyOverlapsWithOrContainsBounds(
makeElement(-10, -10, 110, 110), makeElement(-10, -10, 110, 110),
bbox, bbox,
), ),
@ -121,28 +121,28 @@ describe("elementPartiallyOverlapsWithOrContainsBBox()", () => {
// element overlaps bbox from top-left // element overlaps bbox from top-left
expect( expect(
elementPartiallyOverlapsWithOrContainsBBox( elementPartiallyOverlapsWithOrContainsBounds(
makeElement(-10, -10, 100, 100), makeElement(-10, -10, 100, 100),
bbox, bbox,
), ),
).toBe(true); ).toBe(true);
// element overlaps bbox from top-right // element overlaps bbox from top-right
expect( expect(
elementPartiallyOverlapsWithOrContainsBBox( elementPartiallyOverlapsWithOrContainsBounds(
makeElement(90, -10, 100, 100), makeElement(90, -10, 100, 100),
bbox, bbox,
), ),
).toBe(true); ).toBe(true);
// element overlaps bbox from bottom-left // element overlaps bbox from bottom-left
expect( expect(
elementPartiallyOverlapsWithOrContainsBBox( elementPartiallyOverlapsWithOrContainsBounds(
makeElement(-10, 90, 100, 100), makeElement(-10, 90, 100, 100),
bbox, bbox,
), ),
).toBe(true); ).toBe(true);
// element overlaps bbox from bottom-right // element overlaps bbox from bottom-right
expect( expect(
elementPartiallyOverlapsWithOrContainsBBox( elementPartiallyOverlapsWithOrContainsBounds(
makeElement(90, 90, 100, 100), makeElement(90, 90, 100, 100),
bbox, bbox,
), ),
@ -154,7 +154,7 @@ describe("elementPartiallyOverlapsWithOrContainsBBox()", () => {
// outside diagonally // outside diagonally
expect( expect(
elementPartiallyOverlapsWithOrContainsBBox( elementPartiallyOverlapsWithOrContainsBounds(
makeElement(110, 110, 100, 100), makeElement(110, 110, 100, 100),
bbox, bbox,
), ),
@ -162,28 +162,28 @@ describe("elementPartiallyOverlapsWithOrContainsBBox()", () => {
// outside on the left // outside on the left
expect( expect(
elementPartiallyOverlapsWithOrContainsBBox( elementPartiallyOverlapsWithOrContainsBounds(
makeElement(-110, 10, 50, 50), makeElement(-110, 10, 50, 50),
bbox, bbox,
), ),
).toBe(false); ).toBe(false);
// outside on the right // outside on the right
expect( expect(
elementPartiallyOverlapsWithOrContainsBBox( elementPartiallyOverlapsWithOrContainsBounds(
makeElement(110, 10, 50, 50), makeElement(110, 10, 50, 50),
bbox, bbox,
), ),
).toBe(false); ).toBe(false);
// outside on the top // outside on the top
expect( expect(
elementPartiallyOverlapsWithOrContainsBBox( elementPartiallyOverlapsWithOrContainsBounds(
makeElement(10, -110, 50, 50), makeElement(10, -110, 50, 50),
bbox, bbox,
), ),
).toBe(false); ).toBe(false);
// outside on the bottom // outside on the bottom
expect( expect(
elementPartiallyOverlapsWithOrContainsBBox( elementPartiallyOverlapsWithOrContainsBounds(
makeElement(10, 110, 50, 50), makeElement(10, 110, 50, 50),
bbox, bbox,
), ),
@ -201,7 +201,7 @@ describe("elementsOverlappingBBox()", () => {
const rectOverlappingTopLeft = makeElement(-10, -10, 50, 50); const rectOverlappingTopLeft = makeElement(-10, -10, 50, 50);
expect( expect(
elementsOverlappingBBox({ elementsOverlappingBounds({
bounds: bbox, bounds: bbox,
type: "overlap", type: "overlap",
elements: [ elements: [
@ -223,7 +223,7 @@ describe("elementsOverlappingBBox()", () => {
const rectOverlappingTopLeft = makeElement(-10, -10, 50, 50); const rectOverlappingTopLeft = makeElement(-10, -10, 50, 50);
expect( expect(
elementsOverlappingBBox({ elementsOverlappingBounds({
bounds: bbox, bounds: bbox,
type: "contain", type: "contain",
elements: [ elements: [
@ -245,7 +245,7 @@ describe("elementsOverlappingBBox()", () => {
const rectOverlappingTopLeft = makeElement(-10, -10, 50, 50); const rectOverlappingTopLeft = makeElement(-10, -10, 50, 50);
expect( expect(
elementsOverlappingBBox({ elementsOverlappingBounds({
bounds: bbox, bounds: bbox,
type: "inside", type: "inside",
elements: [ elements: [

View file

@ -1,4 +1,5 @@
import type { import type {
Bounds,
ExcalidrawElement, ExcalidrawElement,
ExcalidrawFreeDrawElement, ExcalidrawFreeDrawElement,
ExcalidrawLinearElement, ExcalidrawLinearElement,
@ -11,7 +12,6 @@ import {
isLinearElement, isLinearElement,
isTextElement, isTextElement,
} from "../excalidraw/element/typeChecks"; } from "../excalidraw/element/typeChecks";
import type { Bounds } from "../excalidraw/element/bounds";
import { getElementBounds } from "../excalidraw/element/bounds"; import { getElementBounds } from "../excalidraw/element/bounds";
import { arrayToMap } from "../excalidraw/utils"; import { arrayToMap } from "../excalidraw/utils";
import type { LocalPoint } from "../math"; import type { LocalPoint } from "../math";
@ -22,15 +22,10 @@ import {
rangeInclusive, rangeInclusive,
} from "../math"; } from "../math";
type Element = NonDeletedExcalidrawElement;
type Elements = readonly NonDeletedExcalidrawElement[];
type Points = readonly LocalPoint[];
/** @returns vertices relative to element's top-left [0,0] position */ /** @returns vertices relative to element's top-left [0,0] position */
const getNonLinearElementRelativePoints = ( const getNonLinearElementRelativePoints = (
element: Exclude< element: Exclude<
Element, NonDeletedExcalidrawElement,
ExcalidrawLinearElement | ExcalidrawFreeDrawElement ExcalidrawLinearElement | ExcalidrawFreeDrawElement
>, >,
): [ ): [
@ -56,14 +51,16 @@ const getNonLinearElementRelativePoints = (
}; };
/** @returns vertices relative to element's top-left [0,0] position */ /** @returns vertices relative to element's top-left [0,0] position */
const getElementRelativePoints = (element: ExcalidrawElement): Points => { const getElementRelativePoints = (
element: ExcalidrawElement,
): readonly LocalPoint[] => {
if (isLinearElement(element) || isFreeDrawElement(element)) { if (isLinearElement(element) || isFreeDrawElement(element)) {
return element.points; return element.points;
} }
return getNonLinearElementRelativePoints(element); return getNonLinearElementRelativePoints(element);
}; };
const getMinMaxPoints = (points: Points) => { const getMinMaxPoints = (points: readonly LocalPoint[]) => {
const ret = points.reduce( const ret = points.reduce(
(limits, [x, y]) => { (limits, [x, y]) => {
limits.minY = Math.min(limits.minY, y); limits.minY = Math.min(limits.minY, y);
@ -90,7 +87,7 @@ const getMinMaxPoints = (points: Points) => {
return ret; return ret;
}; };
const getRotatedBBox = (element: Element): Bounds => { const getRotatedBBox = (element: NonDeletedExcalidrawElement): Bounds => {
const points = getElementRelativePoints(element); const points = getElementRelativePoints(element);
const { cx, cy } = getMinMaxPoints(points); const { cx, cy } = getMinMaxPoints(points);
@ -110,7 +107,7 @@ const getRotatedBBox = (element: Element): Bounds => {
}; };
export const isElementInsideBBox = ( export const isElementInsideBBox = (
element: Element, element: NonDeletedExcalidrawElement,
bbox: Bounds, bbox: Bounds,
eitherDirection = false, eitherDirection = false,
): boolean => { ): boolean => {
@ -138,8 +135,8 @@ export const isElementInsideBBox = (
); );
}; };
export const elementPartiallyOverlapsWithOrContainsBBox = ( export const elementPartiallyOverlapsWithOrContainsBounds = (
element: Element, element: NonDeletedExcalidrawElement,
bbox: Bounds, bbox: Bounds,
): boolean => { ): boolean => {
const elementBBox = getRotatedBBox(element); const elementBBox = getRotatedBBox(element);
@ -158,13 +155,13 @@ export const elementPartiallyOverlapsWithOrContainsBBox = (
); );
}; };
export const elementsOverlappingBBox = ({ export const elementsOverlappingBounds = ({
elements, elements,
bounds, bounds,
type, type,
errorMargin = 0, errorMargin = 0,
}: { }: {
elements: Elements; elements: readonly NonDeletedExcalidrawElement[];
bounds: Bounds | ExcalidrawElement; bounds: Bounds | ExcalidrawElement;
/** safety offset. Defaults to 0. */ /** safety offset. Defaults to 0. */
errorMargin?: number; errorMargin?: number;
@ -194,7 +191,7 @@ export const elementsOverlappingBBox = ({
const isOverlaping = const isOverlaping =
type === "overlap" type === "overlap"
? elementPartiallyOverlapsWithOrContainsBBox(element, adjustedBBox) ? elementPartiallyOverlapsWithOrContainsBounds(element, adjustedBBox)
: type === "inside" : type === "inside"
? isElementInsideBBox(element, adjustedBBox) ? isElementInsideBBox(element, adjustedBBox)
: isElementInsideBBox(element, adjustedBBox, true); : isElementInsideBBox(element, adjustedBBox, true);