@weasel-js/core 1.9.3 → 1.9.4
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/CHANGELOG.md +356 -0
- package/README.md +2 -2
- package/dist/WeaselProvider.js +5 -5
- package/dist/affordances/composeAffordanceLayer.js +10 -20
- package/dist/affordances/composeAffordanceLayer.js.map +1 -1
- package/dist/affordances/cornerResize.js +25 -17
- package/dist/affordances/cornerResize.js.map +1 -1
- package/dist/affordances/hitAffordanceRegions.js +27 -20
- package/dist/affordances/hitAffordanceRegions.js.map +1 -1
- package/dist/affordances/rotationHandle.js +18 -18
- package/dist/affordances/rotationHandle.js.map +1 -1
- package/dist/animation/behaviors/momentum.js +93 -55
- package/dist/animation/behaviors/momentum.js.map +1 -1
- package/dist/animation/loop.js +10 -12
- package/dist/animation/loop.js.map +1 -1
- package/dist/animation/reflow.js +8 -8
- package/dist/animation/reflow.js.map +1 -1
- package/dist/animation/stagger.js +64 -56
- package/dist/animation/stagger.js.map +1 -1
- package/dist/animation/timeline/booking.js +62 -54
- package/dist/animation/timeline/booking.js.map +1 -1
- package/dist/animation/timeline/createTimeline.js +85 -76
- package/dist/animation/timeline/createTimeline.js.map +1 -1
- package/dist/animation/timeline/sampleTrack.js +13 -5
- package/dist/animation/timeline/sampleTrack.js.map +1 -1
- package/dist/animation/useAnimator.js +45 -35
- package/dist/animation/useAnimator.js.map +1 -1
- package/dist/animation/wrappers/animateLifecycle.js +31 -24
- package/dist/animation/wrappers/animateLifecycle.js.map +1 -1
- package/dist/animation/wrappers/animateOnSetPose.js +28 -26
- package/dist/animation/wrappers/animateOnSetPose.js.map +1 -1
- package/dist/animation/wrappers/instantPose.js +13 -0
- package/dist/animation/wrappers/instantPose.js.map +1 -0
- package/dist/{poseDescriptor-D-OH-tu5.d.ts → autoPoseDescriptor-Ct7yqivU.d.ts} +28 -3
- package/dist/barrel/chrome.js +16 -0
- package/dist/barrel/input.js +19 -0
- package/dist/barrel/interactions.js +42 -0
- package/dist/barrel/paint.js +18 -0
- package/dist/barrel/paths.js +19 -0
- package/dist/barrel/renderer.js +20 -0
- package/dist/barrel/routing.js +51 -0
- package/dist/barrel/text.js +18 -0
- package/dist/barrel/viewport.js +52 -0
- package/dist/builtins-DPcdghtB.d.ts +1223 -0
- package/dist/canvas/Canvas.js +363 -448
- package/dist/canvas/Canvas.js.map +1 -1
- package/dist/canvas/CanvasView.js +164 -165
- package/dist/canvas/CanvasView.js.map +1 -1
- package/dist/canvas/CursorCoordsHud.js +37 -32
- package/dist/canvas/CursorCoordsHud.js.map +1 -1
- package/dist/canvas/DrawCanvas.js +49 -43
- package/dist/canvas/DrawCanvas.js.map +1 -1
- package/dist/canvas/MinimapCanvas.js +26 -25
- package/dist/canvas/MinimapCanvas.js.map +1 -1
- package/dist/canvas/NodeShape.js +59 -377
- package/dist/canvas/NodeShape.js.map +1 -1
- package/dist/canvas/PickHud.js +50 -47
- package/dist/canvas/PickHud.js.map +1 -1
- package/dist/canvas/SceneCanvas/GestureDispatcherMounter.js +167 -0
- package/dist/canvas/SceneCanvas/GestureDispatcherMounter.js.map +1 -0
- package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +13 -13
- package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js +67 -0
- package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js.map +1 -0
- package/dist/canvas/SceneCanvas/ToolKeybindingsMounter.js +16 -0
- package/dist/canvas/SceneCanvas/ToolKeybindingsMounter.js.map +1 -0
- package/dist/canvas/SceneCanvas/coordTrace.js +10 -0
- package/dist/canvas/SceneCanvas/coordTrace.js.map +1 -0
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js +112 -70
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
- package/dist/canvas/SceneCanvas/defaultNodeRouting.js +9 -8
- package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
- package/dist/canvas/SceneCanvas/features.js +1 -0
- package/dist/canvas/SceneCanvas/features.js.map +1 -1
- package/dist/canvas/SceneCanvas/isDev.js +12 -0
- package/dist/canvas/SceneCanvas/isDev.js.map +1 -0
- package/dist/canvas/SceneCanvas/poseGeometry.js +29 -28
- package/dist/canvas/SceneCanvas/poseGeometry.js.map +1 -1
- package/dist/canvas/SceneCanvas/poseRegistrars.js +13 -0
- package/dist/canvas/SceneCanvas/poseRegistrars.js.map +1 -0
- package/dist/canvas/SceneCanvas/props.js +38 -0
- package/dist/canvas/SceneCanvas/props.js.map +1 -0
- package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js +21 -62
- package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useDispatcherOverlayLayer.js.map +1 -1
- package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +46 -48
- package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js +33 -33
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
- package/dist/canvas/SceneCanvas.js +534 -788
- package/dist/canvas/SceneCanvas.js.map +1 -1
- package/dist/canvas/SceneViewCanvas.js +45 -43
- package/dist/canvas/SceneViewCanvas.js.map +1 -1
- package/dist/canvas/buildSceneLayer.js +91 -0
- package/dist/canvas/buildSceneLayer.js.map +1 -0
- package/dist/canvas/buildSceneTree.js +16 -19
- package/dist/canvas/buildSceneTree.js.map +1 -1
- package/dist/canvas/canvasRenderer.js +68 -52
- package/dist/canvas/canvasRenderer.js.map +1 -1
- package/dist/canvas/deps/changes.js +13 -0
- package/dist/canvas/deps/changes.js.map +1 -0
- package/dist/canvas/deps/editAnchors.js +28 -28
- package/dist/canvas/deps/insert.js +119 -91
- package/dist/canvas/deps/insert.js.map +1 -1
- package/dist/canvas/deps/slice.js +46 -45
- package/dist/canvas/deps/slice.js.map +1 -1
- package/dist/canvas/deps/textEdit.js +10 -9
- package/dist/canvas/deps/textEdit.js.map +1 -1
- package/dist/canvas/derivedPath.js +46 -23
- package/dist/canvas/derivedPath.js.map +1 -1
- package/dist/canvas/getNodeAtPoint.js +7 -7
- package/dist/canvas/getNodeAtPoint.js.map +1 -1
- package/dist/canvas/hud.module.js +1 -1
- package/dist/canvas/hud.module.js.map +1 -1
- package/dist/canvas/kitRequirements.js +2 -2
- package/dist/canvas/kitRequirements.js.map +1 -1
- package/dist/canvas/layerOrder.js +13 -6
- package/dist/canvas/layerOrder.js.map +1 -1
- package/dist/canvas/minimapMath.js +12 -10
- package/dist/canvas/minimapMath.js.map +1 -1
- package/dist/canvas/nodeShapes/derivedPainter.js +34 -0
- package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -0
- package/dist/canvas/nodeShapes/imagePainter.js +87 -0
- package/dist/canvas/nodeShapes/imagePainter.js.map +1 -0
- package/dist/canvas/nodeShapes/memo.js +9 -0
- package/dist/canvas/nodeShapes/memo.js.map +1 -0
- package/dist/canvas/nodeShapes/nodePaint.js +57 -0
- package/dist/canvas/nodeShapes/nodePaint.js.map +1 -0
- package/dist/canvas/nodeShapes/pathPainter.js +38 -0
- package/dist/canvas/nodeShapes/pathPainter.js.map +1 -0
- package/dist/canvas/nodeShapes/poseBox.js +20 -0
- package/dist/canvas/nodeShapes/poseBox.js.map +1 -0
- package/dist/canvas/nodeShapes/rectFallbackPainter.js +38 -0
- package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -0
- package/dist/canvas/nodeShapes/shapePainter.js +72 -0
- package/dist/canvas/nodeShapes/shapePainter.js.map +1 -0
- package/dist/canvas/nodeShapes/textPainter.js +55 -0
- package/dist/canvas/nodeShapes/textPainter.js.map +1 -0
- package/dist/canvas/overlayRoleStrokes.js.map +1 -1
- package/dist/canvas/paintCull.js +10 -11
- package/dist/canvas/paintCull.js.map +1 -1
- package/dist/canvas/pickWalk.js +52 -63
- package/dist/canvas/pickWalk.js.map +1 -1
- package/dist/canvas/poseRotation.js +11 -23
- package/dist/canvas/poseRotation.js.map +1 -1
- package/dist/canvas/renderDebugSnapshot.js +5 -5
- package/dist/canvas/renderSceneToPixels.js +3 -1
- package/dist/canvas/renderSceneToPixels.js.map +1 -1
- package/dist/canvas/sceneAdapter.js +66 -148
- package/dist/canvas/sceneAdapter.js.map +1 -1
- package/dist/canvas/sceneClipboard.js +112 -0
- package/dist/canvas/sceneClipboard.js.map +1 -0
- package/dist/canvas/sceneSlotWiring.js +4 -4
- package/dist/canvas/sceneSlotWiring.js.map +1 -1
- package/dist/canvas/sceneViewRender.js +39 -40
- package/dist/canvas/sceneViewRender.js.map +1 -1
- package/dist/canvas/useCanvasRenderer.js +14 -6
- package/dist/canvas/useCanvasRenderer.js.map +1 -1
- package/dist/canvas/useCanvasView.js +58 -0
- package/dist/canvas/useCanvasView.js.map +1 -0
- package/dist/canvas/useFrameLoop.js +35 -26
- package/dist/canvas/useFrameLoop.js.map +1 -1
- package/dist/canvas/useSceneLayerList.js +21 -0
- package/dist/canvas/useSceneLayerList.js.map +1 -0
- package/dist/clipboard.d.ts +10 -6
- package/dist/clone.d.ts +4 -3
- package/dist/core/adapters/arrayAdapter.js +48 -39
- package/dist/core/adapters/arrayAdapter.js.map +1 -1
- package/dist/core/device/index.js +5 -4
- package/dist/core/device/useDeviceProfile.js +38 -44
- package/dist/core/device/useDeviceProfile.js.map +1 -1
- package/dist/core/device/watchDevicePixelRatio.js +17 -0
- package/dist/core/device/watchDevicePixelRatio.js.map +1 -0
- package/dist/core/fillInPoseFrame.js.map +1 -1
- package/dist/core/geometry/poseDescriptor.js +21 -19
- package/dist/core/geometry/poseDescriptor.js.map +1 -1
- package/dist/core/geometry/poseFrame.js +44 -0
- package/dist/core/geometry/poseFrame.js.map +1 -0
- package/dist/core/geometry/poseRotation.js.map +1 -1
- package/dist/core/geometry/regionHit.js +9 -10
- package/dist/core/geometry/regionHit.js.map +1 -1
- package/dist/core/geometry/unionBounds.js +31 -29
- package/dist/core/geometry/unionBounds.js.map +1 -1
- package/dist/core/isPlainObject.js +2 -11
- package/dist/core/ops/arrange.js +1 -0
- package/dist/core/ops/arrange.js.map +1 -1
- package/dist/core/ops/create.js +10 -5
- package/dist/core/ops/create.js.map +1 -1
- package/dist/core/ops/delete.js +12 -24
- package/dist/core/ops/delete.js.map +1 -1
- package/dist/core/ops/reorder/index.js +43 -40
- package/dist/core/ops/reorder/index.js.map +1 -1
- package/dist/core/ops/reorder/setChildOrder.js +34 -0
- package/dist/core/ops/reorder/setChildOrder.js.map +1 -0
- package/dist/core/paintKinds.js +5 -5
- package/dist/core/paintKinds.js.map +1 -1
- package/dist/core/scene/NodeProperties.js.map +1 -1
- package/dist/core/scene/appliedLog.js +37 -0
- package/dist/core/scene/appliedLog.js.map +1 -0
- package/dist/core/scene/arrivalWindow.js +101 -0
- package/dist/core/scene/arrivalWindow.js.map +1 -0
- package/dist/core/scene/derivedPath.js.map +1 -1
- package/dist/core/scene/historyFacade.js +84 -0
- package/dist/core/scene/historyFacade.js.map +1 -0
- package/dist/core/scene/kitOps.js +173 -0
- package/dist/core/scene/kitOps.js.map +1 -0
- package/dist/core/scene/layerStack.js +38 -0
- package/dist/core/scene/layerStack.js.map +1 -0
- package/dist/core/scene/lockGuard.js +37 -0
- package/dist/core/scene/lockGuard.js.map +1 -0
- package/dist/core/scene/nodeFnFields.js +5 -6
- package/dist/core/scene/nodeFnFields.js.map +1 -1
- package/dist/core/scene/nodeMemo.js +45 -19
- package/dist/core/scene/nodeMemo.js.map +1 -1
- package/dist/core/scene/nodeStore.js +126 -0
- package/dist/core/scene/nodeStore.js.map +1 -0
- package/dist/core/scene/pickability.js +6 -0
- package/dist/core/scene/pickability.js.map +1 -0
- package/dist/core/scene/poseFeed.js +38 -38
- package/dist/core/scene/poseFeed.js.map +1 -1
- package/dist/core/scene/poseOverrides.js +34 -38
- package/dist/core/scene/poseOverrides.js.map +1 -1
- package/dist/core/scene/renderOrder.js +84 -0
- package/dist/core/scene/renderOrder.js.map +1 -0
- package/dist/core/scene/scene.js +289 -866
- package/dist/core/scene/scene.js.map +1 -1
- package/dist/core/scene/serialization.js +68 -0
- package/dist/core/scene/serialization.js.map +1 -0
- package/dist/core/scene/treeOrder.js +13 -0
- package/dist/core/scene/treeOrder.js.map +1 -0
- package/dist/core/scene/types.js.map +1 -1
- package/dist/core/viewport/meanScale.js +1 -1
- package/dist/core/viewport/meanScale.js.map +1 -1
- package/dist/core/viewport/view.js +12 -12
- package/dist/core/viewport/view.js.map +1 -1
- package/dist/core/viewport/zoomBounds.js +10 -8
- package/dist/core/viewport/zoomBounds.js.map +1 -1
- package/dist/{cornerHandles-BCrDp_mH.d.ts → cornerHandles-CtCb8a3G.d.ts} +1 -1
- package/dist/debug/createDebugOverlayLayer.js +14 -14
- package/dist/debug/createDebugOverlayLayer.js.map +1 -1
- package/dist/debug/flag.js +2 -41
- package/dist/features/chrome-caps/useHoverTracking.js +22 -17
- package/dist/features/chrome-caps/useHoverTracking.js.map +1 -1
- package/dist/features/focus/gateLayer.js +3 -4
- package/dist/features/focus/gateLayer.js.map +1 -1
- package/dist/features/images/imageCache.js +19 -9
- package/dist/features/images/imageCache.js.map +1 -1
- package/dist/features/ingestion/contentHandlers.js +14 -11
- package/dist/features/ingestion/contentHandlers.js.map +1 -1
- package/dist/features/ingestion/index.js +1 -1
- package/dist/features/ingestion/registerKitHandlers.js +7 -7
- package/dist/features/ingestion/weaselJsonHandler.js +17 -14
- package/dist/features/ingestion/weaselJsonHandler.js.map +1 -1
- package/dist/features/meshPaint/lazyKind.js +201 -35
- package/dist/features/meshPaint/lazyKind.js.map +1 -1
- package/dist/features/meshPaint/meshPaint.js +82 -79
- package/dist/features/meshPaint/meshPaint.js.map +1 -1
- package/dist/features/minimap/createMinimapContribution.js +42 -42
- package/dist/features/minimap/layers.js +44 -62
- package/dist/features/minimap/layers.js.map +1 -1
- package/dist/features/parallax/createParallaxPlane.js +10 -14
- package/dist/features/parallax/createParallaxPlane.js.map +1 -1
- package/dist/features/paths/anchorEdits.js +19 -8
- package/dist/features/paths/anchorEdits.js.map +1 -1
- package/dist/features/paths/anchors.js +31 -26
- package/dist/features/paths/anchors.js.map +1 -1
- package/dist/features/paths/booleans.js +2 -2
- package/dist/features/paths/index.js +13 -11
- package/dist/features/paths/markerCommands.js +37 -36
- package/dist/features/paths/markerCommands.js.map +1 -1
- package/dist/features/paths/pathInWorld.js +19 -19
- package/dist/features/paths/pathInWorld.js.map +1 -1
- package/dist/features/paths/pathLayer.js.map +1 -1
- package/dist/features/paths/strokeToPath.js +40 -0
- package/dist/features/paths/strokeToPath.js.map +1 -0
- package/dist/features/paths/tessellate/stroke.js.map +1 -1
- package/dist/features/patterns/commandPaint.js +18 -0
- package/dist/features/patterns/commandPaint.js.map +1 -0
- package/dist/features/patterns/index.js +5 -17
- package/dist/features/patterns/index.js.map +1 -1
- package/dist/features/patterns/rasterizeTile.js +17 -0
- package/dist/features/patterns/rasterizeTile.js.map +1 -0
- package/dist/features/patterns/resolveSpec.js +52 -44
- package/dist/features/patterns/resolveSpec.js.map +1 -1
- package/dist/features/poseRun/usePoseRun.js +8 -3
- package/dist/features/poseRun/usePoseRun.js.map +1 -1
- package/dist/features/selection/SelectionContext.js +5 -0
- package/dist/features/selection/SelectionContext.js.map +1 -1
- package/dist/features/selection/overlay.js +18 -18
- package/dist/features/selection/overlay.js.map +1 -1
- package/dist/features/text/domOffsets.js +82 -0
- package/dist/features/text/domOffsets.js.map +1 -0
- package/dist/features/text/domRuns.js +103 -131
- package/dist/features/text/domRuns.js.map +1 -1
- package/dist/features/text/hitTest.js +5 -2
- package/dist/features/text/hitTest.js.map +1 -1
- package/dist/features/text/isTextData.js +8 -0
- package/dist/features/text/isTextData.js.map +1 -0
- package/dist/features/text/overlayDirection.js +15 -0
- package/dist/features/text/overlayDirection.js.map +1 -0
- package/dist/features/text/runs/rangeStyle.js +32 -34
- package/dist/features/text/runs/rangeStyle.js.map +1 -1
- package/dist/features/text/textCommand.js +34 -33
- package/dist/features/text/textCommand.js.map +1 -1
- package/dist/features/text/textToPath.js +40 -40
- package/dist/features/text/textToPath.js.map +1 -1
- package/dist/features/text/useSceneTextEdit.js +76 -82
- package/dist/features/text/useSceneTextEdit.js.map +1 -1
- package/dist/features/text/useTextEdit.js +184 -160
- package/dist/features/text/useTextEdit.js.map +1 -1
- package/dist/features/tiling/createTiledLayer.js +24 -22
- package/dist/features/tiling/createTiledLayer.js.map +1 -1
- package/dist/index.d.ts +11033 -7749
- package/dist/index.js +316 -309
- package/dist/input/keyState.js +42 -43
- package/dist/input/keyState.js.map +1 -1
- package/dist/input/useKeyModifiers.js +11 -0
- package/dist/input/useKeyModifiers.js.map +1 -0
- package/dist/interactions/actions/align/align.js +8 -8
- package/dist/interactions/actions/clipboard/clipboardOps.js +34 -32
- package/dist/interactions/actions/clipboard/clipboardOps.js.map +1 -1
- package/dist/interactions/actions/clipboard/materializePaste.js +10 -7
- package/dist/interactions/actions/clipboard/materializePaste.js.map +1 -1
- package/dist/interactions/actions/clipboard/wireFormat.js.map +1 -1
- package/dist/interactions/actions/clone/behaviors/cloneByAltDrag.js.map +1 -1
- package/dist/interactions/actions/defaultCommitAdapter.js +20 -18
- package/dist/interactions/actions/defaultCommitAdapter.js.map +1 -1
- package/dist/interactions/actions/defaults/align.js +9 -10
- package/dist/interactions/actions/defaults/align.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorEditing.js +59 -81
- package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
- package/dist/interactions/actions/defaults/areaSelect.js +35 -38
- package/dist/interactions/actions/defaults/areaSelect.js.map +1 -1
- package/dist/interactions/actions/defaults/clipboard.js +26 -10
- package/dist/interactions/actions/defaults/clipboard.js.map +1 -1
- package/dist/interactions/actions/defaults/clone.js +44 -60
- package/dist/interactions/actions/defaults/clone.js.map +1 -1
- package/dist/interactions/actions/defaults/copySubtree.js +40 -0
- package/dist/interactions/actions/defaults/copySubtree.js.map +1 -0
- package/dist/interactions/actions/defaults/distribute.js +14 -13
- package/dist/interactions/actions/defaults/distribute.js.map +1 -1
- package/dist/interactions/actions/defaults/duplicate.js +17 -57
- package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
- package/dist/interactions/actions/defaults/editAnchors.js +18 -18
- package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/flip.js +52 -46
- package/dist/interactions/actions/defaults/flip.js.map +1 -1
- package/dist/interactions/actions/defaults/group.js +43 -48
- package/dist/interactions/actions/defaults/group.js.map +1 -1
- package/dist/interactions/actions/defaults/ingest.js +35 -33
- package/dist/interactions/actions/defaults/ingest.js.map +1 -1
- package/dist/interactions/actions/defaults/insert.js +50 -51
- package/dist/interactions/actions/defaults/insert.js.map +1 -1
- package/dist/interactions/actions/defaults/insertPathAnchor.js +7 -7
- package/dist/interactions/actions/defaults/lassoSelect.js +67 -70
- package/dist/interactions/actions/defaults/lassoSelect.js.map +1 -1
- package/dist/interactions/actions/defaults/move.js +267 -249
- package/dist/interactions/actions/defaults/move.js.map +1 -1
- package/dist/interactions/actions/defaults/nudge.js +40 -68
- package/dist/interactions/actions/defaults/nudge.js.map +1 -1
- package/dist/interactions/actions/defaults/nudgeSteps.js +37 -0
- package/dist/interactions/actions/defaults/nudgeSteps.js.map +1 -0
- package/dist/interactions/actions/defaults/reorder.js +2 -0
- package/dist/interactions/actions/defaults/reorder.js.map +1 -1
- package/dist/interactions/actions/defaults/resize.js +104 -98
- package/dist/interactions/actions/defaults/resize.js.map +1 -1
- package/dist/interactions/actions/defaults/rotate.js +111 -110
- package/dist/interactions/actions/defaults/rotate.js.map +1 -1
- package/dist/interactions/actions/defaults/setFill.js +7 -7
- package/dist/interactions/actions/defaults/setFillOpacity.js +4 -4
- package/dist/interactions/actions/defaults/setStroke.js +12 -12
- package/dist/interactions/actions/defaults/setStrokeOpacity.js +6 -6
- package/dist/interactions/actions/defaults/sliceOps.js +19 -19
- package/dist/interactions/actions/defaults/viewportDragPan.js +37 -26
- package/dist/interactions/actions/defaults/viewportDragPan.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/geometry.js +28 -23
- package/dist/interactions/actions/edit-anchors/geometry.js.map +1 -1
- package/dist/interactions/actions/flip/helpers.js +30 -17
- package/dist/interactions/actions/flip/helpers.js.map +1 -1
- package/dist/interactions/actions/hitSelection.js +8 -0
- package/dist/interactions/actions/hitSelection.js.map +1 -0
- package/dist/interactions/actions/lasso-select/behaviors/selectFromLasso.js +14 -18
- package/dist/interactions/actions/lasso-select/behaviors/selectFromLasso.js.map +1 -1
- package/dist/interactions/actions/move/behaviors/snapBackOrDelete.js +5 -5
- package/dist/interactions/actions/move/behaviors/snapBackOrDelete.js.map +1 -1
- package/dist/interactions/actions/move/behaviors/snapToContainer.js +32 -21
- package/dist/interactions/actions/move/behaviors/snapToContainer.js.map +1 -1
- package/dist/interactions/actions/move/gestureAdapter.js +32 -15
- package/dist/interactions/actions/move/gestureAdapter.js.map +1 -1
- package/dist/interactions/actions/move/landedDescendants.js +33 -0
- package/dist/interactions/actions/move/landedDescendants.js.map +1 -0
- package/dist/interactions/actions/outlines/createOutlines.js +18 -18
- package/dist/interactions/actions/poseDescriptorDep.js +16 -4
- package/dist/interactions/actions/poseDescriptorDep.js.map +1 -1
- package/dist/interactions/actions/poseFrame.js +11 -6
- package/dist/interactions/actions/poseFrame.js.map +1 -1
- package/dist/interactions/actions/previewOverrides.js +5 -5
- package/dist/interactions/actions/previewOverrides.js.map +1 -1
- package/dist/interactions/actions/selectionSubtree.js +69 -0
- package/dist/interactions/actions/selectionSubtree.js.map +1 -0
- package/dist/interactions/actions/useStandardActions.js +66 -62
- package/dist/interactions/actions/useStandardActions.js.map +1 -1
- package/dist/interactions/gestures/dragGesture/dragGesture.js +4 -7
- package/dist/interactions/gestures/dragGesture/dragGesture.js.map +1 -1
- package/dist/interactions/gestures/dragGesture/dragPointOf.js +23 -0
- package/dist/interactions/gestures/dragGesture/dragPointOf.js.map +1 -0
- package/dist/interactions/gestures/handleDrag/handleDrag.js +21 -26
- package/dist/interactions/gestures/handleDrag/handleDrag.js.map +1 -1
- package/dist/interactions/gestures/pointerDrag/pointerDrag.js +8 -6
- package/dist/interactions/gestures/pointerDrag/pointerDrag.js.map +1 -1
- package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js +8 -4
- package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js.map +1 -1
- package/dist/{math-Drrw4ewZ.d.ts → math-J-ACHyEg.d.ts} +1 -2
- package/dist/math.d.ts +2 -3
- package/dist/move.d.ts +12 -11
- package/dist/{originProjection-DEklpDc_.d.ts → originProjection-gHnOup4e.d.ts} +1 -1
- package/dist/{registerProgram-BMZRDgrh.d.ts → registerProgram-T9S_PfAX.d.ts} +18 -7
- package/dist/renderer/DrawCommand.js.map +1 -1
- package/dist/renderer/WeaselRenderer.js +75 -73
- package/dist/renderer/WeaselRenderer.js.map +1 -1
- package/dist/renderer/cache/GLImageCache.js +15 -5
- package/dist/renderer/cache/GLImageCache.js.map +1 -1
- package/dist/renderer/cache/GLMeshCache.js +23 -23
- package/dist/renderer/cache/GLMeshCache.js.map +1 -1
- package/dist/renderer/cache/GLTextureCache.js +18 -1
- package/dist/renderer/cache/GLTextureCache.js.map +1 -1
- package/dist/renderer/cache/cache.js.map +1 -1
- package/dist/renderer/cache/markerCommandCache.js.map +1 -1
- package/dist/renderer/cache/strokeMeshCache.js.map +1 -1
- package/dist/renderer/cullDrawCommands.js +11 -11
- package/dist/renderer/draw/batch.js +133 -0
- package/dist/renderer/draw/batch.js.map +1 -0
- package/dist/renderer/draw/clip.js +89 -0
- package/dist/renderer/draw/clip.js.map +1 -0
- package/dist/renderer/draw/customShader.js +230 -0
- package/dist/renderer/draw/customShader.js.map +1 -0
- package/dist/renderer/draw/drawTriangles.js +8 -0
- package/dist/renderer/draw/drawTriangles.js.map +1 -0
- package/dist/renderer/draw/effects.js +28 -0
- package/dist/renderer/draw/effects.js.map +1 -0
- package/dist/renderer/draw/flushBatch.js +35 -0
- package/dist/renderer/draw/flushBatch.js.map +1 -0
- package/dist/renderer/draw/gradient.js +90 -0
- package/dist/renderer/draw/gradient.js.map +1 -0
- package/dist/renderer/draw/image.js +38 -0
- package/dist/renderer/draw/image.js.map +1 -0
- package/dist/renderer/draw/mesh.js +16 -0
- package/dist/renderer/draw/mesh.js.map +1 -0
- package/dist/renderer/draw/paintBind.js +70 -0
- package/dist/renderer/draw/paintBind.js.map +1 -0
- package/dist/renderer/draw/pathPaint.js +142 -0
- package/dist/renderer/draw/pathPaint.js.map +1 -0
- package/dist/renderer/draw/scratchPass.js +83 -0
- package/dist/renderer/draw/scratchPass.js.map +1 -0
- package/dist/renderer/draw/stencil.js +28 -0
- package/dist/renderer/draw/stencil.js.map +1 -0
- package/dist/renderer/draw/text.js +167 -0
- package/dist/renderer/draw/text.js.map +1 -0
- package/dist/renderer/draw/uniforms.js +77 -0
- package/dist/renderer/draw/uniforms.js.map +1 -0
- package/dist/renderer/draw.js +60 -1116
- package/dist/renderer/draw.js.map +1 -1
- package/dist/renderer/effects/EffectTargets.js +3 -0
- package/dist/renderer/effects/EffectTargets.js.map +1 -1
- package/dist/renderer/effects/composite.js +7 -5
- package/dist/renderer/effects/composite.js.map +1 -1
- package/dist/renderer/index.js +18 -17
- package/dist/renderer/math/color.js +63 -83
- package/dist/renderer/math/color.js.map +1 -1
- package/dist/renderer/math/mat3.js +15 -11
- package/dist/renderer/math/mat3.js.map +1 -1
- package/dist/renderer/math/viewToMat3.js +1 -1
- package/dist/renderer/math/viewToMat3.js.map +1 -1
- package/dist/renderer/shaders/ShaderProgram.js +13 -13
- package/dist/renderer/shaders/ShaderProgram.js.map +1 -1
- package/dist/renderer/shaders/glyphPaint.js +3 -3
- package/dist/renderer/shaders/glyphPaint.js.map +1 -1
- package/dist/renderer/shaders/gradFill.js +2 -1
- package/dist/renderer/shaders/gradFill.js.map +1 -1
- package/dist/renderer/shaders/pathFill.js +2 -1
- package/dist/renderer/shaders/pathFill.js.map +1 -1
- package/dist/renderer/shaders/patternFill.js +2 -1
- package/dist/renderer/shaders/patternFill.js.map +1 -1
- package/dist/renderer/shaders/registerProgram.js.map +1 -1
- package/dist/renderer/strokeLengths.js.map +1 -1
- package/dist/renderer/textures/registerTexture.js +25 -5
- package/dist/renderer/textures/registerTexture.js.map +1 -1
- package/dist/renderer.d.ts +16 -15
- package/dist/renderer.js +17 -16
- package/dist/resize.d.ts +5 -6
- package/dist/resize.js +5 -5
- package/dist/style.css +1 -1
- package/dist/test-hook/install.js +12 -6
- package/dist/test-hook/install.js.map +1 -1
- package/dist/{test-seams-CuIxe0xY.d.ts → test-seams-DM2B87-g.d.ts} +4 -4
- package/dist/test-seams.d.ts +2 -2
- package/dist/tools/builtin/ellipse/useEllipseTool.js +1 -0
- package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
- package/dist/tools/builtin/hand/useHandTool.js +1 -0
- package/dist/tools/builtin/hand/useHandTool.js.map +1 -1
- package/dist/tools/builtin/lasso/useLassoTool.js +11 -11
- package/dist/tools/builtin/lasso/useLassoTool.js.map +1 -1
- package/dist/tools/builtin/line/useLineTool.js +1 -0
- package/dist/tools/builtin/line/useLineTool.js.map +1 -1
- package/dist/tools/builtin/pen/usePenTool.js +153 -103
- package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
- package/dist/tools/builtin/pencil/usePencilTool.js +1 -0
- package/dist/tools/builtin/pencil/usePencilTool.js.map +1 -1
- package/dist/tools/builtin/polygon/usePolygonTool.js +13 -8
- package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
- package/dist/tools/builtin/rect/useRectTool.js +1 -0
- package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
- package/dist/tools/builtin/select/selectionContributions.js +24 -23
- package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
- package/dist/tools/builtin/select/useSelectTool.js +51 -48
- package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
- package/dist/tools/builtin/shared/pointerFollower.js +17 -0
- package/dist/tools/builtin/shared/pointerFollower.js.map +1 -0
- package/dist/tools/builtin/slice/useSliceTool.js +49 -54
- package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
- package/dist/tools/builtin/star/useStarTool.js +22 -16
- package/dist/tools/builtin/star/useStarTool.js.map +1 -1
- package/dist/tools/builtin/text/useTextTool.js +1 -0
- package/dist/tools/builtin/text/useTextTool.js.map +1 -1
- package/dist/tools/useKeybindings.js +14 -30
- package/dist/tools/useKeybindings.js.map +1 -1
- package/dist/{types-B_9g6hMM.d.ts → types-B4iJRHC3.d.ts} +5 -1
- package/dist/{types-A37hYkeR.d.ts → types-k-5Oqh-U.d.ts} +31 -7
- package/dist/util/color.js +3 -3
- package/dist/util/color.js.map +1 -1
- package/dist/util/paint.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +20 -18
- package/dist/autoPoseDescriptor-vCdt55xm.d.ts +0 -27
- package/dist/builtins-C-VgUlWI.d.ts +0 -4514
- package/dist/core/isPlainObject.js.map +0 -1
- package/dist/debug/flag.js.map +0 -1
- package/dist/interactions/actions/defaults/freshNodeId.js +0 -10
- package/dist/interactions/actions/defaults/freshNodeId.js.map +0 -1
- package/dist/renderer/shaders/imageFill.js +0 -14
- package/dist/renderer/shaders/imageFill.js.map +0 -1
- package/dist/tools/prefs.js +0 -62
- package/dist/tools/prefs.js.map +0 -1
|
@@ -2,10 +2,11 @@ import { coordsOutOfMetric as e, polylineIntoMetric as t } from "./tessellate/me
|
|
|
2
2
|
import { getMarker as n } from "../../core/strokeMarkers.js";
|
|
3
3
|
import { markerKeyOf as r, resolveMarkerSize as i } from "../../core/markerInset.js";
|
|
4
4
|
import { markerSites as a } from "./markerSites.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { commandFill as o } from "../patterns/commandPaint.js";
|
|
6
|
+
import { boundsOfPath as s } from "@weasel-js/geom";
|
|
7
|
+
import { extractPolylines as c } from "@weasel-js/geom/tessellate";
|
|
7
8
|
//#region src/features/paths/markerCommands.ts
|
|
8
|
-
function
|
|
9
|
+
function l(e, t) {
|
|
9
10
|
let n = e, r = Math.cos(t.angle), i = Math.sin(t.angle), a = new Float32Array(n.coords.length);
|
|
10
11
|
for (let e = 0; e < n.coords.length; e += 2) {
|
|
11
12
|
let o = n.coords[e], s = n.coords[e + 1];
|
|
@@ -18,15 +19,15 @@ function c(e, t) {
|
|
|
18
19
|
fillRule: n.fillRule
|
|
19
20
|
};
|
|
20
21
|
}
|
|
21
|
-
function
|
|
22
|
-
let
|
|
23
|
-
if (
|
|
22
|
+
function u(e, t, n, r) {
|
|
23
|
+
let i = e ?? n;
|
|
24
|
+
if (i !== "none") return i === "line" ? t.paint : o(i, s(r)) ?? void 0;
|
|
24
25
|
}
|
|
25
|
-
function
|
|
26
|
+
function d(o, s, u, d, f = {}) {
|
|
26
27
|
let p = {
|
|
27
|
-
start:
|
|
28
|
-
mid:
|
|
29
|
-
end:
|
|
28
|
+
start: s.markerStart,
|
|
29
|
+
mid: s.markerMid,
|
|
30
|
+
end: s.markerEnd
|
|
30
31
|
};
|
|
31
32
|
if (p.start === void 0 && p.mid === void 0 && p.end === void 0) return [];
|
|
32
33
|
let m = /* @__PURE__ */ new Map();
|
|
@@ -44,31 +45,31 @@ function u(o, l, u, d, f = {}) {
|
|
|
44
45
|
}), n[e] = !0;
|
|
45
46
|
}
|
|
46
47
|
let h = [];
|
|
47
|
-
for (let f of
|
|
48
|
+
for (let f of c(o, { flattenTolerance: d })) for (let [o, c] of m) {
|
|
48
49
|
let d = o ? {
|
|
49
50
|
...f,
|
|
50
51
|
points: f.points.slice()
|
|
51
52
|
} : f;
|
|
52
53
|
o && t(d, o.metric);
|
|
53
|
-
for (let t of a(d,
|
|
54
|
+
for (let t of a(d, c)) {
|
|
54
55
|
let a = p[t.role];
|
|
55
56
|
if (a === void 0) continue;
|
|
56
|
-
let
|
|
57
|
-
if (
|
|
58
|
-
let d = i(a, u), f =
|
|
57
|
+
let c = n(r(a));
|
|
58
|
+
if (c === void 0) continue;
|
|
59
|
+
let d = i(a, u), f = c.orient === void 0 || c.orient === "auto" ? t.angle : c.orient, m = l(c.path({
|
|
59
60
|
size: d,
|
|
60
|
-
stroke:
|
|
61
|
+
stroke: s
|
|
61
62
|
}), {
|
|
62
63
|
...t,
|
|
63
64
|
angle: f
|
|
64
65
|
});
|
|
65
66
|
o && e(m.coords, o.metric), h.push(o ? {
|
|
66
|
-
entry:
|
|
67
|
+
entry: c,
|
|
67
68
|
size: d,
|
|
68
69
|
path: m,
|
|
69
70
|
space: o
|
|
70
71
|
} : {
|
|
71
|
-
entry:
|
|
72
|
+
entry: c,
|
|
72
73
|
size: d,
|
|
73
74
|
path: m
|
|
74
75
|
});
|
|
@@ -76,11 +77,11 @@ function u(o, l, u, d, f = {}) {
|
|
|
76
77
|
}
|
|
77
78
|
return h;
|
|
78
79
|
}
|
|
79
|
-
function
|
|
80
|
+
function f(e, t) {
|
|
80
81
|
let n = [];
|
|
81
82
|
for (let { entry: r, path: i, size: a, space: o } of e) {
|
|
82
|
-
let e =
|
|
83
|
-
paint:
|
|
83
|
+
let e = u(r.fill, t, "line", i), s = r.outline ? r.outline.width * a : 0, c = r.outline ? {
|
|
84
|
+
paint: u(r.outline.paint, t, "line", i),
|
|
84
85
|
width: o ? { px: s * o.pxPerUnit } : s
|
|
85
86
|
} : null;
|
|
86
87
|
(e !== void 0 || c !== null && c.paint !== void 0) && n.push({
|
|
@@ -97,34 +98,34 @@ function d(e, t) {
|
|
|
97
98
|
}
|
|
98
99
|
return n;
|
|
99
100
|
}
|
|
100
|
-
function
|
|
101
|
+
function p(e, t) {
|
|
101
102
|
if (e.reads === void 0) return null;
|
|
102
103
|
let n = "";
|
|
103
104
|
for (let r of e.reads) n += `${JSON.stringify(t[r]) ?? ""}|`;
|
|
104
105
|
return n;
|
|
105
106
|
}
|
|
106
|
-
var
|
|
107
|
-
function
|
|
108
|
-
let n =
|
|
107
|
+
var m = 64, h = /* @__PURE__ */ new WeakMap(), g = /* @__PURE__ */ new WeakMap();
|
|
108
|
+
function _(e, t) {
|
|
109
|
+
let n = s(e.path({
|
|
109
110
|
size: 1,
|
|
110
111
|
stroke: t
|
|
111
112
|
}));
|
|
112
113
|
return Math.max(Math.abs(n.x), Math.abs(n.x + n.width), Math.abs(n.y), Math.abs(n.y + n.height)) + (e.outline ? e.outline.width / 2 : 0);
|
|
113
114
|
}
|
|
114
|
-
function
|
|
115
|
-
let n =
|
|
115
|
+
function v(e, t) {
|
|
116
|
+
let n = p(e, t);
|
|
116
117
|
if (n === null) {
|
|
117
|
-
let n =
|
|
118
|
-
n === void 0 &&
|
|
118
|
+
let n = g.get(e);
|
|
119
|
+
n === void 0 && g.set(e, n = /* @__PURE__ */ new WeakMap());
|
|
119
120
|
let r = n.get(t);
|
|
120
|
-
return r === void 0 && n.set(t, r =
|
|
121
|
+
return r === void 0 && n.set(t, r = _(e, t)), r;
|
|
121
122
|
}
|
|
122
|
-
let r =
|
|
123
|
-
r === void 0 &&
|
|
123
|
+
let r = h.get(e);
|
|
124
|
+
r === void 0 && h.set(e, r = /* @__PURE__ */ new Map());
|
|
124
125
|
let i = r.get(n);
|
|
125
|
-
return i === void 0 && (r.size >=
|
|
126
|
+
return i === void 0 && (r.size >= m && r.clear(), r.set(n, i = _(e, t))), i;
|
|
126
127
|
}
|
|
127
|
-
function
|
|
128
|
+
function y(e) {
|
|
128
129
|
let t = {
|
|
129
130
|
world: 0,
|
|
130
131
|
px: 0
|
|
@@ -137,12 +138,12 @@ function v(e) {
|
|
|
137
138
|
if (a === void 0) continue;
|
|
138
139
|
let o = n(r(a));
|
|
139
140
|
if (o === void 0) continue;
|
|
140
|
-
let s = typeof a == "string" || a.size === void 0 ? i : a.size, c =
|
|
141
|
+
let s = typeof a == "string" || a.size === void 0 ? i : a.size, c = v(o, e);
|
|
141
142
|
typeof s == "number" ? t.world = Math.max(t.world, c * s) : t.px = Math.max(t.px, c * s.px);
|
|
142
143
|
}
|
|
143
144
|
return t;
|
|
144
145
|
}
|
|
145
146
|
//#endregion
|
|
146
|
-
export {
|
|
147
|
+
export { f as headCommands, d as markerHeads, y as markerReach, p as markerReadsKey };
|
|
147
148
|
|
|
148
149
|
//# sourceMappingURL=markerCommands.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markerCommands.js","names":["placed","src","path","cos","site","sin","coords","i","x","y","resolvePaint","v","p","fallback","stroke","markerHeads","refs","groups","role","space","spaces","want","out","pl","extractPolylines","inSpace","polylineIntoMetric","markerSites","ref","entry","getMarker","markerKeyOf","size","resolveMarkerSize","strokeWidth","angle","head","coordsOutOfMetric","headCommands","heads","fill","width","outline","markerReadsKey","key","field","READINGS_PER_ENTRY","REACH_BY_READING","REACH_BY_STROKE","measureUnitReach","b","boundsOfPath","unitReach","byStroke","reach","byReading","markerReach","unit"],"sources":["../../../src/features/paths/markerCommands.ts"],"sourcesContent":["/**\n * Turning a stroke's markers into draw commands.\n *\n * Separate `PathDrawCommand`s rather than triangles appended to the stroke\n * ribbon: an entry may carry a fill and an outline at once, or a paint that\n * differs from the line's, neither of which one mesh can express — and folding\n * them in would drag the whole marker vocabulary into the ribbon cache key.\n */\n\nimport type { MarkerRef, Stroke } from '@weasel-js/paint';\nimport { type Path, type PolygonPath, boundsOfPath } from '@weasel-js/geom';\nimport type { PathDrawCommand } from '../../renderer/DrawCommand';\nimport { getMarker, type MarkerEntry, type MarkerPaint } from '../../core/strokeMarkers';\nimport { markerKeyOf, resolveMarkerSize } from '../../core/markerInset';\nimport { extractPolylines } from '@weasel-js/geom/tessellate';\nimport { markerSites, type MarkerSite } from './markerSites';\nimport {\n coordsOutOfMetric, polylineIntoMetric, type ScreenSpace, type StrokeSpaces,\n} from './tessellate/metric';\n\n/** Rotate + translate a marker's geometry onto its site. */\nfunction placed(path: Path, site: MarkerSite): PolygonPath {\n const src = path as PolygonPath;\n const cos = Math.cos(site.angle);\n const sin = Math.sin(site.angle);\n const coords = new Float32Array(src.coords.length);\n for (let i = 0; i < src.coords.length; i += 2) {\n const x = src.coords[i], y = src.coords[i + 1];\n coords[i] = site.x + x * cos - y * sin;\n coords[i + 1] = site.y + x * sin + y * cos;\n }\n return { kind: 'polygon', commands: src.commands, coords, fillRule: src.fillRule };\n}\n\nfunction resolvePaint(p: MarkerPaint | undefined, stroke: Stroke, fallback: MarkerPaint) {\n const v = p ?? fallback;\n if (v === 'none') return undefined;\n return v === 'line' ? stroke.paint : v;\n}\n\n/** One head's placed geometry, before any paint is applied. */\nexport interface MarkerHead {\n readonly entry: MarkerEntry;\n readonly path: PolygonPath;\n readonly size: number;\n /** Where it was built, when not in world — `size` is in this space's units. */\n readonly space?: ScreenSpace;\n}\n\n/**\n * Every marker head for `path` under `stroke`, placed but unpainted.\n * `strokeWidth` is the already width-resolved stroke width; `flattenTolerance`\n * matches what the ribbon used, so heads land on the same flattened vertices\n * the stroke did. A head `spaces` puts in a screen space is placed and sized\n * there and comes back in world units, so it meets the line it caps.\n */\nexport function markerHeads(\n path: Path,\n stroke: Stroke,\n strokeWidth: number,\n flattenTolerance: number | undefined,\n spaces: StrokeSpaces = {},\n): MarkerHead[] {\n const refs = { start: stroke.markerStart, mid: stroke.markerMid, end: stroke.markerEnd };\n if (refs.start === undefined && refs.mid === undefined && refs.end === undefined) return [];\n\n // Heads sharing a space share one pass over the polyline mapped into it.\n const groups = new Map<ScreenSpace | undefined, { start: boolean; mid: boolean; end: boolean }>();\n for (const role of ['start', 'mid', 'end'] as const) {\n if (refs[role] === undefined) continue;\n const space = spaces[role];\n let want = groups.get(space);\n if (want === undefined) groups.set(space, (want = { start: false, mid: false, end: false }));\n want[role] = true;\n }\n\n const out: MarkerHead[] = [];\n for (const pl of extractPolylines(path, { flattenTolerance })) {\n for (const [space, want] of groups) {\n const inSpace = space ? { ...pl, points: pl.points.slice() } : pl;\n if (space) polylineIntoMetric(inSpace, space.metric);\n for (const site of markerSites(inSpace, want)) {\n const ref: MarkerRef | undefined = refs[site.role];\n if (ref === undefined) continue;\n const entry = getMarker(markerKeyOf(ref));\n if (entry === undefined) continue;\n const size = resolveMarkerSize(ref, strokeWidth);\n const angle = entry.orient === undefined || entry.orient === 'auto' ? site.angle : entry.orient;\n const head = placed(entry.path({ size, stroke }), { ...site, angle });\n if (space) coordsOutOfMetric(head.coords, space.metric);\n out.push(space ? { entry, size, path: head, space } : { entry, size, path: head });\n }\n }\n }\n return out;\n}\n\n/** `heads` as draw commands in `stroke`'s paint. A head whose fill and outline\n * both resolve to no paint is dropped. */\nexport function headCommands(heads: readonly MarkerHead[], stroke: Stroke): PathDrawCommand[] {\n const out: PathDrawCommand[] = [];\n for (const { entry, path, size, space } of heads) {\n const fill = resolvePaint(entry.fill, stroke, 'line');\n // A head built in a screen space is in world once placed, but its outline\n // width is still in that space's units — so it goes out as the screen\n // width it stands for, and is stroked as exactly as the line.\n const width = entry.outline ? entry.outline.width * size : 0;\n const outline = entry.outline\n ? { paint: resolvePaint(entry.outline.paint, stroke, 'line'), width: space ? { px: width * space.pxPerUnit } : width }\n : null;\n if (fill === undefined && (outline === null || outline.paint === undefined)) continue;\n out.push({\n kind: 'path',\n path,\n ...(fill ? { fill } : {}),\n ...(outline && outline.paint\n ? { stroke: { paint: outline.paint, width: outline.width, cap: 'round', join: 'round' } }\n : {}),\n });\n }\n return out;\n}\n\n/** Every marker command for `path` under `stroke`; see {@link markerHeads}. */\nexport function markerDrawCommands(\n path: Path,\n stroke: Stroke,\n strokeWidth: number,\n flattenTolerance: number | undefined,\n): PathDrawCommand[] {\n return headCommands(markerHeads(path, stroke, strokeWidth, flattenTolerance), stroke);\n}\n\n/**\n * A key for the stroke fields `entry`'s geometry reads, or `null` when the\n * entry does not say which it reads and only the stroke's identity can stand\n * in for them.\n */\nexport function markerReadsKey(entry: MarkerEntry, stroke: Stroke): string | null {\n if (entry.reads === undefined) return null;\n let key = '';\n for (const field of entry.reads) key += `${JSON.stringify(stroke[field]) ?? ''}|`;\n return key;\n}\n\n/** Distinct stroke readings kept per entry before its map is dropped. */\nconst READINGS_PER_ENTRY = 64;\n\n/** Each entry's farthest reach from its anchor at `size` 1, outline included,\n * per value of the fields it reads — or per stroke object, when it does not\n * say which. */\nconst REACH_BY_READING = new WeakMap<MarkerEntry, Map<string, number>>();\nconst REACH_BY_STROKE = new WeakMap<MarkerEntry, WeakMap<Stroke, number>>();\n\nfunction measureUnitReach(entry: MarkerEntry, stroke: Stroke): number {\n const b = boundsOfPath(entry.path({ size: 1, stroke }));\n return Math.max(Math.abs(b.x), Math.abs(b.x + b.width), Math.abs(b.y), Math.abs(b.y + b.height))\n + (entry.outline ? entry.outline.width / 2 : 0);\n}\n\nfunction unitReach(entry: MarkerEntry, stroke: Stroke): number {\n const key = markerReadsKey(entry, stroke);\n if (key === null) {\n let byStroke = REACH_BY_STROKE.get(entry);\n if (byStroke === undefined) REACH_BY_STROKE.set(entry, (byStroke = new WeakMap()));\n let reach = byStroke.get(stroke);\n if (reach === undefined) byStroke.set(stroke, (reach = measureUnitReach(entry, stroke)));\n return reach;\n }\n let byReading = REACH_BY_READING.get(entry);\n if (byReading === undefined) REACH_BY_READING.set(entry, (byReading = new Map()));\n let reach = byReading.get(key);\n if (reach === undefined) {\n if (byReading.size >= READINGS_PER_ENTRY) byReading.clear();\n byReading.set(key, (reach = measureUnitReach(entry, stroke)));\n }\n return reach;\n}\n\n/**\n * How far `stroke`'s markers can paint from the vertex each sits on, split by\n * where each is built: `world` in world units for a head with a world size,\n * `px` in screen pixels for one with a `{ px }` size. A head with no size\n * takes the stroke width, and with it the width's units. Culling and a path\n * node's grab reach both measure through here.\n */\nexport function markerReach(stroke: Stroke): { world: number; px: number } {\n const reach = { world: 0, px: 0 };\n const width = stroke.width ?? 1;\n for (const ref of [stroke.markerStart, stroke.markerMid, stroke.markerEnd]) {\n if (ref === undefined) continue;\n const entry = getMarker(markerKeyOf(ref));\n if (entry === undefined) continue;\n const size = typeof ref === 'string' || ref.size === undefined ? width : ref.size;\n const unit = unitReach(entry, stroke);\n if (typeof size === 'number') reach.world = Math.max(reach.world, unit * size);\n else reach.px = Math.max(reach.px, unit * size.px);\n }\n return reach;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"markerCommands.js","names":["placed","src","path","cos","site","sin","coords","i","x","y","resolvePaint","v","p","fallback","stroke","commandFill","boundsOfPath","head","markerHeads","refs","groups","role","space","spaces","want","out","pl","extractPolylines","inSpace","polylineIntoMetric","markerSites","ref","entry","getMarker","markerKeyOf","size","resolveMarkerSize","strokeWidth","angle","coordsOutOfMetric","headCommands","heads","fill","width","outline","markerReadsKey","key","field","READINGS_PER_ENTRY","REACH_BY_READING","REACH_BY_STROKE","measureUnitReach","b","unitReach","byStroke","reach","byReading","markerReach","unit"],"sources":["../../../src/features/paths/markerCommands.ts"],"sourcesContent":["/**\n * Turning a stroke's markers into draw commands.\n *\n * Separate `PathDrawCommand`s rather than triangles appended to the stroke\n * ribbon: an entry may carry a fill and an outline at once, or a paint that\n * differs from the line's, neither of which one mesh can express — and folding\n * them in would drag the whole marker vocabulary into the ribbon cache key.\n */\n\nimport type { CommandFill, CommandStroke, MarkerRef, Stroke } from '@weasel-js/paint';\nimport { type Path, type PolygonPath, boundsOfPath } from '@weasel-js/geom';\nimport type { PathDrawCommand } from '../../renderer/DrawCommand';\nimport { getMarker, type MarkerEntry, type MarkerPaint } from '../../core/strokeMarkers';\nimport { markerKeyOf, resolveMarkerSize } from '../../core/markerInset';\nimport { extractPolylines } from '@weasel-js/geom/tessellate';\nimport { markerSites, type MarkerSite } from './markerSites';\nimport { commandFill } from '../patterns/commandPaint';\nimport {\n coordsOutOfMetric, polylineIntoMetric, type ScreenSpace, type StrokeSpaces,\n} from './tessellate/metric';\n\n/** Rotate + translate a marker's geometry onto its site. */\nfunction placed(path: Path, site: MarkerSite): PolygonPath {\n const src = path as PolygonPath;\n const cos = Math.cos(site.angle);\n const sin = Math.sin(site.angle);\n const coords = new Float32Array(src.coords.length);\n for (let i = 0; i < src.coords.length; i += 2) {\n const x = src.coords[i], y = src.coords[i + 1];\n coords[i] = site.x + x * cos - y * sin;\n coords[i + 1] = site.y + x * sin + y * cos;\n }\n return { kind: 'polygon', commands: src.commands, coords, fillRule: src.fillRule };\n}\n\n/** A marker's own paint is resolved against its head's box, as a node's is\n * against the node's. */\nfunction resolvePaint(\n p: MarkerPaint | undefined, stroke: CommandStroke, fallback: MarkerPaint, head: PolygonPath,\n): CommandFill | undefined {\n const v = p ?? fallback;\n if (v === 'none') return undefined;\n if (v === 'line') return stroke.paint;\n return commandFill(v, boundsOfPath(head)) ?? undefined;\n}\n\n/** One head's placed geometry, before any paint is applied. */\nexport interface MarkerHead {\n readonly entry: MarkerEntry;\n readonly path: PolygonPath;\n readonly size: number;\n /** Where it was built, when not in world — `size` is in this space's units. */\n readonly space?: ScreenSpace;\n}\n\n/**\n * Every marker head for `path` under `stroke`, placed but unpainted.\n * `strokeWidth` is the already width-resolved stroke width; `flattenTolerance`\n * matches what the ribbon used, so heads land on the same flattened vertices\n * the stroke did. A head `spaces` puts in a screen space is placed and sized\n * there and comes back in world units, so it meets the line it caps.\n */\nexport function markerHeads(\n path: Path,\n stroke: Stroke,\n strokeWidth: number,\n flattenTolerance: number | undefined,\n spaces: StrokeSpaces = {},\n): MarkerHead[] {\n const refs = { start: stroke.markerStart, mid: stroke.markerMid, end: stroke.markerEnd };\n if (refs.start === undefined && refs.mid === undefined && refs.end === undefined) return [];\n\n // Heads sharing a space share one pass over the polyline mapped into it.\n const groups = new Map<ScreenSpace | undefined, { start: boolean; mid: boolean; end: boolean }>();\n for (const role of ['start', 'mid', 'end'] as const) {\n if (refs[role] === undefined) continue;\n const space = spaces[role];\n let want = groups.get(space);\n if (want === undefined) groups.set(space, (want = { start: false, mid: false, end: false }));\n want[role] = true;\n }\n\n const out: MarkerHead[] = [];\n for (const pl of extractPolylines(path, { flattenTolerance })) {\n for (const [space, want] of groups) {\n const inSpace = space ? { ...pl, points: pl.points.slice() } : pl;\n if (space) polylineIntoMetric(inSpace, space.metric);\n for (const site of markerSites(inSpace, want)) {\n const ref: MarkerRef | undefined = refs[site.role];\n if (ref === undefined) continue;\n const entry = getMarker(markerKeyOf(ref));\n if (entry === undefined) continue;\n const size = resolveMarkerSize(ref, strokeWidth);\n const angle = entry.orient === undefined || entry.orient === 'auto' ? site.angle : entry.orient;\n const head = placed(entry.path({ size, stroke }), { ...site, angle });\n if (space) coordsOutOfMetric(head.coords, space.metric);\n out.push(space ? { entry, size, path: head, space } : { entry, size, path: head });\n }\n }\n }\n return out;\n}\n\n/** `heads` as draw commands in `stroke`'s paint. A head whose fill and outline\n * both resolve to no paint is dropped. */\nexport function headCommands(heads: readonly MarkerHead[], stroke: CommandStroke): PathDrawCommand[] {\n const out: PathDrawCommand[] = [];\n for (const { entry, path, size, space } of heads) {\n const fill = resolvePaint(entry.fill, stroke, 'line', path);\n // A head built in a screen space is in world once placed, but its outline\n // width is still in that space's units — so it goes out as the screen\n // width it stands for, and is stroked as exactly as the line.\n const width = entry.outline ? entry.outline.width * size : 0;\n const outline = entry.outline\n ? { paint: resolvePaint(entry.outline.paint, stroke, 'line', path), width: space ? { px: width * space.pxPerUnit } : width }\n : null;\n if (fill === undefined && (outline === null || outline.paint === undefined)) continue;\n out.push({\n kind: 'path',\n path,\n ...(fill ? { fill } : {}),\n ...(outline && outline.paint\n ? { stroke: { paint: outline.paint, width: outline.width, cap: 'round', join: 'round' } }\n : {}),\n });\n }\n return out;\n}\n\n/** Every marker command for `path` under `stroke`; see {@link markerHeads}. */\nexport function markerDrawCommands(\n path: Path,\n stroke: CommandStroke,\n strokeWidth: number,\n flattenTolerance: number | undefined,\n): PathDrawCommand[] {\n return headCommands(markerHeads(path, stroke, strokeWidth, flattenTolerance), stroke);\n}\n\n/**\n * A key for the stroke fields `entry`'s geometry reads, or `null` when the\n * entry does not say which it reads and only the stroke's identity can stand\n * in for them.\n */\nexport function markerReadsKey(entry: MarkerEntry, stroke: Stroke): string | null {\n if (entry.reads === undefined) return null;\n let key = '';\n for (const field of entry.reads) key += `${JSON.stringify(stroke[field]) ?? ''}|`;\n return key;\n}\n\n/** Distinct stroke readings kept per entry before its map is dropped. */\nconst READINGS_PER_ENTRY = 64;\n\n/** Each entry's farthest reach from its anchor at `size` 1, outline included,\n * per value of the fields it reads — or per stroke object, when it does not\n * say which. */\nconst REACH_BY_READING = new WeakMap<MarkerEntry, Map<string, number>>();\nconst REACH_BY_STROKE = new WeakMap<MarkerEntry, WeakMap<Stroke, number>>();\n\nfunction measureUnitReach(entry: MarkerEntry, stroke: Stroke): number {\n const b = boundsOfPath(entry.path({ size: 1, stroke }));\n return Math.max(Math.abs(b.x), Math.abs(b.x + b.width), Math.abs(b.y), Math.abs(b.y + b.height))\n + (entry.outline ? entry.outline.width / 2 : 0);\n}\n\nfunction unitReach(entry: MarkerEntry, stroke: Stroke): number {\n const key = markerReadsKey(entry, stroke);\n if (key === null) {\n let byStroke = REACH_BY_STROKE.get(entry);\n if (byStroke === undefined) REACH_BY_STROKE.set(entry, (byStroke = new WeakMap()));\n let reach = byStroke.get(stroke);\n if (reach === undefined) byStroke.set(stroke, (reach = measureUnitReach(entry, stroke)));\n return reach;\n }\n let byReading = REACH_BY_READING.get(entry);\n if (byReading === undefined) REACH_BY_READING.set(entry, (byReading = new Map()));\n let reach = byReading.get(key);\n if (reach === undefined) {\n if (byReading.size >= READINGS_PER_ENTRY) byReading.clear();\n byReading.set(key, (reach = measureUnitReach(entry, stroke)));\n }\n return reach;\n}\n\n/**\n * How far `stroke`'s markers can paint from the vertex each sits on, split by\n * where each is built: `world` in world units for a head with a world size,\n * `px` in screen pixels for one with a `{ px }` size. A head with no size\n * takes the stroke width, and with it the width's units. Culling and a path\n * node's grab reach both measure through here.\n */\nexport function markerReach(stroke: Stroke): { world: number; px: number } {\n const reach = { world: 0, px: 0 };\n const width = stroke.width ?? 1;\n for (const ref of [stroke.markerStart, stroke.markerMid, stroke.markerEnd]) {\n if (ref === undefined) continue;\n const entry = getMarker(markerKeyOf(ref));\n if (entry === undefined) continue;\n const size = typeof ref === 'string' || ref.size === undefined ? width : ref.size;\n const unit = unitReach(entry, stroke);\n if (typeof size === 'number') reach.world = Math.max(reach.world, unit * size);\n else reach.px = Math.max(reach.px, unit * size.px);\n }\n return reach;\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { applyToPoint as
|
|
1
|
+
import { poseFrameInverseOf as e, poseFrameOf as t } from "../../core/geometry/poseFrame.js";
|
|
2
|
+
import { applyToPoint as n, boundsOfPath as r, boxToBox as i, transformPath as a, translatePath as o } from "@weasel-js/geom";
|
|
3
3
|
//#region src/features/paths/pathInWorld.ts
|
|
4
4
|
function s(e, t) {
|
|
5
5
|
if (e.kind === "rect") return e.x === t.x && e.y === t.y && e.width === t.width && e.height === t.height ? e : {
|
|
@@ -9,30 +9,30 @@ function s(e, t) {
|
|
|
9
9
|
width: t.width,
|
|
10
10
|
height: t.height
|
|
11
11
|
};
|
|
12
|
-
let
|
|
13
|
-
if (
|
|
14
|
-
let o =
|
|
12
|
+
let n = r(e);
|
|
13
|
+
if (n.x === t.x && n.y === t.y && n.width === t.width && n.height === t.height) return e;
|
|
14
|
+
let o = i(n.x, n.y, n.width, n.height, t.x, t.y, t.width, t.height);
|
|
15
15
|
return a(e, o);
|
|
16
16
|
}
|
|
17
|
-
function c(
|
|
18
|
-
let r = s(
|
|
19
|
-
return
|
|
17
|
+
function c(e, n) {
|
|
18
|
+
let r = s(e, n), i = t(n);
|
|
19
|
+
return i ? a(r, i) : r;
|
|
20
20
|
}
|
|
21
|
-
function l(
|
|
22
|
-
let c = e(
|
|
23
|
-
if (
|
|
24
|
-
let [e,
|
|
25
|
-
|
|
21
|
+
function l(i, s) {
|
|
22
|
+
let c = e(i), l = c && t(i), u = c ? a(s, c) : s, d = r(u), f = o(u, -d.x, -d.y), p = d.x, m = d.y;
|
|
23
|
+
if (l) {
|
|
24
|
+
let [e, t] = n(l, d.x + d.width / 2, d.y + d.height / 2);
|
|
25
|
+
p = e - d.width / 2, m = t - d.height / 2;
|
|
26
26
|
}
|
|
27
27
|
return {
|
|
28
28
|
pose: {
|
|
29
|
-
...
|
|
30
|
-
x:
|
|
31
|
-
y:
|
|
32
|
-
width:
|
|
33
|
-
height:
|
|
29
|
+
...i,
|
|
30
|
+
x: p,
|
|
31
|
+
y: m,
|
|
32
|
+
width: d.width,
|
|
33
|
+
height: d.height
|
|
34
34
|
},
|
|
35
|
-
path:
|
|
35
|
+
path: f
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pathInWorld.js","names":["pathInPoseFrame","path","pose","b","boundsOfPath","m","boxToBox","transformPath","pathInWorld","local","
|
|
1
|
+
{"version":3,"file":"pathInWorld.js","names":["pathInPoseFrame","path","pose","b","boundsOfPath","m","boxToBox","transformPath","pathInWorld","local","frame","poseFrameOf","worldEditToStorage","back","poseFrameInverseOf","unrotated","worldPath","aligned","translatePath","x","y","cx","cy","applyToPoint"],"sources":["../../../src/features/paths/pathInWorld.ts"],"sourcesContent":["/**\n * Project a leaf path from its source frame into world coordinates given a\n * `{x, y, width, height, rotation?}` pose. Two transforms compose, in this\n * order:\n *\n * 1. Translate so the path's source AABB origin lands at `pose.x, pose.y`.\n * 2. If `pose.rotation` is nonzero, rotate every coord about the unrotated\n * AABB center — i.e. `(pose.x + pose.width/2, pose.y + pose.height/2)`,\n * matching `SceneCanvas.rotateAroundAABBCenter` so the baked geometry\n * lines up with what the renderer draws.\n *\n * RectPaths can't carry rotation as-is (they're axis-aligned by definition);\n * a non-zero `pose.rotation` on a rect promotes the result to a polygon so\n * the four corners are baked into world coords. Callers that need to keep\n * the rect fast-path can pre-check `pose.rotation` themselves.\n *\n * Used by adapters that need true world geometry — most notably boolean ops,\n * where operating on the unrotated source path produces wrong shapes.\n */\n\nimport {\n applyToPoint,\n boxToBox,\n boundsOfPath,\n type Path,\n type PolygonPath,\n translatePath,\n transformPath,\n} from '@weasel-js/geom';\nimport { poseFrameInverseOf, poseFrameOf, type PoseLinear } from 'core/geometry/poseFrame';\n\n/** Subset of pose fields this helper consumes. Matches the kit's auto-rotate\n * convention (`SceneCanvas.defaultDrawOne`): `x/y/width/height` define an\n * AABB whose unrotated center is the rotation pivot. */\nexport interface PathInWorldPose {\n x: number;\n y: number;\n width: number;\n height: number;\n rotation?: number;\n transform?: PoseLinear;\n}\n\n/**\n * Project a stored path into the pose's **local frame** — translate (and, for\n * rects, rebase to the pose's `width`/`height`) so the path's AABB origin sits\n * at the pose origin. This is translate-only: it does NOT apply\n * `pose.rotation`. Callers that want the fully world-positioned (rotated) path\n * should use `pathInWorld`, which composes this with the rotation wrap.\n *\n * Rects rebase onto the pose's `width`/`height` because resize updates the pose,\n * not the stored `RectPath` — so the live size lives on the pose. Honoring it\n * here keeps this in lockstep with the renderer (`SceneCanvas` paints the rect\n * at `pose.width/height`); otherwise world-space consumers (slice, booleans,\n * release-compound) would operate on the stale pre-resize dimensions.\n *\n * Used by the local seams: the default painter (paint + silhouette), and as the\n * building block `pathInWorld`/`findShapeSilhouette` rotate on top of.\n */\nexport function pathInPoseFrame(path: Path, pose: PathInWorldPose): Path {\n if (path.kind === 'rect') {\n if (\n path.x === pose.x && path.y === pose.y\n && path.width === pose.width && path.height === pose.height\n ) return path;\n return { kind: 'rect', x: pose.x, y: pose.y, width: pose.width, height: pose.height };\n }\n // Polygon: rebase the path's own AABB into the pose box via a box→box affine.\n // Identity when the bounds already equal the pose box (geometry already tracks\n // the pose) — behavior-preserving there; for geometry-in-data nodes whose pose\n // box was resized independently, it scales the contents to fill the box,\n // fixing the anchor bug.\n const b = boundsOfPath(path);\n if (b.x === pose.x && b.y === pose.y && b.width === pose.width && b.height === pose.height) {\n return path;\n }\n const m = boxToBox(b.x, b.y, b.width, b.height, pose.x, pose.y, pose.width, pose.height);\n return transformPath(path, m);\n}\n\n/**\n * Bake `pose` into `path`'s coordinates so the returned path is positioned\n * in world space. Returns a fresh `Path` instance; never mutates inputs.\n */\nexport function pathInWorld(path: Path, pose: PathInWorldPose): Path {\n // Step 1: project into the pose's local frame (translate; rebase rect dims).\n // Shares `pathInPoseFrame` with the renderer so world geometry never diverges\n // from what's painted — notably for resized rects (pose dims, not path dims).\n const local = pathInPoseFrame(path, pose);\n\n // Step 2: apply the pose's frame about its AABB center, if any: its\n // rotation and its `transform`, composed by `poseFrameOf` as the renderer's\n // wrap composes them. `transformPath` handles the rect->polygon promotion\n // and bezier control points.\n const frame = poseFrameOf(pose);\n if (!frame) return local;\n return transformPath(local, frame) as PolygonPath;\n}\n\n/**\n * Inverse of `pathInWorld` for a path in **world** space: the stored path\n * in the pose's own frame plus the pose that draws it there. Takes the path\n * back through the pose's frame (`poseFrameOf`), so the pose's `rotation`\n * and `transform` are preserved, then fits the pose's AABB fields to the\n * result. The new box's center is placed where the old frame carries it,\n * since the pivot moves with the box: `pathInWorld(result.path, result.pose)`\n * reproduces `worldPath` whether or not its bounds changed.\n *\n * Generic in the pose type so consumer-defined pose fields (including\n * `rotation`) survive the round-trip. Used by the anchor-edit commit, its\n * live preview, and the default slice dep.\n */\nexport function worldEditToStorage<P extends PathInWorldPose>(\n pose: P,\n worldPath: PolygonPath,\n): { pose: P; path: PolygonPath };\nexport function worldEditToStorage<P extends PathInWorldPose>(pose: P, worldPath: Path): { pose: P; path: Path };\nexport function worldEditToStorage<P extends PathInWorldPose>(\n pose: P,\n worldPath: Path,\n): { pose: P; path: Path } {\n // A singular `transform` has no way back, and the path is stored as drawn.\n const back = poseFrameInverseOf(pose);\n const frame = back && poseFrameOf(pose);\n const unrotated = back ? transformPath(worldPath, back) : worldPath;\n const b = boundsOfPath(unrotated);\n const aligned = translatePath(unrotated, -b.x, -b.y);\n let x = b.x, y = b.y;\n if (frame) {\n const [cx, cy] = applyToPoint(frame, b.x + b.width / 2, b.y + b.height / 2);\n x = cx - b.width / 2;\n y = cy - b.height / 2;\n }\n return {\n pose: { ...pose, x, y, width: b.width, height: b.height },\n path: aligned,\n };\n}\n"],"mappings":";;;AA2DA,SAAgBA,EAAgB,GAAY,GAA6B;CACvE,IAAIC,EAAK,SAAS,QAKhB,OAHEA,EAAK,MAAMC,EAAK,KAAKD,EAAK,MAAMC,EAAK,KAClCD,EAAK,UAAUC,EAAK,SAASD,EAAK,WAAWC,EAAK,SAC9CD,IACF;EAAE,MAAM;EAAQ,GAAGC,EAAK;EAAG,GAAGA,EAAK;EAAG,OAAOA,EAAK;EAAO,QAAQA,EAAK;CAAO;CAOtF,IAAMC,IAAIC,EAAaH,CAAI;CAC3B,IAAIE,EAAE,MAAMD,EAAK,KAAKC,EAAE,MAAMD,EAAK,KAAKC,EAAE,UAAUD,EAAK,SAASC,EAAE,WAAWD,EAAK,QAClF,OAAOD;CAET,IAAMI,IAAIC,EAASH,EAAE,GAAGA,EAAE,GAAGA,EAAE,OAAOA,EAAE,QAAQD,EAAK,GAAGA,EAAK,GAAGA,EAAK,OAAOA,EAAK,MAAM;CACvF,OAAOK,EAAcN,GAAMI,CAAC;AAC9B;AAMA,SAAgBG,EAAY,GAAY,GAA6B;CAInE,IAAMC,IAAQT,EAAgBC,GAAMC,CAAI,GAMlCQ,IAAQC,EAAYT,CAAI;CAE9B,OADKQ,IACEH,EAAcE,GAAOC,CAAK,IADdD;AAErB;AAoBA,SAAgBG,EACd,GACA,GACyB;CAEzB,IAAMC,IAAOC,EAAmBZ,CAAI,GAC9BQ,IAAQG,KAAQF,EAAYT,CAAI,GAChCa,IAAYF,IAAON,EAAcS,GAAWH,CAAI,IAAIG,GACpDb,IAAIC,EAAaW,CAAS,GAC1BE,IAAUC,EAAcH,GAAW,CAACZ,EAAE,GAAG,CAACA,EAAE,CAAC,GAC/CgB,IAAIhB,EAAE,GAAGiB,IAAIjB,EAAE;CACnB,IAAIO,GAAO;EACT,IAAM,CAACW,GAAIC,KAAMC,EAAab,GAAOP,EAAE,IAAIA,EAAE,QAAQ,GAAGA,EAAE,IAAIA,EAAE,SAAS,CAAC;EAE1E,AADA,IAAIkB,IAAKlB,EAAE,QAAQ,GACnB,IAAImB,IAAKnB,EAAE,SAAS;CACtB;CACA,OAAO;EACL,MAAM;GAAE,GAAGD;GAAM;GAAG;GAAG,OAAOC,EAAE;GAAO,QAAQA,EAAE;EAAO;EACxD,MAAMc;CACR;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pathLayer.js","names":["PLACEHOLDER_FILL","PLACEHOLDER_STROKE","createPathLayer","opts","children","nodes","getNodes","idx","node","isHidden","path","getPath","fillFromHook","getFill","strokeFromHook","getStroke","baseVColors","getVertexColors","baseStrokeVColors","getStrokeVertexColors","strokeVWidths","getStrokeVertexWidths","nodeId","tMs","colorOverrides","now","vColors","strokeVColors","anchorCount","countPathAnchors","expectedLen","useVColors","useStrokeVColors","fill","useStrokeVWidths","baseStroke","stroke"],"sources":["../../../src/features/paths/pathLayer.ts"],"sourcesContent":["/**\n * `RenderLayer` that fills/strokes `Path` instances. Mirrors the shape of\n * `createTextLayer`: caller hands over a `getNodes()` enumerator and a\n * `getPath(node)` lookup; the layer iterates and renders. FillStyle and\n * stroke are looked up per node so consumers can keep the path geometry\n * separate from the visual style record (the typical scene-graph layout).\n */\n\nimport { type DrawCommand } from '../../renderer';\nimport { type FillStyle, type Stroke } from '@weasel-js/paint';\nimport type { RenderLayer } from 'core/layers/render';\nimport type { Path } from '@weasel-js/geom';\nimport { countPathAnchors } from './anchors';\nimport type { ColorOverrideRegistry } from '../../animation/colorRegistry';\n\nconst PLACEHOLDER_FILL: FillStyle = { color: '#ffffff' };\nconst PLACEHOLDER_STROKE: Stroke = { paint: { color: '#ffffff' }, width: 1 };\n\n\n/** Options for `createPathLayer`. */\nexport interface CreatePathLayerOpts<T> {\n id?: string;\n label?: string;\n getNodes: () => readonly T[];\n getPath: (node: T) => Path;\n /** Per-node fill paint. Return `null`/`undefined` to skip filling. */\n getFill?: (node: T) => FillStyle | null | undefined;\n /** Per-node stroke. Return `null`/`undefined` to skip stroking. */\n getStroke?: (node: T) => Stroke | null | undefined;\n /** Optional per-node hide hook (e.g., suppress while editing). */\n isHidden?: (node: T) => boolean;\n /**\n * Per-node fill vertex-color array, flat RGBA-per-path-anchor, in\n * 0..1 floats (same color space as `PathDrawCommand.vertexColors`).\n * Length must be `4 × countPathAnchors(getPath(node))`. When set and\n * `getFill` returns null/undefined, a white placeholder fill is\n * synthesized so the renderer's per-vertex shader path activates.\n */\n getVertexColors?: (node: T) => number[] | null | undefined;\n /**\n * Per-node stroke vertex-color array, flat RGBA-per-path-anchor, in\n * 0..1 floats (same color space as `Stroke.vertexColors`).\n * Length must be `4 × countPathAnchors(getPath(node))`. When set and\n * `getStroke` returns null/undefined, a white 1px placeholder stroke\n * is synthesized.\n */\n getStrokeVertexColors?: (node: T) => number[] | null | undefined;\n /**\n * Per-node stroke vertex-width array (one width per anchor).\n * Length must be `countPathAnchors(getPath(node))`. The tessellator\n * interpolates half-widths along each segment for tapered strokes.\n * When set and `getStroke` returns null/undefined, a 1px placeholder\n * stroke is synthesized so the per-anchor widths take effect.\n */\n getStrokeVertexWidths?: (node: T) => number[] | null | undefined;\n /**\n * Optional color override registry, typically `animator.colorOverrides`.\n * When set, the renderer consults it before falling back to\n * `getVertexColors` / `getStrokeVertexColors`. Function-form overrides\n * receive the base color array and the current animation timestamp.\n */\n colorOverrides?: ColorOverrideRegistry;\n /** Clock used to timestamp function-form color overrides. Defaults to\n * `performance.now`. Override in tests. */\n now?: () => number;\n}\n\n/** Build a `RenderLayer` that fills/strokes `Path` instances enumerated from a node list. */\nexport function createPathLayer<T>(opts: CreatePathLayerOpts<T>): RenderLayer<unknown> {\n const {\n id = 'paths', label = 'Paths',\n getNodes, getPath, getFill, getStroke, isHidden,\n getVertexColors, getStrokeVertexColors, getStrokeVertexWidths,\n colorOverrides,\n now = (): number => (typeof performance !== 'undefined' ? performance.now() : Date.now()),\n } = opts;\n const warned = new Set<string>();\n const isDev = typeof import.meta !== 'undefined'\n && (import.meta as unknown as { env?: { DEV?: boolean } }).env\n && (import.meta as unknown as { env?: { DEV?: boolean } }).env?.DEV;\n\n return {\n id,\n label,\n draw: () => {\n const children: DrawCommand[] = [];\n const nodes = getNodes();\n for (let idx = 0; idx < nodes.length; idx++) {\n const node = nodes[idx];\n if (isHidden?.(node)) continue;\n const path = getPath(node);\n const fillFromHook = getFill?.(node);\n const strokeFromHook = getStroke?.(node);\n const baseVColors = getVertexColors?.(node);\n const baseStrokeVColors = getStrokeVertexColors?.(node);\n const strokeVWidths = getStrokeVertexWidths?.(node);\n\n const nodeId = (node as { id?: string }).id ?? String(idx);\n const tMs = colorOverrides ? now() : 0;\n\n const vColors = colorOverrides\n ? colorOverrides.resolve(nodeId, 'fill', baseVColors ?? undefined, tMs)\n : baseVColors;\n const strokeVColors = colorOverrides\n ? colorOverrides.resolve(nodeId, 'stroke', baseStrokeVColors ?? undefined, tMs)\n : baseStrokeVColors;\n\n const nodeKey = nodeId;\n const anchorCount = countPathAnchors(path);\n const expectedLen = 4 * anchorCount;\n\n let useVColors: readonly number[] | null = null;\n if (vColors != null) {\n if (vColors.length === expectedLen) {\n useVColors = vColors;\n } else if (isDev) {\n const key = `${id}:${nodeKey}:fill`;\n if (!warned.has(key)) {\n warned.add(key);\n console.warn(\n `[createPathLayer ${id}] node ${nodeKey}: fill vertexColors length ${vColors.length}, expected ${expectedLen}; dropping`,\n );\n }\n }\n }\n\n let useStrokeVColors: readonly number[] | null = null;\n if (strokeVColors != null) {\n if (strokeVColors.length === expectedLen) {\n useStrokeVColors = strokeVColors;\n } else if (isDev) {\n const key = `${id}:${nodeKey}:stroke`;\n if (!warned.has(key)) {\n warned.add(key);\n console.warn(\n `[createPathLayer ${id}] node ${nodeKey}: stroke vertexColors length ${strokeVColors.length}, expected ${expectedLen}; dropping`,\n );\n }\n }\n }\n\n // Synthesize placeholder only when colors validate (useVColors != null),\n // not on any raw hook return. Mismatched-length arrays drop both the\n // colors and placeholder so the dev signal (console warning) isn't duplicated.\n const fill: FillStyle | undefined =\n fillFromHook != null ? fillFromHook\n : (useVColors != null ? PLACEHOLDER_FILL : undefined);\n\n let useStrokeVWidths: number[] | null = null;\n if (strokeVWidths != null) {\n if (strokeVWidths.length === anchorCount) {\n useStrokeVWidths = strokeVWidths;\n } else if (isDev) {\n const key = `${id}:${nodeKey}:strokeWidths`;\n if (!warned.has(key)) {\n warned.add(key);\n console.warn(\n `[createPathLayer ${id}] node ${nodeKey}: stroke vertexWidths length ${strokeVWidths.length}, expected ${anchorCount}; dropping`,\n );\n }\n }\n }\n\n const baseStroke: Stroke | undefined =\n strokeFromHook != null ? strokeFromHook\n : (useStrokeVColors != null || useStrokeVWidths != null ? PLACEHOLDER_STROKE : undefined);\n\n const stroke: Stroke | undefined =\n baseStroke != null\n ? {\n ...baseStroke,\n ...(useStrokeVColors != null ? { vertexColors: useStrokeVColors as number[] } : {}),\n ...(useStrokeVWidths != null ? { vertexWidths: useStrokeVWidths } : {}),\n }\n : baseStroke;\n\n if (fill == null && stroke == null) continue;\n\n children.push({\n kind: 'path',\n path,\n ...(fill != null ? { fill } : {}),\n ...(stroke != null ? { stroke } : {}),\n ...(useVColors != null ? { vertexColors: useVColors as number[] } : {}),\n });\n }\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n return children;\n },\n };\n}\n"],"mappings":";;;AAeA,IAAMA,IAA8B,EAAE,OAAO,UAAU,GACjDC,IAA6B;CAAE,OAAO,EAAE,OAAO,UAAU;CAAG,OAAO;AAAE;AAoD3E,SAAgBC,EAAmB,GAAoD;CACrF,IAAM,EACJ,QAAK,SAAS,WAAQ,SACtB,aAAU,YAAS,YAAS,cAAW,aACvC,oBAAiB,0BAAuB,0BACxC,mBACA,eAAqB,OAAO,cAAgB,MAAc,YAAY,IAAI,IAAI,KAAK,IAAI,MACrFC;CAMJ,OAAO;EACL;EACA;EACA,YAAY;GACV,IAAMC,IAA0B,CAAC,GAC3BC,IAAQC,EAAS;GACvB,KAAK,IAAIC,IAAM,GAAGA,IAAMF,EAAM,QAAQ,KAAO;IAC3C,IAAMG,IAAOH,EAAME;IACnB,IAAIE,IAAWD,CAAI,GAAG;IACtB,IAAME,IAAOC,EAAQH,CAAI,GACnBI,IAAeC,IAAUL,CAAI,GAC7BM,IAAiBC,IAAYP,CAAI,GACjCQ,IAAcC,IAAkBT,CAAI,GACpCU,IAAoBC,IAAwBX,CAAI,GAChDY,IAAgBC,IAAwBb,CAAI,GAE5Cc,IAAUd,EAAyB,MAAM,OAAOD,CAAG,GACnDgB,IAAMC,IAAiBC,EAAI,IAAI,GAE/BC,IAAUF,IACZA,EAAe,QAAQF,GAAQ,QAAQN,KAAe,KAAA,GAAWO,CAAG,IACpEP,GACEW,IAAgBH,IAClBA,EAAe,QAAQF,GAAQ,UAAUJ,KAAqB,KAAA,GAAWK,CAAG,IAC5EL,GAGEU,IAAcC,EAAiBnB,CAAI,GACnCoB,IAAc,IAAIF,GAEpBG,IAAuC;IAC3C,AAAIL,KAAW,QACTA,EAAQ,WAAWI,MACrB,IAAaJ;IAYjB,IAAIM,IAA6C;IACjD,AAAIL,KAAiB,QACfA,EAAc,WAAWG,MAC3B,IAAmBH;IAevB,IAAMM,IACJrB,MACGmB,KAAc,OAA0B,KAAA,IAAnB/B,IAEtBkC,IAAoC;IACxC,AAAId,KAAiB,QACfA,EAAc,WAAWQ,MAC3B,IAAmBR;IAYvB,IAAMe,IACJrB,MACGkB,KAAoB,QAAQE,KAAoB,OAAOjC,IAAqB,KAAA,IAE3EmC,IACJD,KAAc,OAMVA,IALA;KACE,GAAGA;KACH,GAAIH,KAAoB,OAAwD,CAAC,IAAlD,EAAE,cAAcA,EAA6B;KAC5E,GAAIE,KAAoB,OAA4C,CAAC,IAAtC,EAAE,cAAcA,EAAiB;IAClE;IAGN,CAAID,KAAQ,QAAQG,KAAU,SAE9BhC,EAAS,KAAK;KACZ,MAAM;KACN;KACA,GAAI6B,KAAQ,OAAkB,CAAC,IAAZ,EAAE,QAAK;KAC1B,GAAIG,KAAU,OAAoB,CAAC,IAAd,EAAE,UAAO;KAC9B,GAAIL,KAAc,OAAkD,CAAC,IAA5C,EAAE,cAAcA,EAAuB;IAClE,CAAC;GACH;GAEA,OAAO3B;EACT;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"pathLayer.js","names":["PLACEHOLDER_FILL","PLACEHOLDER_STROKE","createPathLayer","opts","children","nodes","getNodes","idx","node","isHidden","path","getPath","fillFromHook","getFill","strokeFromHook","getStroke","baseVColors","getVertexColors","baseStrokeVColors","getStrokeVertexColors","strokeVWidths","getStrokeVertexWidths","nodeId","tMs","colorOverrides","now","vColors","strokeVColors","anchorCount","countPathAnchors","expectedLen","useVColors","useStrokeVColors","fill","useStrokeVWidths","baseStroke","stroke"],"sources":["../../../src/features/paths/pathLayer.ts"],"sourcesContent":["/**\n * `RenderLayer` that fills/strokes `Path` instances. Mirrors the shape of\n * `createTextLayer`: caller hands over a `getNodes()` enumerator and a\n * `getPath(node)` lookup; the layer iterates and renders. CommandFill and\n * stroke are looked up per node so consumers can keep the path geometry\n * separate from the visual style record (the typical scene-graph layout).\n */\n\nimport { type DrawCommand } from '../../renderer';\nimport { type CommandFill, type CommandStroke } from '@weasel-js/paint';\nimport type { RenderLayer } from 'core/layers/render';\nimport type { Path } from '@weasel-js/geom';\nimport { countPathAnchors } from './anchors';\nimport type { ColorOverrideRegistry } from '../../animation/colorRegistry';\n\nconst PLACEHOLDER_FILL: CommandFill = { color: '#ffffff' };\nconst PLACEHOLDER_STROKE: CommandStroke = { paint: { color: '#ffffff' }, width: 1 };\n\n\n/** Options for `createPathLayer`. */\nexport interface CreatePathLayerOpts<T> {\n id?: string;\n label?: string;\n getNodes: () => readonly T[];\n getPath: (node: T) => Path;\n /** Per-node fill paint. Return `null`/`undefined` to skip filling. */\n getFill?: (node: T) => CommandFill | null | undefined;\n /** Per-node stroke. Return `null`/`undefined` to skip stroking. */\n getStroke?: (node: T) => CommandStroke | null | undefined;\n /** Optional per-node hide hook (e.g., suppress while editing). */\n isHidden?: (node: T) => boolean;\n /**\n * Per-node fill vertex-color array, flat RGBA-per-path-anchor, in\n * 0..1 floats (same color space as `PathDrawCommand.vertexColors`).\n * Length must be `4 × countPathAnchors(getPath(node))`. When set and\n * `getFill` returns null/undefined, a white placeholder fill is\n * synthesized so the renderer's per-vertex shader path activates.\n */\n getVertexColors?: (node: T) => number[] | null | undefined;\n /**\n * Per-node stroke vertex-color array, flat RGBA-per-path-anchor, in\n * 0..1 floats (same color space as `Stroke.vertexColors`).\n * Length must be `4 × countPathAnchors(getPath(node))`. When set and\n * `getStroke` returns null/undefined, a white 1px placeholder stroke\n * is synthesized.\n */\n getStrokeVertexColors?: (node: T) => number[] | null | undefined;\n /**\n * Per-node stroke vertex-width array (one width per anchor).\n * Length must be `countPathAnchors(getPath(node))`. The tessellator\n * interpolates half-widths along each segment for tapered strokes.\n * When set and `getStroke` returns null/undefined, a 1px placeholder\n * stroke is synthesized so the per-anchor widths take effect.\n */\n getStrokeVertexWidths?: (node: T) => number[] | null | undefined;\n /**\n * Optional color override registry, typically `animator.colorOverrides`.\n * When set, the renderer consults it before falling back to\n * `getVertexColors` / `getStrokeVertexColors`. Function-form overrides\n * receive the base color array and the current animation timestamp.\n */\n colorOverrides?: ColorOverrideRegistry;\n /** Clock used to timestamp function-form color overrides. Defaults to\n * `performance.now`. Override in tests. */\n now?: () => number;\n}\n\n/** Build a `RenderLayer` that fills/strokes `Path` instances enumerated from a node list. */\nexport function createPathLayer<T>(opts: CreatePathLayerOpts<T>): RenderLayer<unknown> {\n const {\n id = 'paths', label = 'Paths',\n getNodes, getPath, getFill, getStroke, isHidden,\n getVertexColors, getStrokeVertexColors, getStrokeVertexWidths,\n colorOverrides,\n now = (): number => (typeof performance !== 'undefined' ? performance.now() : Date.now()),\n } = opts;\n const warned = new Set<string>();\n const isDev = typeof import.meta !== 'undefined'\n && (import.meta as unknown as { env?: { DEV?: boolean } }).env\n && (import.meta as unknown as { env?: { DEV?: boolean } }).env?.DEV;\n\n return {\n id,\n label,\n draw: () => {\n const children: DrawCommand[] = [];\n const nodes = getNodes();\n for (let idx = 0; idx < nodes.length; idx++) {\n const node = nodes[idx];\n if (isHidden?.(node)) continue;\n const path = getPath(node);\n const fillFromHook = getFill?.(node);\n const strokeFromHook = getStroke?.(node);\n const baseVColors = getVertexColors?.(node);\n const baseStrokeVColors = getStrokeVertexColors?.(node);\n const strokeVWidths = getStrokeVertexWidths?.(node);\n\n const nodeId = (node as { id?: string }).id ?? String(idx);\n const tMs = colorOverrides ? now() : 0;\n\n const vColors = colorOverrides\n ? colorOverrides.resolve(nodeId, 'fill', baseVColors ?? undefined, tMs)\n : baseVColors;\n const strokeVColors = colorOverrides\n ? colorOverrides.resolve(nodeId, 'stroke', baseStrokeVColors ?? undefined, tMs)\n : baseStrokeVColors;\n\n const nodeKey = nodeId;\n const anchorCount = countPathAnchors(path);\n const expectedLen = 4 * anchorCount;\n\n let useVColors: readonly number[] | null = null;\n if (vColors != null) {\n if (vColors.length === expectedLen) {\n useVColors = vColors;\n } else if (isDev) {\n const key = `${id}:${nodeKey}:fill`;\n if (!warned.has(key)) {\n warned.add(key);\n console.warn(\n `[createPathLayer ${id}] node ${nodeKey}: fill vertexColors length ${vColors.length}, expected ${expectedLen}; dropping`,\n );\n }\n }\n }\n\n let useStrokeVColors: readonly number[] | null = null;\n if (strokeVColors != null) {\n if (strokeVColors.length === expectedLen) {\n useStrokeVColors = strokeVColors;\n } else if (isDev) {\n const key = `${id}:${nodeKey}:stroke`;\n if (!warned.has(key)) {\n warned.add(key);\n console.warn(\n `[createPathLayer ${id}] node ${nodeKey}: stroke vertexColors length ${strokeVColors.length}, expected ${expectedLen}; dropping`,\n );\n }\n }\n }\n\n // Synthesize placeholder only when colors validate (useVColors != null),\n // not on any raw hook return. Mismatched-length arrays drop both the\n // colors and placeholder so the dev signal (console warning) isn't duplicated.\n const fill: CommandFill | undefined =\n fillFromHook != null ? fillFromHook\n : (useVColors != null ? PLACEHOLDER_FILL : undefined);\n\n let useStrokeVWidths: number[] | null = null;\n if (strokeVWidths != null) {\n if (strokeVWidths.length === anchorCount) {\n useStrokeVWidths = strokeVWidths;\n } else if (isDev) {\n const key = `${id}:${nodeKey}:strokeWidths`;\n if (!warned.has(key)) {\n warned.add(key);\n console.warn(\n `[createPathLayer ${id}] node ${nodeKey}: stroke vertexWidths length ${strokeVWidths.length}, expected ${anchorCount}; dropping`,\n );\n }\n }\n }\n\n const baseStroke: CommandStroke | undefined =\n strokeFromHook != null ? strokeFromHook\n : (useStrokeVColors != null || useStrokeVWidths != null ? PLACEHOLDER_STROKE : undefined);\n\n const stroke: CommandStroke | undefined =\n baseStroke != null\n ? {\n ...baseStroke,\n ...(useStrokeVColors != null ? { vertexColors: useStrokeVColors as number[] } : {}),\n ...(useStrokeVWidths != null ? { vertexWidths: useStrokeVWidths } : {}),\n }\n : baseStroke;\n\n if (fill == null && stroke == null) continue;\n\n children.push({\n kind: 'path',\n path,\n ...(fill != null ? { fill } : {}),\n ...(stroke != null ? { stroke } : {}),\n ...(useVColors != null ? { vertexColors: useVColors as number[] } : {}),\n });\n }\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n return children;\n },\n };\n}\n"],"mappings":";;;AAeA,IAAMA,IAAgC,EAAE,OAAO,UAAU,GACnDC,IAAoC;CAAE,OAAO,EAAE,OAAO,UAAU;CAAG,OAAO;AAAE;AAoDlF,SAAgBC,EAAmB,GAAoD;CACrF,IAAM,EACJ,QAAK,SAAS,WAAQ,SACtB,aAAU,YAAS,YAAS,cAAW,aACvC,oBAAiB,0BAAuB,0BACxC,mBACA,eAAqB,OAAO,cAAgB,MAAc,YAAY,IAAI,IAAI,KAAK,IAAI,MACrFC;CAMJ,OAAO;EACL;EACA;EACA,YAAY;GACV,IAAMC,IAA0B,CAAC,GAC3BC,IAAQC,EAAS;GACvB,KAAK,IAAIC,IAAM,GAAGA,IAAMF,EAAM,QAAQ,KAAO;IAC3C,IAAMG,IAAOH,EAAME;IACnB,IAAIE,IAAWD,CAAI,GAAG;IACtB,IAAME,IAAOC,EAAQH,CAAI,GACnBI,IAAeC,IAAUL,CAAI,GAC7BM,IAAiBC,IAAYP,CAAI,GACjCQ,IAAcC,IAAkBT,CAAI,GACpCU,IAAoBC,IAAwBX,CAAI,GAChDY,IAAgBC,IAAwBb,CAAI,GAE5Cc,IAAUd,EAAyB,MAAM,OAAOD,CAAG,GACnDgB,IAAMC,IAAiBC,EAAI,IAAI,GAE/BC,IAAUF,IACZA,EAAe,QAAQF,GAAQ,QAAQN,KAAe,KAAA,GAAWO,CAAG,IACpEP,GACEW,IAAgBH,IAClBA,EAAe,QAAQF,GAAQ,UAAUJ,KAAqB,KAAA,GAAWK,CAAG,IAC5EL,GAGEU,IAAcC,EAAiBnB,CAAI,GACnCoB,IAAc,IAAIF,GAEpBG,IAAuC;IAC3C,AAAIL,KAAW,QACTA,EAAQ,WAAWI,MACrB,IAAaJ;IAYjB,IAAIM,IAA6C;IACjD,AAAIL,KAAiB,QACfA,EAAc,WAAWG,MAC3B,IAAmBH;IAevB,IAAMM,IACJrB,MACGmB,KAAc,OAA0B,KAAA,IAAnB/B,IAEtBkC,IAAoC;IACxC,AAAId,KAAiB,QACfA,EAAc,WAAWQ,MAC3B,IAAmBR;IAYvB,IAAMe,IACJrB,MACGkB,KAAoB,QAAQE,KAAoB,OAAOjC,IAAqB,KAAA,IAE3EmC,IACJD,KAAc,OAMVA,IALA;KACE,GAAGA;KACH,GAAIH,KAAoB,OAAwD,CAAC,IAAlD,EAAE,cAAcA,EAA6B;KAC5E,GAAIE,KAAoB,OAA4C,CAAC,IAAtC,EAAE,cAAcA,EAAiB;IAClE;IAGN,CAAID,KAAQ,QAAQG,KAAU,SAE9BhC,EAAS,KAAK;KACZ,MAAM;KACN;KACA,GAAI6B,KAAQ,OAAkB,CAAC,IAAZ,EAAE,QAAK;KAC1B,GAAIG,KAAU,OAAoB,CAAC,IAAd,EAAE,UAAO;KAC9B,GAAIL,KAAc,OAAkD,CAAC,IAA5C,EAAE,cAAcA,EAAuB;IAClE,CAAC;GACH;GAEA,OAAO3B;EACT;CACF;AACF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { resolveStrokeWidth as e, tessellateStroke as t } from "./tessellate/stroke.js";
|
|
2
|
+
import { strokeInsets as n } from "../../core/markerInset.js";
|
|
3
|
+
import { markerHeads as r } from "./markerCommands.js";
|
|
4
|
+
import { pathIntersect as i, pathSubtract as a, pathUnion as o, trianglesToPath as s } from "@weasel-js/geom/booleans";
|
|
5
|
+
//#region src/features/paths/strokeToPath.ts
|
|
6
|
+
var c = {
|
|
7
|
+
kind: "polygon",
|
|
8
|
+
commands: /* @__PURE__ */ new Uint8Array(),
|
|
9
|
+
coords: /* @__PURE__ */ new Float32Array(),
|
|
10
|
+
fillRule: "nonzero"
|
|
11
|
+
};
|
|
12
|
+
function l(u, d, f = {}) {
|
|
13
|
+
let p = e(d.width ?? 1, f.scale ?? 1);
|
|
14
|
+
if (!(p > 0)) return c;
|
|
15
|
+
let m = f.flattenTolerance, h = {
|
|
16
|
+
...d,
|
|
17
|
+
width: p
|
|
18
|
+
}, g = n(h, p), _ = d.align ?? "center", v = _ !== "center" && u.kind !== "rect", y = v ? {
|
|
19
|
+
...h,
|
|
20
|
+
width: p * 2,
|
|
21
|
+
align: "center",
|
|
22
|
+
vertexWidths: d.vertexWidths?.map((e) => e * 2)
|
|
23
|
+
} : h, b = t(u, y, {
|
|
24
|
+
flattenTolerance: m,
|
|
25
|
+
startInset: g.start,
|
|
26
|
+
endInset: g.end
|
|
27
|
+
}), x = s(b.vertices, b.indices);
|
|
28
|
+
v && (x = _ === "inner" ? i(x, u) : a(x, u));
|
|
29
|
+
let S = [];
|
|
30
|
+
for (let { entry: e, path: t, size: n } of r(u, h, p, m)) (e.fill ?? "line") !== "none" && S.push(t), e.outline && (e.outline.paint ?? "line") !== "none" && S.push(l(t, {
|
|
31
|
+
width: e.outline.width * n,
|
|
32
|
+
cap: "round",
|
|
33
|
+
join: "round"
|
|
34
|
+
}, f));
|
|
35
|
+
return S.length === 0 ? x : o(x, ...S);
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { l as strokeToPath };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=strokeToPath.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strokeToPath.js","names":["EMPTY","strokeToPath","width","resolveStrokeWidth","stroke","opts","tolerance","sized","insets","strokeInsets","align","cut","path","ribbonStroke","w","mesh","tessellateStroke","ribbon","trianglesToPath","pathIntersect","pathSubtract","heads","head","markerHeads","entry","size","pathUnion"],"sources":["../../../src/features/paths/strokeToPath.ts"],"sourcesContent":["/**\n * A stroke as geometry: the region its ink covers, as a fillable path.\n *\n * The ribbon the renderer draws already has the joins, caps, dashes, and\n * marker trims; unioning its triangles gives the silhouette with every\n * self-overlap resolved, so the outline and the drawn stroke cannot disagree.\n */\nimport type { Stroke } from '@weasel-js/paint';\nimport type { Path, PolygonPath } from '@weasel-js/geom';\nimport { pathIntersect, pathSubtract, pathUnion, trianglesToPath } from '@weasel-js/geom/booleans';\nimport { strokeInsets } from '../../core/markerInset';\nimport { resolveStrokeWidth, tessellateStroke } from './tessellate/stroke';\nimport { markerHeads } from './markerCommands';\n\n/** Options for {@link strokeToPath}. */\nexport interface StrokeToPathOptions {\n /** Screen pixels per world unit, for a `{ px }` width or marker size.\n * Default 1. */\n scale?: number;\n /** Flatness tolerance for curves, in the path's units. */\n flattenTolerance?: number;\n}\n\nconst EMPTY: PolygonPath = {\n kind: 'polygon', commands: new Uint8Array(0), coords: new Float32Array(0), fillRule: 'nonzero',\n};\n\n/**\n * The region `stroke` covers when drawn along `path`, as a path to fill:\n * width, cap, join, miter limit, dash, alignment, per-vertex widths, and\n * marker heads all included. Curves are flattened, so the result holds only\n * M/L/Z. A stroke with no width answers an empty path.\n */\nexport function strokeToPath(path: Path, stroke: Stroke, opts: StrokeToPathOptions = {}): PolygonPath {\n const width = resolveStrokeWidth(stroke.width ?? 1, opts.scale ?? 1);\n if (!(width > 0)) return EMPTY;\n const tolerance = opts.flattenTolerance;\n const sized: Stroke = { ...stroke, width };\n const insets = strokeInsets(sized, width);\n const align = stroke.align ?? 'center';\n // A rect's aligned ribbon is built in place. Any other path gets a centered\n // ribbon twice as wide, cut to the side of the path it belongs on.\n const cut = align !== 'center' && path.kind !== 'rect';\n const ribbonStroke: Stroke = cut\n ? { ...sized, width: width * 2, align: 'center', vertexWidths: stroke.vertexWidths?.map((w) => w * 2) }\n : sized;\n const mesh = tessellateStroke(path, ribbonStroke, {\n flattenTolerance: tolerance, startInset: insets.start, endInset: insets.end,\n });\n let ribbon = trianglesToPath(mesh.vertices, mesh.indices);\n if (cut) ribbon = align === 'inner' ? pathIntersect(ribbon, path) : pathSubtract(ribbon, path);\n\n const heads: PolygonPath[] = [];\n for (const { entry, path: head, size } of markerHeads(path, sized, width, tolerance)) {\n if ((entry.fill ?? 'line') !== 'none') heads.push(head);\n if (entry.outline && (entry.outline.paint ?? 'line') !== 'none') {\n heads.push(strokeToPath(head, { width: entry.outline.width * size, cap: 'round', join: 'round' }, opts));\n }\n }\n return heads.length === 0 ? ribbon : pathUnion(ribbon, ...heads);\n}\n"],"mappings":";;;;;AAuBA,IAAMA,IAAqB;CACzB,MAAM;CAAW,0BAAU,IAAI,WAAY;CAAG,wBAAQ,IAAI,aAAc;CAAG,UAAU;AACvF;AAQA,SAAgBC,EAAa,GAAY,GAAgB,IAA4B,CAAC,GAAgB;CACpG,IAAMC,IAAQC,EAAmBC,EAAO,SAAS,GAAGC,EAAK,SAAS,CAAC;CACnE,IAAI,EAAEH,IAAQ,IAAI,OAAOF;CACzB,IAAMM,IAAYD,EAAK,kBACjBE,IAAgB;EAAE,GAAGH;EAAQ;CAAM,GACnCI,IAASC,EAAaF,GAAOL,CAAK,GAClCQ,IAAQN,EAAO,SAAS,UAGxBO,IAAMD,MAAU,YAAYE,EAAK,SAAS,QAC1CC,IAAuBF,IACzB;EAAE,GAAGJ;EAAO,OAAOL,IAAQ;EAAG,OAAO;EAAU,cAAcE,EAAO,cAAc,KAAK,MAAMU,IAAI,CAAC;CAAE,IACpGP,GACEQ,IAAOC,EAAiBJ,GAAMC,GAAc;EAChD,kBAAkBP;EAAW,YAAYE,EAAO;EAAO,UAAUA,EAAO;CAC1E,CAAC,GACGS,IAASC,EAAgBH,EAAK,UAAUA,EAAK,OAAO;CACxD,AAAIJ,MAAK,IAASD,MAAU,UAAUS,EAAcF,GAAQL,CAAI,IAAIQ,EAAaH,GAAQL,CAAI;CAE7F,IAAMS,IAAuB,CAAC;CAC9B,KAAK,IAAM,EAAE,UAAO,MAAMC,GAAM,aAAUC,EAAYX,GAAML,GAAOL,GAAOI,CAAS,GAEjF,CADKkB,EAAM,QAAQ,YAAY,UAAQH,EAAM,KAAKC,CAAI,GAClDE,EAAM,YAAYA,EAAM,QAAQ,SAAS,YAAY,UACvDH,EAAM,KAAKpB,EAAaqB,GAAM;EAAE,OAAOE,EAAM,QAAQ,QAAQC;EAAM,KAAK;EAAS,MAAM;CAAQ,GAAGpB,CAAI,CAAC;CAG3G,OAAOgB,EAAM,WAAW,IAAIJ,IAASS,EAAUT,GAAQ,GAAGI,CAAK;AACjE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stroke.js","names":["EMPTY_MESH","resolveStrokeWidth","resolveScreenLength","width","scale","tessellateStroke","stroke","join","cap","align","miterLimit","DEFAULT_MITER_LIMIT","varyingThreshold","DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD","metric","opts","rectInMetric","path","rectIntoMetric","workingPath","aligned","alignedStrokeRect","polylines","extractPolylines","vertexWidths","worldWidthInMetric","tx","ty","worldWidthInMetricAlongWorld","ux","uy","dash","verts","idx","anchorA","anchorB","anchorT","ribbonSpace","pl","populatePolylineWidths","trimmed","trimInSpace","subs","splitForDash","sub","vertices","coordsOutOfMetric","expandPolyline","half","pts","ptCount","segCount","plA","plB","plT","plHalf","w","segs","segSrcIdx","s","ha","hb","seg","makeSeg","closerSrcIdx","last","first","haCloser","hbCloser","closer","segBaseIdx","startSrc","endSrc","base","nxA","nyA","nxB","nyB","joinCount","j","firstBase","lastBase","lastSrc","ROUND_CAP_STEP_RAD","emitCap","sign","atEnd","dx","dy","cx","cy","nx","ny","ox","oy","lOut","endA","endB","endT","rOut","leftIdx","rightIdx","pivotIdx","startAngle","sweep","steps","stepAngle","prevIdx","i","ang","fx","fy","newIdx","bx","ax","by","ay","len","emitJoin","a","b","aBase","bBase","adx","ady","bdx","bdy","cross","onPositive","aOuterEnd","bOuterStart","cornerSrc","cornerA","cornerB","cornerT","halfAtCorner","taperA","taperB","aOX","aOY","bOX","bOY","apex","lineLineIntersect","apexIdx","jIdx","ROUND_STEP_RAD","emitRoundJoin","startX","startY","endX","endY","r","emitBevel","denom","t","bpx","apx","bpy","apy"],"sources":["../../../../src/features/paths/tessellate/stroke.ts"],"sourcesContent":["import type { Path } from '@weasel-js/core';\nimport type { ScreenLength, Stroke } from '@weasel-js/paint';\nimport { alignedStrokeRect, resolveScreenLength } from '@weasel-js/paint';\nimport { type Mesh, extractPolylines, type Polyline } from '@weasel-js/geom/tessellate';\nimport { splitForDash } from './dash';\nimport { populatePolylineWidths, type WidthScale } from './vertexWidths';\nimport { type InsetLength, trimInSpace } from './insets';\nimport {\n type StrokeMetric, coordsOutOfMetric, rectIntoMetric, worldWidthInMetric, worldWidthInMetricAlongWorld,\n} from './metric';\n\n/** Options for stroke tessellation. */\nexport interface StrokeOptions {\n flattenTolerance?: number;\n /**\n * Shorten each open subpath by this much at its start / end: a length in\n * the ribbon's space, or one measured in a space of its own. Stroke markers\n * use this so a filled head is not speared by its own line; the caller\n * resolves the distance, because this layer knows nothing about the marker\n * registry.\n */\n startInset?: InsetLength;\n endInset?: InsetLength;\n /**\n * Build the ribbon in this metric's space and map it back out, so the\n * transform it was derived from draws it at an exact screen width — see\n * `./metric`. Width and dash are then lengths in that space; `vertexWidths`\n * stay world widths. The path is flattened in world first, so the ribbon\n * follows the same vertices its fill does.\n */\n metric?: StrokeMetric;\n}\n\nconst EMPTY_MESH: Mesh = {\n vertices: new Float32Array(0),\n indices: new Uint32Array(0),\n anchorA: new Uint32Array(0),\n anchorB: new Uint32Array(0),\n anchorT: new Float32Array(0),\n};\n\ntype Cap = 'butt' | 'round' | 'square';\ntype Join = 'miter' | 'round' | 'bevel';\n\n/** Per-segment data the join code needs. */\ninterface Seg {\n ax: number; ay: number;\n bx: number; by: number;\n /** Unit perpendicular (rotated 90° CCW from direction). */\n nxUnit: number; nyUnit: number;\n /** Half-width at the start (a) and end (b) endpoints. Equal for uniform\n * strokes; differ when the polyline carries per-point widths. */\n halfA: number; halfB: number;\n len: number;\n}\n\n/** Resolve a stroke width to world units. A number is already world units;\n * `{ px }` is screen pixels divided by the accumulated scale, so it holds its\n * on-screen thickness as the view zooms. */\nexport function resolveStrokeWidth(width: ScreenLength, scale: number): number {\n return resolveScreenLength(width, scale);\n}\n\n/**\n * Build a triangle-mesh ribbon from a stroked Path.\n *\n * Supports:\n * - cap: 'butt' | 'round' | 'square'\n * - join: 'miter' | 'round' | 'bevel'\n * - center alignment directly; RectPath inner/outer alignment via a\n * pre-shifted rect; PolygonPath inner/outer alignment via stencil at the\n * renderer level\n * - dash splitting\n */\nexport function tessellateStroke(\n path: Path,\n stroke: Stroke,\n opts: StrokeOptions = {},\n): Mesh {\n // The renderer resolves `{ px }` against the accumulated transform before it\n // gets here; a stroke that reaches the tessellator unresolved is read at\n // scale 1, matching what the ribbon cache keys it under.\n const width = resolveStrokeWidth(stroke.width ?? 1, 1);\n if (width <= 0) return EMPTY_MESH;\n const join: Join = stroke.join ?? 'miter';\n const cap: Cap = stroke.cap ?? 'butt';\n const align = stroke.align ?? 'center';\n const miterLimit = stroke.miterLimit ?? DEFAULT_MITER_LIMIT;\n const varyingThreshold = stroke.varyingWidthJoinThreshold ?? DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD;\n\n // RectPath fast path for inner/outer alignment: shift the rect by half-width\n // so the ribbon's center alignment lands the stroke on the desired side of\n // the original geometric edge. Arbitrary paths handle alignment via stencil\n // clipping at the renderer level (not here).\n const metric = opts.metric;\n const rectInMetric = metric && path.kind === 'rect' ? rectIntoMetric(path, metric) : null;\n let workingPath: Path = rectInMetric ?? path;\n if (workingPath.kind === 'rect' && align !== 'center') {\n const aligned = alignedStrokeRect(workingPath, align, width);\n workingPath = {\n kind: 'rect',\n x: aligned.x,\n y: aligned.y,\n width: aligned.width,\n height: aligned.height,\n };\n }\n\n const polylines = extractPolylines(workingPath, opts);\n // Per-point widths come first, in the space the points are in, so a trim\n // interpolates them rather than re-reading an anchor's.\n const vertexWidths = stroke.vertexWidths && stroke.vertexWidths.length > 0 ? stroke.vertexWidths : null;\n const scale: WidthScale | undefined = !metric ? undefined\n : rectInMetric ? (tx, ty) => worldWidthInMetric(metric, tx, ty)\n : (ux, uy) => worldWidthInMetricAlongWorld(metric, ux, uy);\n const dash = stroke.dash ?? [];\n const verts: number[] = [];\n const idx: number[] = [];\n const anchorA: number[] = [];\n const anchorB: number[] = [];\n const anchorT: number[] = [];\n\n // Trim before dashing, so the dash pattern fits the visible line rather than\n // running off under a marker head.\n const ribbonSpace = metric ?? null;\n for (const pl of polylines) {\n if (vertexWidths) populatePolylineWidths(pl, vertexWidths, width, scale);\n const trimmed = trimInSpace(pl, rectInMetric ? ribbonSpace : null, ribbonSpace, opts.startInset, opts.endInset);\n if (trimmed === null) continue;\n const subs = dash.length > 0 ? splitForDash(trimmed, dash) : [trimmed];\n for (const sub of subs) {\n expandPolyline(sub, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT);\n }\n }\n\n const vertices = new Float32Array(verts);\n if (metric) coordsOutOfMetric(vertices, metric);\n return {\n vertices,\n indices: new Uint32Array(idx),\n anchorA: new Uint32Array(anchorA),\n anchorB: new Uint32Array(anchorB),\n anchorT: new Float32Array(anchorT),\n };\n}\n\nfunction expandPolyline(\n pl: Polyline,\n width: number,\n join: Join,\n cap: Cap,\n miterLimit: number,\n varyingThreshold: number,\n verts: number[],\n idx: number[],\n anchorA: number[],\n anchorB: number[],\n anchorT: number[],\n): void {\n const half = width / 2;\n const pts = pl.points;\n const ptCount = pts.length / 2;\n const segCount = ptCount - 1;\n if (segCount < 1) return;\n\n // Anchor params per polyline point (set by extractPolylines; splitForDash\n // re-derives them on dash boundaries). Default to A=B=0,t=0 if missing.\n const plA = pl.anchorA ?? new Uint32Array(ptCount);\n const plB = pl.anchorB ?? new Uint32Array(ptCount);\n const plT = pl.anchorT ?? new Float32Array(ptCount);\n // Per-point half-widths: derived from pl.widths if present, else the\n // uniform half. Indexing matches `pts` (one entry per polyline point).\n const plHalf = pl.widths\n ? pl.widths.map((w) => w / 2)\n : null;\n\n const segs: Seg[] = [];\n const segSrcIdx: number[] = []; // For each seg, the polyline-point index of its start.\n for (let s = 0; s < segCount; s++) {\n const ha = plHalf ? plHalf[s] : half;\n const hb = plHalf ? plHalf[s + 1] : half;\n const seg = makeSeg(pts[s * 2], pts[s * 2 + 1], pts[(s + 1) * 2], pts[(s + 1) * 2 + 1], ha, hb);\n if (seg) {\n segs.push(seg);\n segSrcIdx.push(s);\n }\n }\n let closerSrcIdx = -1;\n if (pl.closed && segs.length >= 1) {\n const last = segs[segs.length - 1];\n const first = segs[0];\n // Closer: from last point back to point 0. Half-widths are the\n // already-resolved ones at those source indices.\n const haCloser = plHalf ? plHalf[ptCount - 1] : half;\n const hbCloser = plHalf ? plHalf[0] : half;\n const closer = makeSeg(last.bx, last.by, first.ax, first.ay, haCloser, hbCloser);\n if (closer) {\n segs.push(closer);\n closerSrcIdx = ptCount - 1; // start is the last polyline point.\n segSrcIdx.push(closerSrcIdx);\n }\n }\n if (segs.length === 0) return;\n\n // Emit ribbon quads. For each emitted vertex, record the (A, B, t) of the\n // source polyline point that the geometric vertex sits at. Per-end\n // half-widths produce trapezoidal segments when the stroke tapers.\n const segBaseIdx: number[] = [];\n for (let s = 0; s < segs.length; s++) {\n const seg = segs[s];\n const startSrc = segSrcIdx[s];\n // End-source index for the seg. For closer (closed-path wraparound), end is point 0.\n const endSrc = (s === segs.length - 1 && pl.closed && closerSrcIdx >= 0) ? 0 : startSrc + 1;\n const base = verts.length / 2;\n segBaseIdx.push(base);\n const nxA = seg.nxUnit * seg.halfA, nyA = seg.nyUnit * seg.halfA;\n const nxB = seg.nxUnit * seg.halfB, nyB = seg.nyUnit * seg.halfB;\n // L0, R0 — at seg.ax/ay with start half-width.\n verts.push(seg.ax + nxA, seg.ay + nyA);\n anchorA.push(plA[startSrc]); anchorB.push(plB[startSrc]); anchorT.push(plT[startSrc]);\n verts.push(seg.ax - nxA, seg.ay - nyA);\n anchorA.push(plA[startSrc]); anchorB.push(plB[startSrc]); anchorT.push(plT[startSrc]);\n // L1, R1 — at seg.bx/by with end half-width.\n verts.push(seg.bx + nxB, seg.by + nyB);\n anchorA.push(plA[endSrc]); anchorB.push(plB[endSrc]); anchorT.push(plT[endSrc]);\n verts.push(seg.bx - nxB, seg.by - nyB);\n anchorA.push(plA[endSrc]); anchorB.push(plB[endSrc]); anchorT.push(plT[endSrc]);\n idx.push(base, base + 1, base + 2, base + 1, base + 3, base + 2);\n }\n\n // Joins between consecutive segments.\n const joinCount = pl.closed ? segs.length : segs.length - 1;\n for (let j = 0; j < joinCount; j++) {\n emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT, segSrcIdx, plA, plB, plT);\n }\n\n // Caps. Use the local half-width at the cap end (seg.halfA at start, seg.halfB at end).\n if (!pl.closed && cap !== 'butt') {\n const first = segs[0];\n const firstBase = segBaseIdx[0];\n emitCap(first, firstBase + 0, firstBase + 1, false, first.halfA, cap, verts, idx, anchorA, anchorB, anchorT, plA[0], plB[0], plT[0]);\n\n const last = segs[segs.length - 1];\n const lastBase = segBaseIdx[segs.length - 1];\n const lastSrc = segSrcIdx[segs.length - 1] + 1;\n emitCap(last, lastBase + 2, lastBase + 3, true, last.halfB, cap, verts, idx, anchorA, anchorB, anchorT, plA[lastSrc], plB[lastSrc], plT[lastSrc]);\n }\n}\n\nconst ROUND_CAP_STEP_RAD = (10 * Math.PI) / 180;\n\n/**\n * Emit a cap at one end of a segment. `leftIdx`/`rightIdx` are the buffer\n * indices of the L (perpendicular +n) and R (perpendicular -n) vertices at\n * the segment endpoint where the cap attaches. `atEnd` chooses which end of\n * the segment to extend from (and which cap-direction sign to use).\n */\nfunction emitCap(\n seg: Seg, leftIdx: number, rightIdx: number, atEnd: boolean,\n half: number, cap: Cap,\n verts: number[], idx: number[],\n anchorA: number[], anchorB: number[], anchorT: number[],\n endA: number, endB: number, endT: number,\n): void {\n // Outward direction = forward at end, backward at start.\n const sign = atEnd ? 1 : -1;\n const dx = (seg.bx - seg.ax) / seg.len * sign;\n const dy = (seg.by - seg.ay) / seg.len * sign;\n const cx = atEnd ? seg.bx : seg.ax;\n const cy = atEnd ? seg.by : seg.ay;\n\n // Perpendicular offset at this cap end uses the local half-width\n // (varies along the polyline when vertexWidths is set).\n const nx = seg.nxUnit * half;\n const ny = seg.nyUnit * half;\n\n if (cap === 'square') {\n const ox = cx + dx * half;\n const oy = cy + dy * half;\n const lOut = verts.length / 2;\n verts.push(ox + nx, oy + ny);\n anchorA.push(endA); anchorB.push(endB); anchorT.push(endT);\n const rOut = verts.length / 2;\n verts.push(ox - nx, oy - ny);\n anchorA.push(endA); anchorB.push(endB); anchorT.push(endT);\n // Two triangles forming the cap rectangle. Wind so triangles are CCW\n // viewed from the front; orientation symmetric for start vs. end caps.\n if (atEnd) {\n idx.push(leftIdx, lOut, rOut, leftIdx, rOut, rightIdx);\n } else {\n idx.push(leftIdx, rOut, lOut, leftIdx, rightIdx, rOut);\n }\n return;\n }\n\n if (cap === 'round') {\n // Pivot vertex at the endpoint center; fan from L through the outward\n // 180° arc to R.\n const pivotIdx = verts.length / 2;\n verts.push(cx, cy);\n anchorA.push(endA); anchorB.push(endB); anchorT.push(endT);\n\n // Starting angle: direction from pivot to L (= +n direction).\n const startAngle = Math.atan2(ny, nx);\n // Sweep 180° toward the outward direction. The outward unit is (dx, dy);\n // we rotate from +n to -n the \"outward\" way. The dot product of (dx, dy)\n // with the perpendicular tells us which way around the arc to go.\n // A start-cap (sign = -1) sweeps backward; end-cap (sign = +1) sweeps forward.\n const sweep = atEnd ? -Math.PI : Math.PI;\n const steps = Math.max(1, Math.ceil(Math.abs(sweep) / ROUND_CAP_STEP_RAD));\n const stepAngle = sweep / steps;\n\n let prevIdx = leftIdx;\n for (let i = 1; i < steps; i++) {\n const ang = startAngle + i * stepAngle;\n const fx = cx + Math.cos(ang) * half;\n const fy = cy + Math.sin(ang) * half;\n const newIdx = verts.length / 2;\n verts.push(fx, fy);\n anchorA.push(endA); anchorB.push(endB); anchorT.push(endT);\n if (atEnd) idx.push(prevIdx, newIdx, pivotIdx);\n else idx.push(prevIdx, pivotIdx, newIdx);\n prevIdx = newIdx;\n }\n if (atEnd) idx.push(prevIdx, rightIdx, pivotIdx);\n else idx.push(prevIdx, pivotIdx, rightIdx);\n }\n}\n\nfunction makeSeg(ax: number, ay: number, bx: number, by: number, halfA: number, halfB: number): Seg | null {\n const dx = bx - ax, dy = by - ay;\n const len = Math.hypot(dx, dy);\n if (len === 0) return null;\n return {\n ax, ay, bx, by,\n nxUnit: -dy / len, nyUnit: dx / len,\n halfA, halfB,\n len,\n };\n}\n\n/**\n * Miter limit for a stroke that does not set one. SVG's default, not\n * Canvas2D's 10, because the serializer omits the attribute when the field is\n * unset — so a kit stroke exported to SVG is read back by every other renderer\n * as 4, and a kit that used 10 would disagree with its own export. 10 also\n * lets an acute corner throw a spike four times the half-width, which is what\n * a glyph outline's sharpest vertices do.\n */\nconst DEFAULT_MITER_LIMIT = 4;\n/** Default `Stroke.varyingWidthJoinThreshold`. */\nconst DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD = 1.5;\n\nfunction emitJoin(\n segs: Seg[], segBaseIdx: number[], j: number, join: Join,\n miterLimit: number,\n varyingThreshold: number,\n verts: number[], idx: number[],\n anchorA: number[], anchorB: number[], anchorT: number[],\n segSrcIdx: number[],\n plA: Uint32Array, plB: Uint32Array, plT: Float32Array,\n): void {\n const a = segs[j];\n const b = segs[(j + 1) % segs.length];\n const aBase = segBaseIdx[j];\n const bBase = segBaseIdx[(j + 1) % segs.length];\n\n const adx = a.bx - a.ax, ady = a.by - a.ay;\n const bdx = b.bx - b.ax, bdy = b.by - b.ay;\n const cross = adx * bdy - ady * bdx;\n if (cross === 0) return; // straight (collinear), no wedge\n const onPositive = cross > 0;\n\n const aOuterEnd = onPositive ? aBase + 3 : aBase + 2;\n const bOuterStart = onPositive ? bBase + 1 : bBase + 0;\n\n // The corner of join `j` is the start of segment `j+1` (which is the END of\n // segment `j` — they share the same polyline point). For the wraparound join\n // on a closed polyline, j+1 wraps to segment 0 whose start is the closer\n // segment's endpoint, which is polyline point 0.\n const nextSeg = (j + 1) % segs.length;\n // The corner anchor is the start of the next segment (shared endpoint).\n // For a closed path's final join, nextSeg wraps to 0, whose start is\n // polyline point 0 — correct.\n const cornerSrc = segSrcIdx[nextSeg];\n const cornerA = plA[cornerSrc];\n const cornerB = plB[cornerSrc];\n const cornerT = plT[cornerSrc];\n // The two adjoining half-widths agree at the shared corner (a.halfB ===\n // b.halfA for a single-contour polyline), so use either for miter-limit\n // sizing. Force bevel when either adjacent segment itself tapers by\n // more than `varyingThreshold` — the miter apex math computes against\n // outer edges parallel to the segment direction, an assumption that\n // breaks on tapered trapezoids (outer edges fan in/out).\n const halfAtCorner = a.halfB;\n const taperA = Math.max(a.halfA, a.halfB) / Math.max(1e-9, Math.min(a.halfA, a.halfB));\n const taperB = Math.max(b.halfA, b.halfB) / Math.max(1e-9, Math.min(b.halfA, b.halfB));\n const forceBevelForVarying = taperA > varyingThreshold || taperB > varyingThreshold;\n\n if (join === 'bevel' || forceBevelForVarying) {\n emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);\n return;\n }\n\n if (join === 'miter') {\n // Compute apex: intersection of A's outer edge extended forward and\n // B's outer edge extended backward. If miter length > MITER_LIMIT * half,\n // fall back to bevel.\n const aOX = verts[aOuterEnd * 2], aOY = verts[aOuterEnd * 2 + 1];\n const bOX = verts[bOuterStart * 2], bOY = verts[bOuterStart * 2 + 1];\n const apex = lineLineIntersect(aOX, aOY, adx, ady, bOX, bOY, -bdx, -bdy);\n if (!apex) {\n emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);\n return;\n }\n const miterLen = Math.hypot(apex[0] - a.bx, apex[1] - a.by);\n if (miterLen > miterLimit * halfAtCorner) {\n emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);\n return;\n }\n // A miter join's outer face is a kite quadrilateral with vertices\n // (R1, apex, R0, J) — covered by TWO triangles. The bevel inner half\n // (R1, R0, J) fills the area between the joint corner and the diagonal\n // hypotenuse R0-R1; the outer extension (R1, apex, R0) fills from the\n // hypotenuse to the apex point. Without the bevel half, an \"inverted\n // triangle\" gap opens at every miter corner.\n const apexIdx = verts.length / 2;\n verts.push(apex[0], apex[1]);\n anchorA.push(cornerA); anchorB.push(cornerB); anchorT.push(cornerT);\n const jIdx = verts.length / 2;\n verts.push(a.bx, a.by);\n anchorA.push(cornerA); anchorB.push(cornerB); anchorT.push(cornerT);\n if (onPositive) {\n idx.push(aOuterEnd, apexIdx, bOuterStart);\n idx.push(aOuterEnd, bOuterStart, jIdx);\n } else {\n idx.push(aOuterEnd, bOuterStart, apexIdx);\n idx.push(aOuterEnd, jIdx, bOuterStart);\n }\n return;\n }\n\n if (join === 'round') {\n emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);\n return;\n }\n}\n\nconst ROUND_STEP_RAD = (10 * Math.PI) / 180; // ~10° per fan triangle\n\nfunction emitRoundJoin(\n a: Seg, aOuterEnd: number, bOuterStart: number, onPositive: boolean,\n verts: number[], idx: number[],\n anchorA: number[], anchorB: number[], anchorT: number[],\n cornerA: number, cornerB: number, cornerT: number,\n): void {\n // Pivot = joint center; emit fresh vertex.\n const cx = a.bx, cy = a.by;\n const pivotIdx = verts.length / 2;\n verts.push(cx, cy);\n anchorA.push(cornerA); anchorB.push(cornerB); anchorT.push(cornerT);\n\n const startX = verts[aOuterEnd * 2] - cx;\n const startY = verts[aOuterEnd * 2 + 1] - cy;\n const endX = verts[bOuterStart * 2] - cx;\n const endY = verts[bOuterStart * 2 + 1] - cy;\n const r = Math.hypot(startX, startY);\n\n const startAngle = Math.atan2(startY, startX);\n const endAngle = Math.atan2(endY, endX);\n // Outer-side sweep: for CCW turns (cross > 0, onPositive true) outer is on\n // the -n side and the arc sweeps from R1 (south of joint) clockwise to R0\n // (east of joint) — i.e., negative sweep. For CW turns, sweep is positive.\n // Pick the SHORT arc that crosses the outer wedge. For onPositive=true\n // (CW turn in screen coords with y-down), the outer wedge sits where the\n // natural sweep is positive; for onPositive=false, where it is negative.\n // If the natural sweep has the wrong sign, wrap by ±2π.\n let sweep = endAngle - startAngle;\n if (onPositive && sweep < 0) sweep += 2 * Math.PI;\n else if (!onPositive && sweep > 0) sweep -= 2 * Math.PI;\n\n const steps = Math.max(1, Math.ceil(Math.abs(sweep) / ROUND_STEP_RAD));\n const stepAngle = sweep / steps;\n\n let prevIdx = aOuterEnd;\n for (let i = 1; i < steps; i++) {\n const ang = startAngle + i * stepAngle;\n const fx = cx + Math.cos(ang) * r;\n const fy = cy + Math.sin(ang) * r;\n const newIdx = verts.length / 2;\n verts.push(fx, fy);\n anchorA.push(cornerA); anchorB.push(cornerB); anchorT.push(cornerT);\n if (onPositive) idx.push(prevIdx, pivotIdx, newIdx);\n else idx.push(prevIdx, newIdx, pivotIdx);\n prevIdx = newIdx;\n }\n if (onPositive) idx.push(prevIdx, pivotIdx, bOuterStart);\n else idx.push(prevIdx, bOuterStart, pivotIdx);\n}\n\nfunction emitBevel(\n a: Seg, aOuterEnd: number, bOuterStart: number, onPositive: boolean,\n verts: number[], idx: number[],\n anchorA: number[], anchorB: number[], anchorT: number[],\n cornerA: number, cornerB: number, cornerT: number,\n): void {\n const jIdx = verts.length / 2;\n verts.push(a.bx, a.by);\n anchorA.push(cornerA); anchorB.push(cornerB); anchorT.push(cornerT);\n if (onPositive) idx.push(aOuterEnd, jIdx, bOuterStart);\n else idx.push(aOuterEnd, bOuterStart, jIdx);\n}\n\n/**\n * Solve for the intersection of line A (point ap, direction ad) with line B\n * (point bp, direction bd). Returns null if parallel or near-parallel.\n */\nfunction lineLineIntersect(\n apx: number, apy: number, adx: number, ady: number,\n bpx: number, bpy: number, bdx: number, bdy: number,\n): [number, number] | null {\n const denom = adx * bdy - ady * bdx;\n if (Math.abs(denom) < 1e-9) return null;\n const t = ((bpx - apx) * bdy - (bpy - apy) * bdx) / denom;\n return [apx + t * adx, apy + t * ady];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stroke.js","names":["EMPTY_MESH","resolveStrokeWidth","resolveScreenLength","width","scale","tessellateStroke","stroke","join","cap","align","miterLimit","DEFAULT_MITER_LIMIT","varyingThreshold","DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD","metric","opts","rectInMetric","path","rectIntoMetric","workingPath","aligned","alignedStrokeRect","polylines","extractPolylines","vertexWidths","worldWidthInMetric","tx","ty","worldWidthInMetricAlongWorld","ux","uy","dash","verts","idx","anchorA","anchorB","anchorT","ribbonSpace","pl","populatePolylineWidths","trimmed","trimInSpace","subs","splitForDash","sub","vertices","coordsOutOfMetric","expandPolyline","half","pts","ptCount","segCount","plA","plB","plT","plHalf","w","segs","segSrcIdx","s","ha","hb","seg","makeSeg","closerSrcIdx","last","first","haCloser","hbCloser","closer","segBaseIdx","startSrc","endSrc","base","nxA","nyA","nxB","nyB","joinCount","j","firstBase","lastBase","lastSrc","ROUND_CAP_STEP_RAD","emitCap","sign","atEnd","dx","dy","cx","cy","nx","ny","ox","oy","lOut","endA","endB","endT","rOut","leftIdx","rightIdx","pivotIdx","startAngle","sweep","steps","stepAngle","prevIdx","i","ang","fx","fy","newIdx","bx","ax","by","ay","len","emitJoin","a","b","aBase","bBase","adx","ady","bdx","bdy","cross","onPositive","aOuterEnd","bOuterStart","cornerSrc","cornerA","cornerB","cornerT","halfAtCorner","taperA","taperB","aOX","aOY","bOX","bOY","apex","lineLineIntersect","apexIdx","jIdx","ROUND_STEP_RAD","emitRoundJoin","startX","startY","endX","endY","r","emitBevel","denom","t","bpx","apx","bpy","apy"],"sources":["../../../../src/features/paths/tessellate/stroke.ts"],"sourcesContent":["import type { Path } from '@weasel-js/geom';\nimport type { ScreenLength, Stroke } from '@weasel-js/paint';\nimport { alignedStrokeRect, resolveScreenLength } from '@weasel-js/paint';\nimport { type Mesh, extractPolylines, type Polyline } from '@weasel-js/geom/tessellate';\nimport { splitForDash } from './dash';\nimport { populatePolylineWidths, type WidthScale } from './vertexWidths';\nimport { type InsetLength, trimInSpace } from './insets';\nimport {\n type StrokeMetric, coordsOutOfMetric, rectIntoMetric, worldWidthInMetric, worldWidthInMetricAlongWorld,\n} from './metric';\n\n/** Options for stroke tessellation. */\nexport interface StrokeOptions {\n flattenTolerance?: number;\n /**\n * Shorten each open subpath by this much at its start / end: a length in\n * the ribbon's space, or one measured in a space of its own. Stroke markers\n * use this so a filled head is not speared by its own line; the caller\n * resolves the distance, because this layer knows nothing about the marker\n * registry.\n */\n startInset?: InsetLength;\n endInset?: InsetLength;\n /**\n * Build the ribbon in this metric's space and map it back out, so the\n * transform it was derived from draws it at an exact screen width — see\n * `./metric`. Width and dash are then lengths in that space; `vertexWidths`\n * stay world widths. The path is flattened in world first, so the ribbon\n * follows the same vertices its fill does.\n */\n metric?: StrokeMetric;\n}\n\nconst EMPTY_MESH: Mesh = {\n vertices: new Float32Array(0),\n indices: new Uint32Array(0),\n anchorA: new Uint32Array(0),\n anchorB: new Uint32Array(0),\n anchorT: new Float32Array(0),\n};\n\ntype Cap = 'butt' | 'round' | 'square';\ntype Join = 'miter' | 'round' | 'bevel';\n\n/** Per-segment data the join code needs. */\ninterface Seg {\n ax: number; ay: number;\n bx: number; by: number;\n /** Unit perpendicular (rotated 90° CCW from direction). */\n nxUnit: number; nyUnit: number;\n /** Half-width at the start (a) and end (b) endpoints. Equal for uniform\n * strokes; differ when the polyline carries per-point widths. */\n halfA: number; halfB: number;\n len: number;\n}\n\n/** Resolve a stroke width to world units. A number is already world units;\n * `{ px }` is screen pixels divided by the accumulated scale, so it holds its\n * on-screen thickness as the view zooms. */\nexport function resolveStrokeWidth(width: ScreenLength, scale: number): number {\n return resolveScreenLength(width, scale);\n}\n\n/**\n * Build a triangle-mesh ribbon from a stroked Path.\n *\n * Supports:\n * - cap: 'butt' | 'round' | 'square'\n * - join: 'miter' | 'round' | 'bevel'\n * - center alignment directly; RectPath inner/outer alignment via a\n * pre-shifted rect; PolygonPath inner/outer alignment via stencil at the\n * renderer level\n * - dash splitting\n */\nexport function tessellateStroke(\n path: Path,\n stroke: Stroke,\n opts: StrokeOptions = {},\n): Mesh {\n // The renderer resolves `{ px }` against the accumulated transform before it\n // gets here; a stroke that reaches the tessellator unresolved is read at\n // scale 1, matching what the ribbon cache keys it under.\n const width = resolveStrokeWidth(stroke.width ?? 1, 1);\n if (width <= 0) return EMPTY_MESH;\n const join: Join = stroke.join ?? 'miter';\n const cap: Cap = stroke.cap ?? 'butt';\n const align = stroke.align ?? 'center';\n const miterLimit = stroke.miterLimit ?? DEFAULT_MITER_LIMIT;\n const varyingThreshold = stroke.varyingWidthJoinThreshold ?? DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD;\n\n // RectPath fast path for inner/outer alignment: shift the rect by half-width\n // so the ribbon's center alignment lands the stroke on the desired side of\n // the original geometric edge. Arbitrary paths handle alignment via stencil\n // clipping at the renderer level (not here).\n const metric = opts.metric;\n const rectInMetric = metric && path.kind === 'rect' ? rectIntoMetric(path, metric) : null;\n let workingPath: Path = rectInMetric ?? path;\n if (workingPath.kind === 'rect' && align !== 'center') {\n const aligned = alignedStrokeRect(workingPath, align, width);\n workingPath = {\n kind: 'rect',\n x: aligned.x,\n y: aligned.y,\n width: aligned.width,\n height: aligned.height,\n };\n }\n\n const polylines = extractPolylines(workingPath, opts);\n // Per-point widths come first, in the space the points are in, so a trim\n // interpolates them rather than re-reading an anchor's.\n const vertexWidths = stroke.vertexWidths && stroke.vertexWidths.length > 0 ? stroke.vertexWidths : null;\n const scale: WidthScale | undefined = !metric ? undefined\n : rectInMetric ? (tx, ty) => worldWidthInMetric(metric, tx, ty)\n : (ux, uy) => worldWidthInMetricAlongWorld(metric, ux, uy);\n const dash = stroke.dash ?? [];\n const verts: number[] = [];\n const idx: number[] = [];\n const anchorA: number[] = [];\n const anchorB: number[] = [];\n const anchorT: number[] = [];\n\n // Trim before dashing, so the dash pattern fits the visible line rather than\n // running off under a marker head.\n const ribbonSpace = metric ?? null;\n for (const pl of polylines) {\n if (vertexWidths) populatePolylineWidths(pl, vertexWidths, width, scale);\n const trimmed = trimInSpace(pl, rectInMetric ? ribbonSpace : null, ribbonSpace, opts.startInset, opts.endInset);\n if (trimmed === null) continue;\n const subs = dash.length > 0 ? splitForDash(trimmed, dash) : [trimmed];\n for (const sub of subs) {\n expandPolyline(sub, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT);\n }\n }\n\n const vertices = new Float32Array(verts);\n if (metric) coordsOutOfMetric(vertices, metric);\n return {\n vertices,\n indices: new Uint32Array(idx),\n anchorA: new Uint32Array(anchorA),\n anchorB: new Uint32Array(anchorB),\n anchorT: new Float32Array(anchorT),\n };\n}\n\nfunction expandPolyline(\n pl: Polyline,\n width: number,\n join: Join,\n cap: Cap,\n miterLimit: number,\n varyingThreshold: number,\n verts: number[],\n idx: number[],\n anchorA: number[],\n anchorB: number[],\n anchorT: number[],\n): void {\n const half = width / 2;\n const pts = pl.points;\n const ptCount = pts.length / 2;\n const segCount = ptCount - 1;\n if (segCount < 1) return;\n\n // Anchor params per polyline point (set by extractPolylines; splitForDash\n // re-derives them on dash boundaries). Default to A=B=0,t=0 if missing.\n const plA = pl.anchorA ?? new Uint32Array(ptCount);\n const plB = pl.anchorB ?? new Uint32Array(ptCount);\n const plT = pl.anchorT ?? new Float32Array(ptCount);\n // Per-point half-widths: derived from pl.widths if present, else the\n // uniform half. Indexing matches `pts` (one entry per polyline point).\n const plHalf = pl.widths\n ? pl.widths.map((w) => w / 2)\n : null;\n\n const segs: Seg[] = [];\n const segSrcIdx: number[] = []; // For each seg, the polyline-point index of its start.\n for (let s = 0; s < segCount; s++) {\n const ha = plHalf ? plHalf[s] : half;\n const hb = plHalf ? plHalf[s + 1] : half;\n const seg = makeSeg(pts[s * 2], pts[s * 2 + 1], pts[(s + 1) * 2], pts[(s + 1) * 2 + 1], ha, hb);\n if (seg) {\n segs.push(seg);\n segSrcIdx.push(s);\n }\n }\n let closerSrcIdx = -1;\n if (pl.closed && segs.length >= 1) {\n const last = segs[segs.length - 1];\n const first = segs[0];\n // Closer: from last point back to point 0. Half-widths are the\n // already-resolved ones at those source indices.\n const haCloser = plHalf ? plHalf[ptCount - 1] : half;\n const hbCloser = plHalf ? plHalf[0] : half;\n const closer = makeSeg(last.bx, last.by, first.ax, first.ay, haCloser, hbCloser);\n if (closer) {\n segs.push(closer);\n closerSrcIdx = ptCount - 1; // start is the last polyline point.\n segSrcIdx.push(closerSrcIdx);\n }\n }\n if (segs.length === 0) return;\n\n // Emit ribbon quads. For each emitted vertex, record the (A, B, t) of the\n // source polyline point that the geometric vertex sits at. Per-end\n // half-widths produce trapezoidal segments when the stroke tapers.\n const segBaseIdx: number[] = [];\n for (let s = 0; s < segs.length; s++) {\n const seg = segs[s];\n const startSrc = segSrcIdx[s];\n // End-source index for the seg. For closer (closed-path wraparound), end is point 0.\n const endSrc = (s === segs.length - 1 && pl.closed && closerSrcIdx >= 0) ? 0 : startSrc + 1;\n const base = verts.length / 2;\n segBaseIdx.push(base);\n const nxA = seg.nxUnit * seg.halfA, nyA = seg.nyUnit * seg.halfA;\n const nxB = seg.nxUnit * seg.halfB, nyB = seg.nyUnit * seg.halfB;\n // L0, R0 — at seg.ax/ay with start half-width.\n verts.push(seg.ax + nxA, seg.ay + nyA);\n anchorA.push(plA[startSrc]); anchorB.push(plB[startSrc]); anchorT.push(plT[startSrc]);\n verts.push(seg.ax - nxA, seg.ay - nyA);\n anchorA.push(plA[startSrc]); anchorB.push(plB[startSrc]); anchorT.push(plT[startSrc]);\n // L1, R1 — at seg.bx/by with end half-width.\n verts.push(seg.bx + nxB, seg.by + nyB);\n anchorA.push(plA[endSrc]); anchorB.push(plB[endSrc]); anchorT.push(plT[endSrc]);\n verts.push(seg.bx - nxB, seg.by - nyB);\n anchorA.push(plA[endSrc]); anchorB.push(plB[endSrc]); anchorT.push(plT[endSrc]);\n idx.push(base, base + 1, base + 2, base + 1, base + 3, base + 2);\n }\n\n // Joins between consecutive segments.\n const joinCount = pl.closed ? segs.length : segs.length - 1;\n for (let j = 0; j < joinCount; j++) {\n emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT, segSrcIdx, plA, plB, plT);\n }\n\n // Caps. Use the local half-width at the cap end (seg.halfA at start, seg.halfB at end).\n if (!pl.closed && cap !== 'butt') {\n const first = segs[0];\n const firstBase = segBaseIdx[0];\n emitCap(first, firstBase + 0, firstBase + 1, false, first.halfA, cap, verts, idx, anchorA, anchorB, anchorT, plA[0], plB[0], plT[0]);\n\n const last = segs[segs.length - 1];\n const lastBase = segBaseIdx[segs.length - 1];\n const lastSrc = segSrcIdx[segs.length - 1] + 1;\n emitCap(last, lastBase + 2, lastBase + 3, true, last.halfB, cap, verts, idx, anchorA, anchorB, anchorT, plA[lastSrc], plB[lastSrc], plT[lastSrc]);\n }\n}\n\nconst ROUND_CAP_STEP_RAD = (10 * Math.PI) / 180;\n\n/**\n * Emit a cap at one end of a segment. `leftIdx`/`rightIdx` are the buffer\n * indices of the L (perpendicular +n) and R (perpendicular -n) vertices at\n * the segment endpoint where the cap attaches. `atEnd` chooses which end of\n * the segment to extend from (and which cap-direction sign to use).\n */\nfunction emitCap(\n seg: Seg, leftIdx: number, rightIdx: number, atEnd: boolean,\n half: number, cap: Cap,\n verts: number[], idx: number[],\n anchorA: number[], anchorB: number[], anchorT: number[],\n endA: number, endB: number, endT: number,\n): void {\n // Outward direction = forward at end, backward at start.\n const sign = atEnd ? 1 : -1;\n const dx = (seg.bx - seg.ax) / seg.len * sign;\n const dy = (seg.by - seg.ay) / seg.len * sign;\n const cx = atEnd ? seg.bx : seg.ax;\n const cy = atEnd ? seg.by : seg.ay;\n\n // Perpendicular offset at this cap end uses the local half-width\n // (varies along the polyline when vertexWidths is set).\n const nx = seg.nxUnit * half;\n const ny = seg.nyUnit * half;\n\n if (cap === 'square') {\n const ox = cx + dx * half;\n const oy = cy + dy * half;\n const lOut = verts.length / 2;\n verts.push(ox + nx, oy + ny);\n anchorA.push(endA); anchorB.push(endB); anchorT.push(endT);\n const rOut = verts.length / 2;\n verts.push(ox - nx, oy - ny);\n anchorA.push(endA); anchorB.push(endB); anchorT.push(endT);\n // Two triangles forming the cap rectangle. Wind so triangles are CCW\n // viewed from the front; orientation symmetric for start vs. end caps.\n if (atEnd) {\n idx.push(leftIdx, lOut, rOut, leftIdx, rOut, rightIdx);\n } else {\n idx.push(leftIdx, rOut, lOut, leftIdx, rightIdx, rOut);\n }\n return;\n }\n\n if (cap === 'round') {\n // Pivot vertex at the endpoint center; fan from L through the outward\n // 180° arc to R.\n const pivotIdx = verts.length / 2;\n verts.push(cx, cy);\n anchorA.push(endA); anchorB.push(endB); anchorT.push(endT);\n\n // Starting angle: direction from pivot to L (= +n direction).\n const startAngle = Math.atan2(ny, nx);\n // Sweep 180° toward the outward direction. The outward unit is (dx, dy);\n // we rotate from +n to -n the \"outward\" way. The dot product of (dx, dy)\n // with the perpendicular tells us which way around the arc to go.\n // A start-cap (sign = -1) sweeps backward; end-cap (sign = +1) sweeps forward.\n const sweep = atEnd ? -Math.PI : Math.PI;\n const steps = Math.max(1, Math.ceil(Math.abs(sweep) / ROUND_CAP_STEP_RAD));\n const stepAngle = sweep / steps;\n\n let prevIdx = leftIdx;\n for (let i = 1; i < steps; i++) {\n const ang = startAngle + i * stepAngle;\n const fx = cx + Math.cos(ang) * half;\n const fy = cy + Math.sin(ang) * half;\n const newIdx = verts.length / 2;\n verts.push(fx, fy);\n anchorA.push(endA); anchorB.push(endB); anchorT.push(endT);\n if (atEnd) idx.push(prevIdx, newIdx, pivotIdx);\n else idx.push(prevIdx, pivotIdx, newIdx);\n prevIdx = newIdx;\n }\n if (atEnd) idx.push(prevIdx, rightIdx, pivotIdx);\n else idx.push(prevIdx, pivotIdx, rightIdx);\n }\n}\n\nfunction makeSeg(ax: number, ay: number, bx: number, by: number, halfA: number, halfB: number): Seg | null {\n const dx = bx - ax, dy = by - ay;\n const len = Math.hypot(dx, dy);\n if (len === 0) return null;\n return {\n ax, ay, bx, by,\n nxUnit: -dy / len, nyUnit: dx / len,\n halfA, halfB,\n len,\n };\n}\n\n/**\n * Miter limit for a stroke that does not set one. SVG's default, not\n * Canvas2D's 10, because the serializer omits the attribute when the field is\n * unset — so a kit stroke exported to SVG is read back by every other renderer\n * as 4, and a kit that used 10 would disagree with its own export. 10 also\n * lets an acute corner throw a spike four times the half-width, which is what\n * a glyph outline's sharpest vertices do.\n */\nconst DEFAULT_MITER_LIMIT = 4;\n/** Default `Stroke.varyingWidthJoinThreshold`. */\nconst DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD = 1.5;\n\nfunction emitJoin(\n segs: Seg[], segBaseIdx: number[], j: number, join: Join,\n miterLimit: number,\n varyingThreshold: number,\n verts: number[], idx: number[],\n anchorA: number[], anchorB: number[], anchorT: number[],\n segSrcIdx: number[],\n plA: Uint32Array, plB: Uint32Array, plT: Float32Array,\n): void {\n const a = segs[j];\n const b = segs[(j + 1) % segs.length];\n const aBase = segBaseIdx[j];\n const bBase = segBaseIdx[(j + 1) % segs.length];\n\n const adx = a.bx - a.ax, ady = a.by - a.ay;\n const bdx = b.bx - b.ax, bdy = b.by - b.ay;\n const cross = adx * bdy - ady * bdx;\n if (cross === 0) return; // straight (collinear), no wedge\n const onPositive = cross > 0;\n\n const aOuterEnd = onPositive ? aBase + 3 : aBase + 2;\n const bOuterStart = onPositive ? bBase + 1 : bBase + 0;\n\n // The corner of join `j` is the start of segment `j+1` (which is the END of\n // segment `j` — they share the same polyline point). For the wraparound join\n // on a closed polyline, j+1 wraps to segment 0 whose start is the closer\n // segment's endpoint, which is polyline point 0.\n const nextSeg = (j + 1) % segs.length;\n // The corner anchor is the start of the next segment (shared endpoint).\n // For a closed path's final join, nextSeg wraps to 0, whose start is\n // polyline point 0 — correct.\n const cornerSrc = segSrcIdx[nextSeg];\n const cornerA = plA[cornerSrc];\n const cornerB = plB[cornerSrc];\n const cornerT = plT[cornerSrc];\n // The two adjoining half-widths agree at the shared corner (a.halfB ===\n // b.halfA for a single-contour polyline), so use either for miter-limit\n // sizing. Force bevel when either adjacent segment itself tapers by\n // more than `varyingThreshold` — the miter apex math computes against\n // outer edges parallel to the segment direction, an assumption that\n // breaks on tapered trapezoids (outer edges fan in/out).\n const halfAtCorner = a.halfB;\n const taperA = Math.max(a.halfA, a.halfB) / Math.max(1e-9, Math.min(a.halfA, a.halfB));\n const taperB = Math.max(b.halfA, b.halfB) / Math.max(1e-9, Math.min(b.halfA, b.halfB));\n const forceBevelForVarying = taperA > varyingThreshold || taperB > varyingThreshold;\n\n if (join === 'bevel' || forceBevelForVarying) {\n emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);\n return;\n }\n\n if (join === 'miter') {\n // Compute apex: intersection of A's outer edge extended forward and\n // B's outer edge extended backward. If miter length > MITER_LIMIT * half,\n // fall back to bevel.\n const aOX = verts[aOuterEnd * 2], aOY = verts[aOuterEnd * 2 + 1];\n const bOX = verts[bOuterStart * 2], bOY = verts[bOuterStart * 2 + 1];\n const apex = lineLineIntersect(aOX, aOY, adx, ady, bOX, bOY, -bdx, -bdy);\n if (!apex) {\n emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);\n return;\n }\n const miterLen = Math.hypot(apex[0] - a.bx, apex[1] - a.by);\n if (miterLen > miterLimit * halfAtCorner) {\n emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);\n return;\n }\n // A miter join's outer face is a kite quadrilateral with vertices\n // (R1, apex, R0, J) — covered by TWO triangles. The bevel inner half\n // (R1, R0, J) fills the area between the joint corner and the diagonal\n // hypotenuse R0-R1; the outer extension (R1, apex, R0) fills from the\n // hypotenuse to the apex point. Without the bevel half, an \"inverted\n // triangle\" gap opens at every miter corner.\n const apexIdx = verts.length / 2;\n verts.push(apex[0], apex[1]);\n anchorA.push(cornerA); anchorB.push(cornerB); anchorT.push(cornerT);\n const jIdx = verts.length / 2;\n verts.push(a.bx, a.by);\n anchorA.push(cornerA); anchorB.push(cornerB); anchorT.push(cornerT);\n if (onPositive) {\n idx.push(aOuterEnd, apexIdx, bOuterStart);\n idx.push(aOuterEnd, bOuterStart, jIdx);\n } else {\n idx.push(aOuterEnd, bOuterStart, apexIdx);\n idx.push(aOuterEnd, jIdx, bOuterStart);\n }\n return;\n }\n\n if (join === 'round') {\n emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);\n return;\n }\n}\n\nconst ROUND_STEP_RAD = (10 * Math.PI) / 180; // ~10° per fan triangle\n\nfunction emitRoundJoin(\n a: Seg, aOuterEnd: number, bOuterStart: number, onPositive: boolean,\n verts: number[], idx: number[],\n anchorA: number[], anchorB: number[], anchorT: number[],\n cornerA: number, cornerB: number, cornerT: number,\n): void {\n // Pivot = joint center; emit fresh vertex.\n const cx = a.bx, cy = a.by;\n const pivotIdx = verts.length / 2;\n verts.push(cx, cy);\n anchorA.push(cornerA); anchorB.push(cornerB); anchorT.push(cornerT);\n\n const startX = verts[aOuterEnd * 2] - cx;\n const startY = verts[aOuterEnd * 2 + 1] - cy;\n const endX = verts[bOuterStart * 2] - cx;\n const endY = verts[bOuterStart * 2 + 1] - cy;\n const r = Math.hypot(startX, startY);\n\n const startAngle = Math.atan2(startY, startX);\n const endAngle = Math.atan2(endY, endX);\n // Outer-side sweep: for CCW turns (cross > 0, onPositive true) outer is on\n // the -n side and the arc sweeps from R1 (south of joint) clockwise to R0\n // (east of joint) — i.e., negative sweep. For CW turns, sweep is positive.\n // Pick the SHORT arc that crosses the outer wedge. For onPositive=true\n // (CW turn in screen coords with y-down), the outer wedge sits where the\n // natural sweep is positive; for onPositive=false, where it is negative.\n // If the natural sweep has the wrong sign, wrap by ±2π.\n let sweep = endAngle - startAngle;\n if (onPositive && sweep < 0) sweep += 2 * Math.PI;\n else if (!onPositive && sweep > 0) sweep -= 2 * Math.PI;\n\n const steps = Math.max(1, Math.ceil(Math.abs(sweep) / ROUND_STEP_RAD));\n const stepAngle = sweep / steps;\n\n let prevIdx = aOuterEnd;\n for (let i = 1; i < steps; i++) {\n const ang = startAngle + i * stepAngle;\n const fx = cx + Math.cos(ang) * r;\n const fy = cy + Math.sin(ang) * r;\n const newIdx = verts.length / 2;\n verts.push(fx, fy);\n anchorA.push(cornerA); anchorB.push(cornerB); anchorT.push(cornerT);\n if (onPositive) idx.push(prevIdx, pivotIdx, newIdx);\n else idx.push(prevIdx, newIdx, pivotIdx);\n prevIdx = newIdx;\n }\n if (onPositive) idx.push(prevIdx, pivotIdx, bOuterStart);\n else idx.push(prevIdx, bOuterStart, pivotIdx);\n}\n\nfunction emitBevel(\n a: Seg, aOuterEnd: number, bOuterStart: number, onPositive: boolean,\n verts: number[], idx: number[],\n anchorA: number[], anchorB: number[], anchorT: number[],\n cornerA: number, cornerB: number, cornerT: number,\n): void {\n const jIdx = verts.length / 2;\n verts.push(a.bx, a.by);\n anchorA.push(cornerA); anchorB.push(cornerB); anchorT.push(cornerT);\n if (onPositive) idx.push(aOuterEnd, jIdx, bOuterStart);\n else idx.push(aOuterEnd, bOuterStart, jIdx);\n}\n\n/**\n * Solve for the intersection of line A (point ap, direction ad) with line B\n * (point bp, direction bd). Returns null if parallel or near-parallel.\n */\nfunction lineLineIntersect(\n apx: number, apy: number, adx: number, ady: number,\n bpx: number, bpy: number, bdx: number, bdy: number,\n): [number, number] | null {\n const denom = adx * bdy - ady * bdx;\n if (Math.abs(denom) < 1e-9) return null;\n const t = ((bpx - apx) * bdy - (bpy - apy) * bdx) / denom;\n return [apx + t * adx, apy + t * ady];\n}\n"],"mappings":"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"}
|