@tldraw/editor 5.4.0-next.ebc95cf01337 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjs/index.d.ts +67 -1
- package/dist-cjs/index.js +4 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TldrawEditor.js +7 -2
- package/dist-cjs/lib/TldrawEditor.js.map +2 -2
- package/dist-cjs/lib/components/MenuClickCapture.js +6 -8
- package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
- package/dist-cjs/lib/components/default-components/CanvasOverlays.js +1 -3
- package/dist-cjs/lib/components/default-components/CanvasOverlays.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +1 -1
- package/dist-cjs/lib/config/TLUserPreferences.js +6 -2
- package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
- package/dist-cjs/lib/config/createTLStore.js +1 -1
- package/dist-cjs/lib/config/createTLStore.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +164 -280
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +3 -3
- package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js +8 -8
- package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +2 -1
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +79 -6
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js +7 -0
- package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js +0 -2
- package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +1 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +6 -3
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +12 -1
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/editor/overlays/OverlayManager.js +3 -7
- package/dist-cjs/lib/editor/overlays/OverlayManager.js.map +2 -2
- package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js +0 -1
- package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js +3 -1
- package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js +1 -0
- package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +1 -1
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
- package/dist-cjs/lib/editor/tools/StateNode.js +3 -2
- package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
- package/dist-cjs/lib/globals/editors.js +57 -0
- package/dist-cjs/lib/globals/editors.js.map +7 -0
- package/dist-cjs/lib/globals/menus.js +9 -4
- package/dist-cjs/lib/globals/menus.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -0
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useDocumentEvents.js +39 -9
- package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useLocalStore.js +10 -6
- package/dist-cjs/lib/hooks/useLocalStore.js.map +2 -2
- package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +1 -0
- package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/usePeerIds.js +2 -1
- package/dist-cjs/lib/hooks/usePeerIds.js.map +2 -2
- package/dist-cjs/lib/license/LicenseManager.js +9 -2
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/license/LicenseProvider.js +16 -3
- package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
- package/dist-cjs/lib/license/useLicenseManagerState.js +5 -4
- package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
- package/dist-cjs/lib/primitives/Box.js +2 -1
- package/dist-cjs/lib/primitives/Box.js.map +2 -2
- package/dist-cjs/lib/primitives/Vec.js +4 -3
- package/dist-cjs/lib/primitives/Vec.js.map +2 -2
- package/dist-cjs/lib/primitives/utils.js +2 -1
- package/dist-cjs/lib/primitives/utils.js.map +2 -2
- package/dist-cjs/lib/utils/deepLinks.js +5 -1
- package/dist-cjs/lib/utils/deepLinks.js.map +2 -2
- package/dist-cjs/lib/utils/rotation.js +1 -9
- package/dist-cjs/lib/utils/rotation.js.map +2 -2
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +17 -0
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
- package/dist-cjs/lib/utils/sync/alerts.js +1 -1
- package/dist-cjs/lib/utils/sync/alerts.js.map +1 -1
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +67 -1
- package/dist-esm/index.mjs +9 -2
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawEditor.mjs +9 -4
- package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
- package/dist-esm/lib/components/MenuClickCapture.mjs +6 -8
- package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/CanvasOverlays.mjs +1 -3
- package/dist-esm/lib/components/default-components/CanvasOverlays.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +1 -1
- package/dist-esm/lib/config/TLUserPreferences.mjs +6 -2
- package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
- package/dist-esm/lib/config/createTLStore.mjs +1 -1
- package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +172 -281
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +3 -3
- package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs +9 -9
- package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +2 -1
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +81 -8
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs +7 -0
- package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs +0 -2
- package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +2 -3
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +6 -3
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +12 -1
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/editor/overlays/OverlayManager.mjs +3 -7
- package/dist-esm/lib/editor/overlays/OverlayManager.mjs.map +2 -2
- package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs +0 -1
- package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs +3 -1
- package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs +1 -0
- package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +1 -1
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/StateNode.mjs +3 -2
- package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
- package/dist-esm/lib/globals/editors.mjs +37 -0
- package/dist-esm/lib/globals/editors.mjs.map +7 -0
- package/dist-esm/lib/globals/menus.mjs +9 -4
- package/dist-esm/lib/globals/menus.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -0
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useDocumentEvents.mjs +40 -10
- package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useLocalStore.mjs +11 -7
- package/dist-esm/lib/hooks/useLocalStore.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +1 -0
- package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePeerIds.mjs +2 -1
- package/dist-esm/lib/hooks/usePeerIds.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseManager.mjs +9 -2
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseProvider.mjs +16 -3
- package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
- package/dist-esm/lib/license/useLicenseManagerState.mjs +5 -4
- package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
- package/dist-esm/lib/primitives/Box.mjs +2 -1
- package/dist-esm/lib/primitives/Box.mjs.map +2 -2
- package/dist-esm/lib/primitives/Vec.mjs +4 -3
- package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
- package/dist-esm/lib/primitives/utils.mjs +2 -1
- package/dist-esm/lib/primitives/utils.mjs.map +2 -2
- package/dist-esm/lib/utils/deepLinks.mjs +5 -1
- package/dist-esm/lib/utils/deepLinks.mjs.map +2 -2
- package/dist-esm/lib/utils/rotation.mjs +1 -9
- package/dist-esm/lib/utils/rotation.mjs.map +2 -2
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +17 -0
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
- package/dist-esm/lib/utils/sync/alerts.mjs +1 -1
- package/dist-esm/lib/utils/sync/alerts.mjs.map +1 -1
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +13 -7
- package/src/index.ts +6 -1
- package/src/lib/TldrawEditor.tsx +16 -4
- package/src/lib/components/MenuClickCapture.tsx +19 -18
- package/src/lib/components/default-components/CanvasOverlays.tsx +1 -3
- package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
- package/src/lib/config/TLUserPreferences.test.ts +18 -1
- package/src/lib/config/TLUserPreferences.ts +8 -4
- package/src/lib/config/createTLStore.ts +1 -1
- package/src/lib/editor/Editor.ts +241 -359
- package/src/lib/editor/managers/ClickManager/ClickManager.ts +5 -3
- package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.ts +9 -9
- package/src/lib/editor/managers/FontManager/FontManager.ts +2 -1
- package/src/lib/editor/managers/InputsManager/InputsManager.test.ts +199 -3
- package/src/lib/editor/managers/InputsManager/InputsManager.ts +128 -9
- package/src/lib/editor/managers/ScribbleManager/ScribbleManager.ts +12 -1
- package/src/lib/editor/managers/SnapManager/BoundsSnaps.ts +0 -2
- package/src/lib/editor/managers/TextManager/TextManager.ts +2 -6
- package/src/lib/editor/managers/TickManager/TickManager.test.ts +13 -0
- package/src/lib/editor/managers/TickManager/TickManager.ts +9 -3
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +64 -3
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +18 -1
- package/src/lib/editor/overlays/OverlayManager.ts +3 -7
- package/src/lib/editor/overlays/strokeShapeIndicators.ts +0 -1
- package/src/lib/editor/shapes/BaseFrameLikeShapeUtil.tsx +5 -1
- package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +3 -0
- package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +4 -1
- package/src/lib/editor/tools/StateNode.test.ts +64 -0
- package/src/lib/editor/tools/StateNode.ts +12 -2
- package/src/lib/globals/editors.test.ts +76 -0
- package/src/lib/globals/editors.ts +53 -0
- package/src/lib/globals/menus.test.ts +33 -0
- package/src/lib/globals/menus.ts +11 -4
- package/src/lib/hooks/useCanvasEvents.ts +2 -0
- package/src/lib/hooks/useDocumentEvents.ts +60 -16
- package/src/lib/hooks/useLocalStore.ts +16 -8
- package/src/lib/hooks/usePassThroughWheelEvents.ts +3 -0
- package/src/lib/hooks/usePeerIds.ts +2 -1
- package/src/lib/license/LicenseManager.test.ts +160 -0
- package/src/lib/license/LicenseManager.ts +13 -4
- package/src/lib/license/LicenseProvider.tsx +24 -3
- package/src/lib/license/useLicenseManagerState.ts +8 -6
- package/src/lib/primitives/Box.test.ts +9 -1
- package/src/lib/primitives/Box.ts +3 -1
- package/src/lib/primitives/Vec.test.ts +10 -0
- package/src/lib/primitives/Vec.ts +4 -4
- package/src/lib/primitives/geometry/Arc2d.test.ts +226 -3
- package/src/lib/primitives/geometry/CubicBezier2d.test.ts +173 -3
- package/src/lib/primitives/geometry/CubicSpline2d.test.ts +160 -3
- package/src/lib/primitives/geometry/Ellipse2d.test.ts +243 -3
- package/src/lib/primitives/geometry/Geometry2d.test.ts +553 -1
- package/src/lib/primitives/geometry/Group2d.test.ts +398 -3
- package/src/lib/primitives/geometry/Polyline2d.test.ts +207 -0
- package/src/lib/primitives/geometry/Stadium2d.test.ts +191 -3
- package/src/lib/primitives/utils.test.ts +10 -1
- package/src/lib/primitives/utils.ts +4 -1
- package/src/lib/test/useCanvasEvents.test.tsx +74 -0
- package/src/lib/test/useDocumentEvents.test.tsx +107 -0
- package/src/lib/test/usePassThroughWheelEvents.test.tsx +25 -2
- package/src/lib/utils/deepLinks.test.ts +15 -0
- package/src/lib/utils/deepLinks.ts +7 -1
- package/src/lib/utils/rotation.ts +4 -12
- package/src/lib/utils/sync/TLLocalSyncClient.test.ts +56 -0
- package/src/lib/utils/sync/TLLocalSyncClient.ts +24 -0
- package/src/lib/utils/sync/alerts.ts +1 -1
- package/src/version.ts +3 -3
- package/src/lib/primitives/geometry/Polyline.test.ts +0 -5
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { Mat } from '../Mat'
|
|
2
2
|
import { Vec, VecLike } from '../Vec'
|
|
3
3
|
import { Edge2d } from './Edge2d'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
Geometry2d,
|
|
6
|
+
Geometry2dFilters,
|
|
7
|
+
Geometry2dOptions,
|
|
8
|
+
TransformedGeometry2d,
|
|
9
|
+
} from './Geometry2d'
|
|
5
10
|
import { Group2d } from './Group2d'
|
|
6
11
|
import { Rectangle2d } from './Rectangle2d'
|
|
7
12
|
|
|
@@ -481,3 +486,550 @@ function expectApproxMatch(a: VecLike, b: VecLike) {
|
|
|
481
486
|
expect(a.x).toBeCloseTo(b.x, 0.0001)
|
|
482
487
|
expect(a.y).toBeCloseTo(b.y, 0.0001)
|
|
483
488
|
}
|
|
489
|
+
|
|
490
|
+
// A minimal concrete geometry so the base class implementations can be exercised directly,
|
|
491
|
+
// without the overrides that the built-in shapes add.
|
|
492
|
+
class TestPolygon extends Geometry2d {
|
|
493
|
+
constructor(
|
|
494
|
+
private points: Vec[],
|
|
495
|
+
opts: Partial<Geometry2dOptions> = {}
|
|
496
|
+
) {
|
|
497
|
+
super({ isFilled: false, isClosed: true, ...opts })
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
getVertices() {
|
|
501
|
+
return this.points
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
nearestPoint(point: VecLike): Vec {
|
|
505
|
+
const { points } = this
|
|
506
|
+
if (points.length === 0) return new Vec(0, 0)
|
|
507
|
+
let nearest = points[0]
|
|
508
|
+
let dist = Infinity
|
|
509
|
+
const limit = this.isClosed ? points.length : points.length - 1
|
|
510
|
+
for (let i = 0; i < limit; i++) {
|
|
511
|
+
const p = Vec.NearestPointOnLineSegment(points[i], points[(i + 1) % points.length], point)
|
|
512
|
+
const d = Vec.Dist2(p, point)
|
|
513
|
+
if (d < dist) {
|
|
514
|
+
dist = d
|
|
515
|
+
nearest = p
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return nearest
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
getSvgPathData() {
|
|
522
|
+
return ''
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// A 100x100 square at the origin
|
|
527
|
+
const squarePoints = () => [new Vec(0, 0), new Vec(100, 0), new Vec(100, 100), new Vec(0, 100)]
|
|
528
|
+
const square = (opts: Partial<Geometry2dOptions> = {}) => new TestPolygon(squarePoints(), opts)
|
|
529
|
+
const filledSquare = () => square({ isFilled: true })
|
|
530
|
+
// An open L shape: along the top of the square and down its right side
|
|
531
|
+
const openL = (opts: Partial<Geometry2dOptions> = {}) =>
|
|
532
|
+
new TestPolygon([new Vec(0, 0), new Vec(100, 0), new Vec(100, 100)], { isClosed: false, ...opts })
|
|
533
|
+
|
|
534
|
+
function sortPoints(points: VecLike[]) {
|
|
535
|
+
const round = (n: number) => Math.round(n * 1e6) / 1e6
|
|
536
|
+
return [...points]
|
|
537
|
+
.map((p) => ({ x: round(p.x), y: round(p.y) }))
|
|
538
|
+
.sort((a, b) => a.x - b.x || a.y - b.y)
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
describe('Geometry2d construction', () => {
|
|
542
|
+
it('defaults the optional flags', () => {
|
|
543
|
+
expect(square()).toMatchObject({
|
|
544
|
+
isFilled: false,
|
|
545
|
+
isClosed: true,
|
|
546
|
+
isLabel: false,
|
|
547
|
+
isEmptyLabel: false,
|
|
548
|
+
isInternal: false,
|
|
549
|
+
excludeFromShapeBounds: false,
|
|
550
|
+
debugColor: undefined,
|
|
551
|
+
ignore: undefined,
|
|
552
|
+
})
|
|
553
|
+
})
|
|
554
|
+
|
|
555
|
+
it('keeps the provided flags', () => {
|
|
556
|
+
expect(
|
|
557
|
+
square({
|
|
558
|
+
isFilled: true,
|
|
559
|
+
isLabel: true,
|
|
560
|
+
isEmptyLabel: true,
|
|
561
|
+
isInternal: true,
|
|
562
|
+
excludeFromShapeBounds: true,
|
|
563
|
+
debugColor: 'red',
|
|
564
|
+
ignore: true,
|
|
565
|
+
})
|
|
566
|
+
).toMatchObject({
|
|
567
|
+
isFilled: true,
|
|
568
|
+
isLabel: true,
|
|
569
|
+
isEmptyLabel: true,
|
|
570
|
+
isInternal: true,
|
|
571
|
+
excludeFromShapeBounds: true,
|
|
572
|
+
debugColor: 'red',
|
|
573
|
+
ignore: true,
|
|
574
|
+
})
|
|
575
|
+
})
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
describe('Geometry2d.isExcludedByFilter', () => {
|
|
579
|
+
it('is never excluded without filters', () => {
|
|
580
|
+
expect(square({ isLabel: true }).isExcludedByFilter()).toBe(false)
|
|
581
|
+
expect(square({ isInternal: true }).isExcludedByFilter(undefined)).toBe(false)
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
it('excludes labels and internal geometry according to the filter', () => {
|
|
585
|
+
const plain = square()
|
|
586
|
+
const label = square({ isLabel: true })
|
|
587
|
+
const internal = square({ isInternal: true })
|
|
588
|
+
const both = square({ isLabel: true, isInternal: true })
|
|
589
|
+
const { INCLUDE_ALL, EXCLUDE_LABELS, EXCLUDE_INTERNAL, EXCLUDE_NON_STANDARD } =
|
|
590
|
+
Geometry2dFilters
|
|
591
|
+
|
|
592
|
+
expect([plain, label, internal, both].map((g) => g.isExcludedByFilter(INCLUDE_ALL))).toEqual([
|
|
593
|
+
false,
|
|
594
|
+
false,
|
|
595
|
+
false,
|
|
596
|
+
false,
|
|
597
|
+
])
|
|
598
|
+
expect([plain, label, internal, both].map((g) => g.isExcludedByFilter(EXCLUDE_LABELS))).toEqual(
|
|
599
|
+
[false, true, false, true]
|
|
600
|
+
)
|
|
601
|
+
expect(
|
|
602
|
+
[plain, label, internal, both].map((g) => g.isExcludedByFilter(EXCLUDE_INTERNAL))
|
|
603
|
+
).toEqual([false, false, true, true])
|
|
604
|
+
expect(
|
|
605
|
+
[plain, label, internal, both].map((g) => g.isExcludedByFilter(EXCLUDE_NON_STANDARD))
|
|
606
|
+
).toEqual([false, true, true, true])
|
|
607
|
+
})
|
|
608
|
+
})
|
|
609
|
+
|
|
610
|
+
describe('Geometry2d.hitTestPoint', () => {
|
|
611
|
+
it('hits the interior only when filled or hitInside', () => {
|
|
612
|
+
expect(filledSquare().hitTestPoint(new Vec(50, 50))).toBe(true)
|
|
613
|
+
expect(square().hitTestPoint(new Vec(50, 50))).toBe(false)
|
|
614
|
+
expect(square().hitTestPoint(new Vec(50, 50), 0, true)).toBe(true)
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
it('hits the edge and respects the margin outside', () => {
|
|
618
|
+
expect(square().hitTestPoint(new Vec(100, 50))).toBe(true)
|
|
619
|
+
expect(square().hitTestPoint(new Vec(150, 50))).toBe(false)
|
|
620
|
+
expect(square().hitTestPoint(new Vec(150, 50), 50)).toBe(true)
|
|
621
|
+
expect(square().hitTestPoint(new Vec(150, 50), 49)).toBe(false)
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
it('respects the margin inside an unfilled shape', () => {
|
|
625
|
+
expect(square().hitTestPoint(new Vec(90, 50), 10)).toBe(true)
|
|
626
|
+
expect(square().hitTestPoint(new Vec(90, 50), 9)).toBe(false)
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
it('ignores hitInside for open geometry', () => {
|
|
630
|
+
expect(openL().hitTestPoint(new Vec(90, 10), 0, true)).toBe(false)
|
|
631
|
+
expect(openL({ isFilled: true }).hitTestPoint(new Vec(90, 10))).toBe(false)
|
|
632
|
+
})
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
describe('Geometry2d.distanceToPoint', () => {
|
|
636
|
+
it('is negative inside only when filled or hitInside', () => {
|
|
637
|
+
expect(filledSquare().distanceToPoint(new Vec(50, 50))).toBe(-50)
|
|
638
|
+
expect(square().distanceToPoint(new Vec(50, 50))).toBe(50)
|
|
639
|
+
expect(square().distanceToPoint(new Vec(50, 50), true)).toBe(-50)
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
it('is positive outside', () => {
|
|
643
|
+
expect(filledSquare().distanceToPoint(new Vec(150, 50))).toBe(50)
|
|
644
|
+
expect(square().distanceToPoint(new Vec(-3, -4))).toBe(5)
|
|
645
|
+
})
|
|
646
|
+
|
|
647
|
+
it('is never negative for open geometry', () => {
|
|
648
|
+
expect(openL({ isFilled: true }).distanceToPoint(new Vec(90, 10), true)).toBe(10)
|
|
649
|
+
})
|
|
650
|
+
})
|
|
651
|
+
|
|
652
|
+
describe('Geometry2d.distanceToLineSegment', () => {
|
|
653
|
+
it('falls back to distanceToPoint for a degenerate segment', () => {
|
|
654
|
+
expect(square().distanceToLineSegment(new Vec(150, 50), new Vec(150, 50))).toBe(50)
|
|
655
|
+
expect(filledSquare().distanceToLineSegment(new Vec(50, 50), new Vec(50, 50))).toBe(-50)
|
|
656
|
+
})
|
|
657
|
+
|
|
658
|
+
it('is zero when the segment crosses an edge', () => {
|
|
659
|
+
expect(square().distanceToLineSegment(new Vec(50, -10), new Vec(50, 110))).toBe(0)
|
|
660
|
+
expect(openL().distanceToLineSegment(new Vec(50, -10), new Vec(50, 10))).toBe(0)
|
|
661
|
+
})
|
|
662
|
+
|
|
663
|
+
it('does not count the closing segment of open geometry', () => {
|
|
664
|
+
// the segment crosses the line from (100, 100) back to (0, 0)
|
|
665
|
+
const closedL = new TestPolygon([new Vec(0, 0), new Vec(100, 0), new Vec(100, 100)])
|
|
666
|
+
expect(closedL.distanceToLineSegment(new Vec(20, 30), new Vec(30, 20))).toBe(0)
|
|
667
|
+
expect(openL().distanceToLineSegment(new Vec(20, 30), new Vec(30, 20))).toBeGreaterThan(0)
|
|
668
|
+
})
|
|
669
|
+
|
|
670
|
+
it('measures from the nearest vertex to the segment', () => {
|
|
671
|
+
expect(square().distanceToLineSegment(new Vec(150, -50), new Vec(150, 50))).toBe(50)
|
|
672
|
+
})
|
|
673
|
+
|
|
674
|
+
// Locks in current behaviour, see #10556.
|
|
675
|
+
it('measures from the polygon vertices, not its edges', () => {
|
|
676
|
+
// the segment is 5 units above the middle of the top edge, but 50.25 from the nearest vertex
|
|
677
|
+
expect(square().distanceToLineSegment(new Vec(50, -10), new Vec(50, -5))).toBeCloseTo(
|
|
678
|
+
Math.hypot(50, 5)
|
|
679
|
+
)
|
|
680
|
+
})
|
|
681
|
+
|
|
682
|
+
it('is negative when the nearest point lies inside a filled shape', () => {
|
|
683
|
+
const d = square().distanceToLineSegment(new Vec(40, 50), new Vec(60, 50))
|
|
684
|
+
expect(d).toBeCloseTo(Math.hypot(40, 50))
|
|
685
|
+
expect(filledSquare().distanceToLineSegment(new Vec(40, 50), new Vec(60, 50))).toBe(-d)
|
|
686
|
+
})
|
|
687
|
+
|
|
688
|
+
it('handles degenerate vertex lists', () => {
|
|
689
|
+
expect(() => new TestPolygon([]).distanceToLineSegment(new Vec(0, 0), new Vec(10, 0))).toThrow(
|
|
690
|
+
'nearest point not found'
|
|
691
|
+
)
|
|
692
|
+
expect(
|
|
693
|
+
new TestPolygon([new Vec(3, 4)]).distanceToLineSegment(new Vec(0, 0), new Vec(10, 0))
|
|
694
|
+
).toBe(5)
|
|
695
|
+
})
|
|
696
|
+
})
|
|
697
|
+
|
|
698
|
+
describe('Geometry2d.hitTestLineSegment', () => {
|
|
699
|
+
it('hits when the segment is within the distance', () => {
|
|
700
|
+
expect(square().hitTestLineSegment(new Vec(50, -10), new Vec(50, 110))).toBe(true)
|
|
701
|
+
expect(square().hitTestLineSegment(new Vec(150, -50), new Vec(150, 50))).toBe(false)
|
|
702
|
+
expect(square().hitTestLineSegment(new Vec(150, -50), new Vec(150, 50), 50)).toBe(true)
|
|
703
|
+
expect(square().hitTestLineSegment(new Vec(150, -50), new Vec(150, 50), 49)).toBe(false)
|
|
704
|
+
})
|
|
705
|
+
})
|
|
706
|
+
|
|
707
|
+
describe('Geometry2d.intersectLineSegment', () => {
|
|
708
|
+
it('intersects closed geometry as a polygon', () => {
|
|
709
|
+
expect(sortPoints(square().intersectLineSegment(new Vec(-10, 50), new Vec(110, 50)))).toEqual([
|
|
710
|
+
{ x: 0, y: 50 },
|
|
711
|
+
{ x: 100, y: 50 },
|
|
712
|
+
])
|
|
713
|
+
})
|
|
714
|
+
|
|
715
|
+
it('intersects open geometry as a polyline', () => {
|
|
716
|
+
expect(sortPoints(openL().intersectLineSegment(new Vec(-10, 50), new Vec(110, 50)))).toEqual([
|
|
717
|
+
{ x: 100, y: 50 },
|
|
718
|
+
])
|
|
719
|
+
})
|
|
720
|
+
|
|
721
|
+
it('returns an empty array when there is no intersection', () => {
|
|
722
|
+
expect(square().intersectLineSegment(new Vec(150, 0), new Vec(150, 100))).toEqual([])
|
|
723
|
+
})
|
|
724
|
+
})
|
|
725
|
+
|
|
726
|
+
describe('Geometry2d.intersectCircle', () => {
|
|
727
|
+
it('intersects closed geometry as a polygon', () => {
|
|
728
|
+
expect(sortPoints(square().intersectCircle(new Vec(0, 50), 10))).toEqual([
|
|
729
|
+
{ x: 0, y: 40 },
|
|
730
|
+
{ x: 0, y: 60 },
|
|
731
|
+
])
|
|
732
|
+
})
|
|
733
|
+
|
|
734
|
+
it('intersects open geometry as a polyline', () => {
|
|
735
|
+
expect(openL().intersectCircle(new Vec(0, 50), 10)).toEqual([])
|
|
736
|
+
expect(sortPoints(openL().intersectCircle(new Vec(100, 50), 10))).toEqual([
|
|
737
|
+
{ x: 100, y: 40 },
|
|
738
|
+
{ x: 100, y: 60 },
|
|
739
|
+
])
|
|
740
|
+
})
|
|
741
|
+
|
|
742
|
+
it('returns an empty array when there is no intersection', () => {
|
|
743
|
+
expect(square().intersectCircle(new Vec(50, 50), 10)).toEqual([])
|
|
744
|
+
})
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
describe('Geometry2d.intersectPolygon and intersectPolyline', () => {
|
|
748
|
+
const strip = [new Vec(40, -10), new Vec(60, -10), new Vec(60, 110), new Vec(40, 110)]
|
|
749
|
+
|
|
750
|
+
it('finds where a polygon crosses the edges', () => {
|
|
751
|
+
expect(sortPoints(square().intersectPolygon(strip))).toEqual([
|
|
752
|
+
{ x: 40, y: 0 },
|
|
753
|
+
{ x: 40, y: 100 },
|
|
754
|
+
{ x: 60, y: 0 },
|
|
755
|
+
{ x: 60, y: 100 },
|
|
756
|
+
])
|
|
757
|
+
expect(sortPoints(openL().intersectPolygon(strip))).toEqual([
|
|
758
|
+
{ x: 40, y: 0 },
|
|
759
|
+
{ x: 60, y: 0 },
|
|
760
|
+
])
|
|
761
|
+
})
|
|
762
|
+
|
|
763
|
+
it('finds where a polyline crosses the edges, without closing it', () => {
|
|
764
|
+
// only the x = 60 side of the strip is a polyline segment; x = 40 would be the closing side
|
|
765
|
+
expect(sortPoints(square().intersectPolyline(strip))).toEqual([
|
|
766
|
+
{ x: 60, y: 0 },
|
|
767
|
+
{ x: 60, y: 100 },
|
|
768
|
+
])
|
|
769
|
+
expect(sortPoints(square().intersectPolyline([new Vec(40, -10), new Vec(40, 110)]))).toEqual([
|
|
770
|
+
{ x: 40, y: 0 },
|
|
771
|
+
{ x: 40, y: 100 },
|
|
772
|
+
])
|
|
773
|
+
})
|
|
774
|
+
|
|
775
|
+
it('returns an empty array when there is no intersection', () => {
|
|
776
|
+
const far = [new Vec(200, 0), new Vec(300, 0), new Vec(300, 100)]
|
|
777
|
+
expect(square().intersectPolygon(far)).toEqual([])
|
|
778
|
+
expect(square().intersectPolyline(far)).toEqual([])
|
|
779
|
+
})
|
|
780
|
+
})
|
|
781
|
+
|
|
782
|
+
describe('Geometry2d.interpolateAlongEdge', () => {
|
|
783
|
+
it('walks around closed geometry and back to the start', () => {
|
|
784
|
+
const s = square()
|
|
785
|
+
expect(s.interpolateAlongEdge(0)).toMatchObject({ x: 0, y: 0 })
|
|
786
|
+
expect(s.interpolateAlongEdge(0.125)).toMatchObject({ x: 50, y: 0 })
|
|
787
|
+
expect(s.interpolateAlongEdge(0.5)).toMatchObject({ x: 100, y: 100 })
|
|
788
|
+
expect(s.interpolateAlongEdge(0.875)).toMatchObject({ x: 0, y: 50 })
|
|
789
|
+
expect(s.interpolateAlongEdge(1)).toMatchObject({ x: 0, y: 0 })
|
|
790
|
+
})
|
|
791
|
+
|
|
792
|
+
it('stops at the last vertex of open geometry', () => {
|
|
793
|
+
const l = openL()
|
|
794
|
+
expect(l.interpolateAlongEdge(0.25)).toMatchObject({ x: 50, y: 0 })
|
|
795
|
+
expect(l.interpolateAlongEdge(1)).toMatchObject({ x: 100, y: 100 })
|
|
796
|
+
expect(l.interpolateAlongEdge(2)).toMatchObject({ x: 100, y: 100 })
|
|
797
|
+
})
|
|
798
|
+
|
|
799
|
+
it('clamps negative values to the first vertex', () => {
|
|
800
|
+
expect(square().interpolateAlongEdge(-1)).toMatchObject({ x: 0, y: 0 })
|
|
801
|
+
})
|
|
802
|
+
|
|
803
|
+
it('handles degenerate vertex lists', () => {
|
|
804
|
+
expect(new TestPolygon([]).interpolateAlongEdge(0.5)).toMatchObject({ x: 0, y: 0 })
|
|
805
|
+
expect(new TestPolygon([new Vec(3, 4)]).interpolateAlongEdge(0.5)).toMatchObject({ x: 3, y: 4 })
|
|
806
|
+
})
|
|
807
|
+
})
|
|
808
|
+
|
|
809
|
+
describe('Geometry2d.uninterpolateAlongEdge', () => {
|
|
810
|
+
it('is the inverse of interpolateAlongEdge', () => {
|
|
811
|
+
const s = square()
|
|
812
|
+
expect(s.uninterpolateAlongEdge(new Vec(0, 0))).toBe(0)
|
|
813
|
+
expect(s.uninterpolateAlongEdge(new Vec(50, 0))).toBe(0.125)
|
|
814
|
+
expect(s.uninterpolateAlongEdge(new Vec(100, 50))).toBe(0.375)
|
|
815
|
+
expect(s.uninterpolateAlongEdge(new Vec(50, 100))).toBe(0.625)
|
|
816
|
+
expect(s.uninterpolateAlongEdge(new Vec(0, 50))).toBe(0.875)
|
|
817
|
+
})
|
|
818
|
+
|
|
819
|
+
it('snaps points off the edge to the nearest segment', () => {
|
|
820
|
+
expect(square().uninterpolateAlongEdge(new Vec(50, -20))).toBe(0.125)
|
|
821
|
+
expect(openL().uninterpolateAlongEdge(new Vec(120, 50))).toBe(0.75)
|
|
822
|
+
})
|
|
823
|
+
|
|
824
|
+
it('handles degenerate vertex lists', () => {
|
|
825
|
+
expect(new TestPolygon([]).uninterpolateAlongEdge(new Vec(1, 1))).toBe(0)
|
|
826
|
+
expect(new TestPolygon([new Vec(3, 4)]).uninterpolateAlongEdge(new Vec(1, 1))).toBe(0)
|
|
827
|
+
})
|
|
828
|
+
})
|
|
829
|
+
|
|
830
|
+
describe('Geometry2d.overlapsPolygon', () => {
|
|
831
|
+
const inside = [new Vec(40, 40), new Vec(60, 40), new Vec(60, 60), new Vec(40, 60)]
|
|
832
|
+
const corner = [new Vec(10, 10), new Vec(20, 10), new Vec(20, 20), new Vec(10, 20)]
|
|
833
|
+
const strip = [new Vec(40, -10), new Vec(60, -10), new Vec(60, 110), new Vec(40, 110)]
|
|
834
|
+
const around = [new Vec(-10, -10), new Vec(110, -10), new Vec(110, 110), new Vec(-10, 110)]
|
|
835
|
+
const far = [new Vec(200, 0), new Vec(300, 0), new Vec(300, 100), new Vec(200, 100)]
|
|
836
|
+
|
|
837
|
+
it('overlaps when a vertex is inside the polygon', () => {
|
|
838
|
+
expect(square().overlapsPolygon(around)).toBe(true)
|
|
839
|
+
expect(openL().overlapsPolygon(around)).toBe(true)
|
|
840
|
+
})
|
|
841
|
+
|
|
842
|
+
it('overlaps a polygon inside a filled shape, but not inside a hollow one', () => {
|
|
843
|
+
expect(filledSquare().overlapsPolygon(inside)).toBe(true)
|
|
844
|
+
expect(filledSquare().overlapsPolygon(corner)).toBe(true)
|
|
845
|
+
expect(square().overlapsPolygon(inside)).toBe(false)
|
|
846
|
+
expect(square().overlapsPolygon(corner)).toBe(false)
|
|
847
|
+
})
|
|
848
|
+
|
|
849
|
+
it('overlaps when the edges cross', () => {
|
|
850
|
+
expect(square().overlapsPolygon(strip)).toBe(true)
|
|
851
|
+
expect(openL().overlapsPolygon(strip)).toBe(true)
|
|
852
|
+
})
|
|
853
|
+
|
|
854
|
+
it('does not overlap a polygon that is far away', () => {
|
|
855
|
+
expect(filledSquare().overlapsPolygon(far)).toBe(false)
|
|
856
|
+
expect(openL().overlapsPolygon(far)).toBe(false)
|
|
857
|
+
})
|
|
858
|
+
|
|
859
|
+
it('never overlaps for empty labels', () => {
|
|
860
|
+
expect(filledSquare().overlapsPolygon(around)).toBe(true)
|
|
861
|
+
expect(square({ isFilled: true, isEmptyLabel: true }).overlapsPolygon(around)).toBe(false)
|
|
862
|
+
})
|
|
863
|
+
|
|
864
|
+
it('accepts plain objects', () => {
|
|
865
|
+
expect(square().overlapsPolygon(around.map((v) => ({ x: v.x, y: v.y })))).toBe(true)
|
|
866
|
+
})
|
|
867
|
+
})
|
|
868
|
+
|
|
869
|
+
describe('Geometry2d.isPointInBounds', () => {
|
|
870
|
+
it('checks the bounds with an optional margin', () => {
|
|
871
|
+
expect(openL().isPointInBounds(new Vec(10, 90))).toBe(true)
|
|
872
|
+
expect(openL().isPointInBounds(new Vec(-5, 50))).toBe(false)
|
|
873
|
+
expect(openL().isPointInBounds(new Vec(-5, 50), 5)).toBe(true)
|
|
874
|
+
expect(openL().isPointInBounds(new Vec(105, 105), 5)).toBe(true)
|
|
875
|
+
expect(openL().isPointInBounds(new Vec(105, 106), 5)).toBe(false)
|
|
876
|
+
})
|
|
877
|
+
})
|
|
878
|
+
|
|
879
|
+
describe('Geometry2d measurements', () => {
|
|
880
|
+
it('derives the center from the bounds', () => {
|
|
881
|
+
expect(square().center).toMatchObject({ x: 50, y: 50 })
|
|
882
|
+
expect(new TestPolygon([new Vec(10, 20), new Vec(30, 60)]).center).toMatchObject({
|
|
883
|
+
x: 20,
|
|
884
|
+
y: 40,
|
|
885
|
+
})
|
|
886
|
+
})
|
|
887
|
+
|
|
888
|
+
it('computes a signed shoelace area for closed geometry', () => {
|
|
889
|
+
expect(square().area).toBe(10000)
|
|
890
|
+
expect(new TestPolygon(squarePoints().reverse()).area).toBe(-10000)
|
|
891
|
+
expect(new TestPolygon([new Vec(0, 0), new Vec(10, 0), new Vec(0, 10)]).area).toBe(50)
|
|
892
|
+
})
|
|
893
|
+
|
|
894
|
+
it('has zero area for open geometry', () => {
|
|
895
|
+
expect(openL().area).toBe(0)
|
|
896
|
+
expect(openL({ isFilled: true }).area).toBe(0)
|
|
897
|
+
})
|
|
898
|
+
|
|
899
|
+
it('caches the area', () => {
|
|
900
|
+
const s = square()
|
|
901
|
+
expect(s.area).toBe(10000)
|
|
902
|
+
s['points'].push(new Vec(-100, 50))
|
|
903
|
+
expect(s.area).toBe(10000)
|
|
904
|
+
})
|
|
905
|
+
|
|
906
|
+
it('computes the length, closing the path only for closed geometry', () => {
|
|
907
|
+
expect(square().length).toBe(400)
|
|
908
|
+
expect(openL().length).toBe(200)
|
|
909
|
+
expect(openL().getLength()).toBe(200)
|
|
910
|
+
expect(new TestPolygon([]).getLength()).toBe(0)
|
|
911
|
+
expect(new TestPolygon([new Vec(3, 4)]).getLength()).toBe(0)
|
|
912
|
+
})
|
|
913
|
+
})
|
|
914
|
+
|
|
915
|
+
describe('Geometry2d.toSimpleSvgPath', () => {
|
|
916
|
+
it('draws straight lines through the vertices', () => {
|
|
917
|
+
expect(square().toSimpleSvgPath()).toBe('M0,0L100,0L100,100L0,100Z')
|
|
918
|
+
expect(openL().toSimpleSvgPath()).toBe('M0,0L100,0L100,100')
|
|
919
|
+
expect(new TestPolygon([]).toSimpleSvgPath()).toBe('')
|
|
920
|
+
})
|
|
921
|
+
})
|
|
922
|
+
|
|
923
|
+
describe('Geometry2d.ignoreHit', () => {
|
|
924
|
+
it('never rejects hits by default', () => {
|
|
925
|
+
expect(square().ignoreHit(new Vec(50, 50))).toBe(false)
|
|
926
|
+
expect(square().ignoreHit(new Vec(500, 500))).toBe(false)
|
|
927
|
+
})
|
|
928
|
+
})
|
|
929
|
+
|
|
930
|
+
describe('TransformedGeometry2d delegation', () => {
|
|
931
|
+
// 100x50 rect translated to (50, 100) and scaled by 2: occupies (50, 100) to (250, 200)
|
|
932
|
+
const matrix = Mat.Translate(50, 100).scale(2, 2)
|
|
933
|
+
const rect = () => new Rectangle2d({ width: 100, height: 50, isFilled: true }).transform(matrix)
|
|
934
|
+
|
|
935
|
+
it('copies the flags of the wrapped geometry and applies the overrides', () => {
|
|
936
|
+
const inner = new Rectangle2d({
|
|
937
|
+
width: 100,
|
|
938
|
+
height: 50,
|
|
939
|
+
isFilled: true,
|
|
940
|
+
isLabel: true,
|
|
941
|
+
isInternal: true,
|
|
942
|
+
excludeFromShapeBounds: true,
|
|
943
|
+
})
|
|
944
|
+
expect(inner.transform(matrix)).toMatchObject({
|
|
945
|
+
isFilled: true,
|
|
946
|
+
isClosed: true,
|
|
947
|
+
isLabel: true,
|
|
948
|
+
isInternal: true,
|
|
949
|
+
excludeFromShapeBounds: true,
|
|
950
|
+
})
|
|
951
|
+
expect(
|
|
952
|
+
inner.transform(matrix, {
|
|
953
|
+
isLabel: false,
|
|
954
|
+
isInternal: false,
|
|
955
|
+
debugColor: 'red',
|
|
956
|
+
ignore: true,
|
|
957
|
+
})
|
|
958
|
+
).toMatchObject({ isLabel: false, isInternal: false, debugColor: 'red', ignore: true })
|
|
959
|
+
})
|
|
960
|
+
|
|
961
|
+
it('rejects non-uniform scaling', () => {
|
|
962
|
+
expect(() =>
|
|
963
|
+
new Rectangle2d({ width: 10, height: 10, isFilled: true }).transform(Mat.Scale(2, 3))
|
|
964
|
+
).toThrow('non-uniform scaling is not yet supported')
|
|
965
|
+
})
|
|
966
|
+
|
|
967
|
+
it('scales distances back into the outer space', () => {
|
|
968
|
+
expect(rect().distanceToPoint(new Vec(350, 150))).toBe(100)
|
|
969
|
+
expect(rect().distanceToPoint(new Vec(150, 150))).toBe(-50)
|
|
970
|
+
expect(rect().distanceToLineSegment(new Vec(350, 100), new Vec(350, 200))).toBe(100)
|
|
971
|
+
})
|
|
972
|
+
|
|
973
|
+
it('scales the hit test distance into the inner space', () => {
|
|
974
|
+
expect(rect().hitTestLineSegment(new Vec(350, 100), new Vec(350, 200), 100)).toBe(true)
|
|
975
|
+
expect(rect().hitTestLineSegment(new Vec(350, 100), new Vec(350, 200), 99)).toBe(false)
|
|
976
|
+
expect(rect().hitTestLineSegment(new Vec(150, 50), new Vec(150, 250))).toBe(true)
|
|
977
|
+
})
|
|
978
|
+
|
|
979
|
+
it('maps intersections back into the outer space', () => {
|
|
980
|
+
expect(sortPoints(rect().intersectLineSegment(new Vec(0, 150), new Vec(400, 150)))).toEqual([
|
|
981
|
+
{ x: 50, y: 150 },
|
|
982
|
+
{ x: 250, y: 150 },
|
|
983
|
+
])
|
|
984
|
+
expect(sortPoints(rect().intersectCircle(new Vec(50, 150), 20))).toEqual([
|
|
985
|
+
{ x: 50, y: 130 },
|
|
986
|
+
{ x: 50, y: 170 },
|
|
987
|
+
])
|
|
988
|
+
const polygon = [new Vec(0, 120), new Vec(100, 120), new Vec(100, 180), new Vec(0, 180)]
|
|
989
|
+
expect(sortPoints(rect().intersectPolygon(polygon))).toEqual([
|
|
990
|
+
{ x: 50, y: 120 },
|
|
991
|
+
{ x: 50, y: 180 },
|
|
992
|
+
])
|
|
993
|
+
expect(sortPoints(rect().intersectPolyline([new Vec(0, 120), new Vec(100, 120)]))).toEqual([
|
|
994
|
+
{ x: 50, y: 120 },
|
|
995
|
+
])
|
|
996
|
+
})
|
|
997
|
+
|
|
998
|
+
it('hit tests rotated geometry', () => {
|
|
999
|
+
// rotate the 100x50 rect a quarter turn about the origin: it now spans x in [-50, 0]
|
|
1000
|
+
const rotated = new Rectangle2d({ width: 100, height: 50, isFilled: true }).transform(
|
|
1001
|
+
Mat.Rotate(Math.PI / 2)
|
|
1002
|
+
)
|
|
1003
|
+
expect(rotated.hitTestPoint(new Vec(-25, 50))).toBe(true)
|
|
1004
|
+
expect(rotated.hitTestPoint(new Vec(25, 50))).toBe(false)
|
|
1005
|
+
expect(rotated.bounds.x).toBeCloseTo(-50)
|
|
1006
|
+
expect(rotated.bounds.w).toBeCloseTo(50)
|
|
1007
|
+
expect(rotated.bounds.h).toBeCloseTo(100)
|
|
1008
|
+
})
|
|
1009
|
+
|
|
1010
|
+
it('delegates ignoreHit in the inner space', () => {
|
|
1011
|
+
class RightHalfIgnored extends TestPolygon {
|
|
1012
|
+
override ignoreHit(point: VecLike) {
|
|
1013
|
+
return point.x > 50
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
const g = new RightHalfIgnored(squarePoints()).transform(matrix)
|
|
1017
|
+
expect(g.ignoreHit(new Vec(100, 150))).toBe(false)
|
|
1018
|
+
expect(g.ignoreHit(new Vec(200, 150))).toBe(true)
|
|
1019
|
+
})
|
|
1020
|
+
|
|
1021
|
+
it('composes nested transforms and carries the flags forward', () => {
|
|
1022
|
+
const g = rect()
|
|
1023
|
+
.transform(Mat.Translate(-50, -100), { debugColor: 'red' })
|
|
1024
|
+
.transform(Mat.Scale(0.5, 0.5))
|
|
1025
|
+
expect(g).toBeInstanceOf(TransformedGeometry2d)
|
|
1026
|
+
expect(g.bounds).toMatchObject({ x: 0, y: 0, w: 100, h: 50 })
|
|
1027
|
+
expect(g).toMatchObject({ debugColor: 'red', isFilled: true })
|
|
1028
|
+
})
|
|
1029
|
+
|
|
1030
|
+
it('cannot produce svg path data', () => {
|
|
1031
|
+
expect(() => rect().getSvgPathData(true)).toThrow(
|
|
1032
|
+
'Cannot get SVG path data for transformed geometry.'
|
|
1033
|
+
)
|
|
1034
|
+
})
|
|
1035
|
+
})
|